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];
};