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 ~ { 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]; 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.Node] RETURNS [TextNode.Node] ~ { 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: Tioga.PropList ¬ TextEdit.GetCharPropList[node: node, index: where]; boxCharData: BoxCharData ¬ NARROW[TextEdit.GetPropFromList[charProps, $data]]; IF boxCharData = NIL THEN { hoboRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $hoboRope]]; IF hoboRope # NIL THEN { boxCharData ¬ NEW[BoxCharDataRep ¬ [node: TiogaIO.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 ¬ 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]]; }; }; 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 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 ~ { 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 ~ { 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 ~ { 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]] ~ { rowDif: INT ¬ botWas.level-topWas.level; colDif: INT ¬ botWas.plumb-topWas.plumb; nodeRope: ROPE ¬ TextEditBogus.GetRope[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.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] ~ { 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: REAL ¬ style.GetSpaceWidth[]; font: ImagerFont.Font ¬ NodeStyle.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, 1992 by Xerox Corporation. All rights reserved. Ken Shoemake, January 17, 1991 4:56 am PST Jules Bloomenthal June 11, 1993 7:57 pm PDT 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. HoboArgumentFind: PROC [first, last: TextNode.Location, name: ROPE] RETURNS [found: BOOL _ FALSE, node: TextNode.Ref, where: INT] ~ { Looks for "name=", and returns location following "=". Uses TreeFind. 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]; }; }; Looks for "name=", and returns location following "=". 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[TiogaIO.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.Length[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], context.SetColor[Imager.black]; -- why did Ken add this? 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 Κ2έ–"cedarcode" style•NewlineDelimiter ™šœ ™ Icodešœ Οeœ7™BK™*K™+Idefault™—šΟk ˜ L˜ΨL˜—KšΠlnœž ˜šžœ˜K˜ͺ—Kšžœ˜ Kšœ˜šž˜Kšžœ˜ —headšΟn ™ Kšžœžœžœ˜Kšœžœ˜$Kšœžœ%˜@Kšœžœ(˜FKšœžœ ˜6Kšœžœ#˜Kšœ+žœ˜1—šœ"žœ˜AKšœ.žœ˜4—K˜Kšœ ™ Kšœ,˜,Kšœ0‘˜NK˜K˜—š œžœ˜$Kšœ?™?K˜Kšœ1˜1K˜K˜—š  œ%˜2KšœE™EK™=Kšœžœ™;K˜K˜K˜ZKšœ žœ˜'K˜1Kšœžœ˜K˜Kšœžœ˜Kšœžœ˜ Kšœ žœžœ1˜HKšœ žœžœ0˜JKšœžœ˜Kšœ žœžœ0˜JKšœ žœ ˜Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšœžœ˜K˜Kšœžœ˜Kšœ žœžœ.˜FKšœžœ˜K˜:Kšœ˜Kšœ žœžœ/˜HKšœ žœžœ/˜HKšœžœ˜Kšœžœ˜Kšœžœžœ3˜PKšœ žœžœ˜K˜?K˜K˜=K˜K˜K˜VKšœ=žœ˜GKšœ:žœ˜DKšœ<žœ˜FKšœ<žœ˜Fšžœ˜KšžœEžœ˜S—K˜šžœž˜šžœ˜šžœž˜šžœ˜K˜(Kšœ žœ4˜@K˜,K˜DK˜XK˜&K˜(K˜—šžœ˜K˜K˜+K˜FK˜/K˜K˜——K˜—šžœ˜K˜EK˜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˜7K˜——K˜—K˜KšœN™NK˜#K˜——š !™!š  œžœžœ˜DKš žœžœžœžœžœ˜šžœ˜š  œ˜(Kš œžœ žœžœžœžœ™6Kšœ žœ#˜0Kšœ+˜+Kšžœžœ˜K˜—K˜wK˜5K˜7K•StartOfExpansionn[n: TextNode.Ref, action: NodeProps.MapPropsAction, formatFlag: BOOL _ TRUE, commentFlag: BOOL _ TRUE]˜5Kšžœ ˜Kšœ˜—K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]š œžœžœžœ˜OK˜OKšœžœ-˜Nšžœž˜šžœ˜Kšœ žœžœ1˜Hšžœ ž˜šžœ˜Kšœžœ@žœ žœ˜f˜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šœ žœ4˜BK˜'K˜#šžœ<žœžœžœD˜šžœž˜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™6šžœ˜šžœ˜K˜8K˜|Kš žœžœžœžœ3žœ žœ˜‰K˜—šžœ˜K˜7K˜{Kšžœžœžœ žœ˜ K˜——Kš žœžœžœžœžœžœ˜(K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]š  œžœ(žœžœ žœžœ˜ŒK™NK˜Kšœžœ˜ K˜>šžœ˜šžœ˜šžœ˜!šžœ˜K˜3Kš žœžœžœžœžœ˜&K˜—šžœ˜šžœ ž˜%šžœ˜K˜lK˜šžœžœ(˜4šžœ˜˜Ošœ˜Kšœ7˜7Kšžœ˜ K˜——K˜——K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]š œžœ(žœ žœžœ žœžœžœ˜|K™YK˜Kšœ žœ˜K˜K˜>šžœžœ(˜4šžœ˜˜Nšœ˜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šœ žœ8˜E˜Sšœ˜Kšœ7˜7Kšžœ˜ K˜——K˜——K˜K˜—š  œžœ2žœžœ<˜˜Kšœžœ˜ Kšœ%˜%Kšœ˜Kšžœžœžœžœ˜K˜3Kšœžœ(˜<šžœžœžœž˜.šžœ˜KšœF˜FK˜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˜@Kšœžœžœžœ5˜†Kšœ)‘>˜gKšœžœ+žœ žœ˜RKšœ7žœ˜=Kšžœžœžœ˜K˜——K˜K˜—š œ˜*Kš œ žœžœžœžœžœžœ™Zšžœ(˜*Kšžœ%˜)šžœ˜K˜5K˜@K˜2šœžœ8˜DKšœ+‘!˜LKšœ˜—K˜)Kšœ žœ%˜3KšœžœJ˜išžœ+žœž˜?šžœžœžœ˜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˜7K˜8šžœž˜ Kšžœ˜šžœ˜Kšœƒžœ˜‹K™:Kšœ2žœ˜IKšœ˜Kšœ˜——K˜—Kšžœ%˜)—Kšžœžœžœ˜K˜K˜—š œžœžœžœ˜2K˜=K˜9Kšžœ)žœžœ"˜vK˜K˜——š  ™ š œžœ?žœžœ<˜§K™šžœ˜šžœ˜K™4K˜0K˜?K˜EKšœ˜šžœ)˜+šžœ˜K™5Kšœ'žœ˜;K˜*Kšžœ˜K˜——K˜——K™G˜Kš œ#žœžœžœažœ/žœ˜ΥKšœ žœžœ7˜JKš œžœžœžœžœ žœ˜NK˜*K˜EKšžœ˜K˜—K˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]š œžœ?žœžœ˜ŠK™AKšœ$˜$K˜HK˜JK˜HKšœ žœ6˜DKšœ žœ6˜DK˜5K˜7K˜5K˜Kšœ"˜"K˜$K˜/K˜%K˜/K˜$Kšœ!žœ˜9K˜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˜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˜LK˜LKšœžœ4˜@Kšœ žœ9˜JKšœ žœ9˜JKšœžœ;˜MKšœ žœI˜YK˜9K˜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˜LK˜RK˜LKšœžœ1˜:Kšœ žœ9˜JKšœ žœ9˜JKšœ žœI™YK˜9K˜?K˜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˜LK˜3K˜PK˜K˜—–^ -- [viewer: ViewerClasses.Viewer _ NIL] RETURNS [recordAtom: BOOL _ TRUE, quit: BOOL _ FALSE]š  œžœžœžœ ˜GK˜7K˜:K˜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˜EKš žœžœžœžœžœ˜ K˜uK˜šžœ!žœ˜8šžœ˜Kšœ žœ˜šžœ$˜&šžœ˜K˜/šžœž˜˜Kšœyžœ˜„K˜—˜Kšœžœ˜Kšœqžœ˜|K˜"K˜—Kšžœ˜—K˜—šžœ˜šžœž˜Kšžœ˜Kšžœ+˜/—K˜——K˜—šžœ˜šžœ$˜&šžœ˜K˜/K˜0K˜—šžœ˜šžœž˜Kšžœ˜Kšžœ+˜/—šžœ!ž˜&šžœ˜K˜nK˜K˜Kšœ,˜,Kš žœžœžœžœ žœ žœ ˜NK˜.K˜:K˜:Kšœ.˜.Kšœ5˜5Kšžœ,™.šžœ˜šžœ˜Kšœžœ,˜HK˜4K˜——Kšžœ ˜K˜K˜—š  œžœ:žœ˜VK˜K˜0K˜QK˜QK˜QK˜QKšœžœ˜+K˜K˜—š œžœH˜\K˜K˜,K˜RK˜RK˜RK˜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šœžœQ˜[Kšœžœ.˜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šžœ˜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˜xKšžœ˜—K˜K™8š žœžœžœ"žœžœž˜DKšœ/˜/K˜yKšžœ˜—K˜—š œžœ˜Kšœ˜Kšœ ˜ š žœžœžœ(žœ žœž˜NKšœf˜fKšžœ˜—Kšžœžœ)˜?K˜—Kšœ žœ ˜Kšœ$˜$K˜$K˜K˜—š œžœ,žœ˜LK˜Kšœ%˜%šžœ4žœ žœž˜NKšœžœD˜NKšœžœ˜'šœžœžœž˜.Kšœ-˜-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šœ žœ#˜1Kšœžœžœ˜Kšœžœ ˜Kšœžœ˜ K˜4Kšœžœžœ˜Kšœ žœ˜Kšœžœžœ˜šžœžœžœž˜-šžœž˜Kšœ?žœ˜EKšžœ˜—šž˜Kšžœ-˜5—Kšžœ˜—Kšœžœžœ ˜šžœžœžœ ž˜Kšœžœ˜,šžœž˜šœ˜šžœžœ˜ šžœ˜šžœ ž˜Kšœ=žœ˜CKšœ=žœ˜CKšœ=žœ˜CKšœ=žœ˜CKšžœ˜—Kšœžœ˜ K˜—Kšžœ/˜3—Kšœ˜—šœ˜šžœž˜˜šžœ ž˜K˜K˜Kšžœ2˜9—Kšœžœ˜ K˜—˜šžœ ž˜K˜$Kšžœ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šžœž˜ Kšžœ(˜,Kšžœ(žœ7˜f—K˜K˜—š œ˜&K˜Kšžœžœžœ˜:K˜K˜——š  ™ K˜OKšœ˜KšœDžœ˜JKšœCžœ˜IKšœ;žœ˜AK˜BJšœF™FKšœN˜NKšœE˜EKšœ=˜=KšœQ˜QKšœM˜MKšœ:˜:K˜—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šžœ!˜#šžœ˜K˜KKšžœžœ žœ žœ ˜DK˜——K™Kšœ ΟbœŒ™™šžœž˜šžœ˜Kšœ žœ˜'K˜1K˜(Kšœ žœ4˜@K˜,K˜DK˜XK˜&K˜(K˜—šžœ˜K˜K˜+K˜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˜4Kšœžœžœ˜Kšœ žœ˜Kšœžœžœ˜Kšœžœ˜šžœžœžœž˜-šžœž˜'K˜sKšžœžœ˜—Kšžœ˜—Kšœžœžœ ˜šžœžœžœ ž˜Kšœžœ˜,šžœž˜šœ˜šžœžœ˜ šžœ˜šžœ ž˜Kšœ;žœ˜AKšœ;žœ˜AKšœ;žœ˜AKšœ;žœ˜AKšžœ˜—Kšœžœ˜ K˜—Kšžœ/˜3—Kšœ˜—šœ˜šžœž˜ ˜šžœ ž˜K˜K˜Kšžœ2˜9—Kšœžœ˜ K˜—˜šžœ ž˜K˜$Kšžœ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˜———…—±ΊΣ