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 => displacement1 ← offset1.pos;
right => displacement1 ← offset1.pos + 1.0;
bottom, top => ERROR; -- horizontal alignment with bottom or top is bogus
origin => displacement1 ← offset1.pos +
(box1.leftExtent / (box1.leftExtent + box1.rightExtent));
center => displacement1 ← offset1.pos + 0.5;
ENDCASE => ERROR;
SELECT offset2.wrt FROM
left => displacement2 ← offset2.pos;
right => displacement2 ← offset2.pos + 1.0;
bottom, top => ERROR; -- horizontal alignment with bottom or top is bogus
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
bottom => displacement1 ← offset1.pos;
top => displacement1 ← offset1.pos + 1.0;
left, right => ERROR; -- vertical alignment with left or right is bogus
origin => displacement1 ← offset1.pos +
(box1.descent / (box1.descent + box1.ascent));
center => displacement1 ← offset1.pos + 0.5;
ENDCASE => ERROR;
SELECT offset2.wrt FROM
bottom => displacement2 ← offset2.pos;
top => displacement2 ← offset2.pos + 1.0;
left, right => ERROR; -- vertical alignment with left or right is bogus
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
dsa - get the first box that we say we want to align with respect to, and make its lower left hand corner the origin of absolute coordinates
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]];
dsa - note that since we made the lower left hand corner of tempbox the origin of absolute coordinates above, farLeft <= 0.0 and farDown<= 0.0, hence all extents above will get set to nonnegative values.
dsa - note that we accept the default offset of [0.0, 0.0]
scale the absolute boxes => relative boxes (extents & offsets)
dsa - the scaling of offsets that MathBox.Scale will do is ok since the offsets of all the relBoxes have been changed to be with respect to our new origin of coordinates, and that myRelBox has offset of [0.0, 0.0]
scaleVec ← [1 / myBox.Width[], 1/ myBox.Height[]];
relBoxes ← MathBox.MapBoxList[relBoxes, LocalScale];
myRelBox ← MathBox.Scale[myBox, scaleVec];
dsa - n.b. myRelBox is an absolute box
compute relative origin location
dsa - why do we do this?
dsa - presumably the new origin will be somewhere inside myRelBox?
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 DECREASING IN [0..nRows) DO
FOR c: NAT DECREASING 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.