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] ~ { 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]; DoPaint: PROC ~ { IF paintInfo.box = NIL THEN { context.Move; context.SetFont[paintInfo.font]; context.ShowRope[paintInfo.chars]; } ELSE { context.Move; IF paintInfo.showBounds THEN { DoPaintBounds: PROC ~ { context.SetStrokeWidth[1.0]; context.SetGray[0.5]; context.MaskStrokeTrajectory[trajectory: ImagerPath.LineToX[ ImagerPath.LineToY[ ImagerPath.LineToX[ ImagerPath.LineToY[ ImagerPath.MoveTo[ [paintInfo.box.bounds.xmin, paintInfo.box.bounds.ymin]], paintInfo.box.bounds.ymax], paintInfo.box.bounds.xmax], paintInfo.box.bounds.ymin], paintInfo.box.bounds.xmin]]; }; Imager.DoSaveAll[context, DoPaintBounds]; }; context.SetFont[paintInfo.font]; TiogaImager.Render[paintInfo.box, context, [0.0, 0.0]]; }; }; Imager.DoSaveAll[context, DoPaint]; }; CopyDocument: PROC [root: TextNode.Ref] RETURNS [TextNode.Ref] ~ { IF root=NIL THEN RETURN[NIL] ELSE { CopyAction: NodeProps.MapPropsAction = { newValue: REF _ NodeProps.CopyInfo[name, value]; NodeProps.PutProp[newRoot, name, newValue]; RETURN[FALSE]; }; span: TextNode.Span _ TextNode.MakeNodeSpan[first: TextNode.FirstChild[root], last: TextNode.LastLocWithin[root].node]; copy: TextNode.Span _ EditSpanSupport.CopySpan[span]; newRoot: TextNode.Ref _ TextNode.Root[copy.start.node]; [] _ NodeProps.MapProps[n: root, action: CopyAction]; RETURN [newRoot]; }; }; GetDataFromLoc: PROC [node: TextNode.Ref, where: INT] RETURNS [BoxCharData] ~ { charProps: Atom.PropList _ TextEdit.GetCharPropList[node: node, index: where]; boxCharData: BoxCharData _ NARROW[charProps.GetPropFromList[$data]]; IF boxCharData = NIL THEN { hoboRope: ROPE _ NARROW[charProps.GetPropFromList[$hoboRope]]; IF hoboRope # NIL THEN { boxCharData _ NEW[BoxCharDataRep _ [node: PutGet.FromRope[hoboRope], screenBox: NIL, printBox: NIL]]; TextEdit.PutCharProp[node: node, index: where, name: $data, value: boxCharData]; } ELSE { boxCharData _ NIL; }; }; 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 ~ { 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 ~ { 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 ~ { 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]] ~ { rowDif: INT _ botWas.level-topWas.level; colDif: INT _ botWas.plumb-topWas.plumb; nodeRope: ROPE _ TextNode.NodeRope[loc.node]; ris: IO.STREAM; kind: IO.TokenKind; token: ROPE; expected: {toprow, topcol, botrow, botcol} _ toprow; seen: BOOL _ FALSE; rectRope: ROPE _ "="; topSeen, botSeen: BOOL _ FALSE; FOR i: INT _ loc.where-2, i-1 WHILE i >= 0 DO SELECT nodeRope.InlineFetch[i] FROM '\t, '\040 => {rectRope _ nodeRope.Substr[i+1, loc.where-i-1]; EXIT}; ENDCASE; REPEAT FINISHED => rectRope _ nodeRope.Substr[0, loc.where]; ENDLOOP; ris _ IO.RIS[rectRope]; WHILE NOT IO.EndOf[ris] DO [kind, token, ] _ IO.GetCedarTokenRope[ris]; SELECT kind FROM tokenDECIMAL => { IF NOT seen THEN { SELECT expected FROM toprow => {top.level _ Convert.IntFromRope[token]; topSeen _ TRUE}; topcol => {top.plumb _ Convert.IntFromRope[token]; topSeen _ TRUE}; botrow => {bot.level _ Convert.IntFromRope[token]; botSeen _ TRUE}; botcol => {bot.plumb _ Convert.IntFromRope[token]; botSeen _ TRUE}; ENDCASE; seen _ TRUE; } ELSE {Scold["\tGrid corner: Unexpected number."];}; }; tokenSINGLE => { SELECT token.InlineFetch[0] FROM '@ => { SELECT expected FROM toprow => expected _ topcol; botrow => expected _ botcol; ENDCASE => {Scold["\tGrid corner: Unexpected atsign."];}; seen _ FALSE; }; ', => { SELECT expected FROM toprow, topcol => expected _ botrow; ENDCASE => {Scold["\tGrid corner: Unexpected comma."];}; seen _ FALSE; }; '= => EXIT; '/ => { quote: CHAR ~ '\047; ch1: CHAR _ ris.GetChar[]; GetLineup: PROC RETURNS [lineup: FeaturePair] ~ { 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; }; RegisterBoxMaker: PROC [name: ATOM, proc: BoxMakerProc, doc: ROPE] ~ { IF proc = NIL THEN [] _ RefTab.Delete[boxMakerTable, name] ELSE [] _ RefTab.Store[boxMakerTable, name, NEW[BoxMakerRecRep _ [name: name, proc: proc, doc: doc]]]; }; ShowTicsCmdProc: Commander.CommandProc ~ { IF ticWidth = 0.0 THEN ticWidth _ 1.0 ELSE ticWidth _ 0.0; }; Commander.Register["HoboShowTics", ShowTicsCmdProc, "Toggle Hobo tic display"]; RegisterHoboArtworkClass[]; TEditInput.Register[name: $MakeBox, proc: BoxFromSelection, before: TRUE]; TEditInput.Register[name: $StuffBox, proc: BoxToSelection, before: TRUE]; TEditInput.Register[name: $ViewBox, proc: ViewBox, before: TRUE]; RegisterBoxMaker[noName, NoNameBoxMaker, "No hobo; quoted node."]; RegisterBoxMaker[$tripile, TripileBoxMaker, "Stack of three centered boxes."]; RegisterBoxMaker[$bar, BarBoxMaker, "A horizontal or vertical bar."]; RegisterBoxMaker[$fraction, FractionBoxMaker, "A fraction."]; RegisterBoxMaker[$limit, LimitBoxMaker, "Operator with upper and lower limits."]; RegisterBoxMaker[$axisCenter, AxisCenteredBoxMaker, "An axis centered box."]; RegisterBoxMaker[$grid, GridBoxMaker, "Boxes on a grid."]; END. .. BigBoxMaker: PROC [first, last: TextNode.Location, train: Train, bounds: Imager.VEC _ arbBounds] RETURNS [aBox: TiogaImager.Box] ~ { default: BoxCharData ~ emptyBoxChar; height: REAL _ HoboArgumentReal[first, last, "height", -1.0].val; width: REAL _ HoboArgumentReal[first, last, "width", -1.0].val; found: BOOL _ FALSE; hitNode: TextNode.Ref; where: INT; charSet, charSet2: TextEdit.CharSet _ 0; char: CHAR _ '|; char2: CHAR _ 0C; charBox: TiogaImager.Box _ NIL; [found, charBox] _ HoboArgumentBox[first, last, "char", NIL]; IF found THEN { } ELSE { [found, hitNode, where] _ HoboArgumentFind[first, last, "char"]; IF found AND TextEdit.Size[hitNode] > where THEN { [charSet, char] _ TextEdit.FetchChar[text: loc.node, index: loc.where]; IF TextEdit.Size[hitNode] > where+1 THEN { [charSet2, char2] _ TextEdit.FetchChar[text: loc.node, index: loc.where+1]; IF charSet2 # charSet OR char2 = 40C OR char2 = '\t THEN char2 _ 0C; }; IF boxCharRope = NIL THEN { fontSize: INTEGER _ style.GetFontSizeI[]; font: ImagerFont.Font _ TEditFormat.GetFont[style]; 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 RETURN[BoxInTrain[default, train]]; }; }; IF muckWithPostfixProperty THEN { postfixValue, leading, topLeading, bottomLeading: ROPE _ NIL; -- for postfix properties leading _ Convert.RopeFromReal[displayBox.Height[]]; topLeading _ Convert.RopeFromReal[displayBox.Extents[].ascent + 12.0]; bottomLeading _ Convert.RopeFromReal[displayBox.Extents[].descent + 12.0]; postfixValue _ Rope.Cat[leading, " pt the leading 2 .copy .gt 3 1 .roll .ifelse leading "]; postfixValue _ Rope.Cat[postfixValue, topLeading, " pt the topLeading 2 .copy .gt 3 1 .roll .ifelse topLeading "]; postfixValue _ Rope.Cat[postfixValue, topLeading, " pt the topIndent 2 .copy .gt 3 1 .roll .ifelse topIndent "]; postfixValue _ Rope.Cat[postfixValue, bottomLeading, " pt the bottomLeading 2 .copy .gt 3 1 .roll .ifelse bottomLeading "]; NodeProps.PutProp[loc.node, $Postfix, postfixValue]; }; GridRep: TYPE ~ RECORD [ nRows, nCols: CARD _ 0, entryList: LIST OF GridEntry _ NIL, xminCol, yminRow, xmaxCol, ymaxRow, widthCol, heightRow: RealSeq _ NIL ]; RealSeq: TYPE ~ REF RealSeqRep; RealSeqRep: TYPE ~ RECORD [used: INT _ 0, seq: SEQUENCE size: CARDINAL OF REAL]; GetCorners: PROC [topWas, botWas: Corner, rowStep, colStep: INT, loc: TextNode.Location] RETURNS [top: Corner _ [0, 0], bot: Corner_ [1, 1], lineup: FeaturePair _ ['n, 'n]] ~ { rowDif: INT _ botWas.row-topWas.row; colDif: INT _ botWas.col-topWas.col; nodeRope: ROPE _ TextNode.NodeRope[loc.node]; ris: IO.STREAM; kind: IO.TokenKind; token: ROPE; expected: {toprow, topcol, botrow, botcol} _ toprow; seen: BOOL _ FALSE; rectRope: ROPE _ "="; topSeen, botSeen: BOOL _ FALSE; c: CHAR; FOR i: INT _ loc.where-2, i-1 WHILE i >= 0 DO SELECT c _ nodeRope.InlineFetch[i] FROM '0, '1, '2, '3, '4, '5, '6, '7, '8, '9, '@, ',, '/, 'l, 'r, 'c, 'n => rectRope _ Rope.FromChar[c].Concat[rectRope]; ENDCASE => EXIT; 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.row _ Convert.IntFromRope[token]; topSeen _ TRUE}; topcol => {top.col _ Convert.IntFromRope[token]; topSeen _ TRUE}; botrow => {bot.row _ Convert.IntFromRope[token]; botSeen _ TRUE}; botcol => {bot.col _ 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; '/ => { ch: CHAR _ IO.GetChar[ris]; IF ch = '= THEN { Scold["\tPremature equal sign"]; EXIT } ELSE lineup _ [ch, ch]; }; ENDCASE => {Scold["\tGrid corner: Unexpected single token."];}; }; ENDCASE => {Scold["\tGrid corner: Unexpected token."];}; ENDLOOP; SELECT TRUE FROM NOT topSeen AND NOT botSeen => { top.row _ topWas.row+rowStep; top.col _ topWas.col+colStep; bot.row _ top.row+rowDif; bot.col _ top.col+colDif; }; topSeen AND NOT botSeen => { bot.row _ top.row+rowDif; bot.col _ top.col+colDif; }; NOT topSeen AND botSeen => { top.row _ top.row-rowDif; top.col _ top.col-colDif; }; topSeen AND botSeen => {--No defaulting needed--}; ENDCASE; }; ªHoboImpl.mesa Copyright Ó 1990 by Xerox Corporation. All rights reserved. Ken Shoemake, January 17, 1991 4:56 am PST Abbreviations For Imported Types Defs For Private Types (local to module) Constants Instance Variables Character Artwork Procedures effects: Registers $Hobo as a Tioga character artwork class. create the class register it effects: Unregisters $Hobo as a Tioga character artwork class. effects: Formats math expression artwork represented by ROPE at loc. Returns character artwork for formatted expression. Note that OfStyle: TYPE ~ { screen, print, base } create artwork char effects: Paints charArtwork onto context. retrieve painting information x: REAL _ -paintInfo.extents.leftExtent; y: REAL _ -paintInfo.extents.descent; w: REAL _ paintInfo.extents.leftExtent+paintInfo.extents.rightExtent; h: REAL _ paintInfo.extents.descent+paintInfo.extents.ascent; font: ImagerFont.Font _ ImagerFont.FindScaled["Xerox/XC1/Tioga-Classic-12", 12.0]; chExtents: ImagerFont.Extents _ font.RopeBoundingBox[paintInfo.chars]; eqExtents: ImagerFont.Extents _ font.RopeBoundingBox["="]; vscale: REAL _ MAX[1.0, 2*paintInfo.reach/(chExtents.ascent+chExtents.descent)]; hscale: REAL _ RealFns.SqRt[RealFns.SqRt[vscale]]; vshift: REAL _ (eqExtents.ascent+eqExtents.descent)/2 - eqExtents.descent; local procedure to do painting within a DoSaveAll context.SetColor[paintInfo.color]; context.MaskRectangle[[x: x, y: y, w: w, h: h]]; context.SetColor[Imager.black]; pretend that current x,y position in context is origin while actually painting Formatted Boxes To and From Tioga [name: ATOM, value: REF] RETURNS [quit: BOOL _ FALSE]; Is it safe to do this while formatting? If not, could spawn a process . . . boxCharData _ NEW[BoxCharDataRep _ [node: TextEdit.DocFromNode[TextEdit.FromRope["#"]], screenBox: NIL, printBox: NIL]]; Returns start and end of node, which may be initial leftDelim and final rightDelim. Returns name if node is bracketed with leftDelim"hobo=name" and rightDelim. Looks for "name=", and returns location following "=". Uses TreeFind. Looks for "name=X", where X should be BoxChar or node, and returns data for X. Looks for "name=X", where X should be real literal, and returns value for X, else default. Looks for "name=X", where X should be int literal, and returns value for X, else default. Looks for "name=X", where X should be atom literal, and returns value for X, else default. [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE] [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE] [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE] May need TextEdit.DocFromNode[PutGet.FromRope[data.node]] Box Makers Ordinary old box from node Try a one-liner, which ought to be very common . . . Whew! It all fit. Bounding box can be tight easily. Looks like a one-liner won't do it. Return sloppy bounding box. Sigh. Attempting a composite box!! args are "upper=U center=C lower=L" rootData: BoxCharData _ HoboArgumentBox[first, last, "root", default].val; rootBox: TiogaImager.Box _ BoxInTrain[rootData, train]; GridInsertEntry[grid, [1, 0], [2, 1], [level: 'n, plumb: 'c], rootBox]; axisHeight: REAL _ HoboArgumentReal[first, last, "axisHeight", GetAxisHeight[train]].val; For matrices and tables. Takes a series of boxes, and aligns them both ways. IF Rope.SkipTo[mid, 0, "C"] < Rope.InlineLength[mid] Extend graph plumb and level lines to include botCorner of new entry. Spread sides of span far enough to accomodate box. Include offset to base point, decimal point, or center point. grid.origin _ [0, ySize]; bounds: [xmin: 0, ymin: 0, xmax: xSize, ymax: ySize], Should look for explicit Now, Opp before "=" here Possibly use "r@c,ropp@copp/arac=", where omitted r or c defaults to 0 Omitted opp or now defaults to keep same opp-now difference (clipped to 0) Omitted all just increments by step Column alignment type ac is l, r, c, d, or n, for left, right, center, decimal, or natural Row alignment type ar is t, b, c, or n, for top, bottom, center, or natural; default n << Check for generalized decimal lineups like "/'?=" and "/t'\t=". >> Cover: TYPE ~ REF CoverRep; CoverRep: TYPE ~ RECORD [ min: INT _ 0, max: INT _ 1, dif: INT _ 1, inc: INT _ 1, end: INT _ 1, its: INT _ 1, align: ["c", "."], tab: "\t" ]; GetCoverage: PROC [plumbWas, levelWas: Cover, : TextNode.Location] RETURNS [plumbCover: Cover, levelCover: Cover, style: ROPE] ~ { Syntax: [min:maxend align] or {min:+dif^its}. nodeRope: ROPE _ TextNode.NodeRope[loc.node]; ris: IO.STREAM; kind: IO.TokenKind; token: ROPE; FOR i: INT _ loc.where-2, i-1 WHILE i >= 0 DO SELECT nodeRope.InlineFetch[i] FROM '\t, '\040 => {rectRope _ nodeRope.Substr[i+1, loc.where-i-1]; EXIT}; ENDCASE; REPEAT FINISHED => rectRope _ nodeRope.Substr[0, loc.where]; ENDLOOP; ris _ IO.RIS[rectRope]; WHILE NOT IO.EndOf[ris] DO [kind, token, ] _ IO.GetCedarTokenRope[ris]; SELECT kind FROM tokenDECIMAL => { }; tokenSINGLE => { SELECT token.InlineFetch[0] FROM '@ => { }; ENDCASE => {Scold["\tGrid cover: Unexpected single token."];}; }; ENDCASE => {Scold["\tGrid cover: Unexpected token."];}; ENDLOOP; }; Box Maker Registration Start Code RegisterBoxMaker[$big, BigBoxMaker, "Big brackets of various sorts."]; Incomplete code For drawing big {[(<|>)]} characters in a box. args are "char=C height=h width=w" Scale a BoxChar character. Perhaps use box's natural expansion capability. Stretch a special character. << To be continued . . . >> The idea here is to create a special class of box, like bars, with its own draw procedure and with suitable data -- [({< or a box -- to be drawn at size. compute appropriate postfix point values to place on current node in order to set a maximum value, the following JaM code fragment does this mumble pt the leading 2 .copy .gt 3 1 .roll .ifelse leading put Postfix properties on node containing displayExpr to insure proper spacing Stale code Should look for explicit Now, Opp before "=" here Possibly use "r@c,ropp@copp/a=", where omitted r or c defaults to 0 Omitted opp or now defaults to keep same opp-now difference (clipped to 0) Omitted all just increments by step Alignment type a is l, r, c, or n, for left, right, center, natural �Ê4y˜šœ ™ Icode™Kšœ+�œ˜1—šœ"�œ˜AKšœ.�œ˜4—K˜Kšœ ™ Kšœ,˜,Kšœ0 ˜NK˜K˜—šŸœ�œ˜$Kšœ?™?K˜Kšœ1˜1K˜K˜—šŸ œ�œm�œ˜�KšœE™EK™=Kšœ�œ™;K˜K˜KšœZ˜ZKšœ �œ˜)Kšœ3˜3K˜Kšœ�œ˜Kšœ�œ˜ Kšœ �œ�œ'˜>Kšœ �œ�œ&˜@Kšœ�œ˜Kšœ �œ�œ&˜@Kšœ �œ ˜Kšœ �œ˜Kšœ �œ˜Kšœ �œ˜Kšœ�œ˜K˜Kšœ�œ˜Kšœ �œ�œ$˜Kšœ �œ�œ%˜>Kšœ�œ˜Kšœ�œ˜Kšœ�œ�œ)˜FKšœ �œ�œ˜Kšœ?˜?K˜KšœG˜GKšœV˜VKšœ=�œ˜GKšœ:�œ˜DKšœ<�œ˜FKšœ<�œ˜Fš�œ ˜"Kš�œE�œ˜S—K˜š�œ�˜š�œ˜š�œ�˜š�œ˜Kšœ(˜(Kšœ �œ4˜@Kšœ,˜,KšœD˜DKšœX˜XKšœ&˜&Kšœ(˜(K˜—š�œ˜Kšœ˜Kšœ+˜+KšœF˜FKšœ/˜/Kšœ˜K˜——K˜—š�œ˜KšœY˜YKšœF˜FKšœ/˜/Kšœ˜K˜——K˜Kšœ™šœ�œ˜'KšœF�œJ�œb˜ùK˜—Kš�œ˜K˜K˜K˜—šŸ œ�œ<˜OKšœ*™*K™Kšœ™Kšœ�œ˜0Kšœ�œ!™(Kšœ�œ™%Kšœ�œ>™EKšœ�œ6™=KšœR™RKšœF™FKšœ:™:Kšœ�œ�œ>™PKšœ�œ&™2Kšœ�œ>™JK˜Kšœ1™1šŸœ�œ˜K˜š�œ�˜š�œ˜K˜ Kšœ"™"K™0K™Kšœ™Kšœ ˜ Kšœ"˜"K˜—š�œ˜K˜ š�œ˜Kš�œ˜šŸ œ�œ˜K˜Kšœ˜KšœÁ˜ÁK˜—K˜)K˜—Kšœ ˜ K˜7K˜——K˜—K˜KšœN™NK˜#K˜——šŸ!™!šŸ œ�œ�œ˜BKš �œ�œ�œ�œ�œ˜š�œ˜šŸ œ˜(Kš œ�œ �œ�œ�œ�œ™6Kšœ �œ#˜0Kšœ+˜+Kš�œ�œ˜K˜—Kšœw˜wKšœ5˜5Kšœ7˜7K•StartOfExpansionn[n: TextNode.Ref, action: NodeProps.MapPropsAction, formatFlag: BOOL _ TRUE, commentFlag: BOOL _ TRUE]šœ5˜5Kš�œ ˜Kšœ˜—K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸœ�œ�œ�œ˜OKšœN˜NKšœ�œ#˜Dš�œ�˜š�œ˜Kšœ �œ�œ'˜>š�œ �˜š�œ˜Kšœ�œ?�œ �œ˜ešœP˜PKšœL™L—K˜—š�œ˜Kšœ�œ˜Kšœ�œS�œ �œ™yK˜——K˜——Kš�œ ˜K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸœ�œ�œ�œ-˜lK™SK™KKšœ �œ/˜=Kšœ'˜'Kšœ#˜#š�œ<�œ�œ�œ<˜•š�œ�˜Kšœ �œ:˜Fš�œ˜Kš�œN˜R—Kš�œ˜——K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸœ�œ(�œ�œ �œ�œ�œ˜…K™FKšœ�œ˜š�œ˜š�œ˜Kšœp�œ�œS˜Óš�œ�œ�œ*�˜`Kš�œ˜—K˜—š�œ˜Kšœo�œ�œS˜ÓK˜——K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]š Ÿœ�œ(�œ�œ �œ�œ˜ŒK™NK˜Kšœ�œ˜ Kšœ>˜>š�œ˜š�œ˜š�œ˜!š�œ˜Kšœ3˜3Kš �œ�œ�œ�œ�œ˜&K˜—š�œ˜š�œ �˜%š�œ˜Kšœl˜lKšœ<˜˜>š�œ�œ(˜4š�œ˜šœK˜Kšœ˜Kšœ7˜7Kš�œ˜ K˜——K˜——K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸœ�œ(�œ �œ�œ �œ�œ�œ˜|K™YK˜Kšœ �œ˜K˜Kšœ>˜>š�œ�œ(˜4š�œ˜šœJ˜Jšœ˜Kšœ7˜7Kš�œ˜ K˜——K˜——K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸœ�œ(�œ �œ�œ �œ�œ�œ˜K™ZK˜Kšœ �œ˜K˜Kšœ>˜>š�œ�œ(˜4š�œ˜Kšœ �œ4˜AšœS˜Sšœ˜Kšœ7˜7Kš�œ˜ K˜——K˜——K˜K˜—šŸ œ�œ2�œ�œ<˜˜Kšœ�œ˜ Kšœ%˜%Kšœ˜Kš�œ�œ�œ�œ˜Kšœ3˜3Kšœ�œ(˜<š�œ�œ�œ�˜.š�œ˜KšœF˜FKšœ9˜9K˜—Kš�œ<˜@—K˜K˜—šŸ œ�œ2�œ�œ˜uš�œ˜š�œ˜š�œ�˜Kš�œ8˜<—Kšœ˜K˜—š�œ˜š�œ�˜Kš�œ7˜;—Kšœ˜K˜——K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸœ�œ�œ˜Kšœ�œ˜ Kšœ˜K˜K˜—šŸœ˜,Kš œ �œ�œ�œ�œ�œ�œ™Zš�œ) *˜UKš�œ%˜)š�œ˜Kšœ9˜9Kš œ�œ�œ �œ�œ�œ'˜Kšœ) >˜gKšœ�œ+�œ �œ˜RKšœ7�œ˜=Kš�œ�œ�œ˜K˜——K˜K˜—šŸœ˜*Kš œ �œ�œ�œ�œ�œ�œ™Zš�œ(˜*Kš�œ%˜)š�œ˜Kšœ5˜5Kšœ9˜9Kšœ2˜2šœ�œ8˜DKšœ$ !˜EKšœ˜—Kšœ)˜)Kšœ �œ+˜9Kš œ�œ�œ/�œ/�œ2˜¹š�œ*�œ�˜>š�œ�œ�œ˜PKš�œ �œ˜)—Kš�œ˜—Kšœ %˜=K˜K˜ Kšœ ˜7Kšœ$˜$K–w[viewer: ViewerClasses.Viewer, hint: ViewerOps.PaintHint, clearClient: BOOL _ TRUE, whatChanged: REF ANY _ NIL]˜=Kšœ+�œ˜1K˜——Kš�œ�œ�œ˜K˜K˜—šŸœ˜#Kš œ �œ�œ�œ�œ�œ�œ™Zš�œ˜š�œ˜Kšœ7˜7Kšœ8˜8š�œ�˜ Kš�œ˜š�œ˜Kšœƒ�œ˜‹Kšœ9™9Kšœ2�œ˜IKšœ˜Kšœ˜——K˜—Kš�œ%˜)—Kš�œ�œ�œ˜K˜K˜—šŸœ�œ�œ�œ˜2Kšœ=˜=Kšœ9˜9Kš�œ)�œ�œ"˜vK˜K˜——šŸ ™ šŸœ�œ?�œ �œ<˜§K™Kšœ �œ˜+š�œ˜š�œ˜K™4Kšœ0˜0Kšœ=˜=KšœR˜RKšœ˜š�œ)˜+š�œ˜K™5Kšœ'�œ˜;K˜*Kš�œ˜K˜——K˜——K™G˜Kš œ#�œ�œ�œ^�œ/�œ˜ÒKšœ �œ�œ7˜JKš œ�œ�œ�œ�œ �œ˜NK˜*KšœE˜EKš�œ˜K˜—K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸœ�œ?�œ �œ˜ŠK™AKšœ$˜$KšœH˜HKšœJ˜JKšœH˜HKšœ �œ6˜DKšœ �œ6˜DKšœ5˜5Kšœ7˜7Kšœ5˜5K˜Kšœ"˜"K˜$Kšœ/˜/Kšœ%˜%Kšœ/˜/Kšœ$˜$Kšœ!�œ˜9Kšœm˜mKšœ,˜,K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸ œ�œ�œ.˜_Kšœ˜Kšœ\˜\K˜*K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸ œ�œ?�œ �œ˜ˆKšœ˜Kšœ$˜$KšœR˜RKšœJ™JKšœ�œ4˜@Kšœ�œ;˜MKšœ?˜?Kšœ7™7K˜Kšœ3�˜4KšœK˜KKšœG™GKšœ2˜2Kšœ�œ˜Kšœ%˜%Kšœ ˜ K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸœ�œ?�œ �œ˜ŒKšœ˜Kšœ$˜$KšœL˜LKšœL˜LKšœ�œ4˜@Kšœ �œ9˜JKšœ �œ9˜JKšœ�œ;˜MKšœ �œI˜YKšœ9˜9Kšœ9˜9K˜Kšœ3�˜4KšœH˜HKšœH˜HKšœ?�œ˜QKšœ?�œ˜QKšœ2˜2Kšœ>�œ˜OKšœ�œ˜Kšœ%˜%Kšœ ˜ K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸ œ�œ?�œ �œ˜‡Kšœ˜Kšœ$˜$KšœL˜LKšœR˜RKšœL˜LKšœ�œ1˜:Kšœ �œ9˜JKšœ �œ9˜JKšœ �œI™YKšœ9˜9Kšœ?˜?Kšœ9˜9K˜Kšœ!�˜"KšœH˜HKšœK˜KKšœH˜HKšœB�œ˜TKšœB�œ˜TKšœ�œ˜Kšœ&˜&Kšœ˜K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸ œ�œ?�œ �œ<˜¤Kšœ�œ4˜@Kšœ�œ3˜>šœ�œ˜"Kšœ ˜ Kšœ7˜7Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ�œ˜'Kšœ˜—K˜K˜—šŸ œ�œA�œ˜YšŸœ�œ˜Kšœ˜Kšœ˜Kšœ˜K˜-K˜—Kšœ�œ ˜!K˜K˜K˜—šŸœ�œ?�œ �œ<˜­Kšœ$˜$Kšœ �œI˜YKšœ�œ˜ KšœL˜LK˜3KšœP˜PK˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸ œ�œ�œ�œ ˜GKšœ9˜9Kšœ:˜:KšœH˜HK˜K˜—šŸ œ�œ?�œ �œ<˜¥K™MKšœ$˜$Kšœ˜Kšœ�œ˜Kšœ �œG˜VKšœ �œG˜VKšœ�œ9˜KKšœ �œ7˜HKšœ�œ;˜OKšœ�œ9˜LKšœ�œ?˜XKšœ�œ˜Kšœ!�œ˜%K˜Kšœ �œ2˜>Kšœ �œ2˜>Kšœ�œ˜Kšœ�œ˜Kšœ�œ�œ˜ Kšœ˜Kšœ(˜(K˜šŸ œ�œ�œ�œ˜$Kšœ�œ˜ KšœE˜EKš �œ�œ�œ�œ�œ˜ K˜uKšœ˜š�œ!�œ˜8š�œ˜Kšœ �œ˜š�œ$˜&š�œ˜K˜/š�œ�˜˜Kšœg�œ˜rK˜—˜Kšœ�œ˜Kšœ_�œ˜jK˜"K˜—Kš�œ˜—K˜—š�œ˜š�œ˜Kš�œ˜Kš�œ+˜/—K˜——K˜—š�œ˜š�œ$˜&š�œ˜K˜/Kšœ0˜0K˜—š�œ˜š�œ˜Kš�œ˜Kš�œ+˜/—š�œ!�˜&š�œ˜Kšœn˜nKšœ<˜˜>K˜Kšœ,˜,Kš �œ�œ�œ�œ �œ �œ ˜TKšœ.˜.Kšœ:˜:Kšœ:˜:Kšœ.˜.Kšœ5˜5Kš�œ2™4š�œ˜š�œ˜Kšœ�œ,˜HKšœ4˜4K˜——Kš�œ ˜K˜K˜—šŸ œ�œ:�œ˜VKšœ˜Kšœ0˜0KšœQ˜QKšœQ˜QKšœQ˜QKšœQ˜QKšœ�œ˜+K˜K˜—šŸœ�œH˜\K˜Kšœ,˜,KšœR˜RKšœR˜RKšœR˜RKšœR˜RKšœ�œ˜.K˜K˜—šŸœ�œZ˜oKšœ�œ ˜Kšœ�œ�œ˜'Kšœ�œ�œ˜'Kšœ�œ�œ˜'Kšœ�œ�œ˜'Kšœ�œ ˜Kšœ�œ ˜Kšœ �œ#˜2Kšœ �œ#˜3K˜K˜Kšœ�œ˜%K™EK™2K™=š�œ�˜šœ ˜ KšœŸ˜ŸK˜—šœ ˜ Kšœ«˜«Kšœ!˜!K˜—šœ ˜ Kšœ_�œ˜pK˜K˜—šœ ˜ Kšœ_�œ˜pK˜K˜—Kš�œc�œ ˜w—š�œ�˜šœ ˜ Kšœž˜žK˜—šœ ˜ Kšœ©˜©Kšœ ˜ K˜—šœ ˜ Kšœ_�œ ˜oK˜K˜—šœ ˜ Kšœ_�œ ˜oK˜K˜—šœ˜Kšœ �œ˜Kš�œ�œ˜.š�œ �œ�˜šœ#˜#Kšœ�œL˜VKšœ�œ.˜9Kšœ>˜>Kšœ˜K˜—Kš�œ˜—Kšœ£˜£K˜—Kš�œc�œ ˜v—š�œ�œ˜)Kš�œ^˜b—Kšœ�œ.˜CK˜K˜—šŸ œ�œ�œ˜AšŸœ˜Kšœ&�œ˜4—Kšœ#�œ˜,Kšœ%�œ (˜XKšœ%�œ &˜VK˜+K˜K˜MKšœM˜Mš�œ˜!Kš�œ ˜ Kš�œ>˜B—š�œ˜!Kš�œ ˜Kš�œ>˜B—Kšœ™Kšœ˜šœ�œ˜ Kšœ ˜ Kšœ5™5KšœF˜FKšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜—K˜K˜—šŸ œ�œA�œ˜ZšŸœ�œ˜Kšœ˜Kšœ ˜ š �œ�œ�œ%�œ�œ�˜IKšœ'˜'Kšœx˜xKš�œ˜—K˜Kšœ˜š �œ�œ�œ"�œ�œ�˜DKšœ/˜/Kšœy˜yKš�œ˜—K˜—šŸœ�œ˜Kšœ˜Kšœ ˜ š �œ�œ�œ(�œ �œ�˜NKšœf˜fKš�œ˜—Kš�œ�œ)˜?K˜—Kšœ �œ ˜Kšœ$˜$K˜$K˜K˜—šŸœ�œ,�œ˜LK˜Kšœ%˜%š�œ4�œ �œ�˜NKšœ�œD˜NKšœ�œ"˜-šœ�œ�œ�˜4Kšœ-˜-Kš�œ˜ —š�œ˜Kš�œF˜JKš�œI�˜N—Kš�œ˜—K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸ œ�œ,�œ�œP˜°K™1Kš œÏuœ¡œÏdœ¢œ&™FK™JK™#Kšœ¢œB™ZKšœ¢œA™VKšœ�œ˜(Kšœ�œ˜(Kšœ �œ˜-Kšœ�œ�œ˜Kšœ�œ ˜Kšœ�œ˜ Kšœ4˜4Kšœ�œ�œ˜Kšœ �œ˜Kšœ�œ�œ˜š�œ�œ�œ�˜-š�œ�˜#Kšœ?�œ˜EKš�œ˜—š�˜Kš�œ-˜5—Kš�œ˜—Kšœ�œ�œ ˜š�œ�œ�œ �˜Kšœ�œ˜,š�œ�˜šœ˜š�œ�œ˜ š�œ˜š�œ �˜Jšœ=�œ˜CJšœ=�œ˜CJšœ=�œ˜CJšœ=�œ˜CJš�œ˜—Kšœ�œ˜ K˜—Kš�œ/˜3—Kšœ˜—šœ˜š�œ�˜ ˜š�œ �˜J˜J˜Jš�œ2˜9—Kšœ�œ˜ K˜—˜š�œ �˜J˜$Jš�œ1˜8—Kšœ�œ˜ K˜—Kšœ�œ˜ ˜Kšœ�œ ˜Kšœ�œ˜šŸ œ�œ�œ˜-K˜K™Eš�œ�˜Kšœ(�œ ˜:Kšœ �œ ˜Kš�œ˜—š�œ�˜Kšœ�œ ˜Kšœ �œ ˜Kš�œ˜—Kš�œ˜š�˜˜ Kšœ˜K˜#Kš�œ˜K˜—˜ K˜#Kš�œ˜K˜——K˜—Kšœ˜K˜—Kš�œ8˜?—K˜—Kš�œ1˜8—Kš�œ˜—š�œ�œ�˜š�œ �œ�œ ˜ K˜!K˜!Kšœ˜Kšœ˜K˜—šœ�œ�œ ˜Kšœ˜Kšœ˜K˜—š�œ �œ ˜Kšœ˜Kšœ˜K˜—Kšœ�œ  œ˜2Kš�œ˜—K˜K˜—Jšœ�œ�œ ™šœ �œ�œ™Jšœ�œ™ Jšœ�œ™ Jšœ�œ™ Jšœ�œ™ Jšœ�œ™ Jšœ�œ™ J™J™ Jšœ™J™—šŸ œ�œ2�œ/�œ™~J™J™2Kšœ �œ™-Kšœ�œ�œ™Kšœ�œ ™Kšœ�œ™ š�œ�œ�œ�™-š�œ�™#Kšœ?�œ™EKš�œ™—š�™Kš�œ-™5—Kš�œ™—Kšœ�œ�œ ™š�œ�œ�œ �™Kšœ�œ™,š�œ�™šœ™Kšœ™—šœ™š�œ�™ ™K™—Kš�œ7™>—K™—Kš�œ0™7—Kš�œ™—J™J™——šŸ™šŸœ�œ�œ�œ˜Fš�œ�˜ Jš�œ(˜,Jš�œ(�œ7˜f—J˜J˜—šŸœ˜&J˜Jš�œ�œ�œ˜:J˜J˜——šŸ ™ J˜OJšœ˜JšœD�œ˜JJšœC�œ˜IJšœ;�œ˜AJ˜BJšœF™FJšœN˜NJšœE˜EJšœ=˜=JšœQ˜QJšœM˜MJšœ:˜:J˜—Kš�œ˜˜–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]™–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]šŸ œ�œ?�œ �œ˜„K™RKšœ$˜$Kšœ�œ5˜AKšœ�œ4˜?Kšœ�œ�œ˜K˜Kšœ�œ˜ K˜(Kšœ�œ˜Kšœ�œ˜Kšœ�œ˜Kšœ8�œ˜=š�œ˜š�œ˜K™KK˜—š�œ˜K™Kšœ@˜@š�œ�œ˜+š�œ˜KšœG˜Gš�œ!˜#š�œ˜KšœK˜KKš�œ�œ �œ �œ ˜DK˜——K™Kšœ ÏbœŒ™™š�œ�˜š�œ˜Kšœ �œ˜)Kšœ3˜3Kšœ(˜(Kšœ �œ4˜@Kšœ,˜,KšœD˜DKšœX˜XKšœ&˜&Kšœ(˜(K˜—š�œ˜Kšœ˜Kšœ+˜+KšœF˜FKšœ/˜/Kšœ˜K˜——K˜—Kš�œ�œ˜(—K˜K˜——K˜K˜—š�œ�œ˜!KšœA™AK™JK™;Kšœ2�œ�œ ˜XK˜4K˜GK˜JK˜[K˜rK˜pK˜{K˜KšœN™NK˜4Kšœ˜—K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]™ šœ �œ�œ˜Kšœ�œ˜Kšœ �œ�œ �œ˜#KšœC�˜FK˜—Kšœ �œ�œ ˜Kšœ �œ�œ�œ �œ�œ�œ�œ˜PšŸ œ�œ,�œ�œP˜°K™1Kšœ¡œ¡œ(™CK™JK™#K™CKšœ�œ˜$Kšœ�œ˜$Kšœ �œ˜-Kšœ�œ�œ˜Kšœ�œ ˜Kšœ�œ˜ Kšœ4˜4Kšœ�œ�œ˜Kšœ �œ˜Kšœ�œ�œ˜Kšœ�œ˜š�œ�œ�œ�˜-š�œ�˜'K˜sKš�œ�œ˜—Kš�œ˜—Kšœ�œ�œ ˜š�œ�œ�œ �˜Kšœ�œ˜,š�œ�˜šœ˜š�œ�œ˜ š�œ˜š�œ �˜Jšœ;�œ˜AJšœ;�œ˜AJšœ;�œ˜AJšœ;�œ˜AJš�œ˜—Kšœ�œ˜ K˜—Kš�œ/˜3—Kšœ˜—šœ˜š�œ�˜ ˜š�œ �˜J˜J˜Jš�œ2˜9—Kšœ�œ˜ K˜—˜š�œ �˜J˜$Jš�œ1˜8—Kšœ�œ˜ K˜—Kšœ�œ˜ ˜Kšœ�œ�œ˜š�œ˜ Kš�œ$�œ˜.Kš�œ˜—K˜—Kš�œ8˜?—K˜—Kš�œ1˜8—Kš�œ˜—š�œ�œ�˜š�œ �œ�œ ˜ K˜K˜Kšœ˜Kšœ˜K˜—šœ�œ�œ ˜Kšœ˜Kšœ˜K˜—š�œ �œ ˜Kšœ˜Kšœ˜K˜—Kšœ�œ  œ˜2Kš�œ˜—K˜K˜—K˜———…—±¢þÅ