TableBaseImpl.Mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Rick Beach, February 18, 1985 8:59:19 pm PST
DIRECTORY
Convert, CornerStitching, EditSpan, EditSpanSupport, IO, Real, Rope, TableBase, TextNode, TiogaFileOps, TSArtwork, TSGraphic, TSOutput, TSTypes;
TableBaseImpl: CEDAR PROGRAM
IMPORTS Convert, CornerStitching, EditSpan, EditSpanSupport, IO, Rope, TextNode, TiogaFileOps, TSTypes
EXPORTS TableBase = {
OPEN TableBase;
ImplementationError: PUBLIC ERROR [ROPE] = CODE;
UnimplementedCase: PUBLIC ERROR = CODE;
A Tioga table is expected to be contained within one Tioga branch. The branch root node contains the fillInOrder of ByRowThenColumn or ByColumnThenRow. If omitted, then order is defaulted to ByRowThenColumn. An optional template can be included for empty nodes created in the table by specifying EmptyNodeTemplate in the table-fillInOrder-node. The first child node is saved for this purpose. Each table box is expected to be a child of the branch root node in the appropriate order. The contents of each table box is expected to be the nested subbranch under the table box node. An example:
Grid <rowGrids> Rows <columnGrids> Columns <table-fillInOrder> [EmptyNodeTemplate]
optional emptyNodeTemplate
<table-box-header>
table-box-contents-branch
<table-box-header>
table-box-contents-branch
. . .
<table-box-header>
table-box-contents-branch
where <rowGrids> and <columnGrids> are in the range 0..LAST[CARDINAL],
<table-fillInOrder> is one of byRowThenColumn or byColumnThenRow,
EmptyNodeTemplate specifies that the first child of this node is a template to use for empty nodes in the table,
<table-box-header> is <boxType> (a,b) (c,d) <attributes>
where <boxType> is one of box, rule, or background
and (a,b) (c,d) are the top left and bottom right grid coordinates of the box,
<attributes> are <rowAlignment> <colAlignment> <alignmentCharacter> <bearoff-distances>
BranchToTable: PUBLIC PROCEDURE [node: TextNode.Ref] RETURNS [table: RefTable] ~ {
s: IO.STREAMIO.RIS[RopeOfNode[node].Concat["\n"]];
objects: ObjectList ← NEW[ObjectListRep ← [IO.GetRefAnyLine[s]]];
atom: ATOM;
tableBoxHeader: TextNode.Ref ← TextNode.FirstChild[node];
table ← NEW[Table];
table.tableGrid ← CornerStitching.NewTesselation[];
table.branch ← node;
IF FirstObject[objects] = NIL THEN
ERROR ImplementationError["You have to supply some clues for me in the root node of the table."];
Grid <rowGrids> Rows <columnGrids> Columns
IF NOT ISTYPE[FirstObject[objects], ATOM] THEN
ERROR ImplementationError["Expecting Grid keyword first."];
atom ← NARROW[NextObject[objects]];
IF atom # $Grid AND atom # $grid THEN
ERROR ImplementationError["Expecting Grid keyword first, found something else."];
IF NOT ISTYPE[FirstObject[objects], REF INT] THEN
ERROR ImplementationError["Expecting number of row grids after Grid keyword."];
table.rowGrids ← NARROW[NextObject[objects], REF INT]^;
IF NOT ISTYPE[FirstObject[objects], ATOM] THEN
ERROR ImplementationError["Expecting Rows keyword after number of vertical grids."];
atom ← NARROW[NextObject[objects], ATOM];
IF atom # $Rows AND atom # $rows THEN
ERROR ImplementationError["Expecting Rows keyword between the numbers."];
IF NOT ISTYPE[FirstObject[objects], REF INT] THEN
ERROR ImplementationError["Expecting number of column grids after Rows keyword."];
table.columnGrids ← NARROW[NextObject[objects], REF INT]^;
IF NOT ISTYPE[FirstObject[objects], ATOM] THEN
ERROR ImplementationError["Expecting Columns keyword after number of vertical grids."];
atom ← NARROW[NextObject[objects], ATOM];
IF atom # $Columns AND atom # $columns THEN
ERROR ImplementationError["Expecting Columns keyword between the numbers."];
<fill-in-order> could be either byRowThenColumn or byColumnThenRow
IF NOT ISTYPE[FirstObject[objects], ATOM] THEN
ERROR ImplementationError["Expecting fill in order after grid specification."];
table.fillInOrder ← SELECT NARROW[NextObject[objects], ATOM] FROM
$ByRowThenColumn => byRowThenColumn,
$ByColumnThenRow => byColumnThenRow,
ENDCASE => ERROR ImplementationError["Unrecognized fill-in-order keyword."];
<gridOverlay> is optional
IF FirstObject[objects] # NIL AND ISTYPE[FirstObject[objects], ATOM] AND
NARROW[FirstObject[objects], ATOM] = $GridOverlay THEN {
[] ← NextObject[objects];
table.gridOverlayThickness ← GetDimn[objects];
table.gridOverlayHue ← GetReal[objects];
table.gridOverlaySaturation ← GetReal[objects];
table.gridOverlayBrightness ← GetReal[objects];
};
EmptyNodeTemplate is optional.
If present, the first child node is a template for empty table boxes.
IF FirstObject[objects] # NIL AND ISTYPE[FirstObject[objects], ATOM] AND
NARROW[FirstObject[objects], ATOM] = $EmptyNodeTemplate THEN {
table.emptyNodeTemplateRoot ← TextNode.NewTextNode[];
[] ← NextObject[objects];
[] ← EditSpan.Copy[
destRoot: table.emptyNodeTemplateRoot,
sourceRoot: TextNode.Root[node],
dest: TextNode.MakeNodeLoc[table.emptyNodeTemplateRoot],
source: TextNode.MakeNodeSpan[tableBoxHeader, tableBoxHeader],
nesting: 1];
tableBoxHeader ← TextNode.NthSibling[tableBoxHeader, 1];
};
Check for supplied constraints
IF FirstObject[objects] # NIL AND ISTYPE[FirstObject[objects], ATOM] AND
NARROW[FirstObject[objects], ATOM] = $RowConstraints THEN {
constraint: RefConstraint;
WHILE (constraint ← ParseConstraints[table~table, node~tableBoxHeader, row~TRUE]) # NIL DO
table.rowConstraints ← CONS[constraint, table.rowConstraints];
tableBoxHeader ← TextNode.NthSibling[tableBoxHeader, 1];
ENDLOOP;
[] ← NextObject[objects];
};
IF FirstObject[objects] # NIL AND ISTYPE[FirstObject[objects], ATOM] AND
NARROW[FirstObject[objects], ATOM] = $ColConstraints THEN {
constraint: RefConstraint;
WHILE (constraint ← ParseConstraints[table~table, node~tableBoxHeader, row~FALSE]) # NIL DO
table.colConstraints ← CONS[constraint, table.colConstraints];
tableBoxHeader ← TextNode.NthSibling[tableBoxHeader, 1];
ENDLOOP;
[] ← NextObject[objects];
};
IF FirstObject[objects] # NIL THEN
ERROR ImplementationError["Extraneous objects on table header"];
Parse successive table box headers
WHILE tableBoxHeader # NIL DO
entry: RefTableEntry ← ParseTableBox[table, tableBoxHeader];
InsertEntryInGrid[table, entry];
tableBoxHeader ← TextNode.Next[tableBoxHeader];
ENDLOOP;
table.branch ← NIL; -- same some space, eh?
};
InsertEntryInGrid: PUBLIC PROCEDURE [table: RefTable, entry: RefTableEntry] ~ {
WITH entry SELECT FROM
box: RefTableBox => {
rect: CornerStitching.Rect ~ InsideGridToRect[box.left, box.top, box.right, box.bottom];
CornerStitching.ChangeRect[plane~table.tableGrid, rect~rect, newvalue~box, checkOldvalue~TRUE, oldvalue~NIL !
CornerStitching.TileValue =>
ERROR ImplementationError["Box overlaps another box or rule"]];
};
rule: RefTableRule => {
rect: CornerStitching.Rect ~ OnGridToRect[rule.left, rule.top, rule.right, rule.bottom];
overlap: BOOLEANFALSE;
CornerStitching.ChangeRect[plane~table.tableGrid, rect~rect, newvalue~rule, checkOldvalue~FALSE];
CornerStitching.ChangeRect[plane~table.tableGrid, rect~rect, newvalue~rule, checkOldvalue~TRUE, oldvalue~NIL !
CornerStitching.TileValue => {overlap ← TRUE; CONTINUE}];
IF rule overlaps with boxes THEN
ERROR ImplementationError["Rule overlaps another box"];
IF rule overlaps with rule THEN handle corner and intersections;
};
background: RefTableBackground => {
table.backgrounds ← CONS[background, table.backgrounds];
};
ENDCASE => ERROR UnimplementedCase;
};
ParseTableBox: PROCEDURE [table: RefTable, tableBoxHeader: TextNode.Ref] RETURNS [entry: RefTableEntry] ~ {
s: IO.STREAM ~ IO.RIS[RopeOfNode[tableBoxHeader].Concat["\n"]];
objects: ObjectList ← NEW[ObjectListRep ← [IO.GetRefAnyLine[s]]];
gridList: LIST OF REF ANY;
top, left, bottom, right: GridNumber;
boxType: BoxType;
<boxType> (a,b) (c,d) <attributes>
IF NOT ISTYPE[FirstObject[objects], ATOM] THEN
ERROR ImplementationError["Expected keyword box type first in the table box header"];
SELECT NARROW[NextObject[objects], ATOM] FROM
$Box => boxType ← box;
$Rule => boxType ← rule;
$Background => boxType ← background;
ENDCASE => ERROR ImplementationError["Unrecognized box type keyword."];
IF NOT ISTYPE[FirstObject[objects], LIST OF REF ANY] THEN
ERROR ImplementationError["Expected grid coordinates as first object in table box header"];
gridList ← NARROW[NextObject[objects], LIST OF REF ANY];
top ← NARROW[gridList.first, REF INT]^;
left ← NARROW[gridList.rest.first, REF INT]^;
IF NOT ISTYPE[FirstObject[objects], LIST OF REF ANY] THEN
ERROR ImplementationError["Expected grid coordinates as second object in table box header"];
gridList ← NARROW[NextObject[objects], LIST OF REF ANY];
bottom ← NARROW[gridList.first, REF INT]^;
right ← NARROW[gridList.rest.first, REF INT]^;
IF top > table.rowGrids OR bottom > table.rowGrids OR left > table.columnGrids OR right > table.columnGrids THEN
ERROR ImplementationError["Coordinates are outside the table grid."];
SELECT boxType FROM
box => {
box: RefTableBox ← NEW[TableBox ← [top~top, left~left, bottom~bottom, right~right, entrySpecs~box[]]];
IF FirstObject[objects] # NIL THEN {
vertical alignment specification is optional
box.rowAlignment ← SELECT NARROW[NextObject[objects], ATOM] FROM
$FlushTop => flushTop,
$FlushBottom => flushBottom,
$Center => center,
$Centre => center,
$TopBaseline => topBaseline,
$BottomBaseline => bottomBaseline,
$CenterOnTopBaseline => centerOnTopBaseline,
$CentreOnTopBaseline => centerOnTopBaseline,
$CenterOnBottomBaseline => centerOnBottomBaseline,
$CentreOnBottomBaseline => centerOnBottomBaseline,
ENDCASE => ERROR ImplementationError["expected vertical alignment as third object in table box header"];
};
IF FirstObject[objects] # NIL THEN {
horizontal alignment specification is optional
box.colAlignment ← SELECT NARROW[NextObject[objects], ATOM] FROM
$FlushLeft => flushLeft,
$FlushRight => flushRight,
$Center => center,
$Centre => center,
ENDCASE => ERROR ImplementationError["expected horizontal alignment as fourth object in table box header"];
};
IF FirstObject[objects] # NIL AND
ISTYPE[FirstObject[objects], ATOM] AND
$CharAlign = NARROW[NextObject[objects], ATOM] THEN {
character alignment is optional but must be followed by a character
box.alignOnChar ← TRUE;
box.alignChar ← NARROW[NextObject[objects], REF CHAR]^;
};
IF FirstObject[objects] # NIL THEN {
bearoff extents are optional but must be last
box.bearoffExtents[left] ← GetDimn[objects];
box.bearoffExtents[right] ← GetDimn[objects];
box.bearoffExtents[up] ← GetDimn[objects];
box.bearoffExtents[down] ← GetDimn[objects];
};
box.node ← TextNode.FirstChild[tableBoxHeader];
entry ← box;
};
rule => {
rule: RefTableRule ← NEW[TableRule ← [top~top, left~left, bottom~bottom, right~right, entrySpecs~rule[]]];
IF top # bottom AND left # right THEN
ERROR ImplementationError["Rules must be along a grid line with top = bottom or left = right"];
rule.orientation ← IF top = bottom THEN horizontal ELSE vertical;
rule.thickness ← GetDimn[objects];
entry ← rule;
};
background => {
background: RefTableBackground ← NEW[TableBackground ← [top~top, left~left, bottom~bottom, right~right, entrySpecs~background[]]];
background.hue ← GetReal[objects];
background.saturation ← GetReal[objects];
background.brightness ← GetReal[objects];
entry ← background;
};
ENDCASE => ERROR UnimplementedCase;
IF FirstObject[objects] # NIL THEN
ERROR ImplementationError["Extraneous objects for this table entry"];
};
ParseConstraints: PROC [table: RefTable, node: TextNode.Ref, row: BOOLEAN] RETURNS [constraint: RefConstraint] ~ {
s: IO.STREAM ~ IO.RIS[RopeOfNode[node].Concat["\n"]];
sign: REAL ← +1.0;
coeff: Coefficient;
coeffs: LIST OF Coefficient;
equality: BOOLEAN;
tokenKind: IO.TokenKind;
token: ROPE;
charsSkipped: INT;
Check that the expected constraint keyword appears
[tokenKind, token, charsSkipped] ← IO.GetCedarTokenRope[s];
IF tokenKind # tokenID THEN
ERROR ImplementationError["Expected constraint keyword to come first"];
IF row AND NOT token.Equal["RowConstraint"] OR
~row AND NOT token.Equal["ColConstraint"] THEN
RETURN [NIL];
Accept a leading negative sign for the constraint expression
[tokenKind, token, charsSkipped] ← IO.GetCedarTokenRope[s];
IF token.Equal["-"] THEN {
sign ← -1.0;
[tokenKind, token, charsSkipped] ← IO.GetCedarTokenRope[s];
}
ELSE
sign ← +1.0;
Accept an expression of the form: <term> <+|-> <expression>
Where <term> is of the form: <number> * <id>
DO
SELECT tokenKind FROM
tokenREAL => coeff.coefficient ← sign*Convert.RealFromRope[token];
tokenDECIMAL => coeff.coefficient ← sign*Convert.IntFromRope[token];
ENDCASE => ERROR ImplementationError["Expected numeric coefficient"];
[tokenKind, token, charsSkipped] ← IO.GetCedarTokenRope[s];
IF tokenKind # tokenSINGLE OR NOT token.Equal["*"] THEN
ERROR ImplementationError["Expected multiply sign"];
[tokenKind, token, charsSkipped] ← IO.GetCedarTokenRope[s];
IF tokenKind # tokenID THEN
ERROR ImplementationError["Expected identifier"];
coeff.unknown ← token;
coeffs ← CONS[coeff, coeffs];
[tokenKind, token, charsSkipped] ← IO.GetCedarTokenRope[s];
SELECT TRUE FROM
token.Equal["="] => { equality ← TRUE; EXIT; };
token.Equal[">="] => { equality ← FALSE; EXIT; };
token.Equal["+"] => sign ← +1.0;
token.Equal["-"] => sign ← -1.0;
ENDCASE => ERROR ImplementationError["Expected additive or relational operator"];
[tokenKind, token, charsSkipped] ← IO.GetCedarTokenRope[s];
ENDLOOP;
[tokenKind, token, charsSkipped] ← IO.GetCedarTokenRope[s];
IF NOT token.Equal["0"] AND NOT token.Equal["0.0"] THEN
ERROR ImplementationError["Expected right-hand side to always be 0 or 0.0"];
constraint ← NEW[Constraint ← [equality, coeffs]];
};
ObjectList: TYPE ~ REF ObjectListRep;
ObjectListRep: TYPE ~ RECORD[list: LIST OF REF ANY];
FirstObject: PROCEDURE[objects: ObjectList] RETURNS [o: REF ANY] = {
IF objects # NIL AND objects.list # NIL THEN
o ← objects.list.first;
};
NextObject: PROCEDURE[objects: ObjectList] RETURNS [o: REF ANY] = {
IF objects = NIL OR objects.list = NIL THEN
ERROR ImplementationError["prematurely exhausted the supply of objects for this table entry"];
o ← objects.list.first;
objects.list ← objects.list.rest;
};
GetDimn: PROC [objects: ObjectList] RETURNS [TSTypes.Dimn] ~ {
object: REF ANY ~ NextObject[objects];
r: REAL;
multiplier: REAL;
IF ISTYPE[object, REF REAL] THEN r ← NARROW[object, REF REAL]^
ELSE IF ISTYPE[object, REF INT] THEN r ← NARROW[object, REF INT]^
ELSE ERROR ImplementationError["Expecting a real number and didn't get it"];
multiplier ← SELECT NARROW[NextObject[objects], ATOM] FROM
$in => 72.0,
$pt => 72.0/72.27,
$cm => 72.0/2.54,
$mm => 72.0/25.4,
$bp => 1.0,
ENDCASE => 1.0;
RETURN [TSTypes.Pt[r*multiplier]];
};
GetReal: PROC [objects: ObjectList] RETURNS [r: REAL] ~ {
object: REF ANY ~ NextObject[objects];
IF ISTYPE[object, REF REAL] THEN r ← NARROW[object, REF REAL]^
ELSE IF ISTYPE[object, REF INT] THEN r ← NARROW[object, REF INT]^
ELSE ERROR ImplementationError["Expecting a real number and didn't get it"];
};
TableToBranch: PUBLIC PROCEDURE [table: RefTable] RETURNS [nodeRef: TextNode.Ref] ~ {
branchRoot, branch, prevLast: TiogaFileOps.Ref;
nodeRope: ROPE;
CopyNodeAsChildOfLastNode: PROCEDURE [node: TextNode.Ref, prevLast: TiogaFileOps.Ref] = {
prevLastTextNode: TextNode.Ref;
TRUSTED { prevLastTextNode ← LOOPHOLE[prevLast] };
prevLastTextNode.child ← EditSpanSupport.CopySpan[TextNode.MakeNodeSpan[node, TextNode.LastWithin[node]]].start.node;
prevLastTextNode.child.next ← prevLastTextNode;
};
InsertTableBox: EnumeratedEntryProc = {
PROCEDURE [table: RefTable, entry: RefTableEntry] RETURNS [stop: BOOLEANFALSE];
rope: ROPE ← TableEntryToRope[entry];
prevLast ← TiogaFileOps.InsertAsLastChild[branch, prevLast];
TiogaFileOps.SetContents[prevLast, rope];
TiogaFileOps.SetFormat[prevLast, "block"];
WITH entry SELECT FROM
box: RefTableBox => CopyNodeAsChildOfLastNode[box.node, prevLast];
ENDCASE => NULL;
};
branchRoot ← TiogaFileOps.CreateRoot[];
TRUSTED { branch ← LOOPHOLE[EditSpan.Copy[
destRoot: LOOPHOLE[branchRoot],
sourceRoot: TextNode.Root[table.branch],
dest: TextNode.MakeNodeLoc[LOOPHOLE[branchRoot]],
source: TextNode.MakeNodeSpan[table.branch, table.branch],
nesting: 1].start.node] };
prevLast ← NIL;
nodeRope ← "Grid";
nodeRope ← nodeRope.Concat[IO.PutFR[" %g Rows %g Columns", IO.card[table.rowGrids], IO.card[table.columnGrids]]];
nodeRope ← nodeRope.Concat[SELECT table.fillInOrder FROM
byRowThenColumn => " ByRowThenColumn",
byColumnThenRow => " ByColumnThenRow",
ENDCASE => ""];
IF table.gridOverlayThickness # TSTypes.nilDimn THEN {
nodeRope ← nodeRope.Concat[" GridOverlay"];
nodeRope ← nodeRope.Concat[IO.PutFR[" %g pt", IO.real[table.gridOverlayThickness.texPts]]];
nodeRope ← nodeRope.Concat[IO.PutFR[" %g %g %g", IO.real[table.gridOverlayHue], IO.real[table.gridOverlaySaturation], IO.real[table.gridOverlayBrightness]]];
};
fill in table attributes here . . . if we had any (not yet implemented)
IF table.emptyNodeTemplateRoot # NIL THEN {
nodeRope ← nodeRope.Concat[" EmptyNodeTemplate"];
CopyNodeAsChildOfLastNode[TextNode.FirstChild[table.emptyNodeTemplateRoot], branch];
TRUSTED { prevLast ← LOOPHOLE[TextNode.FirstChild[LOOPHOLE[branch]]] };
};
TiogaFileOps.SetContents[branch, nodeRope];
IF table.backgrounds # NIL THEN {
FOR b: LIST OF RefTableBackground ← table.backgrounds, b.rest WHILE b # NIL DO
background: RefTableBackground ← b.first;
[] ← InsertTableBox[table, background];
ENDLOOP;
};
fill in table entries here
IF table.fillInOrder = byRowThenColumn THEN
EnumerateByRows[table, InsertTableBox]
ELSE
EnumerateByColumns[table, InsertTableBox];
TRUSTED { nodeRef ← LOOPHOLE[branch] };
};
TableEntryToRope: PUBLIC PROCEDURE [entry: RefTableEntry] RETURNS [rope: ROPENIL] ~ {
rope ← SELECT entry.boxType FROM
box => "Box",
rule => "Rule",
background => "Background",
ENDCASE => "";
rope ← rope.Concat[" "];
rope ← rope.Concat[IO.PutFR["(%g, %g) (%g, %g)", IO.card[entry.top], IO.card[entry.left], IO.card[entry.bottom], IO.card[entry.right]]];
rope ← rope.Concat[" "];
WITH entry SELECT FROM
box: RefTableBox => {
rope ← rope.Concat[SELECT box.rowAlignment FROM
flushTop => "FlushTop",
flushBottom => "FlushBottom",
center => "Center",
topBaseline => "TopBaseline",
bottomBaseline => "BottomBaseline",
centerOnTopBaseline => "CenterOnTopBaseline",
centerOnBottomBaseline => "CenterOnBottomBaseline",
ENDCASE => ""];
rope ← rope.Concat[" "];
rope ← rope.Concat[SELECT box.colAlignment FROM
flushLeft => "FlushLeft",
flushRight => "FlushRight",
center => "Center",
ENDCASE => ""];
IF box.alignOnChar THEN {
rope ← rope.Concat[" CharAlign"];
rope ← rope.Cat["'", Rope.FromChar[box.alignChar]];
};
IF box.bearoffExtents[left] # TSTypes.nilDimn THEN {
rope ← rope.Concat[" "];
rope ← rope.Concat[IO.PutFR["%g pt %g pt %g pt %g pt", IO.real[box.bearoffExtents[left]], IO.real[box.bearoffExtents[right]], IO.real[box.bearoffExtents[up]], IO.real[box.bearoffExtents[down]]]]
};
};
rule: RefTableRule => {
rope ← rope.Concat[IO.PutFR["%g pt", IO.real[rule.thickness.texPts]]];
};
background: RefTableBackground => {
rope ← rope.Concat[IO.PutFR["%g %g %g", IO.real[background.hue], IO.real[background.saturation], IO.real[background.brightness]]];
};
ENDCASE => ERROR UnimplementedCase;
};
RopeOfNode: PROCEDURE [node: TextNode.Ref] RETURNS [rope: ROPENIL] ~ {
WITH node SELECT FROM x: TextNode.RefTextNode => RETURN[x.rope]; ENDCASE => NULL;
};
BoxFromTile: PUBLIC PROCEDURE [tile: CornerStitching.TilePtr] RETURNS [box: RefTableBox] ~ {
IF tile.Value # NIL AND ISTYPE[tile.Value, RefTableBox] THEN
box ← NARROW[tile.Value];
};
TileFromBox: PUBLIC PROCEDURE [box: RefTableBox] RETURNS [tile: CornerStitching.TilePtr] ~ {
oops -- we need the table to find the table grid to use CornerStitching.TileAt
ERROR ImplementationError["TileFromBox not implemented yet"];
};
InsideGridToRect: PUBLIC PROCEDURE [left, top, right, bottom: GridNumber] RETURNS [CornerStitching.Rect] = {
RETURN [[4*left+2, 4*top+2, 4*right-1, 4*bottom-1]]
};
OnGridToRect: PUBLIC PROCEDURE [left, top, right, bottom: GridNumber] RETURNS [CornerStitching.Rect] = {
RETURN [[4*left, 4*top, 4*right+1, 4*bottom+1]]
};
EnumerateByRows: PUBLIC PROCEDURE [table: RefTable, entryProc: EnumeratedEntryProc] ~ {
EnumerateTable[table~table, entryProc~entryProc, left~FIRST[GridNumber], right~LAST[GridNumber], top~FIRST[GridNumber], bottom~LAST[GridNumber]];
};
EnumerateByColumns: PUBLIC PROCEDURE [table: RefTable, entryProc: EnumeratedEntryProc] ~ {
EnumerateByRows[table, entryProc];
};
EnumerateTable: PUBLIC PROCEDURE [table: RefTable, entryProc: EnumeratedEntryProc, left: GridNumber ← FIRST[GridNumber], right: GridNumber ← LAST[GridNumber], top: GridNumber ← FIRST[GridNumber], bottom: GridNumber ← LAST[GridNumber]] ~ {
TileProc: CornerStitching.PerTileProc ~ {
entry: RefTableEntry ← NARROW[CornerStitching.Value[tile]];
IF entry # NIL THEN [] ← entryProc[table, entry];
RETURN[NIL];
};
[] ← CornerStitching.EnumerateArea[plane~table.tableGrid, rect~OnGridToRect[left, top, right, bottom], perTile~TileProc];
};
WithinGridLines: PUBLIC PROCEDURE [entry: RefTableBox, which: RowOrColumn, grid1, grid2: GridNumber] RETURNS [BOOLEAN] ~ {
RETURN [SELECT which FROM
row => entry.top >= grid1 AND entry.bottom <= grid2,
column => entry.left >= grid1 AND entry.right <= grid2,
ENDCASE => FALSE]
};
}.