-- TexMathB.mesa

-- Tex routines for dealing with math mode
-- last written by Doug Wyatt, December 5, 1979  2:23 AM

DIRECTORY
	TexDefs: FROM "TexDefs",
	TexErrorDefs: FROM "TexErrorDefs",
	TexFontDefs: FROM "TexFontDefs",
	TexGlueDefs: FROM "TexGlueDefs",
	TexMathDefs: FROM "TexMathDefs",
	TexMathOpDefs: FROM "TexMathOpDefs",
	TexMemDefs: FROM "TexMemDefs",
	TexNoadDefs: FROM "TexNoadDefs",
	TexNodeDefs: FROM "TexNodeDefs",
	TexPackDefs: FROM "TexPackDefs",
	TexTableDefs: FROM "TexTableDefs";

TexMathB: PROGRAM
IMPORTS TexErrorDefs,TexFontDefs,TexGlueDefs,TexMathOpDefs,TexMemDefs,
	TexNoadDefs,TexNodeDefs,TexPackDefs,TexTableDefs
EXPORTS TexMathOpDefs =
BEGIN OPEN TexNodeDefs,TexNoadDefs,TexDefs,TexMathOpDefs,TexMathDefs;

-- ****************************
--  Major math mode procedures
-- ****************************

-- these global variables are maintained by MlistToHlist as it
-- runs through an mlist
curstyle: MathStyle; -- the style used at noad q
cursize: MathSize; -- FontSize[curstyle], the type size used at noad q
drt: Dimn; -- the default rule thickness
penalties: BOOLEAN;
maxh,maxd: Dimn; -- the maximum height,depth of the mlist


-- the table that governs inter-element mlist spacing
SpaceArray: TYPE = ARRAY MType OF ARRAY MType OF MathSpace;
spaceArray: POINTER TO SpaceArray←NIL;

MathBInit: PROCEDURE =
	BEGIN
	spaceArray←TexMemDefs.AllocMem[SIZE[SpaceArray]];
	spaceArray↑←
	[
	[   no,  thin,    op, thick,    no,    no,    no], -- Ord
	[ thin,  thin,   nil, thick,    no,    no,    no], -- Op
	[   op,    op,   nil,   nil,    op,   nil,   nil], -- Bin
	[thick, thick,   nil,    no, thick,    no,    no], -- Rel
	[   no,    no,   nil,    no,    no,    no,    no], -- Open
	[   no,  thin,    op, thick,    no,    no,    no], -- Close
	[   th,    th,   nil, thick,    th,    th,    th]  -- Punct
	];
--	   Ord,    Op,   Bin,   Rel,  Open, Close, Punct
	END;

SpaceTable: PROCEDURE[a,b: MType] RETURNS[MathSpace] = --INLINE--
	BEGIN RETURN[spaceArray[a][b]] END;

MCharToFChar: PROCEDURE[mc: MChar, size: MathSize] RETURNS[FChar] = --INLINE--
	BEGIN
	RETURN[[font: MathFontTable[size, mc.mfont], char: mc.char]];
	END;

WrongMFieldType: SIGNAL = CODE;

-- this asserts that the given MField contains a box
-- after debugging, it could be replaced by a LOOPHOLE
BoxF: PROCEDURE[f: MFieldPtr] RETURNS[BoxNodePtr] = --INLINE--
	BEGIN
	WITH ff:f SELECT FROM
		box => RETURN[ff.box];
		ENDCASE => ERROR WrongMFieldType;
	END;

-- this is used for vtop
TopHeight: PROCEDURE[p: NodePtr] RETURNS[Dimn] = --INLINE--
	BEGIN
	WHILE p#NIL
		DO
		WITH pp:p SELECT FROM
			char => RETURN[TexFontDefs.CharHt[pp.c]];
			box => RETURN[pp.height];
			rule => RETURN[pp.height];
			glue => EXIT;
			ENDCASE; -- continue looping
		p←p.link;
		ENDLOOP;
	RETURN[0];
	END;

SetStyle: PROCEDURE[s: MathStyle] = --INLINE--
	BEGIN cursize←FontSize[curstyle←s] END;

DispStyle: PROCEDURE RETURNS[BOOLEAN] = --INLINE--
	BEGIN RETURN[curstyle.style=disp] END;

DoVctr: PROCEDURE[r: BoxNodePtr, vctr: VctrType] = --INLINE--
	BEGIN
	SELECT vctr FROM
		vcenter => r.shiftamt←(r.height-r.depth)/2-MathPar[axisheight,cursize];
		vtop => r.shiftamt←r.height-TopHeight[r.head];
		ENDCASE => ERROR; -- invalid VctrType
	END;

OpKern: PROCEDURE[b: BoxNodePtr] RETURNS[kern: Dimn] = --INLINE--
	BEGIN
	p: CharNodePtr;
	-- now set kern nonzero if the operator in box b is a single
	-- character in the mathex font, having a nonzero MathKern
	IF (p←SingleCharBox[b])#NIL THEN
		BEGIN
		ch: FChar←p.c; -- ch is the character in the char Node
		IF ch.font=MathFontTable[text,ex] THEN
			RETURN[TexFontDefs.CharIc[ch]]; -- ch is in the mathex font
		END;
	RETURN[0];
	END;


CancelBin: PROCEDURE[r: CommonNoadPtr] = --INLINE--
	BEGIN
	IF r#NIL AND r.mtype=Bin THEN r.mtype←Ord;
	END;

NotBinContext: PROCEDURE[r: CommonNoadPtr] RETURNS[BOOLEAN] = --INLINE--
	BEGIN
	RETURN[r=NIL OR (SELECT r.mtype FROM
		Bin,Op,Rel,Open,Punct => TRUE, ENDCASE => FALSE)];
	END;

DoSqrt: PROCEDURE[b: BoxNodePtr] RETURNS[BoxNodePtr] = --INLINE--
	BEGIN
	radical: Delimiter=[small:[TRUE,[sy,160C]],large:[TRUE,[ex,160C]]];
	r: BoxNodePtr; --  box containing the radical symbol
	clr: Dimn; -- extra blank space above operand
	list: NodeListPtr←InitNodeList[];
	b←CleanBox[b];
	IF DispStyle[] THEN clr←MathFontPar[xheight,cursize]/4+drt
	ELSE clr←(5*drt)/4;
	r←VarSymbol[radical, curstyle, b.height+b.depth+clr+drt];
	-- Now r points to a box containing a radical sign of sufficient
	-- size. The upper left corner of the corresponding rule should
	-- touch the upper right corner of this box. We still need to
	-- raise or lower the box appropriately.
	r.shiftamt←(r.height-r.depth-b.height+b.depth-clr-drt)/2;
	-- Now top of box minus drt = b.height+clr plus half the excess
	r.link←OverBar[b, r.height-r.shiftamt, drt,
		(IF penalties THEN drt ELSE 2*drt)];
	StoreNode[list,r];
	RETURN[TexPackDefs.HBox[list]];
	END;

DoOver: PROCEDURE[b: BoxNodePtr] RETURNS[BoxNodePtr] = --INLINE--
	BEGIN
	b←CleanBox[b];
	RETURN[OverBar[b, b.height+3*drt, drt,
		(IF penalties THEN drt ELSE 2*drt)]];
	END;

DoUnder: PROCEDURE[b: BoxNodePtr] RETURNS[BoxNodePtr] = --INLINE--
	BEGIN
	list: NodeListPtr←InitNodeList[];
	StoreNode[list,b←CleanBox[b]]; -- first the operand
	StoreNode[list,MakeSpace[2*drt]]; -- then some glue
	StoreNode[list,FractionRule[drt]]; -- finally, the underbar
	RETURN[MakeBoxNode[dir: vlist, head: FinishNodeList[list],
		h: b.height, d: b.depth+(IF penalties THEN 4 ELSE 5)*drt, w: b.width]];
	END;

DoAccent: PROCEDURE[b: BoxNodePtr, accent: MChar]
	RETURNS[BoxNodePtr] = --INLINE--
	BEGIN
	-- Slants are not taken into account in mathmode accents, since
	-- the sizes of math characters are already adjusted for slant
	p: BoxNodePtr;
	g: GlueNodePtr;
	h,t: Dimn;
	list: NodeListPtr←InitNodeList[];

	p←BoxChar[accent, curstyle, FALSE].box; -- the accent char, in proper size
	b←CleanBox[b];
	g←MakeGlueNode[TexGlueDefs.CommonGlue[lowerfill]];
	-- make a vlist
	StoreNode[list,p]; -- the accent
	StoreNode[list,g]; -- some lowerfill glue
	StoreNode[list,b]; -- the accentee
	p.shiftamt←(b.width-p.width)/2; -- center the accent
	h←b.height;
	t←MathFontPar[xheight,cursize]; -- we will raise the accent by h-t
	p.width←0; -- the accent won't count in determining the new width
	RETURN[TexPackDefs.VPack[list, p.height+h-t]];
	END;

DoOpLimits: PROCEDURE[middle,upper,lower: BoxNodePtr, kern: Dimn]
	RETURNS[BoxNodePtr] = --INLINE--
	BEGIN
	shiftup,shiftdown,maxw,h,d,extra: Dimn;
	list: NodeListPtr←InitNodeList[];

	IF upper=NIL AND lower=NIL THEN RETURN[middle]; -- no limits to do
	IF middle=NIL THEN middle←NullBox[];

	-- limits are to be centered above and below the operator
	-- (except modified by kern, the upper limit being shifted
	-- right and the lower limit shifted left by kern/2 each)
	IF upper#NIL THEN
		BEGIN
		upper←CleanBox[upper];
		shiftup←MAX[MathExPar[bigopspacing3]-upper.depth,
			MathExPar[bigopspacing1]];
		END
	ELSE BEGIN upper←NullBox[]; shiftup←0 END;
	IF lower#NIL THEN
		BEGIN
		lower←CleanBox[lower];
		shiftdown←MAX[MathExPar[bigopspacing4]-lower.height,
			MathExPar[bigopspacing2]];
		END
	ELSE BEGIN lower←NullBox[]; shiftdown←0 END;

	maxw←MAX[middle.width-kern, lower.width, upper.width];
	upper←ReBox[upper,maxw,kern];
	middle←ReBox[CleanBox[middle],maxw,kern/2];
	lower←ReBox[lower,maxw,0];

	extra←MathExPar[bigopspacing5]; -- extra space above and below limits
	h←middle.height-middle.shiftamt+upper.depth+upper.height;
	IF shiftup#0 THEN h←h+shiftup+extra;
	d←middle.depth+middle.shiftamt+lower.height+lower.depth;
	IF shiftdown#0 THEN d←d+shiftdown+extra;
	IF shiftup=0 THEN StoreNode[list,upper]
	ELSE
		BEGIN
		StoreNode[list,MakeSpace[extra]];
		StoreNode[list,upper];
		StoreNode[list,MakeSpace[shiftup]];
		END;
	StoreNode[list,middle];
	IF shiftdown=0 THEN StoreNode[list,lower]
	ELSE
		BEGIN
		StoreNode[list,MakeSpace[shiftdown]];
		StoreNode[list,lower];
		StoreNode[list,MakeSpace[extra]];
		END;
	RETURN[MakeBoxNode[dir: vlist, head: FinishNodeList[list],
		h: h, d: d, w: maxw+kern]];
	END;

SupTable: PROCEDURE[s: MathStyle] RETURNS[MathParType] = --INLINE--
	BEGIN
	RETURN[IF s.variant=atop THEN sup3
		ELSE (IF s.style=disp THEN sup1 ELSE sup2)]
	END;

DoScripts: PROCEDURE[b,bsup,bsub: BoxNodePtr, kern: Dimn, op: BOOLEAN]
	RETURNS[BoxNodePtr] = --INLINE--
	BEGIN
	-- Process the sub/superscripts of noad q
	d: BoxNodePtr;
	shiftup,shiftdown: Dimn←0;

	IF bsup=NIL AND bsub=NIL THEN RETURN[b]; -- no super or subscript

	IF b#NIL AND (op OR SingleCharBox[b]=NIL OR b.shiftamt#0) THEN
		BEGIN -- the operand is not simply a character
		siz: MathSize←FontSize[ScrStyle[curstyle]];
		shiftup←MAX[0, b.height-b.shiftamt-MathPar[supdrop,siz]];
		shiftdown←MAX[0, b.depth+b.shiftamt+MathPar[subdrop,siz]];
		END;
	-- shiftup and shiftdown are minimum amounts to shift baselines
	 
	IF bsup=NIL THEN
		BEGIN -- subscript but no superscript
		d←CleanBox[bsub];
		shiftdown←MAX[shiftdown, MathPar[sub1,cursize]];
		-- make sure that the subscript doesn't get above the baseline
		-- plus four-fifths the xheight
		shiftdown←MAX[shiftdown, d.height-(4*MathFontPar[xheight,cursize])/5];
		d.shiftamt←shiftdown;
		IF kern#0 THEN
			BEGIN
			list: NodeListPtr←InitNodeList[];
			StoreNode[list,MakeSpace[-kern]];
			StoreNode[list,d];
			d←TexPackDefs.HBox[list];
			END;
		END
	ELSE
		BEGIN -- superscript present
		shiftup←MAX[shiftup, MathPar[SupTable[curstyle],cursize]];
		d←CleanBox[bsup];
		-- make sure that the exponent doesn't get below the basline plus
		-- one-fourth the xheight
		shiftup←MAX[shiftup, MathFontPar[xheight,cursize]/4+d.depth];
		IF bsub=NIL THEN d.shiftamt←-shiftup -- superscript but no subscript
		ELSE
			BEGIN -- both subscript and superscript
			c: BoxNodePtr←CleanBox[bsub];
			delta: Dimn;
			list: NodeListPtr←InitNodeList[];
			shiftdown←MAX[shiftdown, MathPar[sub2,cursize]];
			delta←(d.depth+c.height+drt)-(shiftup+shiftdown);
			IF delta>0 THEN
				BEGIN -- adjust scripts to ensure minimum clearance drt
				shiftup←shiftup+delta/2; shiftdown←shiftdown+delta/2;
				END;
			StoreNode[list,d];
			StoreNode[list,MakeGlueNode[TexGlueDefs.CommonGlue[fill]]];
			StoreNode[list,c];
			c.shiftamt←-kern; -- kern might be nonzero if np.ctype=op
			d←TexPackDefs.VPack[list, shiftdown+shiftup+d.height];
			d.shiftamt←shiftdown;
			END;
		END;
	-- Now d points to a box representing the sub/superscripts
	-- and b is the box to attach it to
	IF b=NIL THEN RETURN[d]
	ELSE
		BEGIN
		list: NodeListPtr←InitNodeList[];
		StoreNode[list,b]; StoreNode[list,d];
		RETURN[TexPackDefs.HBox[list]];
		END;
	END;

DoAbove: PROCEDURE[np: AboveNoadPtr] RETURNS[BoxNodePtr] = --INLINE--
	BEGIN
	num: BoxNodePtr←CleanBox[BoxF[@np.numerator]];
	denom: BoxNodePtr←CleanBox[BoxF[@np.denominator]];
	rt: Dimn←np.aboverule; -- rule thickness
	axis: Dimn←MathPar[axisheight,cursize];
	pn,pd: MathParType;
	shiftup,shiftdown,maxw,s: Dimn;
	r: BoxNodePtr;
	ld,rd: BoxNodePtr; -- left and right delimiters
	dispstyle: BOOLEAN←DispStyle[];
	list: NodeListPtr←InitNodeList[];

	IF dispstyle THEN BEGIN pn←num1; pd←denom1 END
	ELSE BEGIN pn←IF rt=0 THEN num3 ELSE num2; pd←denom2 END;
	shiftup←MathPar[pn,cursize];
	shiftdown←MathPar[pd,cursize];
	-- Now axis is the distance from the base line to the center of the
	-- bar line, while shiftup and shiftdown are the standard baseline
	-- displacements for numerator and denominator in the current style.
	-- These standard displacements will be increased, if necessary, to
	-- avoid interference between numerator and denominator.

	-- Center the numerator and denominator by reboxing the smaller one
	maxw←MAX[num.width,denom.width];
	IF num.width<maxw THEN num←ReBox[num,maxw,0];
	IF denom.width<maxw THEN denom←ReBox[denom,maxw,0];

	-- compute actual baseline displacements
	IF rt=0 THEN
		BEGIN -- the case of no fraction line
		-- clr is the minimum clearance desired between num and denom
		clr: Dimn←IF dispstyle THEN 7*drt ELSE 3*drt;
		delta: Dimn←(num.depth+denom.height+clr)-(shiftup+shiftdown);
		IF delta>0 THEN
			BEGIN shiftup←shiftup+delta/2; shiftdown←shiftdown+delta/2 END;
		END
	ELSE
		BEGIN -- the case of a fraction line
		-- clr is the min clearance desired between num, denom, and rule
		clr: Dimn←IF dispstyle THEN 3*rt ELSE rt;
		delta1,delta2: Dimn; -- possible additions to shiftup, shiftdown
		delta1←(num.depth+clr+rt/2)-(shiftup-axis);
		delta2←(denom.height+clr+rt/2)-(shiftdown+axis);
		SELECT TRUE FROM
			(delta1>0 AND delta2>0) => -- both get minimum clearance
				BEGIN shiftup←shiftup+delta1; shiftdown←shiftdown+delta2 END;
			-- otherwise, both get clearance of the good one
			(delta1>0) => shiftup←shiftup+delta1-delta2;
			(delta2>0) => shiftdown←shiftdown+delta2-delta1;
			ENDCASE;
		END;

	-- make the vlist box for the fraction
	StoreNode[list,num]; -- first comes the numerator
	IF rt=0 THEN
		BEGIN -- no rule inserted
		h: Dimn←shiftup+shiftdown-num.depth-denom.height;
		StoreNode[list,MakeSpace[h]]; -- glue inbetween num and denom
		END
	ELSE
		BEGIN
		h1: Dimn←shiftup-num.depth-rt/2-axis;
		h2: Dimn←shiftdown+axis-denom.height-rt/2;
		StoreNode[list,MakeSpace[h1]]; -- glue above fraction bar
		StoreNode[list,FractionRule[rt]]; -- the fraction bar
		StoreNode[list,MakeSpace[h2]]; -- glue below fraction bar
		END;
	StoreNode[list,denom]; -- last comes the denominator
	r←MakeBoxNode[dir: vlist, head: FinishNodeList[list],
		h: num.height+shiftup, d: denom.depth+shiftdown, w: maxw];

	-- Finally, put the fraction into a box with its delimiters
	s←MathPar[(IF dispstyle THEN delim1 ELSE delim2),cursize];
	ld←VarSymbol[np.ldelim, curstyle, s];
	rd←VarSymbol[np.rdelim, curstyle, s];
	ld.shiftamt←(ld.height-ld.depth)/2-axis;
	rd.shiftamt←(rd.height-rd.depth)/2-axis;
	list←InitNodeList[];
	-- ldelim, fraction, rdelim
	StoreNode[list,ld]; StoreNode[list,r]; StoreNode[list,rd];
	RETURN[TexPackDefs.HBox[list]];
	END;


-- This procedure does most of the mathematics formatting: It converts
-- an mlist to an hlist, provided that the noads of the mlist contain no
-- references to other mlists. (The procedure "evalmlist" below makes it
-- possible to assume that this condition is satisfied.) If "penalties"
-- is true, penalty nodes that indicate permissible breaks in the main
-- mlist will be inserted

MlistToHlist: PUBLIC PROCEDURE[p: NoadPtr, style: MathStyle, penlt: BOOLEAN]
	RETURNS[NodePtr] =
	BEGIN
	mlist: NoadListPtr←InitNoadList[]; -- the mlist being scanned
	hlist: NodeListPtr←InitNodeList[]; -- the hlist being formed

	-- We make two passes over the mlist. On the first pass, boxes are
	-- constructed for square roots and fractions, etc., and
	-- sub/superscripts are attached. A few other minor operations are
	-- also done (e.g., binnoads are changed to boxnoads if they don't
	-- appear in the context of binary operators, and the height and depth
	-- are calculated so that left and right delimiters of the appropriate
	-- size will be fabricated. The second pass gets rid of all noads,
	-- and hooks together the desired hlist including appropriate
	-- glue and penalty nodes

	IF p=NIL THEN RETURN[NIL]; -- avoid degenerate case

	mlist.link←p;
	drt←MathExPar[defaultrulethickness];
	penalties←penlt;
	maxh←maxd←0;
	ScanMlist[mlist,style];
	MakeHlist[mlist,hlist,style];
	RETURN[FinishNodeList[hlist]];
	END;


-- the first pass of MlistToHlist

ScanMlist: PROCEDURE[mlist: NoadListPtr, style: MathStyle] = --INLINE--
	BEGIN
	q: NoadPtr←mlist; -- runs through the mlist
	prevq: NoadPtr←NIL; -- the noad preceding q (prevq.link=q)
	r: CommonNoadPtr←NIL; -- the previous CommonNoad

	-- On this first pass, boxes are constructed for square roots and
	-- fractions, etc., and sub/superscripts are attached. A few other
	-- minor operations are also done (e.g., binnoads are changed to
	-- boxnoads if they don't appear in the context of binary operators,
	-- and the height and depth are calculated so that left and right
	-- delimiters of the appropriate size will be fabricated.

	SetStyle[style];
	FOR prevq←mlist,q UNTIL (q←prevq.link)=NIL
		DO -- the first pass
		WITH qq:q SELECT FROM
			common =>
				BEGIN
				b,sup,sub: BoxNodePtr;
				WITH qqq:qq SELECT FROM
					scripted =>
						BEGIN
						kern: Dimn←0; opflag,oplimits: BOOLEAN←FALSE;
						SELECT qq.mtype FROM
							Rel,Close,Punct => CancelBin[r];
							Bin => IF NotBinContext[r] THEN qq.mtype←Ord;
							ENDCASE;
						b←BoxF[@qqq.operand];
						WITH qqqq:qqq SELECT FROM
							none => NULL;
							vctr => DoVctr[b,qqqq.vctr];
							op =>
								BEGIN
								opflag←TRUE; kern←OpKern[b]; oplimits←kern=0;
								IF qqqq.limitswitch THEN oplimits←NOT oplimits;
								END;
							sqrt => b←DoSqrt[b];
							over => b←DoOver[b];
							under => b←DoUnder[b];
							accent => b←DoAccent[b,qqqq.accent];
							ENDCASE => ERROR; -- bad NType
						-- attach sub/superscripts if present
						sup←BoxF[@qqq.supscr]; sub←BoxF[@qqq.subscr];
						IF oplimits AND DispStyle[] THEN b←DoOpLimits[b,sup,sub,kern]
						ELSE b←DoScripts[b,sup,sub,kern,opflag];
						END;
					above => b←DoAbove[@qqq];
					delim => b←NIL;
					ENDCASE;
				IF b#NIL THEN
					BEGIN
					qq.box←b;
					maxh←MAX[maxh, b.height-b.shiftamt];
					maxd←MAX[maxd, b.depth+b.shiftamt];
					END;
				r←@qq;
				END;
			node,disc => NULL;
			style => SetStyle[qq.s];
			space =>
				BEGIN
				-- substitute for this space Noad a node Noad for g
				-- g is a pointer to a glue node or NIL
				g: NodePtr←MakeMathGlue[qq.sp, cursize];
				n: NoadPtr←MakeNodeNoad[g];
				n.link←qq.link;
				prevq.link←q←n;
				END;
			ENDCASE => ERROR; -- invalid noad type
		ENDLOOP; -- end of first pass loop
	END;

-- The second pass of MlistToHlist

MakeHlist: PROCEDURE[mlist: NoadListPtr, hlist: NodeListPtr,
	style: MathStyle] =
	BEGIN
	q: NoadPtr; -- runs through the mlist
	nextq: NoadPtr; -- holds q.link while q is freed
	rtype,t: MType; -- previous and current node types, for spacing
	binpen: Penalty←TexTableDefs.CurPenalty[mbpen]; -- penalty for break at Bin
	relpen: Penalty←TexTableDefs.CurPenalty[mrpen]; -- penalty for break at Rel
	pen: Penalty; -- penalty for breaking after q
	break: BOOLEAN; -- TRUE if a break is allowed and pen has been set
	Appnd: PROCEDURE[x: NodePtr] = --INLINE--
		BEGIN IF x#NIL THEN StoreNode[hlist,x] END;

	-- The second pass simply goes through and inserts the appropriate
	-- spacing, returning the noads to free storage. It also handles
	-- leftnoads and rightnoads, since we now know maxh and maxd

	rtype←Open;
	SetStyle[style];

	FOR q←mlist.link,nextq WHILE q#NIL
		DO -- second pass loop
		break←FALSE;
		WITH qq:q SELECT FROM
			common =>
				BEGIN
				WITH qqq:qq SELECT FROM
					scripted,above => NULL;
					delim =>
						BEGIN
						axis: Dimn←MathPar[axisheight,FontSize[style]];
						s: Dimn←MAX[maxh-axis,maxd+axis]; -- max distance from axis
						b: BoxNodePtr←VarSymbol[qqq.delim, curstyle, 2*s];
						b.shiftamt←(b.height-b.depth)/2-axis;
						qq.box←b;
						END;
					ENDCASE => ERROR; -- bad CommonNoad type
				SELECT (t←qq.mtype) FROM
					Bin => BEGIN pen←binpen; break←TRUE END;
					Rel => BEGIN pen←relpen; break←TRUE END;
					ENDCASE;
				Appnd[InterElementGlue[rtype,t,cursize]];
				Appnd[qq.box];
				IF break AND penalties AND NOT PenaltyNodeFollows[@qq] THEN
					Appnd[MakePenaltyNode[pen]];
				rtype←t;
				END;
			node => Appnd[qq.p];
			disc => Appnd[MakeDiscNode[MCharToFChar[qq.c, cursize]]];
			style => SetStyle[qq.s];
			ENDCASE => ERROR; -- invalid Noad type
		nextq←q.link;
		ENDLOOP;
	END;

EvalMlist: PUBLIC PROCEDURE[p: NoadPtr, style: MathStyle, penalties: BOOLEAN]
	RETURNS[NodePtr] =
	BEGIN
	-- This procedure converts the general mlist pointed to by p into
	-- an hlist, using the given style for the main mlist. The effect is
	-- like MlistToHlist except that the given mlist may have sub-mlists, or it
	-- might refer to math characters that aren't already in boxes. This is the
	-- procedure that controls the implicit styles in math formulas. Recursion
	-- occurs when evalmlist calls boxfield which calls evalmlist
	q: NoadPtr;
	curstyle: MathStyle←style;

	FOR q←p,q.link WHILE q#NIL
		DO
		WITH qq:q SELECT FROM
		-- we must remove non-box fields from noad q
		common => WITH qqq:qq SELECT FROM
			scripted =>
				BEGIN
				scrstyle: MathStyle←ScrStyle[curstyle];
				SELECT qqq.stype FROM
					sqrt,over => BoxField[@qqq.operand, UndStyle[curstyle], TRUE];
					op => BoxOp[@qqq.operand,curstyle];
					ENDCASE => BoxField[@qqq.operand, curstyle, ~KernScript[@qqq]];
					-- The last parameter to BoxField essentially makes a "kerned"
					-- symbol when there is a subscript but no superscript.
					-- Otherwise the italic correction is included as the box is made
				BoxField[@qqq.supscr,scrstyle,TRUE];
				BoxField[@qqq.subscr,UndStyle[scrstyle],TRUE];
				END;
			above =>
				BEGIN
				BoxField[@qqq.numerator,NumStyle[curstyle],TRUE];
				BoxField[@qqq.denominator,DenomStyle[curstyle],TRUE];
				END;
			delim => NULL;
			ENDCASE => ERROR; -- bad SType in common Noad
		node,disc,space => NULL;
		style => curstyle←qq.s;
		ENDCASE => ERROR; -- bad Noad type
		ENDLOOP;
	RETURN[MlistToHlist[p, style, penalties]];
	END;

PenaltyNodeFollows: PROCEDURE[p: NoadPtr] RETURNS[BOOLEAN] = --INLINE--
	BEGIN
	q: NoadPtr←p.link;
	IF q#NIL THEN WITH qq:q SELECT FROM
		node => IF qq.p#NIL AND qq.p.type=penalty THEN RETURN[TRUE];
		ENDCASE;
	RETURN[FALSE];
	END;

InterElementGlue: PROCEDURE[a,b: MType, size: MathSize]
	RETURNS[GlueNodePtr] = --INLINE--
	BEGIN
	g: GlueNodePtr←NIL;
	quad: Dimn←MathFontPar[quad,size];
	SELECT SpaceTable[a,b] FROM
		no => NULL; -- no space
		thin => g←MathGlue[thinGlue, quad];
		th => IF size=text THEN g←MathGlue[thinGlue, quad];
		thick => IF size=text THEN g←MathGlue[thickGlue, quad];
		op => IF size=text THEN g←MathGlue[opGlue, quad];
		ENDCASE => ERROR TexErrorDefs.Confusion; -- invalid SpaceTable entry
	RETURN[g];
	END;

NullMField: PROCEDURE[f: MFieldPtr] RETURNS[BOOLEAN] = --INLINE--
	BEGIN
	WITH ff:f SELECT FROM
		box => RETURN[ff.box=NIL];
		ENDCASE;
	RETURN[FALSE];
	END;

KernScript: PROCEDURE[np: ScriptedNoadPtr]
	RETURNS[BOOLEAN] = --INLINE--
	BEGIN
	RETURN[NullMField[@np.supscr] AND NOT NullMField[@np.subscr]];
	END;

BoxOp: PROCEDURE[op: MFieldPtr, style: MathStyle] = --INLINE--
	BEGIN
	-- check for a single character op in \mathex
	singlchrxop: BOOLEAN←FALSE;
	WITH ff:op SELECT FROM
		mchar => IF ff.mchar.mfont=ex THEN
			BEGIN
			singlchrxop←TRUE;
			IF style.style=disp THEN
				BEGIN OPEN TexFontDefs;
				ltype: LargerType; linfo: LargerInfo; 
				[ltype,linfo]←NextLarger[MCharToFChar[ff.mchar,text]];
				WITH linfo SELECT ltype FROM
					nextlarger => ff.mchar.char←next; -- use larger size if available
					ENDCASE;
				END;
			END;
		ENDCASE;
	BoxField[op, style, TRUE];
	IF singlchrxop THEN WITH ff:op SELECT FROM
		box =>
			BEGIN
			b: BoxNodePtr←ff.box;
			b.shiftamt←-MathPar[axisheight,FontSize[style]]-b.depth/2;
			-- shift the character so that its height above the axis
			-- exceeds its depth below the axis by the character height
			END;
		ENDCASE => ERROR WrongMFieldType;
	END;

BoxField: PUBLIC PROCEDURE[f: MFieldPtr, style: MathStyle, corr: BOOLEAN] =
	BEGIN
	-- This procedure converts a noad field into the corresponding box.
	-- If corr is true, the italic correction occurs at the right of a
	-- single-character box. Recursion comes about when BoxField calls
	-- EvalMlist which calls BoxField.
	WITH ff:f SELECT FROM
		box => RETURN; -- nothing to do if already boxed
		mlist => f↑←[box[BoxNode[EvalMlist[ff.mlist, style, FALSE]]]];
		mchar => f↑←[box[BoxChar[ff.mchar, style, corr].box]];
		ENDCASE => ERROR; -- invalid MField tag
	END;

BoxNode: PROCEDURE[q: NodePtr] RETURNS[BoxNodePtr] = --INLINE--
	BEGIN
	list: NodeListPtr;
	IF q=NIL THEN RETURN[NIL];
	WITH qq:q SELECT FROM
		box => IF qq.link=NIL THEN RETURN[@qq];
		ENDCASE;
	list←InitNodeList[]; list.link←q; list.last←NIL;
	RETURN[TexPackDefs.HBox[list]];
	END;

--  initialization
MathBInit;

END.