MathRulesImpl.mesa
Carl Waldspurger, August 30, 1986 4:35:33 pm PDT
DIRECTORY
MathBox,
MathTypes USING [Style],
Imager USING [Context],
ImagerFont USING [Extents],
Vector USING [VEC, Sub],
Rope USING [ROPE, Find, Substr, Cat],
Convert USING [RopeFromAtom, AtomFromRope, CardFromRope, RopeFromCard],
MathRules;
MathRulesImpl: CEDAR PROGRAM
IMPORTS MathBox, Vector, Convert, Rope
EXPORTS MathRules ~
BEGIN
Imported Type Abbreviations
VEC: TYPE ~ Vector.VEC;
ROPE: TYPE ~ Rope.ROPE;
BOX: TYPE ~ MathBox.BOX;
Style: TYPE ~ MathTypes.Style;
AtomBoxProc: TYPE ~ MathRules.AtomBoxProc;
AtomPaintProc: TYPE ~ MathRules.AtomPaintProc;
CompoundBoxProc: TYPE ~ MathRules.CompoundBoxProc;
CompositionProc: TYPE ~ MathRules.CompositionProc;
Alignment2D: TYPE ~ MathRules.Alignment2D;
Alignment: TYPE ~ MathRules.Alignment;
Offset: TYPE ~ MathRules.Offset;
TaggedOffset: TYPE ~ MathRules.TaggedOffset;
Size: TYPE ~ MathRules.Size;
Constants
smallGap: REAL = 0.05;
medGap: REAL = 0.1;
bigGap: REAL = 0.25;
matrixGap: REAL = 0.4;
Alignment Operations
AlignHorizontal: PUBLIC PROC[box1: ImagerFont.Extents, offset1: Offset, box2: BOX, offset2: Offset] RETURNS[REAL] ~ {
effects: Horizontally aligns box1 with box2 using specified offsets.
Returns x offset for box1.
displacement1, displacement2: REAL ← 0.0;
SELECT offset1.wrt FROM
left, bottom => displacement1 ← offset1.pos;
right, top => displacement1 ← offset1.pos + 1.0;
origin => displacement1 ← offset1.pos +
(box1.leftExtent / (box1.leftExtent + box1.rightExtent));
center => displacement1 ← offset1.pos + 0.5;
ENDCASE => ERROR;
SELECT offset2.wrt FROM
left, bottom => displacement2 ← offset2.pos;
right, top => displacement2 ← offset2.pos + 1.0;
origin => displacement2 ← offset2.pos +
(box2.Extents[].leftExtent / box2.Width[]);
center => displacement2 ← offset2.pos + 0.5;
ENDCASE => ERROR;
RETURN[MathBox.AlignHorizontal[box1, displacement1, box2, displacement2]];
};
AlignVertical: PUBLIC PROC[box1: ImagerFont.Extents, offset1: Offset, box2: BOX, offset2: Offset] RETURNS[REAL] ~ {
effects: Vertically aligns box1 with box2 using specified offsets.
Returns y offset for box1.
displacement1, displacement2: REAL;
SELECT offset1.wrt FROM
left, bottom => displacement1 ← offset1.pos;
right, top => displacement1 ← offset1.pos + 1.0;
origin => displacement1 ← offset1.pos +
(box1.descent / (box1.descent + box1.ascent));
center => displacement1 ← offset1.pos + 0.5;
ENDCASE => ERROR;
SELECT offset2.wrt FROM
left, bottom => displacement2 ← offset2.pos;
right, top => displacement2 ← offset2.pos + 1.0;
origin => displacement2 ← offset2.pos +
(box2.Extents[].descent / box2.Height[]);
center => displacement2 ← offset2.pos + 0.5;
ENDCASE => ERROR;
RETURN[MathBox.AlignVertical[box1, displacement1, box2, displacement2]];
};
Automated Layout Composition
Compose: PUBLIC PROC[boxes: LIST OF BOX, alignments: LIST OF Alignment2D, hOrigin, vOrigin: TaggedOffset] RETURNS[BOX, LIST OF BOX] ~ {
effects: Performs the layout composition for boxes using alignments.
Returns information expected from a CompositionProc.
local declarations
relBoxes: LIST OF BOXNIL; -- cons up return values; relative units
myBox, myRelBox: BOX; -- box which encloses entire expression
myExtents: ImagerFont.Extents;
tempBox, attachHRelBox, attachVRelBox: BOX;
farLeft, farRight, farUp, farDown: REAL ← 0.0;
scaleVec, originOffset: VEC ← [0.0, 0.0];
dummyPtExtents: ImagerFont.Extents ← [leftExtent: 0.0, rightExtent: 0.001, descent: 0.0, ascent: 0.001]; -- null-sized extents used for alignment purposes
should really ENABLE boxNotFound => ERROR unable
local procedures
LocalScale: PROC[b: BOX] RETURNS[BOX] ~ {
scaledBox: BOX ← MathBox.Scale[b, scaleVec];
RETURN[MathBox.ChangeType[scaledBox, relative]];
};
LocalFixOffset: PROC[b: BOX] RETURNS[BOX] ~ {
RETURN[MathBox.ChangeOffset[b, Vector.Sub[b.Offset[], originOffset]]];
};
IF alignments = NIL THEN ERROR unable[$noAlignments];
n.b. input boxes are in absolute units (boxes), output boxes are in relative units (relBoxes)
but we are still working in real absolute units here
get first referenced box & save it as the initial (temporary) origin
tempBox ← MathBox.GetBox[alignments.first.hAttach.tag, boxes];
relBoxes ← CONS[MathBox.ChangeOffset[tempBox, [0.0, 0.0]], relBoxes];
compute offset locations in absolute (n.b. not relative) units
FOR l: LIST OF Alignment2D ← alignments, l.rest UNTIL l = NIL DO
tempBox ← MathBox.GetBox[l.first.tag, boxes];
attachHRelBox ← MathBox.GetBox[l.first.hAttach.tag, relBoxes];
attachVRelBox ← MathBox.GetBox[l.first.vAttach.tag, relBoxes];
align tempBox to attach H&V boxes and save it
relBoxes ← CONS[MathBox.ChangeOffset[tempBox, [AlignHorizontal[tempBox.Extents[], l.first.hAttach.offset1, attachHRelBox, l.first.hAttach.offset2], AlignVertical[tempBox.Extents[], l.first.vAttach.offset1, attachVRelBox, l.first.vAttach.offset2]]], relBoxes];
ENDLOOP;
find extreme edge points to determine myBox size
FOR l: LIST OF BOX ← relBoxes, l.rest UNTIL l = NIL DO
farLeft ← MIN[farLeft, l.first.Offset[].x - l.first.Extents[].leftExtent];
farRight ← MAX[farRight, l.first.Offset[].x + l.first.Extents[].rightExtent];
farDown ← MIN[farDown, l.first.Offset[].y - l.first.Extents[].descent];
farUp ← MAX[farUp, l.first.Offset[].y + l.first.Extents[].ascent];
ENDLOOP;
set myBox dimensions in REAL units
myBox ← MathBox.MakeBox[$composeMyBox, NIL, other, absolute, [leftExtent: -farLeft, rightExtent: farRight, descent: -farDown, ascent: farUp]];
scale the absolute boxes => relative boxes (extents & offsets)
scaleVec ← [1 / myBox.Width[], 1/ myBox.Height[]];
relBoxes ← MathBox.MapBoxList[relBoxes, LocalScale];
myRelBox ← MathBox.Scale[myBox, scaleVec];
compute relative origin location
IF hOrigin.tag = $self THEN {
attachHRelBox ← myRelBox;
}
ELSE {
attachHRelBox ← MathBox.GetBox[hOrigin.tag, relBoxes];
};
IF vOrigin.tag = $self THEN {
attachVRelBox ← myRelBox;
}
ELSE {
attachVRelBox ← MathBox.GetBox[vOrigin.tag, relBoxes];
};
originOffset ← [
AlignHorizontal[dummyPtExtents, [origin], attachHRelBox, hOrigin.offset],
AlignVertical[dummyPtExtents, [origin], attachVRelBox, vOrigin.offset]
];
adjust all relative box offsets by originOffset
relBoxes ← MathBox.MapBoxList[relBoxes, LocalFixOffset];
adjust myRelBox w.r.t new origin
myExtents ← myRelBox.Extents[];
myRelBox ← MathBox.ChangeExtents[myRelBox, [leftExtent: myExtents.leftExtent + originOffset.x, rightExtent: myExtents.rightExtent - originOffset.x, descent: myExtents.descent + originOffset.y, ascent: myExtents.ascent - originOffset.y]];
myBox ← MathBox.Scale[myRelBox, [myBox.Width[], myBox.Height[]]];
RETURN[myBox, relBoxes];
};
AtomFromRowCol: PUBLIC PROC[row, col: NAT] RETURNS[ATOM] ~ {
effects: Returns the atom $r{row}c{col}
rowRope: ROPE ← Convert.RopeFromCard[row];
colRope: ROPE ← Convert.RopeFromCard[col];
RETURN[Convert.AtomFromRope[Rope.Cat["r", rowRope, "c", colRope]]];
};
RowColFromAtom: PUBLIC PROC[rc: ATOM] RETURNS[NAT, NAT] ~ {
requires: rc is of the form $r#c#
effects: Returns the row and column indexes associated with $r#c#
SIGNALS badFormat if rc does not conform to the format $r#c#
local declarations
rcRope: ROPE ← Convert.RopeFromAtom[from: rc, quote: FALSE];
rPos: INT ← Rope.Find[rcRope, "r"];
cPos: INT ← Rope.Find[rcRope, "c"];
row, col: NAT ← 0;
IF (cPos = -1) OR (rPos = -1) OR (cPos < rPos) THEN ERROR badFormat;
row ← Convert.CardFromRope[Rope.Substr[rcRope, rPos+1, (cPos - (rPos+1))]];
col ← Convert.CardFromRope[Rope.Substr[rcRope, cPos+1]];
RETURN[row, col];
};
ComposeMatrix: PUBLIC PROC[nRows, nCols: NAT, boxes: LIST OF BOX, spaceBox, openSymBox, closeSymBox: BOX] RETURNS[BOX, LIST OF BOX, BOX, BOX] ~ {
effects: Performs the layout composition for a generalized matrix.
Returns information expected by Format, preserving input row&col orderings.
type declarations
NatSeq: TYPE ~ REF NatSeqRec;
NatSeqRec: TYPE ~ RECORD[
elements: SEQUENCE size: NAT OF NAT
];
Element: TYPE ~ RECORD [
box: BOXNIL
];
Row: TYPE ~ REF RowRec;
RowRec: TYPE ~ RECORD [
elements: SEQUENCE numberOfCols: NAT OF Element
];
Matrix: TYPE ~ REF MatrixRec;
MatrixRec: TYPE ~ RECORD[
rows: SEQUENCE numberOfRows: NAT OF Row
];
local declatations
tallest: NatSeq ← NEW[NatSeqRec[nRows]];
widest: NatSeq ← NEW[NatSeqRec[nCols]];
a: Matrix;
myBox, myRelBox: BOX;
relBoxes: LIST OF BOXNIL;
maxHeight, maxWidth, scaleFactor, yOffset: REAL ← 0.0;
currentRow, currentCol: NAT ← 0;
tempElt: Element;
farLeft, farRight, farUp, farDown: REAL ← 0.0;
scaleVec, originOffset: VEC ← [0.0, 0.0];
myExtents: ImagerFont.Extents;
closeBox, openBox: BOX;
horizSpace: BOX ← spaceBox; -- inter-column spacing
vertSpace: BOX ← spaceBox; -- inter-row spacing
convert boxes into a SEQUENCE (ugh!) matrix type
instantiate matrix "a" (isn't CLU's array[]$create() much nicer, really?)
a ← NEW[MatrixRec[nRows]];
FOR row: NAT IN [0..nRows) DO
a[row] ← NEW[RowRec[nCols]];
ENDLOOP;
FOR elements: LIST OF BOX ← boxes, elements.rest UNTIL elements = NIL DO
[currentRow, currentCol] ← RowColFromAtom[elements.first.Tag[]];
a[currentRow - 1][currentCol - 1].box ← elements.first;
ENDLOOP;
mark tallest elt in each row
FOR r:NAT IN [0..nRows) DO
tallest[r] ← 0;
maxHeight ← 0.0;
FOR c:NAT IN [0..nCols) DO
IF a[r][c].box = NIL THEN ERROR unable[$missingElement]; -- missing r,c element
IF a[r][c].box.Height[] > maxHeight THEN {
tallest[r] ← c;
maxHeight ← a[r][c].box.Height[];
};
ENDLOOP;
ENDLOOP;
mark widest elt in each row
FOR c:NAT IN [0..nCols) DO
widest[c] ← 0;
maxWidth ← 0.0;
FOR r:NAT IN [0..nRows) DO
IF a[r][c].box.Width[] > maxWidth THEN {
widest[c] ← r;
maxWidth ← a[r][c].box.Width[];
};
ENDLOOP;
ENDLOOP;
vertically align tallest elts, row by row
a[0][tallest[0]].box ← MathBox.ChangeOffset[a[0][tallest[0]].box, [0.0, 0.0]]; -- set vOrigin
FOR r:NAT IN [1..nRows) DO
tempElt ← a[r][tallest[r]]; -- just shorthand
align a vertical space between each row
vertSpace ← MathBox.ChangeOffset[vertSpace, [0.0, AlignVertical[vertSpace.Extents[], [top], a[r-1][tallest[r-1]].box, [bottom]]]];
a[r][tallest[r]].box ← MathBox.ChangeOffset[tempElt.box, [0.0, AlignVertical[tempElt.box.Extents[], [top], vertSpace, [bottom]]]];
ENDLOOP;
vertically align all other (shorter) elts row by row
FOR r:NAT IN [0..nRows) DO
FOR c:NAT IN [0..nCols) DO
IF c # tallest[r] THEN
a[r][c].box ← MathBox.ChangeOffset[a[r][c].box, [0.0, AlignVertical[a[r][c].box.Extents[], [origin], a[r][tallest[r]].box, [origin]]]];
ENDLOOP;
ENDLOOP;
horizontally align widest elts, col by col
a[widest[0]][0].box ← MathBox.ChangeOffset[a[widest[0]][0].box, [0.0, a[widest[0]][0].box.Offset[].y]]; -- set horizontal origin
FOR c:NAT IN [1..nCols) DO
tempElt ← a[widest[c]][c]; -- just shorthand
align a horizSpace between each column
horizSpace ← MathBox.ChangeOffset[horizSpace, [AlignHorizontal[horizSpace.Extents[], [left], a[widest[c-1]][c-1].box, [right]], 0.0]]; -- punt vertical offset
a[widest[c]][c].box ← MathBox.ChangeOffset[tempElt.box, [AlignHorizontal[tempElt.box.Extents[], [left], horizSpace, [right]], tempElt.box.Offset[].y]];
ENDLOOP;
horizontally align all other (narrower) elts col by col
FOR c:NAT IN [0..nCols) DO
FOR r:NAT IN [0..nRows) DO
IF r # widest[c] THEN
a[r][c].box ← MathBox.ChangeOffset[a[r][c].box, [AlignHorizontal[a[r][c].box.Extents[], [center], a[widest[c]][c].box, [center]], a[r][c].box.Offset[].y]];
ENDLOOP;
ENDLOOP;
now find extreme edge points to determine myBox size
FOR r:NAT IN [0..nRows) DO
FOR c:NAT IN [0..nCols) DO
farLeft ← MIN[farLeft, a[r][c].box.Offset[].x - a[r][c].box.Extents[].leftExtent];
farRight ← MAX[farRight, a[r][c].box.Offset[].x + a[r][c].box.Extents[].rightExtent];
farDown ← MIN[farDown, a[r][c].box.Offset[].y - a[r][c].box.Extents[].descent];
farUp ← MAX[farUp, a[r][c].box.Offset[].y + a[r][c].box.Extents[].ascent];
ENDLOOP;
ENDLOOP;
preliminary "myBox" for aligning open and close symbols
myBox ← MathBox.MakeBox[$composeMatrixBox, NIL, other, absolute, [leftExtent: -farLeft, rightExtent: farRight, descent: -farDown, ascent: farUp]];
magnify (don't allow too much shrinkage - using hardcoded #s here is a kludge,
but I have a plane to catch...) openSymBox and closeSymBox boxes (absolute boxes)
scaleFactor ← MAX[0.25, ((1 + medGap) * (-farDown + farUp)) / openSymBox.Height[]];
openBox ← MathBox.Scale[openSymBox, [scaleFactor, scaleFactor]];
scaleFactor ← MAX[0.25, ((1 + medGap) * (-farDown + farUp)) / closeSymBox.Height[]];
closeBox ← MathBox.Scale[closeSymBox, [scaleFactor, scaleFactor]];
align openBox and closeBox
align using myBox info
openBox ← MathBox.ChangeOffset[openBox, [
AlignHorizontal[openBox.Extents[], [right, medGap], myBox, [left]],
AlignVertical[openBox.Extents[], [center], myBox, [center]]]];
closeBox ← MathBox.ChangeOffset[closeBox, [
AlignHorizontal[closeBox.Extents[], [left, -medGap], myBox, [right]],
AlignVertical[closeBox.Extents[], [center], myBox, [center]]]];
recompute extreme points
farLeft ← openBox.Offset[].x - openBox.Extents[].leftExtent;
farRight ← closeBox.Offset[].x + closeBox.Extents[].rightExtent;
farUp ← openBox.Offset[].y + openBox.Extents[].ascent;
farDown ← openBox.Offset[].y - openBox.Extents[].descent;
myBox ← MathBox.MakeBox[$composeMatrixBox, NIL, other, absolute, [leftExtent: -farLeft, rightExtent: farRight, descent: -farDown, ascent: farUp]];
scale the absolute boxes => relative boxes (extents & offsets)
scaleVec ← [1 / myBox.Width[], 1/ myBox.Height[]];
FOR r:NAT IN [0..nRows) DO
FOR c:NAT IN [0..nCols) DO
scaledBox: BOX ← MathBox.Scale[a[r][c].box, scaleVec];
a[r][c].box ← MathBox.ChangeType[scaledBox, relative];
ENDLOOP;
ENDLOOP;
myRelBox ← MathBox.Scale[myBox, scaleVec];
openBox ← MathBox.Scale[openBox, scaleVec];
openBox ← MathBox.ChangeType[openBox, relative];
closeBox ← MathBox.Scale[closeBox, scaleVec];
closeBox ← MathBox.ChangeType[closeBox, relative];
compute relative origin location
let origin be thru center; find center using dummy alignment extents
yOffset ← AlignVertical[[leftExtent: 0.0, rightExtent: 0.1, descent: 0.0, ascent: 0.1], [origin], openBox, [center]];
originOffset ← [0.0, yOffset];
adjust all relative boxes by origin offset amount
FOR r:NAT IN [0..nRows) DO
FOR c:NAT IN [0..nCols) DO
a[r][c].box ← MathBox.ChangeOffset[a[r][c].box, Vector.Sub[a[r][c].box.Offset[], originOffset]];
ENDLOOP;
ENDLOOP;
openBox ← MathBox.ChangeOffset[openBox, Vector.Sub[openBox.Offset[], originOffset]];
closeBox ← MathBox.ChangeOffset[closeBox, Vector.Sub[closeBox.Offset[], originOffset]];
adjust myRelBox w.r.t new origin
myExtents ← myRelBox.Extents[];
myRelBox ← MathBox.ChangeExtents[myRelBox, [leftExtent: myExtents.leftExtent + originOffset.x, rightExtent: myExtents.rightExtent - originOffset.x, descent: myExtents.descent + originOffset.y, ascent: myExtents.ascent - originOffset.y]];
myBox ← MathBox.Scale[myRelBox, [myBox.Width[], myBox.Height[]]];
reconstruct LIST OF BOX for return value (from MATRIX sequence structure)
relBoxes ← NIL; -- cons up new list from matrix a
FOR r: NAT IN [0..nRows) DO
FOR c: NAT IN [0..nCols) DO
relBoxes ← CONS[a[r][c].box, relBoxes];
ENDLOOP;
ENDLOOP;
RETURN[myBox, relBoxes, openBox, closeBox];
};
Size Operations
ComputeSize: PUBLIC PROC[base, adjustment: Size] RETURNS[Size] ~ {
effects: Returns the size of adjustment applied to base.
SELECT base FROM
normal => RETURN[adjustment];
script => {
SELECT adjustment FROM
normal => RETURN[script];
script, scriptscript => RETURN[scriptscript];
big => RETURN[normal];
ENDCASE => ERROR;
};
scriptscript => {
SELECT adjustment FROM
normal, script, scriptscript => RETURN[scriptscript];
big => RETURN[script];
ENDCASE => ERROR;
};
big => {
SELECT adjustment FROM
normal, big => RETURN[big];
script => RETURN[normal];
scriptscript => RETURN[script];
ENDCASE => ERROR;
};
ENDCASE => ERROR;
};
VecFromSize: PUBLIC PROC[size: Size] RETURNS[VEC] ~ {
effects: Returns scaling vector corresponding to size (e.g. normal = unit vec)
SELECT size FROM
normal => RETURN[[1.0, 1.0]];
script => RETURN[[0.6, 0.6]];
scriptscript => RETURN[[0.36, 0.36]];
big => RETURN[[1.5, 1.5]];
ENDCASE => ERROR;
};
Signals & Errors
unable: PUBLIC ERROR[reason: ATOM] = CODE;
badFormat: PUBLIC ERROR = CODE;
END.