Box Makers
NoNameBoxMaker:
PROC [first, last: TextNode.Location, train: Train, bounds: Imager.
VEC ← arbBounds]
RETURNS [myBox: TiogaImager.Box ← TiogaImager.EmptyBox[[2, 0]]] ~ {
Ordinary old box from node
screenStyle: BOOL ← train.styleOps # print;
IF first.node = last.node
THEN {
Try a one-liner, which ought to be very common . . .
nodeStyle: NodeStyle.Ref ← NodeStyleOps.Alloc[];
NodeStyleOps.ApplyAll[nodeStyle, first.node, train.styleOps];
myBox ← TiogaImager.FormatLine[[first.node, 0], bounds.x, nodeStyle, screenStyle];
NodeStyleOps.Free[nodeStyle];
IF myBox.nChars = TextEdit.Size[first.node]
THEN {
Whew! It all fit. Bounding box can be tight easily.
myBox.bounds ← TEditFormat.BoundingBox[NARROW[myBox.data]];
myBox.escapement ← [myBox.bounds.xmax, 0];
RETURN;
};
};
Looks like a one-liner won't do it. Return sloppy bounding box. Sigh.
{
myFN: TiogaImager.FormattedNodes ← IF first.node # NIL
THEN TiogaImager.FormatNodes[
start: [first.node, 0], bounds: bounds, screenStyle: screenStyle]
ELSE [TiogaImager.EmptyBox[], TextNode.MakeNodeLoc[NIL]];
subBoxList: LIST OF TiogaImager.Box ← TiogaImager.UnBox[myBox ← myFN.box];
vshift: REAL ← IF subBoxList # NIL THEN -subBoxList.first.escapement.y ELSE 0;
myBox.escapement ← [myBox.bounds.xmax, 0];
myBox ← myBox.ModifyBox[ImagerTransformation.Translate[[0, vshift]]];
RETURN;
};
};
TripileBoxMaker:
PROC [first, last: TextNode.Location, train: Train, bounds: Imager.
VEC ← arbBounds]
RETURNS [triBox: TiogaImager.Box] ~ {
Attempting a composite box!! args are "upper=U center=C lower=L"
default: BoxCharData ~ emptyBoxChar;
upper: BoxCharData ← HoboArgumentBox[first, last, "upper", default].val;
center: BoxCharData ← HoboArgumentBox[first, last, "center", default].val;
lower: BoxCharData ← HoboArgumentBox[first, last, "lower", default].val;
upperGap: REAL ← HoboArgumentReal[first, last, "upperGap", 0.0].val;
lowerGap: REAL ← HoboArgumentReal[first, last, "lowerGap", 0.0].val;
upperBox: TiogaImager.Box ← BoxInTrain[upper, train];
centerBox: TiogaImager.Box ← BoxInTrain[center, train];
lowerBox: TiogaImager.Box ← BoxInTrain[lower, train];
ub, cb, lb: Imager.Box;
uBox, cBox, lBox: TiogaImager.Box;
[uBox, ub] ← TopCenterBox[upperBox];
uBox.escapement.y ← uBox.escapement.y-upperGap;
[cBox, cb] ← TopCenterBox[centerBox];
cBox.escapement.y ← cBox.escapement.y-lowerGap;
[lBox, lb] ← TopCenterBox[lowerBox];
triBox ← TiogaImager.BoxFromList[LIST[uBox, cBox, lBox]];
triBox ← triBox.ModifyBox[
ImagerTransformation.Translate[[-triBox.bounds.xmin, cb.ymax-uBox.escapement.y]]];
triBox.escapement ← [triBox.bounds.xmax, 0];
};
TopCenterBox:
PROC [box: TiogaImager.Box]
RETURNS [tcBox: TiogaImager.Box, bds: Imager.Box] ~ {
bds ← box.bounds;
tcBox ← box.ModifyBox[
ImagerTransformation.Translate[[-(bds.xmin+bds.xmax)/2, -bds.ymax]]];
tcBox.escapement ← [0, bds.ymin-bds.ymax];
};
RootBoxMaker:
PROC [first, last: TextNode.Location, train: Train, bounds: Imager.
VEC ← arbBounds]
RETURNS [fractionBox: TiogaImager.Box]
~ {
default: BoxCharData ~ emptyBoxChar;
radicandData: BoxCharData ← HoboArgumentBox[first, last, "radicand", default].val;
rootData: BoxCharData ← HoboArgumentBox[first, last, "root", default].val;
barGap: REAL ← HoboArgumentReal[first, last, "barGap", 2.0].val;
barThickness: REAL ← HoboArgumentReal[first, last, "barThickness", 0.72].val;
radicandBox: TiogaImager.Box ← BoxInTrain[radicandData, train];
rootBox: TiogaImager.Box ← BoxInTrain[rootData, train];
grid: Grid ← EmptyGrid[];
ExtendGraph[grid, [2, 4], 2*barGap+barThickness, 0];
GridInsertEntry[grid, [0, 3], [2, 4], [level: 'n, plumb: 'c], radicandBox];
GridInsertEntry[grid, [1, 0], [2, 1], [level: 'n, plumb: 'c], rootBox];
GridInsertSep[grid, [1, 0], [1, 1], barThickness];
grid.plumbBase ← NIL;
grid.levelBase ← LineName[0, "0NL2"];
fractionBox ← BoxFromGrid[grid];
};
FractionBoxMaker:
PROC [first, last: TextNode.Location, train: Train, bounds: Imager.
VEC ← arbBounds]
RETURNS [fractionBox: TiogaImager.Box]
~ {
default: BoxCharData ~ emptyBoxChar;
numerData: BoxCharData ← HoboArgumentBox[first, last, "numer", default].val;
denomData: BoxCharData ← HoboArgumentBox[first, last, "denom", default].val;
barGap: REAL ← HoboArgumentReal[first, last, "barGap", 2.0].val;
numerOffset: REAL ← HoboArgumentReal[first, last, "numerOffset", 4.0].val;
denomOffset: REAL ← HoboArgumentReal[first, last, "denomOffset", 8.0].val;
barThickness: REAL ← HoboArgumentReal[first, last, "barThickness", 0.72].val;
axisHeight: REAL ← HoboArgumentReal[first, last, "axisHeight", GetAxisHeight[train]].val;
numerBox: TiogaImager.Box ← BoxInTrain[numerData, train];
denomBox: TiogaImager.Box ← BoxInTrain[denomData, train];
grid: Grid ← EmptyGrid[];
ExtendGraph[grid, [2, 1], 2*barGap+barThickness, 0];
GridInsertEntry[grid, [0, 0], [1, 1], [level: 'n, plumb: 'c], numerBox];
GridInsertEntry[grid, [1, 0], [2, 1], [level: 'n, plumb: 'c], denomBox];
[] ← SpreadSpan[grid, LineName[0, "NL", 1], LineName[1, "SL"], NIL, numerOffset];
[] ← SpreadSpan[grid, LineName[1, "SL"], LineName[1, "NL", 2], NIL, denomOffset];
GridInsertSep[grid, [1, 0], [1, 1], barThickness];
[] ← SpreadSpan[grid, LineName[1, "SL"], LineName[0, "BASE"], NIL, axisHeight];
grid.plumbBase ← NIL;
grid.levelBase ← LineName[0, "BASE"];
fractionBox ← BoxFromGrid[grid];
};
LimitBoxMaker:
PROC [first, last: TextNode.Location, train: Train, bounds: Imager.
VEC ← arbBounds]
RETURNS [limitsBox: TiogaImager.Box]
~ {
default: BoxCharData ~ emptyBoxChar;
upperData: BoxCharData ← HoboArgumentBox[first, last, "upper", default].val;
operatorData: BoxCharData ← HoboArgumentBox[first, last, "operator", default].val;
lowerData: BoxCharData ← HoboArgumentBox[first, last, "lower", default].val;
gap: REAL ← HoboArgumentReal[first, last, "gap", 2.0].val;
upperOffset: REAL ← HoboArgumentReal[first, last, "upperOffset", 4.0].val;
lowerOffset: REAL ← HoboArgumentReal[first, last, "lowerOffset", 8.0].val;
axisHeight: REAL ← HoboArgumentReal[first, last, "axisHeight", GetAxisHeight[train]].val;
upperBox: TiogaImager.Box ← BoxInTrain[upperData, train];
operatorBox: TiogaImager.Box ← BoxInTrain[operatorData, train];
lowerBox: TiogaImager.Box ← BoxInTrain[lowerData, train];
grid: Grid ← EmptyGrid[];
ExtendGraph[grid, [3, 1], gap, 0];
GridInsertEntry[grid, [0, 0], [1, 1], [level: 'n, plumb: 'c], upperBox];
GridInsertEntry[grid, [1, 0], [2, 1], [level: 'n, plumb: 'c], operatorBox];
GridInsertEntry[grid, [2, 0], [3, 1], [level: 'n, plumb: 'c], lowerBox];
[] ← SpreadSpan[grid, LineName[0, "NL", 1], LineName[1, "NL", 2], NIL, upperOffset];
[] ← SpreadSpan[grid, LineName[1, "NL", 2], LineName[2, "NL", 3], NIL, lowerOffset];
grid.plumbBase ← NIL;
grid.levelBase ← LineName[1, "NL", 2];
limitsBox ← BoxFromGrid[grid];
};
BarBoxMaker:
PROC [first, last: TextNode.Location, train: Train, bounds: Imager.
VEC ← arbBounds]
RETURNS [myBox: TiogaImager.Box ← TiogaImager.EmptyBox[[2, 0]]] ~ {
height: REAL ← HoboArgumentReal[first, last, "height", 1.0].val;
width: REAL ← HoboArgumentReal[first, last, "width", 1.0].val;
myBox ←
NEW[TiogaImager.BoxRep ← [
nChars: 0,
bounds: [xmin: 0, ymin: -height, xmax: width, ymax: 0],
expansion: [0, 0],
escapement: [width, 0],
stretch: [0, 0],
shrink: [0, 0],
class: barClass,
data: NEW[BarDataRep ← [width, height]]
]];
};
BarRender:
PROC [box: TiogaImager.Box, context: Imager.Context, position: Imager
.VEC] ~ {
DoBar:
PROC ~ {
context.SetXY[position];
context.StartUnderline[];
context.SetXRel[data.width];
context.MaskUnderline[dy: 0, h: data.height];
};
data: BarData ← NARROW[box.data];
context.DoSaveAll[DoBar];
};
AxisCenteredBoxMaker:
PROC [first, last: TextNode.Location, train: Train, bounds: Imager.
VEC ← arbBounds]
RETURNS [myBox: TiogaImager.Box ← TiogaImager.EmptyBox[[2, 0]]] ~ {
default: BoxCharData ~ emptyBoxChar;
axisHeight: REAL ← HoboArgumentReal[first, last, "axisHeight", GetAxisHeight[train]].val;
center: REAL;
myBox ← BoxInTrain[HoboArgumentBox[first, last, "arg", default].val, train];
center ← (myBox.bounds.ymax + myBox.bounds.ymin)/2;
myBox ← myBox.ModifyBox[ImagerTransformation.Translate[[0, axisHeight-center]]];
};
GetAxisHeight:
PROC [train: Train]
RETURNS [axisHeight:
REAL ← 0.0] ~ {
font: ImagerFont.Font ← TEditFormat.GetFont[train.style];
eqExtents: ImagerFont.Extents ← font.RopeBoundingBox["="];
axisHeight ← (eqExtents.ascent+eqExtents.descent)/2 - eqExtents.descent;
};
GridBoxMaker:
PROC [first, last: TextNode.Location, train: Train, bounds: Imager.
VEC ← arbBounds]
RETURNS [myBox: TiogaImager.Box ← TiogaImager.EmptyBox[[2, 0]]] ~ {
For matrices and tables. Takes a series of boxes, and aligns them both ways.
default: BoxCharData ~ emptyBoxChar;
gridEntry: BoxCharData;
base: ATOM ~ Atom.EmptyAtom[];
plumbBase: ROPE ← Atom.GetPName[HoboArgumentName[first, last, "plumbBase", base].val];
levelBase: ROPE ← Atom.GetPName[HoboArgumentName[first, last, "levelBase", base].val];
verticalSkip: REAL ← HoboArgumentReal[first, last, "verticalSkip", 32].val;
verticalGap: REAL ← HoboArgumentReal[first, last, "verticalGap", 3].val;
horizontalSkip: REAL ← HoboArgumentReal[first, last, "horizontalSkip", 20].val;
horizontalGap: REAL ← HoboArgumentReal[first, last, "horizontalGap", 3].val;
defaultBarThickness: REAL ← HoboArgumentReal[first, last, "defaultBarThickness", 0].val;
barThickness: REAL ← 0.0;
defaultFillColor: Imager.Color ← NIL;
fillColor: Imager.Color;
rowStep: INT ← HoboArgumentInt[first, last, "rowStep", 0].val;
colStep: INT ← HoboArgumentInt[first, last, "colStep", 1].val;
rowNow, colNow: INT ← 0;
rowOpp, colOpp: INT ← 1;
found, foundOther: BOOL ← FALSE;
lineup: FeaturePair ← ['n, 'n];
loc, nextLoc, endLoc: TextNode.Location;
grid: Grid ← EmptyGrid[];
GetNextEntry:
PROC
RETURNS[
BOOL] ~ {
size: INT;
[found, loc.node, loc.where] ← HoboArgumentFind[nextLoc, endLoc, ""];
IF NOT found THEN RETURN[FALSE];
[[rowNow, colNow], [rowOpp, colOpp], lineup]
← GetCorners[[rowNow, colNow], [rowOpp, colOpp], rowStep, colStep, loc];
size ← TextEdit.Size[loc.node];
IF (foundOther ← lineup.plumb = 's
OR lineup.plumb = 'f)
THEN {
gridEntry ← NIL;
IF TextEdit.Size[loc.node] > loc.where
THEN {
nextLoc ← [node: loc.node, where: loc.where+1];
SELECT lineup.plumb
FROM
's => {
barThickness ← Convert.RealFromRope[
loc.node.NodeRope[].Substr[loc.where, size-1]
! Convert.Error => {CONTINUE}];
};
'f => {
gray: REAL ← 0.25;
gray ← Convert.RealFromRope[
loc.node.NodeRope[].Substr[loc.where, size-1]
! Convert.Error => {CONTINUE}];
fillColor ← Imager.MakeGray[gray];
};
ENDCASE;
}
ELSE {
IF loc.node.last
THEN nextLoc ← endLoc
ELSE nextLoc ← [node: loc.node.next, where: 0];
};
}
ELSE {
IF TextEdit.Size[loc.node] > loc.where
THEN {
nextLoc ← [node: loc.node, where: loc.where+1];
gridEntry ← GetDataFromLoc[loc.node, loc.where];
}
ELSE {
IF loc.node.last
THEN nextLoc ← endLoc
ELSE nextLoc ← [node: loc.node.next, where: 0];
IF TextNode.FirstChild[loc.node] #
NIL
THEN {
argSpan: TextNode.Span ← TextNode.MakeNodeSpan[
TextNode.FirstChild[loc.node], TextNode.LastWithin[loc.node]];
copySpan: TextNode.Span ← EditSpanSupport.CopySpan[argSpan];
copyRoot: TextNode.Ref ← TextNode.Root[copySpan.start.node];
EditSpan.Inherit[TextNode.Root[loc.node], copyRoot, TRUE];
gridEntry ← NEW[BoxCharDataRep ← [node: copyRoot]];
}
ELSE gridEntry ← NIL;
};
};
RETURN[TRUE];
};
[found, loc.node, loc.where] ← HoboArgumentFind[first, last, "entries"];
IF NOT found THEN RETURN;
nextLoc ← [node: TextNode.FirstChild[loc.node], where: 0];
endLoc ← TextNode.LastLocWithin[loc.node];
WHILE GetNextEntry[]
DO
ExtendGraph[grid, [rowOpp, colOpp], verticalGap, horizontalGap];
IF gridEntry #
NIL
THEN GridInsertEntry[grid, [rowNow, colNow], [rowOpp, colOpp], lineup, BoxInTrain[gridEntry, train]]
ELSE {
IF foundOther
THEN
SELECT lineup.plumb
FROM
's => GridInsertSep[grid, [rowNow, colNow], [rowOpp, colOpp], barThickness];
'f => GridInsertFill[grid, [rowNow, colNow], [rowOpp, colOpp], fillColor];
ENDCASE;
};
ENDLOOP;
grid.plumbBase ← plumbBase;
grid.levelBase ← levelBase;
myBox ← BoxFromGrid[grid];
};
EmptyGrid:
PROC RETURNS [grid: Grid]
~ {grid ← NEW[GridRep ← [centerList: RefTab.Create[], graph: QPSetup.NewGraph[]]]};
LineName:
PROC [topLine:
INT, name:
ROPE, botLine:
INT ← -1]
RETURNS [
ROPE]
~ {RETURN[Rope.Cat[Convert.RopeFromInt[topLine], name,
IF botLine < 0 THEN "" ELSE Convert.RopeFromInt[botLine]]]};
ExtendGraph:
PROC [grid: Grid, botCorner: Corner, verticalGap, horizontalGap:
REAL]
~ {
graph: Graph ← grid.graph;
FOR this:
NAT
IN (grid.maxLevel..botCorner.level]
DO
ExtendGraphOne[graph, this, "SL", "TL", "BL", verticalGap];
ENDLOOP;
grid.maxLevel ← MAX[grid.maxLevel, botCorner.level];
FOR this:
NAT
IN (grid.maxPlumb..botCorner.plumb]
DO
ExtendGraphOne[graph, this, "SP", "LP", "RP", horizontalGap];
ENDLOOP;
grid.maxPlumb ← MAX[grid.maxPlumb, botCorner.plumb];
};
ExtendGraphOne:
PROC [graph: Graph, this:
NAT, sep, top, bot:
ROPE, gap:
REAL]
~ {
prev: NAT ← this-1;
prevSep: Vertex ← QPSetup.AcquireVertex[graph, LineName[prev, sep]];
prevTop: Vertex ← QPSetup.AcquireVertex[graph, LineName[prev, top]];
thisBot: Vertex ← QPSetup.AcquireVertex[graph, LineName[this, bot]];
thisSep: Vertex ← QPSetup.AcquireVertex[graph, LineName[this, sep]];
sPt: Edge ← QPSetup.AcquireEdge[graph, prevSep, prevTop];
pt: Edge ← QPSetup.AcquireEdge[graph, prevTop, thisBot];
bPs: Edge ← QPSetup.AcquireEdge[graph, thisBot, thisSep];
AdjustEdge[graph, sPt, gap/2, 100.0];
AdjustEdge[graph, bPs, gap/2, 100.0];
};
AdjustEdge:
PROC [graph: Graph, edge: Edge,
spread, squeeze:
REAL ← 0.0, relax:
BOOL ←
TRUE]
~ {
QPSetup.SpreadEdge[graph, edge, MAX[edge.eData.spread, spread]];
IF squeeze # 0.0 THEN QPSetup.SqueezeEdge[graph, edge, squeeze];
IF relax THEN QPSetup.RelaxEdge[graph, edge, MAX[edge.eData.relaxed, spread]];
};
SpreadSpan:
PROC [
grid: Grid, top, bot, mid:
ROPE, spread:
REAL, offset:
REAL ← 0.0, relax:
BOOL ←
TRUE]
RETURNS [Vertex]
~ {
graph: Graph ← grid.graph;
topVertex: Vertex ← QPSetup.AcquireVertex[graph, top];
botVertex: Vertex ← QPSetup.AcquireVertex[graph, bot];
midVertex: Vertex;
span: Edge ← QPSetup.AcquireEdge[graph, topVertex, botVertex];
topMid, midBot: Edge;
AdjustEdge[graph, span, spread, 0.0, relax];
IF Rope.InlineIsEmpty[mid] THEN RETURN[IF offset < 0 THEN topVertex ELSE botVertex];
midVertex ← QPSetup.AcquireVertex[graph, mid];
topMid ← QPSetup.AcquireEdge[graph, topVertex, midVertex];
midBot ← QPSetup.AcquireEdge[graph, midVertex, botVertex];
AdjustEdge[graph, topMid, offset, 0.0, relax];
AdjustEdge[graph, midBot, spread-offset, 0.0, relax];
IF Rope.SkipTo[mid, 0, "C"] < Rope.InlineLength[mid]
IF Rope.Fetch[mid, 0] = 'C
THEN {
eqn: QPSetup.TermList ← LIST[[topMid.eData, 1.0], [midBot.eData, -1.0]];
[] ← RefTab.Insert[grid.centerList, midVertex, eqn];
};
RETURN[midVertex];
};
GridInsertSep:
PROC [grid: Grid, topCorner, botCorner: Corner, barThickness:
REAL] ~ {
graph: Graph ← grid.graph;
sepInfo: GridSep ← [barThickness: barThickness];
sepInfo.topLevel ← QPSetup.AcquireVertex[graph, LineName[topCorner.level, "SL"]];
sepInfo.topPlumb ← QPSetup.AcquireVertex[graph, LineName[topCorner.plumb, "SP"]];
sepInfo.botLevel ← QPSetup.AcquireVertex[graph, LineName[botCorner.level, "SL"]];
sepInfo.botPlumb ← QPSetup.AcquireVertex[graph, LineName[botCorner.plumb, "SP"]];
grid.sepList ← CONS[sepInfo, grid.sepList];
};
GridInsertFill:
PROC [grid: Grid, topCorner, botCorner: Corner, fillColor: Imager.Color] ~ {
graph: Graph ~ grid.graph;
fillInfo: GridFill ← [fillColor: fillColor];
fillInfo.topLevel ← QPSetup.AcquireVertex[graph, LineName[topCorner.level, "SL"]];
fillInfo.topPlumb ← QPSetup.AcquireVertex[graph, LineName[topCorner.plumb, "SP"]];
fillInfo.botLevel ← QPSetup.AcquireVertex[graph, LineName[botCorner.level, "SL"]];
fillInfo.botPlumb ← QPSetup.AcquireVertex[graph, LineName[botCorner.plumb, "SP"]];
grid.fillList ← CONS[fillInfo, grid.fillList];
};
GridInsertEntry:
PROC [grid: Grid, topCorner, botCorner: Corner, lineup: FeaturePair, box: TiogaImager.Box] ~ {
quote: CHAR ~ '\047;
xmin: REAL ~ MIN[0.0, box.bounds.xmin];
ymin: REAL ~ MIN[0.0, box.bounds.ymin];
xmax: REAL ~ MAX[0.0, box.bounds.xmax];
ymax: REAL ~ MAX[0.0, box.bounds.ymax];
width: REAL ~ xmax-xmin;
height: REAL ~ ymax-ymin;
trueWidth: REAL ~ box.bounds.xmax-box.bounds.xmin;
trueHeight: REAL ~ box.bounds.ymax-box.bounds.ymin;
graph: Graph ~ grid.graph;
tackLevel, tackPlumb: Vertex;
offsetLevel, offsetPlumb: REAL ← 0.0;
Extend graph plumb and level lines to include botCorner of new entry.
Spread sides of span far enough to accomodate box.
Include offset to base point, decimal point, or center point.
SELECT lineup.level
FROM
'N, 'n => {
tackLevel ← SpreadSpan[grid,
LineName[topCorner.level, "TL"], LineName[botCorner.level, "BL"],
LineName[topCorner.level, "NL", botCorner.level],
height, ymax];
};
'C, 'c => {
tackLevel ← SpreadSpan[grid,
LineName[topCorner.level, "TL"], LineName[botCorner.level, "BL"],
LineName[topCorner.level, "CL", botCorner.level],
trueHeight, trueHeight/2];
offsetLevel ← -ymin-trueHeight/2;
};
'T, 't => {
tackLevel ← SpreadSpan[grid,
LineName[topCorner.level, "TL"], LineName[botCorner.level, "BL"], NIL,
height, -1];
offsetLevel ← -ymax;
};
'B, 'b => {
tackLevel ← SpreadSpan[grid,
LineName[topCorner.level, "TL"], LineName[botCorner.level, "BL"], NIL,
height, +1];
offsetLevel ← -ymin;
};
ENDCASE => tackLevel ← SpreadSpan[grid,
LineName[topCorner.level, "TL"], LineName[botCorner.level, "BL"], NIL, height];
SELECT lineup.plumb
FROM
'N, 'n => {
tackPlumb ← SpreadSpan[grid,
LineName[topCorner.plumb, "LP"], LineName[botCorner.plumb, "RP"],
LineName[topCorner.plumb, "NP", botCorner.plumb],
width, xmin];
};
'C, 'c => {
tackPlumb ← SpreadSpan[grid,
LineName[topCorner.plumb, "LP"], LineName[botCorner.plumb, "RP"],
LineName[topCorner.plumb, "CP", botCorner.plumb],
trueWidth, trueWidth/2];
offsetPlumb ← -xmin-trueWidth/2;
};
'L, 'l => {
tackPlumb ← SpreadSpan[grid,
LineName[topCorner.plumb, "LP"], LineName[botCorner.plumb, "RP"], NIL,
width, -1];
offsetPlumb ← -xmin;
};
'R, 'r => {
tackPlumb ← SpreadSpan[grid,
LineName[topCorner.plumb, "LP"], LineName[botCorner.plumb, "RP"], NIL,
width, +1];
offsetPlumb ← -xmax;
};
'D, 'd, quote => {
toDecimal: REAL ← xmax;
IF lineup.plumb # quote THEN alignOnChar ← '.;
WITH box.data
SELECT
FROM
lineInfo: TEditFormat.LineInfo => {
rope: ROPE ← TextEdit.GetRope[lineInfo.startPos.node].Substr[lineInfo.startPos.where];
decpnt: INT ← rope.SkipTo[0, Rope.FromChar[alignOnChar]];
toDecimal ← TEditFormat.BoundingBox[lineInfo, 0, decpnt].xmax;
offsetPlumb ← -toDecimal;
};
ENDCASE;
tackPlumb ← SpreadSpan[grid,
LineName[topCorner.plumb, "LP"], LineName[botCorner.plumb, "RP"],
LineName[topCorner.plumb, "DP", botCorner.plumb],
width, toDecimal];
};
ENDCASE => tackPlumb ← SpreadSpan[grid,
LineName[topCorner.plumb, "LP"], LineName[botCorner.plumb, "RP"], NIL, width];
IF offsetLevel # 0.0
OR offsetPlumb # 0.0
THEN box ← TiogaImager.ModifyBox[box,
ImagerTransformation.Translate[[offsetPlumb, offsetLevel]]];
grid.entryList ← CONS[[tackLevel, tackPlumb, box], grid.entryList];
};
BoxFromGrid:
PROC [grid: Grid]
RETURNS [box: TiogaImager.Box] ~ {
Center: RefTab.EachPairAction
~ {QPSetup.ConstrainEdges[grid.graph, NARROW[val]]};
xSize, ySize, yCenter, xOrg, yOrg: REAL ← 0;
[] ← SpreadSpan[grid, "00BL", "0SL", NIL, 0.0]; -- Make top corner alphabetically first.
[] ← SpreadSpan[grid, "00RP", "0SP", NIL, 0.0]; -- This should make its coords (0, 0).
[] ← RefTab.Pairs[grid.centerList, Center];
QPSetup.SolveGraph[grid.graph];
xSize ← QPSetup.AcquireVertex[grid.graph, LineName[grid.maxPlumb, "SP"]].val;
ySize ← QPSetup.AcquireVertex[grid.graph, LineName[grid.maxLevel, "SL"]].val;
IF grid.plumbBase.InlineIsEmpty[]
THEN xOrg ← 0
ELSE xOrg ← QPSetup.AcquireVertex[grid.graph, grid.plumbBase].val;
IF grid.levelBase.InlineIsEmpty[]
THEN yOrg ← ySize
ELSE yOrg ← QPSetup.AcquireVertex[grid.graph, grid.levelBase].val;
grid.origin ← [0, ySize];
grid.origin ← [xOrg, yOrg];
box ←
NEW[TiogaImager.BoxRep ← [
nChars: 0,
bounds: [xmin: 0, ymin: 0, xmax: xSize, ymax: ySize],
bounds: [xmin: -xOrg, ymin: yOrg-ySize, xmax: xSize-xOrg, ymax: yOrg],
expansion: [0, 0],
escapement: [xSize-xOrg, 0],
stretch: [0, 0],
shrink: [0, 0],
class: gridClass,
data: grid
]];
};
GridRender:
PROC [box: TiogaImager.Box, context: Imager.Context, position: Imager
.VEC] ~ {
DoGridBackground:
PROC ~ {
context.TranslateT[position];
context.TranslateT[grid.origin];
FOR fill:
LIST
OF GridFill ← grid.fillList, fill.rest
UNTIL fill =
NIL
DO
context.SetColor[fill.first.fillColor];
context.MaskBox[[fill.first.topPlumb.val, -fill.first.botLevel.val,
fill.first.botPlumb.val, -fill.first.topLevel.val]];
ENDLOOP;
context.SetStrokeEnd[square];
context.SetColor[Imager.black];
FOR sep:
LIST
OF GridSep ← grid.sepList, sep.rest
UNTIL sep =
NIL
DO
context.SetStrokeWidth[sep.first.barThickness];
context.MaskVector[[sep.first.topPlumb.val, -sep.first.topLevel.val],
[sep.first.botPlumb.val, -sep.first.botLevel.val]];
ENDLOOP;
};
DoGridForeground:
PROC ~ {
context.TranslateT[position];
context.TranslateT[grid.origin];
FOR entry:
LIST
OF GridEntry ← grid.entryList, entry.rest
UNTIL entry =
NIL
DO
TiogaImager.Render[entry.first.box, context, [entry.first.tackPlumb.val, -entry.first.tackLevel.val]];
ENDLOOP;
IF ticWidth # 0.0 THEN ShowTics[context, position, grid.graph];
};
grid: Grid ← NARROW[box.data];
context.DoSaveAll[DoGridBackground];
context.DoSaveAll[DoGridForeground];
};
ShowTics:
PROC [context: Imager.Context, position: Imager.
VEC, graph: Graph]
~ {
Imager.SetStrokeWidth[context, 0.72];
FOR vL: QPSetup.VertexList ← graph.vList.rest, vL.rest
UNTIL vL.first =
NIL
DO
kind: ROPE ← Rope.Substr[vL.name, Rope.SkipOver[vL.name, 0, "0123456789"], 2];
vTics: BOOL ← Rope.InlineFetch[kind, 1] = 'P;
ticLen:
REAL ←
SELECT Rope.InlineFetch[kind, 0]
FROM
'S => 7, 'N, 'D, 'C => 5, 'R, 'L, 'T, 'B => 3
ENDCASE => 2;
IF vTics
THEN Imager.MaskVector[context, [vL.first.val, 0], [vL.first.val, ticLen]]
ELSE Imager.MaskVector[context, [0, -vL.first.val], [-ticLen, -vL.first.val]];
ENDLOOP;
};
GetCorners:
PROC [topWas, botWas: Corner, rowStep, colStep:
INT, loc: TextNode.Location]
RETURNS [top: Corner ← [0, 0], bot: Corner← [1, 1], lineup: FeaturePair ← ['n, 'c]] ~ {
Should look for explicit Now, Opp before "=" here
Possibly use "r@c,ropp@copp/arac=", where omitted r or c defaults to 0
Omitted opp or now defaults to keep same opp-now difference (clipped to 0)
Omitted all just increments by step
Column alignment type ac is l, r, c, d, or n, for left, right, center, decimal, or natural
Row alignment type ar is t, b, c, or n, for top, bottom, center, or natural; default n
rowDif: INT ← botWas.level-topWas.level;
colDif: INT ← botWas.plumb-topWas.plumb;
nodeRope: ROPE ← TextNode.NodeRope[loc.node];
ris: IO.STREAM;
kind: IO.TokenKind;
token: ROPE;
expected: {toprow, topcol, botrow, botcol} ← toprow;
seen: BOOL ← FALSE;
rectRope: ROPE ← "=";
topSeen, botSeen: BOOL ← FALSE;
FOR i:
INT ← loc.where-2, i-1
WHILE i >= 0
DO
SELECT nodeRope.InlineFetch[i]
FROM
'\t, '\040 => {rectRope ← nodeRope.Substr[i+1, loc.where-i-1]; EXIT};
ENDCASE;
REPEAT
FINISHED => rectRope ← nodeRope.Substr[0, loc.where];
ENDLOOP;
ris ← IO.RIS[rectRope];
WHILE
NOT
IO.EndOf[ris]
DO
[kind, token, ] ← IO.GetCedarTokenRope[ris];
SELECT kind
FROM
tokenDECIMAL => {
IF
NOT seen
THEN {
SELECT expected
FROM
toprow => {top.level ← Convert.IntFromRope[token]; topSeen ← TRUE};
topcol => {top.plumb ← Convert.IntFromRope[token]; topSeen ← TRUE};
botrow => {bot.level ← Convert.IntFromRope[token]; botSeen ← TRUE};
botcol => {bot.plumb ← Convert.IntFromRope[token]; botSeen ← TRUE};
ENDCASE;
seen ← TRUE;
}
ELSE {Scold["\tGrid corner: Unexpected number."];};
};
tokenSINGLE => {
SELECT token.InlineFetch[0]
FROM
'@ => {
SELECT expected
FROM
toprow => expected ← topcol;
botrow => expected ← botcol;
ENDCASE => {Scold["\tGrid corner: Unexpected atsign."];};
seen ← FALSE;
};
', => {
SELECT expected
FROM
toprow, topcol => expected ← botrow;
ENDCASE => {Scold["\tGrid corner: Unexpected comma."];};
seen ← FALSE;
};
'= => EXIT;
'/ => {
quote: CHAR ~ '\047;
ch1: CHAR ← ris.GetChar[];
GetLineup:
PROC
RETURNS [lineup: FeaturePair]
~ {
<< Check for generalized decimal lineups like "/'?=" and "/t'\t=". >>
SELECT ch1
FROM
'= => {Scold["\tPremature equal sign"]; RETURN[['n, 'c]]};
quote => GOTO decimal1;
ENDCASE;
SELECT ris.PeekChar[]
FROM
'= => RETURN[['n, ch1]];
quote => GOTO decimal2;
ENDCASE;
RETURN[[ch1, ris.GetChar[]]];
EXITS
decimal1 => {
ris.Backup[ch1];
alignOnChar ← ris.GetCharLiteral[];
RETURN[['n, quote]];
};
decimal2 => {
alignOnChar ← ris.GetCharLiteral[];
RETURN[[ch1, quote]];
};
};
lineup ← GetLineup[];
};
ENDCASE => {Scold["\tGrid corner: Unexpected single token."];};
};
ENDCASE => {Scold["\tGrid corner: Unexpected token."];};
ENDLOOP;
SELECT
TRUE
FROM
NOT topSeen
AND
NOT botSeen => {
top.level ← topWas.level+rowStep;
top.plumb ← topWas.plumb+colStep;
bot.level ← top.level+rowDif;
bot.plumb ← top.plumb+colDif;
};
topSeen
AND
NOT botSeen => {
bot.level ← top.level+rowDif;
bot.plumb ← top.plumb+colDif;
};
NOT topSeen
AND botSeen => {
top.level ← top.level-rowDif;
top.plumb ← top.plumb-colDif;
};
topSeen AND botSeen => {--No defaulting needed--};
ENDCASE;
};
Cover: TYPE ~ REF CoverRep;
CoverRep: TYPE ~ RECORD [
min: INT ← 0,
max: INT ← 1,
dif: INT ← 1,
inc: INT ← 1,
end: INT ← 1,
its: INT ← 1,
align: ["c", "."],
tab: "\t"
];
GetCoverage: PROC [plumbWas, levelWas: Cover, : TextNode.Location]
RETURNS [plumbCover: Cover, levelCover: Cover, style: ROPE]
~ {
Syntax: [min:max<inc>end align] or {min:+dif^its}.
nodeRope: ROPE ← TextNode.NodeRope[loc.node];
ris: IO.STREAM;
kind: IO.TokenKind;
token: ROPE;
FOR i: INT ← loc.where-2, i-1 WHILE i >= 0 DO
SELECT nodeRope.InlineFetch[i] FROM
'\t, '\040 => {rectRope ← nodeRope.Substr[i+1, loc.where-i-1]; EXIT};
ENDCASE;
REPEAT
FINISHED => rectRope ← nodeRope.Substr[0, loc.where];
ENDLOOP;
ris ← IO.RIS[rectRope];
WHILE NOT IO.EndOf[ris] DO
[kind, token, ] ← IO.GetCedarTokenRope[ris];
SELECT kind FROM
tokenSINGLE => {
SELECT token.InlineFetch[0] FROM
ENDCASE => {Scold["\tGrid cover: Unexpected single token."];};
};
ENDCASE => {Scold["\tGrid cover: Unexpected token."];};
ENDLOOP;
};