<> <> <> <<>> DIRECTORY Atom, Commander, Convert, Hobo, Imager, ImagerBackdoor, ImagerBox, ImagerFont, ImagerPath, ImagerTransformation, IO, EditSpan, EditSpanSupport, MessageWindow, NodeProps, NodeStyle, NodeStyleOps, PutGet, QPSetup, Real, RealFns, RefTab, Rope, TEditDocument, TEditFormat, TEditInput, TEditInputOps, TEditSelection, TextEdit, TextNode, TiogaAccess, TiogaImager, TreeFind, ViewerClasses, ViewerOps, ViewerTools; HoboImpl: CEDAR PROGRAM IMPORTS Atom, Commander, Convert, EditSpan, EditSpanSupport, Imager, ImagerBackdoor, ImagerBox, ImagerFont, ImagerPath, ImagerTransformation, IO, MessageWindow, NodeProps, NodeStyle, NodeStyleOps, PutGet, QPSetup, <> RealFns, RefTab, Rope, TEditFormat, TEditInput, TEditInputOps, TEditSelection, TextEdit, TextNode, TiogaAccess, TiogaImager, TreeFind, ViewerOps, ViewerTools EXPORTS Hobo ~ BEGIN OPEN Hobo; <> ROPE: TYPE ~ Rope.ROPE; Viewer: TYPE ~ ViewerClasses.Viewer; CharacterArtworkClass: TYPE ~ TEditFormat.CharacterArtworkClass; CharacterArtworkClassRep: TYPE ~ TEditFormat.CharacterArtworkClassRep; CharacterArtwork: TYPE ~ TEditFormat.CharacterArtwork; CharacterArtworkRep: TYPE ~ TEditFormat.CharacterArtworkRep; Ref: TYPE = REF NodeBody; NodeBody: PUBLIC TYPE ~ TextNode.Body; Graph: TYPE ~ QPSetup.Graph; Vertex: TYPE ~ QPSetup.Vertex; Edge: TYPE ~ QPSetup.Edge; <> barClass: PUBLIC TiogaImager.Class _ NEW[TiogaImager.ClassRep _ [ Composite: NIL, UnBox: NIL, Render: BarRender, Resolve: NIL, Destroy: NIL ]]; gridClass: PUBLIC TiogaImager.Class _ NEW[TiogaImager.ClassRep _ [ Composite: NIL, UnBox: NIL, Render: GridRender, Resolve: NIL, Destroy: NIL ]]; <> arbBounds: Imager.VEC ~ [586, 606]; -- Client width&height of typical Tioga viewer leftDelim: CHAR ~ 001C; -- Left placeholder symbol \ rightDelim: CHAR ~ 002C; -- Right placeholder symbol \ hoboPrefix: ROPE ~ Rope.Concat[Rope.FromChar[leftDelim], "hobo="]; hoboPrefixLen: INT ~ hoboPrefix.InlineLength; whoWeAreRope: ROPE ~ "BoxChar"; noName: ATOM ~ Atom.EmptyAtom[]; legalNameChars: ROPE ~ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; emptyNode: TextNode.Ref ~ TextEdit.DocFromNode[TextEdit.FromRope[""]]; emptyBoxChar: BoxCharData ~ NEW[BoxCharDataRep _ [node: emptyNode]]; <> selectionBox: BoxCharData _ NEW[BoxCharDataRep _ [node: TextEdit.DocFromNode[TextEdit.FromRope["#"]]]]; boxMakerTable: RefTab.Ref _ RefTab.Create[]; ticWidth: REAL _ 0.0; alignOnChar: CHAR _ '.; <> RegisterHoboArtworkClass: PROC ~ { <> <> class: CharacterArtworkClass _ NEW[CharacterArtworkClassRep _ [name: $Hobo, format: FormatBoxChar, data: NIL]]; oldClass: CharacterArtworkClass _ NEW[CharacterArtworkClassRep _ [name: $BoxChar, format: FormatBoxChar, data: NIL]]; <> TEditFormat.RegisterCharacterArtwork[class]; TEditFormat.RegisterCharacterArtwork[oldClass]; -- For backwards compatibility }; UnRegisterHoboArtworkClass: PROC ~ { <> TEditFormat.UnregisterCharacterArtwork[$BoxChar]; }; FormatBoxChar: PROC[class: CharacterArtworkClass, loc: TextNode.Location, style: NodeStyle.Ref, styleOps: NodeStyleOps.OfStyle] RETURNS[CharacterArtwork] ~ { <> << Returns character artwork for formatted expression.>> << Note that OfStyle: TYPE ~ { screen, print, base }>> charArtwork: CharacterArtwork; charProps: TextEdit.PropList _ TextEdit.GetCharPropList[node: loc.node, index: loc.where]; fontSize: INTEGER _ style.GetFontSizeI[]; font: ImagerFont.Font _ TEditFormat.GetFont[style]; charSet: TextEdit.CharSet _ 0; char: CHAR _ '); chars: ROPE; hoboRope: ROPE _ NARROW[charProps.GetPropFromList[$hoboRope]]; boxCharRope: ROPE _ NARROW[charProps.GetPropFromList[$BoxChar]]; box: TiogaImager.Box _ NIL; stippleRope: ROPE _ NARROW[charProps.GetPropFromList[$stipple]]; stipple: WORD _ 50A0H; leftExtent: REAL _ 0.0; rightExtent: REAL _ 16.0; descent: REAL _ 0.0; ascent: REAL _ 10.0; extents: ImagerFont.Extents; escapement: ImagerFont.VEC; reachRope: ROPE _ NARROW[charProps.GetPropFromList[$reach]]; reach: REAL _ 16.0; eqExtents: ImagerFont.Extents _ font.RopeBoundingBox["="]; chExtents: ImagerFont.Extents; vshiftRope: ROPE _ NARROW[charProps.GetPropFromList[$vshift]]; hshiftRope: ROPE _ NARROW[charProps.GetPropFromList[$hshift]]; hscale, vscale: REAL _ 1.0; hshift, vshift: REAL _ 0.0; showBoundsRope: ROPE _ NARROW[charProps.GetPropFromList[$showBounds]]; showBounds: BOOL _ FALSE; boxCharData: BoxCharData _ GetDataFromLoc[loc.node, loc.where]; [charSet, char] _ TextEdit.FetchChar[text: loc.node, index: loc.where]; chars _ Rope.Cat[Rope.FromChar[377C], Rope.FromChar[0C+charSet], Rope.FromChar[char]]; stipple _ Convert.IntFromRope[stippleRope ! Convert.Error => CONTINUE]; reach _ Convert.RealFromRope[reachRope ! Convert.Error => CONTINUE]; hshift _ Convert.RealFromRope[hshiftRope ! Convert.Error => CONTINUE]; vshift _ Convert.RealFromRope[vshiftRope ! Convert.Error => CONTINUE]; IF showBoundsRope.InlineLength > 0 THEN showBounds _ Convert.BoolFromRope[showBoundsRope ! Convert.Error => CONTINUE]; IF boxCharData = NIL THEN { IF boxCharRope = NIL THEN { chExtents _ font.RopeBoundingBox[chars]; vscale _ MAX[1.0, 2*reach/(chExtents.ascent+chExtents.descent)]; hscale _ RealFns.SqRt[RealFns.SqRt[vscale]]; vshift _ (eqExtents.ascent+eqExtents.descent)/2 - eqExtents.descent; font _ font.Modify[ImagerTransformation.Create[hscale,0,0, 0,vscale,(1-vscale)*vshift]]; extents _ font.RopeBoundingBox[chars]; escapement _ font.RopeEscapement[chars]; } ELSE { chars _ boxCharRope; box _ TiogaImager.BoxFromRope[font, chars]; box _ box.ModifyBox[ImagerTransformation.Translate[[hshift, vshift]]]; extents _ ImagerBox.ExtentsFromBox[box.bounds]; escapement _ box.escapement; }; } ELSE { box _ BoxInTrain[data: boxCharData, train: [loc: loc, style: style, styleOps: styleOps]]; box _ box.ModifyBox[ImagerTransformation.Translate[[hshift, vshift]]]; extents _ ImagerBox.ExtentsFromBox[box.bounds]; escapement _ box.escapement; }; <> charArtwork _ NEW[CharacterArtworkRep _ [paint: PaintBoxChar, extents: extents, escapement: escapement, data: NEW[PaintInfoRep _ [color: ImagerBackdoor.MakeStipple[stipple: stipple, xor: TRUE], extents: extents, reach: reach, font: font, chars: chars, box: box, showBounds: showBounds]]]]; RETURN[charArtwork]; }; PaintBoxChar: PROC [charArtwork: CharacterArtwork, context: Imager.Context] ~ { <> <<>> <> paintInfo: PaintInfo _ NARROW[charArtwork.data]; <> <> <> <> <> <> <> }; }; RETURN[boxCharData] }; HoboNameAndRange: PROC [node: TextNode.Ref] RETURNS [name: ATOM _ noName, start, end: TextNode.Location] ~ { <> <> nodeRope: ROPE _ TextEdit.GetRope[TextNode.FirstChild[node]]; start _ [TextNode.FirstChild[node], 0]; end _ TextNode.LastLocWithin[node]; IF Rope.IsPrefix[prefix: hoboPrefix, subject: nodeRope, case: TRUE] AND end.where > 0 AND TextEdit.FetchChar[end.node, end.where-1].char = rightDelim THEN { nameEnd: INT _ Rope.SkipOver[nodeRope, hoboPrefixLen, legalNameChars]; IF nameEnd > hoboPrefixLen THEN name _ Atom.MakeAtom[nodeRope.Substr[ hoboPrefixLen, nameEnd-hoboPrefixLen]]; }; }; HoboArgumentFind: PROC [first, last: TextNode.Location, name: ROPE] RETURNS [found: BOOL _ FALSE, node: TextNode.Ref, where: INT] ~ { <> at, atEnd, before, after: INT; IF name.InlineLength > 0 THEN { [found, node, at, atEnd, before, after] _ TreeFind.Try[ finder: TreeFind.CreateFromRope[pattern: name, literal: TRUE, word: TRUE], first: first.node, start: first.where, last: last.node, lastLen: last.where+1]; IF (found _ found AND TextEdit.Size[node] > atEnd AND TextEdit.FetchChar[node, atEnd].char = '=) THEN where _ atEnd+1; } ELSE { [found, node, at, where, before, after] _ TreeFind.Try[ finder: TreeFind.CreateFromRope[pattern: "=", literal: TRUE, word: FALSE], first: first.node, start: first.where, last: last.node, lastLen: last.where+1]; }; }; HoboArgumentBox: PROC [first, last: TextNode.Location, name: ROPE, default: BoxCharData] RETURNS [found: BOOL _ FALSE, val: BoxCharData] ~ { <> hitNode: TextNode.Ref; where: INT; [found, hitNode, where] _ HoboArgumentFind[first, last, name]; IF found THEN { IF TextEdit.Size[hitNode] > where THEN { data: BoxCharData _ GetDataFromLoc[hitNode, where]; IF data # NIL THEN RETURN[TRUE, data]; } ELSE { IF TextNode.FirstChild[hitNode] # NIL THEN { argSpan: TextNode.Span _ TextNode.MakeNodeSpan[ TextNode.FirstChild[hitNode], TextNode.LastWithin[hitNode]]; copySpan: TextNode.Span _ EditSpanSupport.CopySpan[argSpan]; copyRoot: TextNode.Ref _ TextNode.Root[copySpan.start.node]; EditSpan.Inherit[TextNode.Root[hitNode], copyRoot, TRUE]; RETURN[TRUE, NEW[BoxCharDataRep _ [node: copyRoot]]]; }; }; }; RETURN[FALSE, default]; }; HoboArgumentReal: PROC [first, last: TextNode.Location, name: ROPE, default: REAL] RETURNS [found: BOOL _ FALSE, val: REAL] ~ { <> hitNode: TextNode.Ref; where, size: INT; val _ default; [found, hitNode, where] _ HoboArgumentFind[first, last, name]; IF found AND (size _ TextEdit.Size[hitNode]) > where THEN { val _ Convert.RealFromRope[TextNode.NodeRope[hitNode].Substr[where, size-1] ! Convert.Error => { Scold[Rope.Cat["\tError in REAL arg \"", name, "\"."]]; CONTINUE; }]; } }; HoboArgumentInt: PROC [first, last: TextNode.Location, name: ROPE, default: INT] RETURNS [found: BOOL _ FALSE, val: INT] ~ { <> hitNode: TextNode.Ref; where, size: INT; val _ default; [found, hitNode, where] _ HoboArgumentFind[first, last, name]; IF found AND (size _ TextEdit.Size[hitNode]) > where THEN { val _ Convert.IntFromRope[TextNode.NodeRope[hitNode].Substr[where, size-1] ! Convert.Error => { Scold[Rope.Cat["\tError in REAL arg \"", name, "\"."]]; CONTINUE; }]; } }; HoboArgumentName: PROC [first, last: TextNode.Location, name: ROPE, default: ATOM] RETURNS [found: BOOL _ FALSE, val: ATOM] ~ { <> hitNode: TextNode.Ref; where, size: INT; val _ default; [found, hitNode, where] _ HoboArgumentFind[first, last, name]; IF found AND (size _ TextEdit.Size[hitNode]) > where THEN { argRope: ROPE _ TextNode.NodeRope[hitNode].Substr[where, size-1]; val _ Convert.AtomFromRope[argRope.Substr[0,argRope.SkipOver[skip: legalNameChars]] ! Convert.Error => { Scold[Rope.Cat["\tError in name arg \"", name, "\"."]]; CONTINUE; }]; } }; BoxFromNode: PROC [node: TextNode.Ref, train: Train, bounds: Imager.VEC _ arbBounds] RETURNS [myBox: TiogaImager.Box _ TiogaImager.EmptyBox[[2, 0]]] ~ { name: ATOM; firstLoc, lastLoc: TextNode.Location; boxMakerRec: BoxMakerRec; IF node = NIL THEN RETURN; [name, firstLoc, lastLoc] _ HoboNameAndRange[node]; boxMakerRec _ NARROW[RefTab.Fetch[boxMakerTable, name].val]; IF boxMakerRec = NIL OR boxMakerRec.proc = NIL THEN { Scold[Rope.Cat["\tUnknown hobo name \"", Atom.GetPName[name], "\"."]]; myBox _ NoNameBoxMaker[firstLoc, lastLoc, train, bounds]; } ELSE myBox _ boxMakerRec.proc[firstLoc, lastLoc, train, bounds]; }; BoxInTrain: PROC [data: BoxCharData, train: Train, bounds: Imager.VEC _ arbBounds] RETURNS [box: TiogaImager.Box] ~ { IF train.styleOps # print THEN { IF data.screenBox = NIL THEN data.screenBox _ BoxFromNode[data.node, train, bounds]; box _ data.screenBox; } ELSE { IF data.printBox = NIL THEN data.printBox _ BoxFromNode[data.node, train, bounds]; box _ data.printBox; }; }; Scold: PROC [msg: ROPE] ~ { MessageWindow.Append[msg, TRUE]; MessageWindow.Blink[]; }; BoxFromSelection: TEditInput.CommandProc ~ { <<[viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]>> IF TEditSelection.pSel.granularity = point -- TiogaAccess blows up on point selection THEN Scold["\tSelect more than a point."] ELSE { reader: TiogaAccess.Reader _ TiogaAccess.FromSelection[]; ref: TextNode.Ref _ IF TiogaAccess.EndOf[reader] THEN TextNode.NewTextNode[] ELSE NARROW[TiogaAccess.GetNodeRefs[reader].root]; myRef: TextNode.Ref _ CopyDocument[ref]; -- make a copy that we own. System will finalize its own copy. selectionBox _ NEW[BoxCharDataRep _ [node: myRef, screenBox: NIL, printBox: NIL]]; MessageWindow.Append["\tNew box made from selection.", TRUE]; RETURN[TRUE, TRUE]; }; }; BoxToSelection: TEditInput.CommandProc ~ { <<[viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]>> IF TEditSelection.pSel.granularity = point THEN Scold["\tSelect more than a point."] ELSE { selectionViewer: Viewer _ TEditSelection.pSel.viewer; reader: TiogaAccess.Reader _ TiogaAccess.FromSelection[]; writer: TiogaAccess.Writer _ TiogaAccess.Create[]; locked: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ { TiogaAccess.WriteSelection[writer]; -- replaces the primary selection }; tc: TiogaAccess.TiogaChar _ reader.Get[]; hoboRope: ROPE _ PutGet.ToRope[selectionBox.node].output; newProps: Atom.PropList _ LIST[ NEW[Atom.DottedPairNode _ [$data, selectionBox]], NEW[Atom.DottedPairNode _ [$hoboRope, hoboRope]], NEW[Atom.DottedPairNode _ [$Artwork, whoWeAreRope]]]; FOR pl: Atom.PropList _ tc.propList, pl.rest UNTIL pl = NIL DO IF pl.first.key # $data AND pl.first.key # $hoboRope AND pl.first.key # $Artwork THEN newProps _ CONS[pl.first, newProps]; ENDLOOP; tc.propList _ newProps; -- preserve immutability of prop list tc.charSet _ 0; tc.char _ 'x; TiogaAccess.Put[writer, tc]; -- puts everything at once TEditInputOps.CallWithLocks[locked]; ViewerOps.PaintViewer[viewer: selectionViewer, hint: client]; MessageWindow.Append["\tBoxChar created.", TRUE]; }; RETURN[TRUE, TRUE]; }; ViewBox: TEditInput.CommandProc ~ { <<[viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]>> IF PrimarySelectionIsOneChar[] THEN { loc: TextNode.Location _ TEditSelection.pSel.start.pos; data: BoxCharData _ GetDataFromLoc[loc.node, loc.where]; IF data = NIL THEN Scold["\tNot a BoxChar."] ELSE { viewer: Viewer _ ViewerTools.MakeNewTextViewer[info: [name: "TiogaFromBoxChar", column: TEditSelection.pSel.viewer.column], paint: FALSE ]; <> viewer.class.set[viewer, CopyDocument[data.node], FALSE, $TiogaDocument]; ViewerOps.OpenIcon[viewer]; }; } ELSE Scold["\tSelect a single BoxChar."]; RETURN[TRUE, TRUE]; }; PrimarySelectionIsOneChar: PROC RETURNS [BOOL] ~ { pSelStart: TextNode.Location _ TEditSelection.pSel.start.pos; pSelEnd: TextNode.Location _ TEditSelection.pSel.end.pos; RETURN[TEditSelection.pSel.granularity # point AND pSelStart.node = pSelEnd.node AND pSelStart.where = pSelEnd.where]; }; <> NoNameBoxMaker: PROC [first, last: TextNode.Location, train: Train, bounds: Imager.VEC _ arbBounds] RETURNS [myBox: TiogaImager.Box _ TiogaImager.EmptyBox[[2, 0]]] ~ { <> screenStyle: BOOL _ train.styleOps # print; IF first.node = last.node THEN { <> 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 { <> myBox.bounds _ TEditFormat.BoundingBox[NARROW[myBox.data]]; myBox.escapement _ [myBox.bounds.xmax, 0]; RETURN; }; }; <> { 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] ~ { <> 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; <> barGap: REAL _ HoboArgumentReal[first, last, "barGap", 2.0].val; barThickness: REAL _ HoboArgumentReal[first, last, "barThickness", 0.72].val; radicandBox: TiogaImager.Box _ BoxInTrain[radicandData, train]; <> grid: Grid _ EmptyGrid[]; ExtendGraph[grid, [2, 4], 2*barGap+barThickness, 0]; GridInsertEntry[grid, [0, 3], [2, 4], [level: 'n, plumb: 'c], radicandBox]; <> 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; <> 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]]] ~ { <> 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.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; <> <> <> 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 _ [xOrg, yOrg]; box _ NEW[TiogaImager.BoxRep _ [ nChars: 0, <> 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]] ~ { <= 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; }; <> <> <> <> <> <> <> <> <> <> <<];>> <<>> <> <<~ {>> <end align] or {min:+dif^its}.>> <> <> <> <> <= 0 DO>> <> < {>> <<};>> < {>> <