-- TexMathA.mesa

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

DIRECTORY
	TexDefs: FROM "TexDefs",
	TexErrorDefs: FROM "TexErrorDefs",
	TexFontDefs: FROM "TexFontDefs",
	TexGlueDefs: FROM "TexGlueDefs",
	TexIODefs: FROM "TexIODefs",
	TexMathDefs: FROM "TexMathDefs",
	TexMathOpDefs: FROM "TexMathOpDefs",
	TexNodeDefs: FROM "TexNodeDefs",
	TexPackDefs: FROM "TexPackDefs",
	TexTableDefs: FROM "TexTableDefs",
	InlineDefs: FROM "InlineDefs";

TexMathA: PROGRAM
IMPORTS TexErrorDefs,TexFontDefs,TexGlueDefs,TexIODefs,TexMathDefs,
	TexNodeDefs,TexPackDefs,TexTableDefs,InlineDefs
EXPORTS TexMathOpDefs =
BEGIN OPEN TexNodeDefs,TexDefs,TexMathDefs,TexMathOpDefs;

AssumptionViolated: SIGNAL = CODE;

fontSize: ARRAY StyleStyle OF MathSize ← [text,text,scr,scrscr];

-- the size associated with a given style
FontSize: PUBLIC PROCEDURE[s: MathStyle] RETURNS[MathSize] = --INLINE--
	BEGIN RETURN[fontSize[s.style]] END;

scrStyle: ARRAY StyleStyle OF StyleStyle ← [script,script,scriptscript,scriptscript];

-- the superscript style associated with a given style
ScrStyle: PUBLIC PROCEDURE[s: MathStyle] RETURNS[MathStyle] = --INLINE--
	BEGIN RETURN[[variant: s.variant, style: scrStyle[s.style]]] END;

-- the variant style associated with a given style
UndStyle: PUBLIC PROCEDURE[s: MathStyle] RETURNS[MathStyle] = --INLINE--
	BEGIN RETURN[[variant: atop, style: s.style]] END;

fracStyle: ARRAY StyleStyle OF StyleStyle ← [text,script,scriptscript,scriptscript];

-- numerator style associated with a given style
NumStyle: PUBLIC PROCEDURE[s: MathStyle] RETURNS[MathStyle] = --INLINE--
	BEGIN RETURN[[variant: s.variant, style: fracStyle[s.style]]] END;

-- denominator style associated with a given style
DenomStyle: PUBLIC PROCEDURE[s: MathStyle] RETURNS[MathStyle] = --INLINE--
	BEGIN RETURN[[variant: atop, style: fracStyle[s.style]]] END;

-- font code for given math font of given size
MathFontTable: PUBLIC PROCEDURE[s: MathSize, t: MFont]
	RETURNS[Font] = --INLINE--
	BEGIN
	RETURN[TexTableDefs.CurMathFont[MFTIndex[mfont: t, msize: s]]];
	END;

MathFontPar: PUBLIC PROCEDURE[par: TexFontDefs.FontParType, size: MathSize]
	RETURNS[Dimn] =
	BEGIN
	RETURN[TexFontDefs.FontPar[MathFontTable[size,sy],par]];
	END;

MathPar: PUBLIC PROCEDURE[par: TexFontDefs.SyParType, size: MathSize]
	RETURNS[Dimn] =
	BEGIN
	RETURN[TexFontDefs.SyPar[MathFontTable[size,sy],par]];
	END;

MathExPar: PUBLIC PROCEDURE[par: TexFontDefs.ExParType]
	RETURNS[Dimn] =
	BEGIN
	RETURN[TexFontDefs.ExPar[MathFontTable[text,ex],par]];
	END;

BoxChar: PUBLIC PROCEDURE[c: MChar, style: MathStyle, corr: BOOLEAN]
	RETURNS[box: BoxNodePtr, fount: Font] =
	BEGIN
	-- This procedure returns a pointer to a box containing the single
	-- math character c, using the style parameter to govern its choice of
	-- fonts. If corr is true, the box is made artificially wider by the
	-- italic correction for c. The return value fount is set to the number
	-- of the font actually used
	f: Font←MathFontTable[FontSize[style],c.mfont];
	ch: FChar←[f,c.char];
	b: BoxNodePtr←MakeCharBox[ch];
	IF corr AND c.mfont#ex THEN
		BEGIN
		ic: Dimn←TexFontDefs.CharIc[ch]; -- italic correction
		IF ic#0 THEN
			BEGIN
			b.width←b.width+ic; b.altered←TRUE; -- mark box nonstandard
			END;
		END;
	RETURN[box: b, fount: f];
	END;

MakeCharBox: PUBLIC PROCEDURE[c: FChar] RETURNS[BoxNodePtr] =
	BEGIN OPEN TexFontDefs;
	RETURN[MakeBoxNode[dir: hlist, head: MakeCharNode[c],
		h: CharHt[c], d: CharDp[c], w: CharWd[c]]];
	END;

-- The following procedure is in a sense the inverse of BoxChar.
-- If the box b contains a single character node, SingleCharBox returns
-- a pointer to that char Node, otherwise it returns NIL
SingleCharBox: PUBLIC PROCEDURE[b: BoxNodePtr]
	RETURNS[CharNodePtr] = --INLINE--
	BEGIN
	c: NodePtr;
	IF b#NIL AND (c←b.head)#NIL AND c.link=NIL THEN
		WITH cc:c SELECT FROM
			char => RETURN[@cc];
			ENDCASE;
	RETURN[NIL];
	END;

-- The boxchar procedure puts single characters into explicit boxes for
-- convenience in the mmode programs. Unfortunately this can consume a
-- lot of memory space, especially in a large table of numeric data,
-- when there are no italic corrections. Therefore the following
-- straightforward procedures are applied after a formula has been built

CompactBox: PUBLIC PROCEDURE[p: BoxNodePtr] RETURNS[NodePtr] =
	BEGIN
	-- The box pointed to by p is replaced by a single character box
	-- if possible, and so are all subboxes within p
	r: NodePtr←p.head;
	IF r=NIL THEN RETURN[p]; -- empty box
	IF r.link#NIL THEN
		BEGIN -- list longer than one node
		list: NodeListPtr←InitNodeList[];
		list.link←r; CompactList[list];
		p.head←FinishNodeList[list]; RETURN[p];
		END;
	WITH rr:r SELECT FROM
		box => p.head←r←CompactBox[@rr];
		ENDCASE;
	-- Unbox a single non-altered unshifted character
	IF r.type=char AND p.shiftamt=0 AND NOT p.altered THEN
		BEGIN p.head←NIL; DsNode[p]; RETURN[r] END
	ELSE RETURN[p];
	END;

CompactList: PUBLIC PROCEDURE[list: NodeListPtr] =
	BEGIN
	-- All boxes in the given list are CompactBoxed
	-- list.last need not be correct when CompactList is called
	-- upon exit, list.link and list.last are both updated
	q,r,s,nextq: NodePtr; -- pointers which run through the list (q=r.link)
	r←list; q←r.link;
	WHILE q#NIL
		DO
		nextq←q.link;
		WITH qq:q SELECT FROM
			box => IF (s←CompactBox[@qq])#q THEN
				BEGIN r.link←q←s; q.link←nextq END;
			ENDCASE;
		r←q; q←nextq;
		ENDLOOP;
	list.last←r;
	END;

MathGlue: PUBLIC PROCEDURE[spec: MathGlueSpec, quad: Dimn]
	RETURNS[GlueNodePtr] =
	BEGIN OPEN InlineDefs, TexGlueDefs;
	-- returns a pointer to a glue node specifying (x/d)*quad,
	-- (y/d)*quad, (z/d)*quad as its glue parameters
	q: CARDINAL←ABS[quad];
	sp,strval,shrval: Dimn;
	p: GluePtr←MakeGlue[];
	sp←LongDiv[LongMult[spec.x,q],spec.d];
	strval←LongDiv[LongMult[spec.y,q],spec.d];
	shrval←LongDiv[LongMult[spec.z,q],spec.d];
	IF quad<0 THEN BEGIN sp←-sp; strval←-strval; shrval←-shrval END;
	p.space←sp; p.flex[str]←[regular,strval]; p.flex[shr]←[regular,shrval];
	RETURN[MakeGlueNode[p]];
	END;

MakeMathGlue: PUBLIC PROCEDURE[sp: MathSpace, size: MathSize]
	RETURNS[GlueNodePtr] =
	BEGIN
	g: GlueNodePtr←NIL;
	quad: Dimn←MathFontPar[quad,size];
	SELECT sp FROM
		thin => g←MathGlue[thinGlue,quad];
		th => IF size=text THEN g←MathGlue[thinGlue,quad];
		thick => IF size=text THEN g←MathGlue[thickGlue,quad];
		quad => g←MathGlue[quadGlue,quad];
		user => IF size=text THEN g←MathGlue[opGlue,quad]
			ELSE g←MathGlue[thinGlue,quad];
		negthin => g←MathGlue[thinGlue,-quad];
		negth => IF size=text THEN g←MathGlue[thinGlue,-quad];
		negthick => IF size=text THEN g←MathGlue[thickGlue,-quad];
		negop => IF size=text THEN g←MathGlue[opGlue,-quad];
		ENDCASE => SIGNAL TexErrorDefs.Confusion; -- invalid MathSpace
	RETURN[g];
	END;

VarSymbol: PUBLIC PROCEDURE[delim: Delimiter, style: MathStyle, size: Dimn]
	RETURNS[BoxNodePtr] =
	BEGIN OPEN TexFontDefs;
	-- This procedure returns a pointer to a box containing a symbol of
	-- varying size, the smallest available symbol whose height+depth is
	-- greater than or equal to the given size. The symbol must also be at
	-- least as large as the symbols of the current style. If necessary, a
	-- large symbol will be constructed from individual pieces. The parameter
	-- delim is a delimiter specification. If delim=nullDelimiter, the box
	-- will be empty and its width will be 2/3 of a thin space.
	p: FChar;
	pdefined: BOOLEAN←FALSE;
	c1,c2: MChar;
	b: BoxNodePtr;

	BEGIN
	IF delim.small.exists THEN
		BEGIN -- try to find small variant that is large enough
		j: MathSize;
		c1←delim.small.mchar;
		pdefined←TRUE;
		-- note that "DECREASING" MathSize means increasing the size!
		FOR j DECREASING IN[FIRST[MathSize]..FontSize[style]]
			DO
			p←[font: MathFontTable[j, c1.mfont], char: c1.char];
			IF (CharHt[p]+CharDp[p])>=size THEN GOTO CharFound;
			ENDLOOP;
		END;
	-- now look for larger symbol in mathex font
	IF NOT delim.large.exists THEN
		GOTO CharFound; -- p is best we can do although it wasn't big enough
	c2←delim.large.mchar;
	IF c2.mfont#ex THEN
		BEGIN OPEN TexIODefs, TexErrorDefs;
		BeginError; Ws["Large delimiter "]; WMCharOctal[c2];
		Ws[" should be in mathex font"];
		Error[EndError[]];
		GOTO CharFound; -- use p if pdefined
		END;
	p←[font: MathFontTable[text,ex], char: c2.char]; pdefined←TRUE;
		DO
		BEGIN
		c: Char;
		ltype: LargerType; linfo: LargerInfo;
		[ltype,linfo]←NextLarger[p];
		WITH info:linfo SELECT ltype FROM
			none => GOTO CharFound; -- there is no larger size
			nextlarger => c←info.next; -- c is next larger char
			extensible =>
				BEGIN -- We will construct a variable-size symbol
				b←ExtendSymbol[info.index, style, size];
				EXIT;
				END;
			ENDCASE;
		IF (CharHt[p]+CharDp[p])>=size THEN GOTO CharFound;
		p.char←c; -- advance to next larger size
		END;
		ENDLOOP;
	EXITS
	CharFound => IF pdefined THEN b←MakeCharBox[p] -- box character p
		ELSE
			BEGIN -- no character found, so fabricate an empty box
			b←NullBox[];
			b.width←MathFontPar[quad,FontSize[style]]/9; b.altered←TRUE;
			END;
	END;
	RETURN[b];
	END;

ExtendSymbol: PROCEDURE[info: TexFontDefs.ExtInfo,
	style: MathStyle, size: Dimn]
	RETURNS[BoxNodePtr] =
	BEGIN OPEN TexFontDefs;
	axis,s,exth: Dimn;
	x: ExtPart;
	echars: ExtArray;
	f: Font;
	n: CARDINAL;
	list: NodeListPtr←InitNodeList[];
	Present: PROCEDURE[x: ExtPart] RETURNS[BOOLEAN] = --INLINE--
		BEGIN RETURN[echars[x].present] END;
	EChar: PROCEDURE[x: ExtPart] RETURNS[FChar] = --INLINE--
		BEGIN RETURN[echars[x].char] END;
	StorePart: PROCEDURE[x: ExtPart] = --INLINE--
		BEGIN StoreNode[list, MakeCharNode[echars[x].char]] END;

	axis←MathPar[axisheight,FontSize[style]];
	echars←ExtensionInfo[info]; f←info.font;
	IF NOT Present[ext] THEN
		ERROR TexErrorDefs.Confusion; -- ext part should always be present
	-- increase s to the appropriate final size
	s←0; FOR x IN [top..bot] DO IF Present[x] THEN s←s+CharDp[EChar[x]] ENDLOOP;
	exth←CharDp[EChar[ext]]; -- exth is the allowable height increment
	-- if there is a middle, must add extensions in pairs
	IF Present[mid] THEN exth←2*exth;
	n←0; WHILE s<size DO n←n+1; s←s+exth ENDLOOP;
	-- now fabricate the symbol as a vlist
	IF Present[top] THEN StorePart[top];
	THROUGH [1..n] DO StorePart[ext] ENDLOOP;
	IF Present[mid] THEN
		BEGIN
		StorePart[mid];
		THROUGH [1..n] DO StorePart[ext] ENDLOOP;
		END;
	IF Present[bot] THEN StorePart[bot];
	RETURN[MakeBoxNode[dir: vlist, head: FinishNodeList[list],
		h: s/2+axis, d: s/2-axis, w: CharWd[EChar[ext]]]];
	END;


FractionRule: PUBLIC PROCEDURE[desiredthickness: Dimn] RETURNS[NodePtr] =
	BEGIN -- yields a rulenode of the given thickness
	-- width extends to boundary of containing vlist, depth is zero
	RETURN[MakeRuleNode[width: nilDimn, height: desiredthickness, depth: 0]];
	END;

OverBar: PUBLIC PROCEDURE[p: NodePtr, desiredheight, desiredthickness, clr: Dimn]
	RETURNS[BoxNodePtr] =
	BEGIN
	-- yields a box consisting of box p with a vinculum placed overhead,
	-- and a blank space (equal to clr) above that. The parameter
	-- "desiredheight" indicates the top of the vinculum, not the top
	-- of the whole box
	list: NodeListPtr←InitNodeList[];
	StoreNode[list, MakeSpace[clr]];
	StoreNode[list, FractionRule[desiredthickness]];
	StoreNode[list, MakeGlueNode[TexGlueDefs.CommonGlue[fill]]];
	StoreNode[list, p];
	RETURN[TexPackDefs.VPack[list, desiredheight+clr]];
	END;

ReBox: PUBLIC PROCEDURE[p: BoxNodePtr, desiredwidth, offset: Dimn]
	RETURNS[BoxNodePtr] =
	BEGIN OPEN TexGlueDefs;
	-- changes box p into a box of width desiredwidth+offset,
	-- centering it with lowerfillglue at each end but shifted right
	-- by the given offset. (The reason for using lowerfillglue is so that
	-- (a) fillglue will still be effective if centering was not really
	-- desired by the user, and (b) lowerfillglue will also shrink so that
	-- the box can be made narrower than its natural width.)
	-- It is assumed that p.shiftamt=0
	q1,q2,q3: NodePtr;
	delta: Dimn←desiredwidth-p.width;
	dir: FlexDir←IF delta<0 THEN shr ELSE str;
	lfg: GluePtr←CommonGlue[lowerfill];
	sumsarray: FlexSums; sums: FlexSumsPtr←@sumsarray;

	IF p.shiftamt#0 THEN SIGNAL AssumptionViolated;

	q1←MakeGlueNode[lfg]; q2←MakeGlueNode[lfg];
	q3←MakeSpace[offset];
	q1.link←q3;

	ClearFlexSums[sums];
	SumFlex[lfg, sums]; SumFlex[lfg, sums]; -- flex from two lfg nodes

	IF p.dir=hlist AND p.glueset=zeroGlueSet AND NOT p.altered THEN
		BEGIN -- a nice box, unwrap it and add the new glue
		b: NodePtr;
		t: NodePtr←q3; -- will eventually point to last node in p's list
		q3.link←p.head; p.head←q1;
		WHILE (b←t.link)#NIL
			DO -- recompute total stretch and shrink
			WITH bb:b SELECT FROM glue => SumFlex[bb.g, sums]; ENDCASE;
			t←b;
			ENDLOOP;
		t.link←q2; -- attach the righthand lowerfillglue node
		p.glueset←SetGlue[dir, ABS[delta], sums]; p.altered←TRUE;
		p.width←desiredwidth+offset;
		RETURN[p];
		END
	ELSE -- put box p (which wasn't so nice) into a larger box
		BEGIN
		b: BoxNodePtr←MakeBoxNode[dir: hlist, head: q1,
			w: desiredwidth+offset, h: p.height, d: p.depth];
		b.glueset←SetGlue[dir, ABS[delta], sums]; b.altered←TRUE;
		q3.link←p; p.link←q2;
		RETURN[b];
		END;
	END;

CleanBox: PUBLIC PROCEDURE[p: BoxNodePtr] RETURNS[BoxNodePtr] =
	BEGIN
	-- makes sure that p points to a box with shiftamt=0,
	-- given that p is either NIL or a pointer to a box
	IF p=NIL THEN RETURN[NullBox[]];
	IF p.link#NIL THEN SIGNAL AssumptionViolated;
	IF p.shiftamt=0 THEN RETURN[p]
	ELSE
		BEGIN
		list: NodeListPtr←InitNodeList[];
		StoreNode[list,p];
		RETURN[TexPackDefs.HBox[list]];
		END;
	END;

END.