<<>> <> <> <> <> <<>> DIRECTORY Atom, Char, Commander, Convert, Hobo, Imager, ImagerBackdoor, ImagerBox, ImagerFont, ImagerPath, ImagerTransformation, IO, EditSpan, EditSpanSupport, MessageWindow, NodeProps, NodeStyle, NodeStyleOps, Prop, Tioga, TiogaIO, QPSetup, Real, RealFns, RefTab, Rope, TextEditBogus, TEditDocument, TEditFormat, TEditInput, TEditInputOps, TEditSelection, TextEdit, TextNode, TiogaAccess, TiogaAccessViewers, TiogaFind, TiogaImager, TextFind, ViewerClasses, ViewerOps, ViewerTools; HoboImpl: CEDAR PROGRAM IMPORTS Atom, Char, Commander, Convert, EditSpan, EditSpanSupport, Imager, ImagerBackdoor, ImagerBox, ImagerFont, ImagerPath, ImagerTransformation, IO, MessageWindow, NodeProps, NodeStyle, NodeStyleOps, TiogaIO, QPSetup, <> RealFns, RefTab, Rope, TEditFormat, TEditInput, TEditInputOps, TEditSelection, TextEdit, TextEditBogus, TextNode, TiogaAccess, TiogaAccessViewers, TiogaFind, TiogaImager, TextFind, 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.Node; 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.Length; 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: TEditFormat.CharacterFormatProc ~ { <> << 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: REAL ¬ style.GetSpaceWidth[]; font: ImagerFont.Font ¬ NodeStyle.GetFont[style]; xchar: TextEdit.XCHAR; charSet: TextEdit.CharSet ¬ 0; char: CHAR ¬ '); chars: ROPE; hoboRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $hoboRope]]; boxCharRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $BoxChar]]; box: TiogaImager.Box ¬ NIL; stippleRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $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[TextEdit.GetPropFromList[charProps, $reach]]; reach: REAL ¬ 16.0; eqExtents: ImagerFont.Extents ¬ font.RopeBoundingBox["="]; chExtents: ImagerFont.Extents; vshiftRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $vshift]]; hshiftRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $hshift]]; hscale, vscale: REAL ¬ 1.0; hshift, vshift: REAL ¬ 0.0; showBoundsRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $showBounds]]; showBounds: BOOL ¬ FALSE; boxCharData: BoxCharData ¬ GetDataFromLoc[loc.node, loc.where]; xchar ¬ TextEdit.FetchChar[text: loc.node, index: loc.where]; charSet _ Char.Set[xchar]; char _ Char.Narrow[xchar]; 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.Length > 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]]; 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 ¬ TextEditBogus.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 Char.Narrow[TextEdit.FetchChar[end.node, end.where-1]] = rightDelim THEN { nameEnd: INT ¬ Rope.SkipOver[nodeRope, hoboPrefixLen, legalNameChars]; IF nameEnd > hoboPrefixLen THEN name ¬ Atom.MakeAtom[nodeRope.Substr[ hoboPrefixLen, nameEnd-hoboPrefixLen]]; }; }; <> <> <> < 0>> <> <<[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];>> < atEnd AND TextEdit.FetchChar[node, atEnd].char = '=)>> <> <<}>> <> <<[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];>> <<};>> <<};>> <<>> HoboArgumentFind: PROC [first, last: TextNode.Location, name: ROPE] RETURNS [found: BOOL ¬ FALSE, node: TextNode.Ref, where: INT] ~ { <> IF name.Length > 0 THEN { target: TextFind.Target ~ TextFind.TargetFromRope[name]; [node: node, matchEnd: where] ¬ TiogaFind.Search[direction: forward, loc1: first, loc2: last, target: target, match: word]; IF node#NIL AND where> 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[TextEditBogus.GetRope[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[TextEditBogus.GetRope[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 ¬ TextEditBogus.GetRope[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.style.kind # 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 ¬ TiogaAccessViewers.FromSelection[]; ref: TextNode.Ref ¬ IF TiogaAccess.EndOf[reader] THEN TextNode.NewTextNode[] ELSE TextNode.Root[TiogaAccess.GetLocation[reader].node]; 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 ¬ TiogaAccessViewers.FromSelection[]; writer: TiogaAccess.Writer ¬ TiogaAccess.Create[]; locked: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ { TiogaAccessViewers.WriteSelection[writer]; -- replaces the primary selection }; tc: TiogaAccess.TiogaChar ¬ reader.Get[]; hoboRope: ROPE ¬ TiogaIO.ToRope[selectionBox.node]; newProps: Tioga.PropList ¬ LIST[ [$data, selectionBox], [$hoboRope, hoboRope], [$Artwork, whoWeAreRope]]; FOR pl: Tioga.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]]] ~ { <> IF first.node = last.node THEN { <> nodeStyle: NodeStyle.Ref ¬ NodeStyleOps.Alloc[]; NodeStyleOps.ApplyAll[nodeStyle, first.node, train.style.kind]; myBox ¬ TiogaImager.FormatLine[[first.node, 0], bounds.x, nodeStyle]; 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, styleKind: train.style.kind] 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 ¬ NodeStyle.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[ Rope.Substr[TextEditBogus.GetRope[loc.node], loc.where, size-1] ! Convert.Error => {CONTINUE}]; }; 'f => { gray: REAL ¬ 0.25; gray ¬ Convert.RealFromRope[ Rope.Substr[TextEditBogus.GetRope[loc.node], loc.where, size-1] ! Convert.Error => {CONTINUE}]; fillColor ¬ Imager.MakeGray[gray]; }; ENDCASE; } ELSE { IF loc.node.next = NIL 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.next = NIL 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.IsEmpty[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 ¬ TextEditBogus.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.IsEmpty[] THEN xOrg ¬ 0 ELSE xOrg ¬ QPSetup.AcquireVertex[grid.graph, grid.plumbBase].val; IF grid.levelBase.IsEmpty[] 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]; <> 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.Fetch[kind, 1] = 'P; ticLen: REAL ¬ SELECT Rope.Fetch[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.Fetch[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.Fetch[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>> <> < {>> <<};>> < {>> <