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 BOX ← NIL; -- 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];
};
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: BOX ← NIL
];
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 BOX ← NIL;
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];
};