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˜———…—±¢þÅ