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
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, <<Real,>> RealFns, RefTab, Rope, TEditFormat, TEditInput, TEditInputOps, TEditSelection, TextEdit, TextEditBogus, TextNode, TiogaAccess, TiogaAccessViewers, TiogaFind, TiogaImager, TextFind, ViewerOps, ViewerTools
EXPORTS Hobo
~
BEGIN
OPEN Hobo;
Abbreviations For Imported Types
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;
Defs For Private Types (local to module)
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
]];
Constants
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]];
Instance Variables
selectionBox: BoxCharData ¬ NEW[BoxCharDataRep ¬ [node: TextEdit.DocFromNode[TextEdit.FromRope["#"]]]];
boxMakerTable: RefTab.Ref ¬ RefTab.Create[];
ticWidth: REAL ¬ 0.0;
alignOnChar: CHAR ¬ '.;
Character Artwork Procedures
RegisterHoboArtworkClass: PROC ~ {
effects: Registers $Hobo as a Tioga character artwork class.
create the class
class: CharacterArtworkClass ¬ NEW[CharacterArtworkClassRep ¬
[name: $Hobo, format: FormatBoxChar, data: NIL]];
oldClass: CharacterArtworkClass ¬ NEW[CharacterArtworkClassRep ¬
[name: $BoxChar, format: FormatBoxChar, data: NIL]];
register it
TEditFormat.RegisterCharacterArtwork[class];
TEditFormat.RegisterCharacterArtwork[oldClass]; -- For backwards compatibility
};
UnRegisterHoboArtworkClass: PROC ~ {
effects: Unregisters $Hobo as a Tioga character artwork class.
TEditFormat.UnregisterCharacterArtwork[$BoxChar];
};
FormatBoxChar: TEditFormat.CharacterFormatProc ~ {
effects: Formats math expression artwork represented by ROPE at loc.
Returns character artwork for formatted expression.
Note that OfStyle: TYPE ~ { screen, print, base }
charArtwork: CharacterArtwork;
charProps: TextEdit.PropList ¬ TextEdit.GetCharPropList[node: loc.node, index: loc.where];
fontSize: REAL ¬ style.GetSpaceWidth[];
font: ImagerFont.Font ¬ NodeStyle.GetFont[style];
xchar: TextEdit.XCHAR;
charSet: TextEdit.CharSet ¬ 0;
char: CHAR ¬ ');
chars: ROPE;
hoboRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $hoboRope]];
boxCharRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $BoxChar]];
box: TiogaImager.Box ¬ NIL;
stippleRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $stipple]];
stipple: WORD ¬ 50A0H;
leftExtent: REAL ¬ 0.0;
rightExtent: REAL ¬ 16.0;
descent: REAL ¬ 0.0;
ascent: REAL ¬ 10.0;
extents: ImagerFont.Extents;
escapement: ImagerFont.VEC;
reachRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $reach]];
reach: REAL ¬ 16.0;
eqExtents: ImagerFont.Extents ¬ font.RopeBoundingBox["="];
chExtents: ImagerFont.Extents;
vshiftRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $vshift]];
hshiftRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $hshift]];
hscale, vscale: REAL ¬ 1.0;
hshift, vshift: REAL ¬ 0.0;
showBoundsRope: ROPE ¬ NARROW[TextEdit.GetPropFromList[charProps, $showBounds]];
showBounds: BOOL ¬ FALSE;
boxCharData: BoxCharData ¬ GetDataFromLoc[loc.node, loc.where];
xchar ¬ TextEdit.FetchChar[text: loc.node, index: loc.where];
charSet ← Char.Set[xchar];
char ← Char.Narrow[xchar];
chars ¬ Rope.Cat[Rope.FromChar[377C], Rope.FromChar[0C+charSet], Rope.FromChar[char]];
stipple ¬ Convert.IntFromRope[stippleRope ! Convert.Error => CONTINUE];
reach ¬ Convert.RealFromRope[reachRope ! Convert.Error => CONTINUE];
hshift ¬ Convert.RealFromRope[hshiftRope ! Convert.Error => CONTINUE];
vshift ¬ Convert.RealFromRope[vshiftRope ! Convert.Error => CONTINUE];
IF showBoundsRope.Length > 0
THEN showBounds ¬ Convert.BoolFromRope[showBoundsRope ! Convert.Error => CONTINUE];
IF boxCharData = NIL
THEN {
IF boxCharRope = NIL
THEN {
chExtents ¬ font.RopeBoundingBox[chars];
vscale ¬ MAX[1.0, 2*reach/(chExtents.ascent+chExtents.descent)];
hscale ¬ RealFns.SqRt[RealFns.SqRt[vscale]];
vshift ¬ (eqExtents.ascent+eqExtents.descent)/2 - eqExtents.descent;
font ¬ font.Modify[ImagerTransformation.Create[hscale,0,0, 0,vscale,(1-vscale)*vshift]];
extents ¬ font.RopeBoundingBox[chars];
escapement ¬ font.RopeEscapement[chars];
}
ELSE {
chars ¬ boxCharRope;
box ¬ TiogaImager.BoxFromRope[font, chars];
box ¬ box.ModifyBox[ImagerTransformation.Translate[[hshift, vshift]]];
extents ¬ ImagerBox.ExtentsFromBox[box.bounds];
escapement ¬ box.escapement;
};
}
ELSE {
box ¬ BoxInTrain[data: boxCharData, train: [loc: loc, style: style]];
box ¬ box.ModifyBox[ImagerTransformation.Translate[[hshift, vshift]]];
extents ¬ ImagerBox.ExtentsFromBox[box.bounds];
escapement ¬ box.escapement;
};
create artwork char
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] ~ {
effects: Paints charArtwork onto context.
retrieve painting information
paintInfo: PaintInfo ¬ NARROW[charArtwork.data];
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: REALMAX[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
DoPaint: PROC ~ {
IF paintInfo.box = NIL
THEN {
context.Move;
context.SetColor[paintInfo.color];
context.MaskRectangle[[x: x, y: y, w: w, h: h]];
context.SetColor[Imager.black];
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]];
};
};
pretend that current x,y position in context is origin while actually painting
Imager.DoSaveAll[context, DoPaint];
};
Formatted Boxes To and From Tioga
CopyDocument: PROC [root: TextNode.Node] RETURNS [TextNode.Node] ~ {
IF root=NIL THEN RETURN[NIL]
ELSE {
CopyAction: NodeProps.MapPropsAction = {
[name: ATOM, value: REF] RETURNS [quit: BOOLFALSE];
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];
Is it safe to do this while formatting? If not, could spawn a process . . .
}
ELSE {
boxCharData ¬ NIL;
boxCharData ← NEW[BoxCharDataRep ←
[node: TextEdit.DocFromNode[TextEdit.FromRope["#"]],
screenBox: NIL, printBox: NIL]];
};
};
RETURN[boxCharData]
};
HoboNameAndRange: PROC [node: TextNode.Ref]
RETURNS [name: ATOM ¬ noName, start, end: TextNode.Location] ~ {
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.
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: BOOLFALSE, 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];
};
};
HoboArgumentFind: PROC [first, last: TextNode.Location, name: ROPE]
RETURNS [found: BOOL ¬ FALSE, node: TextNode.Ref, where: INT] ~ {
Looks for "name=", and returns location following "=".
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<TextEdit.Size[node]
AND TextEdit.FetchChar[node, where] = Char.Widen['=]
THEN { found ¬ TRUE; where ¬ where+1 };
}
ELSE {
target: TextFind.Target ~ TextFind.TargetFromRope["="];
[node: node, matchEnd: where] ¬ TiogaFind.Search[direction: forward,
loc1: first, loc2: last, target: target, match: any];
IF node # NIL THEN found ¬ TRUE;
};
IF NOT found THEN RETURN[FALSE, NIL, 0];
};
HoboArgumentBox: PROC [first, last: TextNode.Location, name: ROPE, default: BoxCharData]
RETURNS [found: BOOL ¬ FALSE, val: BoxCharData] ~ {
Looks for "name=X", where X should be BoxChar or node, and returns data for X.
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] ~ {
Looks for "name=X", where X should be real literal, and returns value for X, else default.
hitNode: TextNode.Ref;
where, size: INT;
val ¬ default;
[found, hitNode, where] ¬ HoboArgumentFind[first, last, name];
IF found AND (size ¬ TextEdit.Size[hitNode]) > where
THEN {
val ¬ Convert.RealFromRope[TextEditBogus.GetRope[hitNode].Substr[where, size-1]
! Convert.Error => {
Scold[Rope.Cat["\tError in REAL arg \"", name, "\"."]];
CONTINUE;
}];
}
};
HoboArgumentInt: PROC [first, last: TextNode.Location, name: ROPE, default: INT]
RETURNS [found: BOOL ¬ FALSE, val: INT] ~ {
Looks for "name=X", where X should be int literal, and returns value for X, else default.
hitNode: TextNode.Ref;
where, size: INT;
val ¬ default;
[found, hitNode, where] ¬ HoboArgumentFind[first, last, name];
IF found AND (size ¬ TextEdit.Size[hitNode]) > where
THEN {
val ¬ Convert.IntFromRope[TextEditBogus.GetRope[hitNode].Substr[where, size-1]
! Convert.Error => {
Scold[Rope.Cat["\tError in REAL arg \"", name, "\"."]];
CONTINUE;
}];
}
};
HoboArgumentName: PROC [first, last: TextNode.Location, name: ROPE, default: ATOM]
RETURNS [found: BOOL ¬ FALSE, val: ATOM] ~ {
Looks for "name=X", where X should be atom literal, and returns value for X, else default.
hitNode: TextNode.Ref;
where, size: INT;
val ¬ default;
[found, hitNode, where] ¬ HoboArgumentFind[first, last, name];
IF found AND (size ¬ TextEdit.Size[hitNode]) > where
THEN {
argRope: ROPE ¬ TextEditBogus.GetRope[hitNode].Substr[where, size-1];
val ¬ Convert.AtomFromRope[argRope.Substr[0,argRope.SkipOver[skip: legalNameChars]]
! Convert.Error => {
Scold[Rope.Cat["\tError in name arg \"", name, "\"."]];
CONTINUE;
}];
}
};
BoxFromNode: PROC [node: TextNode.Ref, train: Train, bounds: Imager.VEC ¬ arbBounds] RETURNS [myBox: TiogaImager.Box ¬ TiogaImager.EmptyBox[[2, 0]]] ~ {
name: ATOM;
firstLoc, lastLoc: TextNode.Location;
boxMakerRec: BoxMakerRec;
IF node = NIL THEN RETURN;
[name, firstLoc, lastLoc] ¬ HoboNameAndRange[node];
boxMakerRec ¬ NARROW[RefTab.Fetch[boxMakerTable, name].val];
IF boxMakerRec = NIL OR boxMakerRec.proc = NIL
THEN {
Scold[Rope.Cat["\tUnknown hobo name \"", Atom.GetPName[name], "\"."]];
myBox ¬ NoNameBoxMaker[firstLoc, lastLoc, train, bounds];
}
ELSE myBox ¬ boxMakerRec.proc[firstLoc, lastLoc, train, bounds];
};
BoxInTrain: PROC [data: BoxCharData, train: Train, bounds: Imager.VEC ¬ arbBounds] RETURNS [box: TiogaImager.Box] ~ {
IF train.style.kind # print
THEN {
IF data.screenBox = NIL
THEN data.screenBox ¬ BoxFromNode[data.node, train, bounds];
box ¬ data.screenBox;
}
ELSE {
IF data.printBox = NIL
THEN data.printBox ¬ BoxFromNode[data.node, train, bounds];
box ¬ data.printBox;
};
};
Scold: PROC [msg: ROPE] ~ {
MessageWindow.Append[msg, TRUE];
MessageWindow.Blink[];
};
BoxFromSelection: TEditInput.CommandProc ~ {
[viewer: ViewerClasses.Viewer ← NIL] RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE]
IF TEditSelection.pSel.granularity = point -- TiogaAccess blows up on point selection
THEN Scold["\tSelect more than a point."]
ELSE {
reader: TiogaAccess.Reader ¬ TiogaAccessViewers.FromSelection[];
ref: TextNode.Ref ¬ IF TiogaAccess.EndOf[reader] THEN TextNode.NewTextNode[] ELSE TextNode.Root[TiogaAccess.GetLocation[reader].node];
myRef: TextNode.Ref ¬ CopyDocument[ref]; -- make a copy that we own. System will finalize its own copy.
selectionBox ¬ NEW[BoxCharDataRep ¬ [node: myRef, screenBox: NIL, printBox: NIL]];
MessageWindow.Append["\tNew box made from selection.", TRUE];
RETURN[TRUE, TRUE];
};
};
BoxToSelection: TEditInput.CommandProc ~ {
[viewer: ViewerClasses.Viewer ← NIL] RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE]
IF TEditSelection.pSel.granularity = point
THEN Scold["\tSelect more than a point."]
ELSE {
selectionViewer: Viewer ¬ TEditSelection.pSel.viewer;
reader: TiogaAccess.Reader ¬ TiogaAccessViewers.FromSelection[];
writer: TiogaAccess.Writer ¬ TiogaAccess.Create[];
locked: PROC [root: TextNode.Ref, tSel: TEditDocument.Selection] ~ {
TiogaAccessViewers.WriteSelection[writer]; -- replaces the primary selection
};
tc: TiogaAccess.TiogaChar ¬ reader.Get[];
hoboRope: ROPE ¬ TiogaIO.ToRope[selectionBox.node];
newProps: Tioga.PropList ¬ LIST[
[$data, selectionBox],
[$hoboRope, hoboRope],
[$Artwork, whoWeAreRope]];
FOR pl: Tioga.PropList ¬ tc.propList, pl.rest UNTIL pl = NIL DO
IF pl.first.key # $data AND pl.first.key # $hoboRope AND pl.first.key # $Artwork
THEN newProps ¬ CONS[pl.first, newProps];
ENDLOOP;
tc.propList ¬ newProps; -- preserve immutability of prop list
tc.charSet ¬ 0;
tc.char ¬ 'x;
TiogaAccess.Put[writer, tc]; -- puts everything at once
TEditInputOps.CallWithLocks[locked];
ViewerOps.PaintViewer[viewer: selectionViewer, hint: client];
MessageWindow.Append["\tBoxChar created.", TRUE];
};
RETURN[TRUE, TRUE];
};
ViewBox: TEditInput.CommandProc ~ {
[viewer: ViewerClasses.Viewer ← NIL] RETURNS [recordAtom: BOOLTRUE, quit: BOOLFALSE]
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 ];
May need TextEdit.DocFromNode[TiogaIO.FromRope[data.node]]
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];
};
Box Makers
NoNameBoxMaker: PROC [first, last: TextNode.Location, train: Train, bounds: Imager.VEC ¬ arbBounds]
RETURNS [myBox: TiogaImager.Box ¬ TiogaImager.EmptyBox[[2, 0]]] ~ {
Ordinary old box from node
IF first.node = last.node
THEN {
Try a one-liner, which ought to be very common . . .
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 {
Whew! It all fit. Bounding box can be tight easily.
myBox.bounds ¬ TEditFormat.BoundingBox[NARROW[myBox.data]];
myBox.escapement ¬ [myBox.bounds.xmax, 0];
RETURN;
};
};
Looks like a one-liner won't do it. Return sloppy bounding box. Sigh.
{
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] ~ {
Attempting a composite box!! args are "upper=U center=C lower=L"
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;
rootData: BoxCharData ← HoboArgumentBox[first, last, "root", 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];
rootBox: TiogaImager.Box ← BoxInTrain[rootData, train];
grid: Grid ¬ EmptyGrid[];
ExtendGraph[grid, [2, 4], 2*barGap+barThickness, 0];
GridInsertEntry[grid, [0, 3], [2, 4], [level: 'n, plumb: 'c], radicandBox];
GridInsertEntry[grid, [1, 0], [2, 1], [level: 'n, plumb: 'c], rootBox];
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;
axisHeight: REAL ← HoboArgumentReal[first, last, "axisHeight", GetAxisHeight[train]].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]]] ~ {
For matrices and tables. Takes a series of boxes, and aligns them both ways.
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.SkipTo[mid, 0, "C"] < Rope.Length[mid]
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;
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.
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 ← [0, ySize];
grid.origin ¬ [xOrg, yOrg];
box ¬ NEW[TiogaImager.BoxRep ¬ [
nChars: 0,
bounds: [xmin: 0, ymin: 0, xmax: xSize, ymax: ySize],
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]; -- why did Ken add this?
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]] ~ {
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
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]
~ {
<< Check for generalized decimal lineups like "/'?=" and "/t'\t=". >>
SELECT ch1 FROM
'= => {Scold["\tPremature equal sign"]; RETURN[['n, 'c]]};
quote => GOTO decimal1;
ENDCASE;
SELECT ris.PeekChar[] FROM
'= => RETURN[['n, ch1]];
quote => GOTO decimal2;
ENDCASE;
RETURN[[ch1, ris.GetChar[]]];
EXITS
decimal1 => {
ris.Backup[ch1];
alignOnChar ¬ ris.GetCharLiteral[];
RETURN[['n, quote]];
};
decimal2 => {
alignOnChar ¬ ris.GetCharLiteral[];
RETURN[[ch1, quote]];
};
};
lineup ¬ GetLineup[];
};
ENDCASE => {Scold["\tGrid corner: Unexpected single token."];};
};
ENDCASE => {Scold["\tGrid corner: Unexpected token."];};
ENDLOOP;
SELECT TRUE FROM
NOT topSeen AND NOT botSeen => {
top.level ¬ topWas.level+rowStep;
top.plumb ¬ topWas.plumb+colStep;
bot.level ¬ top.level+rowDif;
bot.plumb ¬ top.plumb+colDif;
};
topSeen AND NOT botSeen => {
bot.level ¬ top.level+rowDif;
bot.plumb ¬ top.plumb+colDif;
};
NOT topSeen AND botSeen => {
top.level ¬ top.level-rowDif;
top.plumb ¬ top.plumb-colDif;
};
topSeen AND botSeen => {--No defaulting needed--};
ENDCASE;
};
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:max<inc>end 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
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;
};
Start Code
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[$big, BigBoxMaker, "Big brackets of various sorts."];
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.
..
Incomplete code
BigBoxMaker: PROC [first, last: TextNode.Location, train: Train, bounds: Imager.VEC ¬ arbBounds]
RETURNS [aBox: TiogaImager.Box] ~ {
For drawing big {[(<|>)]} characters in a box. args are "char=C height=h width=w"
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 {
Scale a BoxChar character. Perhaps use box's natural expansion capability.
}
ELSE {
Stretch a special character.
[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;
};
<< 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.
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 {
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
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 "];
put Postfix properties on node containing displayExpr to insure proper spacing
NodeProps.PutProp[loc.node, $Postfix, postfixValue];
};
Stale code
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]] ~ {
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
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;
};