--File CIFOutput.mesa
--
Device independent Output module for CIF 2.0 Parser
-- Written by Martin Newell, March 1980.
--
Last updated: June 16, 1981 10:43 AM

DIRECTORY

AuxIntDefs: FROM "AuxIntDefs" USING [IStop],
CIFDevicesDefs: FROM "CIFDevicesDefs" USING [LookupLayer, SetLayer,
OutputText, LayerVisible],
CIFOutputDefs: FROM "CIFOutputDefs" USING[SortType],
CIFUtilitiesDefs: FROM "CIFUtilitiesDefs" USING[DrawRectangleOutline,
GetDisplayContext, GetBaseContext, EnableClipping, DisableClipping],
Graphics: FROM "Graphics" USING [DrawRectangle, DisplayContext, PushContext,
PopContext, StartAreaPath, EnterPoint, Map, DrawArea, Translate],
IODefs: FROM "IODefs" USING [SP, TAB, NUL, WriteLine, WriteString],
ParserErrorDefs: FROM "ParserErrorDefs" USING [ErrorType, Report],
OutputDefs: FROM "OutputDefs" USING [RelationType, VisibleType],
IntDefs: FROM "IntDefs" USING [IUserObject, IScaleLongInt],
IntStorageDefs: FROM "IntStorageDefs" USING [ObjectType],
IntTransDefs: FROM "IntTransDefs"
USING[Push, Pop, Rotate, Translate, RTransformPoint,
RTransformVector],
JaMFnsDefs: FROM "JaMFnsDefs" USING [GetJaMBreak],
ParserTypeDefs: FROM "ParserTypeDefs" USING [Point, Path, LinkedPoint],
Real: FROM "Real" USING [Fix, Float],
RealFns: FROM "RealFns" USING [SqRt],
StringDefs: FROM "StringDefs" USING [AppendChar, StringToLongNumber,
InvalidNumber],
SystemDefs: FROM "SystemDefs" USING [AllocateHeapNode, FreeHeapNode],
Vector USING [Vec];

CIFOutput: PROGRAM
IMPORTS AuxIntDefs, CIFDevicesDefs, CIFUtilitiesDefs, RealFns, Graphics, IODefs, IntDefs, IntTransDefs, JaMFnsDefs, ParserErrorDefs, Real, StringDefs, SystemDefs
EXPORTS OutputDefs, CIFOutputDefs =

BEGIN OPEN AuxIntDefs, CIFDevicesDefs, CIFOutputDefs, CIFUtilitiesDefs, RealFns, Graphics, IODefs, IntDefs, IntStorageDefs, IntTransDefs, OutputDefs, ParserErrorDefs, ParserTypeDefs, JaMFnsDefs, Real, StringDefs, SystemDefs, Vector;

RPoint: TYPE = RECORD[x,y: REAL];
UserObjectType: TYPE = {User9, User94, other};
UserObject: TYPE = POINTER TO UserObjectRecord;
UserObjectRecord: TYPE = RECORD[
SELECT type: UserObjectType FROM
User9 => [--Symbol name
name: PACKED ARRAY [0..0) OF CHARACTER],
User94 => [--Named point
x: REAL,
y: REAL,
name: PACKED ARRAY [0..0) OF CHARACTER],
ENDCASE];

InitOutput: PUBLIC PROCEDURE RETURNS [BOOLEAN] =
BEGIN
RETURN [TRUE];
END;

FinishOutput: PUBLIC PROCEDURE RETURNS [BOOLEAN] =
BEGIN
EnableClipping[];
RETURN[TRUE];
END;

SendMessage: PUBLIC PROCEDURE [type: ParserErrorDefs.ErrorType, message: STRING,
wantCR: BOOLEAN ← TRUE] =
BEGIN
IF wantCR THEN IODefs.WriteLine[message] ELSE IODefs.WriteString[message];
END;

MessageFile: PUBLIC PROCEDURE [fileName: STRING] RETURNS [BOOLEAN] =
BEGIN
RETURN[FALSE];
END;

OutputToFile: PUBLIC PROCEDURE [fileName: STRING] RETURNS [BOOLEAN] =
BEGIN
RETURN[FALSE];
END;

Map: PUBLIC PROCEDURE [layerName: PACKED ARRAY [0..4) OF CHARACTER] RETURNS [CARDINAL] =
BEGIN
RETURN[LookupLayer[layerName]];
END;

OutputWire: PUBLIC PROCEDURE [visible: VisibleType, layerName: CARDINAL, width: LONG CARDINAL, a: ParserTypeDefs.Path] =
BEGIN
length,halfw,offset: REAL;
ap,bp,cp: POINTER TO LinkedPoint;
ab,bc: Point;
lengthab,lengthbc: REAL;
aExt,bExt: REAL;
inline: REAL;
Stepbc: PROCEDURE =
BEGIN --given old c, compute b, new c, bc, lengthbc
--If no distinct new c then b will be A.last
--Filters out verticies giving rise to zero length segments
bp ← cp;
UNTIL cp=a.last DO
cp ← cp.next;
bc ← Point[cp.value.x-bp.value.x, cp.value.y-bp.value.y];
lengthbc ← SqRt[Dot[bc,bc]];
IF lengthbc=0 THEN bp ← cp ELSE EXIT;
ENDLOOP;
END;

--body starts here

IF GetJaMBreak[] THEN
{IStop[];
WriteLine["***Aborted***"];
RETURN;
};
IF a.length=0 OR ~LayerVisible[layerName] THEN RETURN;
IF visible=yes THEN DisableClipping[] ELSE EnableClipping[];
SetLayer[layerName];
halfw ← width/2;

cp ← a.first;
Stepbc[];
IF bp=a.last
THENBEGIN
ROutputFlash[width, bp.value];
RETURN;
END;
bExt ← halfw;
UNTIL bp=a.last DO
ap ← bp; aExt ← bExt;
ab ← bc; lengthab ← lengthbc;
Stepbc[];
IF bp=a.last
THENbExt ← halfw
ELSEBEGIN
IF (inline ← Dot[ab,bc])<0
THENBEGIN
bExt ← 0;
ROutputFlash[width, bp.value];
END
ELSEbExt ← halfw * ABS[Dot[[ab.y,-ab.x],bc]]/
(inline + lengthab*lengthbc);
END;
length ← lengthab+aExt+bExt;
offset ← (lengthab-aExt+bExt)/2;
SELECT TRUE FROM
ab.y=0 =>ROutputBox[length, width,
[IF ab.x>=0 THEN ap.value.x+offset ELSE ap.value.x-offset, ap.value.y]];
ab.x=0 =>ROutputBox[width, length,
[ap.value.x, IF ab.y>=0 THEN ap.value.y+offset ELSE ap.value.y-offset]];
ENDCASE => BEGIN
Push[];
IntTransDefs.Translate[Round[offset],0]; --’a’ to origin
Rotate[ab.x,ab.y];
IntTransDefs.Translate[ap.value.x,ap.value.y]; --’a’ to correct place
ROutputBox[length, width, [0,0]];
Pop[];
END;
ENDLOOP;
END;

Dot: PROCEDURE [p,q: Point] RETURNS[dotProd: REAL] =
BEGIN
RETURN[Float[p.x]*Float[q.x] + Float[p.y]*Float[q.y]];
END;

twoMR2: REAL = 2 - SqRt[2];
R2M1: REAL = SqRt[2] - 1;

OutputFlash: PUBLIC PROCEDURE [visible: VisibleType, layerName: CARDINAL, diameter: LONG CARDINAL, center: ParserTypeDefs.Point] =
--Approximate circle with an octagon
BEGIN
IF GetJaMBreak[] THEN
{IStop[];
WriteLine["***Aborted***"];
RETURN;
};
IF ~LayerVisible[layerName] THEN RETURN;
IF visible=yes THEN DisableClipping[] ELSE EnableClipping[];
SetLayer[layerName];
ROutputFlash[diameter, center];
END;

ROutputFlash: PUBLIC PROCEDURE [diameter: REAL, center: ParserTypeDefs.Point] =
--Internal procedure for really dealing with Flashes
BEGIN
h2,cX,cY,radius: REAL;
dc: DisplayContext ← GetDisplayContext[];

radius ← diameter/2;
h2 ← R2M1*radius;
[cX,cY] ← RTransformPoint[center.x, center.y];

PushContext[dc];
Graphics.Translate[dc, [cX,cY]];
StartAreaPath[dc, FALSE];
EnterPoint[dc, [radius,h2]];
EnterPoint[dc, [h2,radius]];
EnterPoint[dc, [-h2,radius]];
EnterPoint[dc, [-radius,h2]];
EnterPoint[dc, [-radius,-h2]];
EnterPoint[dc, [-h2,-radius]];
EnterPoint[dc, [h2,-radius]];
EnterPoint[dc, [radius,-h2]];
DrawArea[dc];
PopContext[dc];

END;

OutputPolygon: PUBLIC PROCEDURE [visible: VisibleType, layerName: CARDINAL, a: ParserTypeDefs.Path] =
BEGIN
xT,yT: REAL;
p,prev: POINTER TO LinkedPoint;
dc: DisplayContext ← GetDisplayContext[];

IF GetJaMBreak[] THEN
{IStop[];
WriteLine["***Aborted***"];
RETURN;
};
IF a.length=0 OR ~LayerVisible[layerName] THEN RETURN;
IF visible=yes THEN DisableClipping[] ELSE EnableClipping[];
SetLayer[layerName];
StartAreaPath[dc, FALSE];
prev ← NIL;
FOR p ← a.first, p.next UNTIL prev=a.last DO
[xT,yT] ← RTransformPoint[p.value.x,p.value.y];
EnterPoint[dc, [xT, yT]];
prev ← p;
ENDLOOP;
DrawArea[dc];
END;

OutputBox: PUBLIC PROCEDURE [visible: VisibleType, layerName: CARDINAL, length, width: LONG CARDINAL,
center: ParserTypeDefs.Point, xRotation, yRotation: LONG INTEGER] =
BEGIN
centerX: REAL ← center.x;
centerY: REAL ← center.y;

IF GetJaMBreak[] THEN
{IStop[];
WriteLine["***Aborted***"];
RETURN;
};
IF ~LayerVisible[layerName] THEN RETURN;
IF visible=yes THEN DisableClipping[] ELSE EnableClipping[];
SetLayer[layerName];
SELECT TRUE FROM
yRotation=0 =>ROutputBox[length, width, [centerX,centerY]];
xRotation=0 =>ROutputBox[width, length, [centerX,centerY]];
ENDCASE => BEGIN --accomplish rotation AND translation via transformations
Push[];
Rotate[xRotation,yRotation];
IntTransDefs.Translate[center.x,center.y];
ROutputBox[length, width, [0,0]];
Pop[];
END;
END;

ROutputBox: PROCEDURE [length,width: REAL, center: RPoint] =
BEGIN
--Internal procedure for really dealing with boxes
--Rotations must have been dealt with either by interchanging coords or
-- by setting up the appropriate transformation.
lenR: REAL ← length/2;
widR: REAL ← width/2;
ax,ay,bx,by,cx,cy: REAL;
dc: DisplayContext ← GetDisplayContext[];

[ax,ay] ← RTransformVector[lenR,widR];
[bx,by] ← RTransformVector[lenR,-widR];
[cx,cy] ← RTransformPoint[center.x, center.y];

-- Check for "Manhattan" rectangle (common case)
IF ax=bx OR ay=by
THEN DrawRectangle[dc, [cx-ax,cy-ay],[cx+ax,cy+ay]]
ELSE
BEGIN
StartAreaPath[dc, FALSE];
EnterPoint[dc, [cx+ax,cy+ay]];
EnterPoint[dc, [cx+bx,cy+by]];
EnterPoint[dc, [cx-ax,cy-ay]];
EnterPoint[dc, [cx-bx,cy-by]];
DrawArea[dc];
END;
END;

OutputUserCommand: PUBLIC PROCEDURE [command: [0..9], userText: STRING] =
BEGIN
s: STRING ← [2];
--
SendMessage[Other, "User Command: "];
--
AppendDecimal[s,command];
--
AppendChar[s,SP];
--
SendMessage[Other, s, FALSE];
--
SendMessage[Other, userText];
IF command=9 AND userText.length>1 THEN
SELECT userText[0] FROM
SP=>--9 SymbolName
{ind: CARDINAL ← 1;
string: STRING ← [100];
ind ← GetName[userText,ind,string];
IF string.length=0 THEN
BEGIN
Report["Invalid syntax for User Command 9 - expects <name>;",
Other];
RETURN;
END;
--
WriteString["Name:"]; WriteString[string];
--
WriteLine[""];
OutputSymbolName[string];
};
’4=>--94 name x y
IF userText[1]=’ THEN
{ENABLE InvalidNumber =>
{Report["Invalid syntax for User Command 94 - expects <name x y>;",
Other];
CONTINUE;
};
ind: CARDINAL ← 2;
string: STRING ← [100];
number: STRING ← [50];
x,y: LONG INTEGER;
ind ← GetName[userText,ind,string];
IF string.length=0 THEN InvalidNumber;
ind ← GetName[userText,ind,number];
x ← StringToLongNumber[number,10];
ind ← GetName[userText,ind,number];
y ← StringToLongNumber[number,10];
--
WriteString["Name:"]; WriteString[string];
--
WriteString[" x:"]; WriteFloat[x];
--
WriteString[" y:"]; WriteFloat[y];
--
WriteLine[""];
OutputNamedPoint[string, x,y];
};
ENDCASE;
END;

OutputSymbolName: PUBLIC PROCEDURE [name: STRING] =
BEGIN
object9: POINTER TO User9 UserObjectRecord;
i,sizeobject9: CARDINAL;
sizeobject9 ← SIZE[User9 UserObjectRecord]+(name.length+1)/2;
object9 ← AllocateHeapNode[sizeobject9];
object9↑ ← UserObjectRecord[User9[name:]];
FOR i IN [0..name.length) DO
object9.name[i] ← name[i];
ENDLOOP;
IF (name.length MOD 2) # 0 THEN object9.name[name.length] ← NUL;
IUserObject[sizeobject9,object9];
FreeHeapNode[object9];
END;

OutputNamedPoint: PUBLIC PROCEDURE [name: STRING, x,y: LONG INTEGER] =
BEGIN
object94: POINTER TO User94 UserObjectRecord;
i,sizeobject94: CARDINAL;
xx,yy: REAL;
sizeobject94 ← SIZE[User94 UserObjectRecord]+(name.length+1)/2;
object94 ← AllocateHeapNode[sizeobject94];
xx ← Float[IScaleLongInt[x]];
yy ← Float[IScaleLongInt[y]];
object94↑ ← UserObjectRecord[User94[x: xx, y: yy, name:]];
FOR i IN [0..name.length) DO
object94.name[i] ← name[i];
ENDLOOP;
IF (name.length MOD 2) # 0 THEN object94.name[name.length] ← NUL;
IUserObject[sizeobject94,object94];
FreeHeapNode[object94];
END;

GetName: PROCEDURE[string: STRING, ind: CARDINAL, name: STRING]
RETURNS[newind: CARDINAL] =
BEGIN
ch: CHARACTER;
newind ← ind;
name.length ← 0;
--leading space
WHILE newind<string.length AND ((ch←string[newind])=SP OR ch=TAB OR ch=’,) DO
newind←newind+1;
ENDLOOP;
--get name
WHILE newind<string.length AND ((ch←string[newind])#SP AND ch#TAB AND ch#’,) DO
AppendChar[name,ch];
newind←newind+1;
ENDLOOP;
END;

OutputUserObject: PUBLIC PROCEDURE [visible: VisibleType, layerName: CARDINAL,
size: CARDINAL, data: POINTER TO UNSPECIFIED] =
BEGIN
userObject: UserObject ← data;
WITH userObject SELECT FROM
User9 =>NULL;
User94 =>
{string: STRING ← [100];
i,stringlength: INTEGER;
xs,ys: REAL;
ps: Vec;
dc: DisplayContext ← GetDisplayContext[];
bc: DisplayContext ← GetBaseContext[];
stringlength ← (size-SIZE[User94 UserObjectRecord])*2;
FOR i IN [0..stringlength) DO
AppendChar[string,name[i]];
ENDLOOP;
IF stringlength>0 AND name[stringlength-1]=NUL THEN
string.length ← string.length - 1;
[xs,ys] ← RTransformPoint[x,y];
ps ← Graphics.Map[dc,bc, [xs,ys]];
OutputText[string,ps.x,ps.y];
};
ENDCASE => WriteLine["unknown user object"];
END;

-- in chip coordinates, not obvious how to call this yet
OutputBoundingBox: PUBLIC PROCEDURE [left,right,bottom,top: LONG INTEGER] =
BEGIN
DrawRectangleOutline[[left,bottom,right,top]];
END;

-- begin a new page of output
NewPlot: PUBLIC PROCEDURE =
BEGIN
END;

-- finish the current page of output
DonePlot: PUBLIC PROCEDURE =
BEGIN
EnableClipping[];
END;

-- specify window limits
OutWindow: PUBLIC PROCEDURE [left, right, bottom, top: LONG INTEGER] =
BEGIN
WindowLeft ← left;
WindowRight ← right;
WindowBottom ← bottom;
WindowTop ← top;
END;

SetSorting: PUBLIC PROCEDURE [sorting: SortType] =
BEGIN
Sorting ← sorting;
END;

Sorting: SortType ← none;

-- establish ordering
Relation: PUBLIC PROCEDURE [left1, right1, bottom1, top1,
left2, right2, bottom2, top2: LONG INTEGER]
RETURNS [RelationType] =
BEGIN
RETURN[SELECT Sorting FROM
incx=>SELECT left1 FROM
<left2=> rel,
=left2=>same,
ENDCASE=>norel,
decx=>SELECT right1 FROM
>right2=> rel,
=right2=>same,
ENDCASE=>norel,
incy=>SELECT bottom1 FROM
<bottom2=> rel,
=bottom2=>same,
ENDCASE=>norel,
decy=>SELECT top1 FROM
>top2=> rel,
=top2=>same,
ENDCASE=>norel,
ENDCASE => dontcare];
END;

-- determine whether an item should be shown
Visible: PUBLIC PROCEDURE [kind: IntStorageDefs.ObjectType, level: CARDINAL, parentVis: VisibleType,
left,right,bottom,top: LONG INTEGER] RETURNS [VisibleType] =
BEGIN
RETURN[
SELECT TRUE FROM
left>right OR bottom>top => no,
parentVis=yes => yes,
left>WindowLeft AND right<WindowRight AND
bottom>WindowBottom AND top<WindowTop => yes,
left<WindowRight AND right>WindowLeft AND
bottom<WindowTop AND top>WindowBottom => maybe,
ENDCASE => no];
END;

-- returns the bounding box of the primitive interpreted in the current context
BBWire: PUBLIC PROCEDURE [layerName: CARDINAL, width: LONG CARDINAL, a: ParserTypeDefs.Path]
RETURNS [left,right,bottom,top: LONG INTEGER] =
BEGIN
xT,yT: REAL;
p,prev: POINTER TO LinkedPoint;

IF a.length=0 THEN RETURN[0,0,0,0];
minmaxStarted ← FALSE;
prev ← NIL;
FOR p ← a.first, p.next UNTIL prev=a.last DO
[xT,yT] ← RTransformPoint[p.value.x,p.value.y];
UpdateMinMax[xT-width, xT+width, yT-width, yT+width]; -- +-halfw not enough
prev ← p;
ENDLOOP;
RETURN[FloorI[XMin],CeilingI[XMax],FloorI[YMin],CeilingI[YMax]];
END;

BBFlash: PUBLIC PROCEDURE [layerName: CARDINAL, diameter: LONG CARDINAL, center: ParserTypeDefs.Point]
RETURNS [left,right,bottom,top: LONG INTEGER] =
BEGIN
radius: REAL = diameter/2;
xT,yT: REAL;

[xT,yT] ← RTransformPoint[center.x,center.y];
RETURN[FloorI[xT-radius],CeilingI[xT+radius], FloorI[yT-radius],CeilingI[yT+radius]];
END;

BBPolygon: PUBLIC PROCEDURE [layerName: CARDINAL, a: ParserTypeDefs.Path]
RETURNS [left,right,bottom,top: LONG INTEGER] =
BEGIN
xT,yT: REAL;
p,prev: POINTER TO LinkedPoint;

IF a.length=0 THEN RETURN;
minmaxStarted ← FALSE;
prev ← NIL;
FOR p ← a.first, p.next UNTIL prev=a.last DO
[xT,yT] ← RTransformPoint[p.value.x,p.value.y];
UpdateMinMax[xT, xT, yT, yT];
prev ← p;
ENDLOOP;
RETURN[FloorI[XMin],CeilingI[XMax],FloorI[YMin],CeilingI[YMax]];
END;

BBBox: PUBLIC PROCEDURE [layerName: CARDINAL, length, width: LONG CARDINAL,
center: ParserTypeDefs.Point, xRotation, yRotation: LONG INTEGER]
RETURNS [left,right,bottom,top: LONG INTEGER] =
BEGIN
SELECT TRUE FROM
yRotation=0 =>[left,right,bottom,top] ← RBBBox[length, width, [center.x,center.y]];
xRotation=0 =>[left,right,bottom,top] ← RBBBox[width, length, [center.x,center.y]];
ENDCASE => BEGIN --accomplish rotation AND translation via transformations
Push[];
Rotate[xRotation,yRotation];
Translate[center.x,center.y];
[left,right,bottom,top] ← RBBBox[length, width, [0,0]];
Pop[];
END;
END;

RBBBox: PROCEDURE [length,width: REAL, center: RPoint]
RETURNS [left,right,bottom,top: LONG INTEGER] =
BEGIN
--Internal procedure for really dealing with boxes
--Rotations must have been dealt with either by interchanging coords or
-- by setting up the appropriate transformation.

lenR: REAL ← length/2; --depends on length and width being positive
widR: REAL ← width/2;
cx,cy: REAL;
lenT,widT: RPoint; --Transformed half length and Width

[cx,cy] ← RTransformPoint[center.x,center.y];
[widT.x,widT.y] ← RTransformVector[0,widR];
-- Check for "Manhattan" rectangles (common case)
SELECT TRUE FROM
widT.x=0 => RETURN[FloorI[cx-lenR],CeilingI[cx+lenR], FloorI[cy-widR],CeilingI[cy+widR]];
widT.y=0 => RETURN[FloorI[cx-widR],CeilingI[cx+widR], FloorI[cy-lenR],CeilingI[cy+lenR]];
ENDCASE =>
BEGIN
w,h: REAL;
[lenT.x,lenT.y] ← RTransformVector[lenR,0];
-- Arrange rectangle axes to have positive y
IF lenT.y<0 THEN lenT←[-lenT.x,-lenT.y];
IF widT.y<0 THEN widT←[-widT.x,-widT.y];
--We now have one of two similar configurations mirrored about y axis
w ← ABS[lenT.x-widT.x];
h ← ABS[lenT.y+widT.y];
RETURN[FloorI[cx-w],CeilingI[cx+w], FloorI[cy-h],CeilingI[cy+h]];
END;
END;

BBUserObject: PUBLIC PROCEDURE [layerName: CARDINAL,
size: CARDINAL, data: POINTER TO UNSPECIFIED]
RETURNS [left,right,bottom,top: LONG INTEGER] =
BEGIN
userObject: UserObject ← data;
WITH userObject SELECT FROM
User9 => --Meaningless
BEGIN
RETURN[1,0,1,0]; --no bounding box
END;
User94 =>
BEGIN
xi: LONG INTEGER ← Fix[x];
yi: LONG INTEGER ← Fix[y];
RETURN[xi,xi,yi,yi];
END;
ENDCASE =>
BEGIN
WriteLine["Unknown user object in BBUserObject"];
RETURN[0,0,0,0];
END;
END;


WindowLeft, WindowRight, WindowBottom, WindowTop: LONG INTEGER;

--the following procedure is exported to CIFDevicesDefs for use in CIFDevices
--should be done some other way in new CGraphics

--GetLastBB: PUBLIC PROCEDURE RETURNS [left,right,bottom,top: LONG INTEGER] =
--
BEGIN
--
RETURN[MAX[ObjectLeft,WindowLeft], MIN[ObjectRight,WindowRight],
--
MAX[ObjectBottom,WindowBottom], MIN[ObjectTop,WindowTop]];
--
END;


--Private procedures

minmaxStarted: BOOLEAN;
XMin,XMax,YMin,YMax: REAL;

UpdateMinMax: PROCEDURE [xmin,xmax,ymin,ymax: REAL] = INLINE
BEGIN
IF minmaxStarted
THENBEGIN
IF xmin<XMin THEN XMin ← xmin;
IF xmax>XMax THEN XMax ← xmax;
IF ymin<YMin THEN YMin ← ymin;
IF ymax>YMax THEN YMax ← ymax;
END
ELSEBEGIN
XMin ← xmin;
XMax ← xmax;
YMin ← ymin;
YMax ← ymax;
minmaxStarted ← TRUE;
END;
END;

Round: PROCEDURE [r: REAL] RETURNS[i: LONG INTEGER] = INLINE
BEGIN
RETURN[Fix[IF r<0 THEN r-0.5 ELSE r+0.5]];
END;

FloorI: PROCEDURE [r: REAL] RETURNS[i: LONG INTEGER] = INLINE
BEGIN
i ← Fix[r];
IF r<0 THEN i ← Fix[r-i+1] + i-1;
END;

CeilingI: PROCEDURE [r: REAL] RETURNS[i: LONG INTEGER] = INLINE
BEGIN
i ← Fix[r];
IF r>0 THEN i ← Fix[r-i-1] + i+1;
END;

END.