TiogaImagerImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Michael Plass, September 23, 1986 11:40:19 am PDT
DIRECTORY
Atom USING [GetPName, GetPropFromList, PropList, PutPropOnList],
Commander USING [Handle],
Convert USING [RopeFromInt],
Imager USING [Box, ConcatT, Context, DoSave, SetFont, SetXY, ShowRope, ShowXChar, TranslateT],
ImagerBox USING [BoundingBox, BoxFromExtents, BoxFromRect, RectFromBox],
ImagerFont USING [Amplified, BoundingBox, Font, RopeBoundingBox, RopeWidth, Width, XChar],
ImagerTransformation USING [Concat, InverseTransform, Transformation, TransformRectangle, TransformVec],
IO USING [PutRope, STREAM],
MessageWindow USING [Append],
NodeProps USING [GetProp],
NodeStyle USING [GetBottomIndent, GetBottomLeading, GetBottomLeadingShrink, GetBottomLeadingStretch, GetColumns, GetFontSize, GetLeading, GetLeadingShrink, GetLeadingStretch, GetReal, GetTopIndent, GetTopLeading, GetTopLeadingShrink, GetTopLeadingStretch, PointsPerFil, RealParam, Ref],
NodeStyleFont USING [FontFromStyleParams],
NodeStyleOps USING [Alloc, ApplyAll, ApplyFormat, Free, GetStyleParam, OfStyle, nonNumeric],
Process USING [CheckForAbort],
ProcessProps USING [GetProp],
Real USING [LargestNumber, Round],
Rope USING [Cat, Concat, Equal, ROPE, Size],
Scaled USING [Float, FromReal],
TEditFormat USING [Allocate, FormatLine, LineInfo, Paint, Release, Resolve],
TextEdit USING [Size],
TextNode USING [Forward, Level, Location, LocNumber, nullLocation, Ref, Root, StepForward],
TiogaImager USING [Box, Boxes, BoxRep, Class, ClassRep, FilterProc, Fix, FormattedNodes, FormattedPage, InsertID, SepProc],
TJaM USING [Stop],
Vector2 USING [Add, Sub, VEC];
TiogaImagerImpl: CEDAR PROGRAM
IMPORTS Atom, Convert, Imager, ImagerBox, ImagerFont, IO, Process, ProcessProps, ImagerTransformation, Rope, MessageWindow, NodeProps, NodeStyle, NodeStyleFont, NodeStyleOps, Scaled, TEditFormat, TextEdit, TextNode, TJaM, Vector2
EXPORTS TiogaImager
~ BEGIN
Context: TYPE ~ Imager.Context;
Font: TYPE ~ ImagerFont.Font;
Transformation: TYPE ~ ImagerTransformation.Transformation;
VEC: TYPE ~ Vector2.VEC;
XChar: TYPE ~ ImagerFont.XChar;
ROPE: TYPE ~ Rope.ROPE;
Box: TYPE ~ TiogaImager.Box;
Boxes: TYPE ~ TiogaImager.Boxes;
BoxRep: TYPE ~ TiogaImager.BoxRep;
Class: TYPE ~ TiogaImager.Class;
ClassRep: TYPE ~ TiogaImager.ClassRep;
Fix: TYPE ~ TiogaImager.Fix;
FilterProc: TYPE ~ TiogaImager.FilterProc;
FormattedNodes: TYPE ~ TiogaImager.FormattedNodes;
InsertID: TYPE ~ TiogaImager.InsertID;
SepProc: TYPE ~ PROC [InsertID] RETURNS [TiogaImager.Box];
Marks: TYPE ~ Atom.PropList;
FormattedPage: TYPE ~ TiogaImager.FormattedPage;
FormatLine: PUBLIC PROC [start: TextNode.Location, lineWidth: REAL, style: NodeStyle.Ref, screenStyle: BOOL] RETURNS [box: Box] ~ {
lineInfo: TEditFormat.LineInfo ← TEditFormat.Allocate[];
bounds: Imager.Box;
xOffset: REAL;
Process.CheckForAbort[];
TEditFormat.FormatLine[lineInfo: lineInfo, node: start.node, startOffset: MAX[start.where, 0], nodeStyle: style, lineWidth: Scaled.FromReal[lineWidth], doLigsAndKern: TRUE, kind: IF screenStyle THEN screen ELSE print];
Process.CheckForAbort[];
bounds ← [xmin: lineInfo.xmin, ymin: lineInfo.ymin, xmax: lineInfo.xmax, ymax: lineInfo.ymax];
xOffset ← Scaled.Float[lineInfo.xOffset];
bounds.xmin ← bounds.xmin + xOffset;
bounds.xmax ← bounds.xmax + xOffset;
box ← NEW[BoxRep ← [
nChars: lineInfo.nChars,
bounds: bounds,
expansion: [0, 0],
escapement: [0, -NodeStyle.GetLeading[style]],
stretch: [0, 0],
shrink: [0, 0],
class: tiogaClass,
data: lineInfo
]];
};
InsertIDFromRope: PROC [rope: ROPE] RETURNS [InsertID] ~ {
RETURN [SELECT TRUE FROM
Rope.Equal[rope, "top", FALSE] => top,
Rope.Equal[rope, NIL, FALSE] => normal,
Rope.Equal[rope, "bottom", FALSE] => bottom,
Rope.Equal[rope, "foot", FALSE] => foot,
ENDCASE => nil
]
};
Msg: PROC [rope: ROPE] ~ {
WITH ProcessProps.GetProp[$CommanderHandle] SELECT FROM
cmd: Commander.Handle => {
msgStream: IO.STREAM ← cmd.err;
IO.PutRope[msgStream, " "];
IO.PutRope[msgStream, rope];
IO.PutRope[msgStream, "\n"];
};
ENDCASE => {
MessageWindow.Append["TiogaImagerImpl: ", TRUE];
MessageWindow.Append[rope, FALSE];
};
};
GetStyleParam: PROC [style: NodeStyle.Ref, param: ATOM, default: REAL, kind: NodeStyleOps.OfStyle] RETURNS [REAL] ~ {
val: REAL ← default;
val ← NodeStyleOps.GetStyleParam[s: style, name: param, styleName: style.name[style], kind: kind ! TJaM.Stop, NodeStyleOps.nonNumeric => {Msg[Rope.Concat["Undefined StyleParam: ", Atom.GetPName[param]]]; CONTINUE}];
RETURN [val]
};
FormatNodes: PUBLIC PROC [start: TextNode.Location, bounds: VEC, screenStyle: BOOL, filter: FilterProc, sep: TiogaImager.SepProc] RETURNS [FormattedNodes] ~ {
loc: TextNode.Location ← start;
nodeSize: INT ← TextEdit.Size[loc.node];
nodeStyle: NodeStyle.Ref ← NodeStyleOps.Alloc[];
insertID: InsertID ← InsertIDFromRope[NARROW[NodeProps.GetProp[loc.node, $Insert]]];
NextNode: PROC ~ {
nx: TextNode.Ref;
levelDelta: INTEGER;
[nx, levelDelta] ← TextNode.Forward[loc.node];
level ← level + levelDelta;
loc.node ← nx;
loc.where ← 0;
nodeSize ← TextEdit.Size[loc.node];
IF loc.node # NIL THEN {
NodeStyleOps.ApplyAll[nodeStyle, loc.node, IF screenStyle THEN screen ELSE print];
insertID ← InsertIDFromRope[NARROW[NodeProps.GetProp[loc.node, $Insert]]];
};
};
boxes: ARRAY InsertID OF Boxes ← ALL[[NIL, NIL]];
lastNode: ARRAY InsertID OF TextNode.Ref ← ALL[NIL];
result: Boxes ← [NIL, NIL];
h: REAL ← 0.0;
N.B. h is the non-negative height of the page built so far. Note that since the y escapements, and glue are normally negative, we need to subtract them.
level: INT ← TextNode.Level[loc.node];
stopper: NAT ← 10000; -- to prevent runaways
firstTime: BOOLTRUE; -- do loop body at least once, to assure progress
HeightOfSep: PROC [id: InsertID] RETURNS [REAL] ~ {
hTemp: REAL ← 0.0;
b: LIST OF Box ← sep[id];
FOR p: LIST OF Box ← b, p.rest UNTIL p=NIL DO
hTemp ← hTemp - p.first.escapement.y;
ENDLOOP;
RETURN [hTemp];
};
InsertSep: PROC [id: InsertID] ~ {
b: LIST OF Box ← sep[id];
bxs: Boxes ← [NIL, NIL];
FOR p: LIST OF Box ← b, p.rest UNTIL p=NIL DO
Duplicate[p.first];
bxs ← AppendBox[bxs, p.first];
h ← h - p.first.escapement.y;
ENDLOOP;
boxes[id] ← AppendList[bxs, boxes[id].list];
};
kind: NodeStyleOps.OfStyle ~ IF screenStyle THEN screen ELSE print;
keepStretch: REAL ← 0.0;
box: Box ← NIL;
boxToStretch: Box ← NIL;
maxVerticalExpansion: REAL ← 0;
NodeStyleOps.ApplyAll[nodeStyle, loc.node, kind];
maxVerticalExpansion ← GetStyleParam[nodeStyle, $maxVerticalExpansion, 5, kind];
h ← 0;
WHILE firstTime OR (loc.node # NIL AND h <= bounds.y) DO
skip, stop: BOOLFALSE;
xbound: REAL ← Real.LargestNumber;
IF debugPattern # NIL AND Rope.Match[pattern: debugPattern, object: loc.node.rope, case: TRUE] THEN debugCount ← debugCount + 1;
IF filter#NIL THEN [skip, stop, xbound] ← filter[loc, level, [0, -h]];
IF stop THEN {IF skip OR loc.where = nodeSize THEN NextNode[]; EXIT};
IF NOT skip AND NOT firstTime AND loc.where = 0 THEN {
Check for keep processing.
keep: REAL ~ GetStyleParam[nodeStyle, $keep, 0, kind];
IF h + keep > bounds.y THEN {
keepStretch ← GetStyleParam[nodeStyle, $keepStretch, NodeStyle.PointsPerFil, kind];
EXIT;
};
};
IF NOT skip THEN {
lineBox: Box ← FormatLine[loc, MIN[bounds.x, xbound], nodeStyle, screenStyle];
lineBox.stretch.y ← -NodeStyle.GetLeadingStretch[nodeStyle];
lineBox.shrink.y ← -NodeStyle.GetLeadingShrink[nodeStyle];
stopper ← stopper - 1;
IF boxes[insertID].list = NIL
THEN {
This is the first thing in this insert. Put any separators before it, along with the appropriate topIndent and topIndentStretch.
topIndent: REAL ← NodeStyle.GetTopIndent[nodeStyle];
topIndentStretch: REAL ← GetStyleParam[nodeStyle, $topIndentStretch, 0, kind];
IF sep#NIL THEN {
Don't want to put in separators if, in so doing, the inserts won't fit.
hTemp: REAL ← h + topIndent - lineBox.escapement.y;
IF insertID # normal OR boxes[top].list # NIL THEN hTemp ← hTemp + HeightOfSep[insertID];
IF insertID = top AND boxes[normal].list # NIL THEN hTemp ← hTemp + HeightOfSep[normal];
IF hTemp > bounds.y AND NOT firstTime THEN EXIT;
Now either the insert is the only thing on the page, or it will fit along with its separators.
IF hTemp <= bounds.y THEN {
It all fits. This code is complicated a little by the fact that we don't want a separator above the normal text unless there is a top insert.
IF insertID # normal OR boxes[top].list # NIL THEN InsertSep[insertID];
IF insertID = top AND boxes[normal].list # NIL THEN InsertSep[normal];
};
};
boxes[insertID] ← AppendBox[boxes[insertID], EmptyBox[escapement: [0, -topIndent], stretch: [0, -topIndentStretch]]];
h ← h + topIndent;
}
ELSE IF loc.where = 0 THEN {
This is the first line of a node, but not the first thing in this insert. Adjust the topLeading accordingly. For the rest of the lines in the node, the leading is already correct.
prevBox: Box ~ boxes[insertID].last.first;
prevSkip: REAL ~ -prevBox.escapement.y;
newSkip: REAL ~ MAX[prevSkip, NodeStyle.GetTopLeading[nodeStyle]];
IF h - lineBox.escapement.y + (newSkip-prevSkip) > bounds.y AND NOT firstTime THEN EXIT;
h ← h + (newSkip-prevSkip);
prevBox.escapement.y ← -newSkip;
prevBox.shrink.y ← prevBox.shrink.y-NodeStyle.GetTopLeadingShrink[nodeStyle];
prevBox.stretch.y ← prevBox.stretch.y-NodeStyle.GetTopLeadingStretch[nodeStyle];
lineBox.stretch.y ← -NodeStyle.GetLeadingStretch[nodeStyle];
};
IF h - lineBox.escapement.y > bounds.y AND NOT firstTime THEN EXIT;
IF lineBox.nChars = 0 AND nodeSize > 0 THEN ERROR;
loc.where ← loc.where + lineBox.nChars;
IF loc.where = nodeSize THEN {
lineBox.escapement.y ← -NodeStyle.GetBottomLeading[nodeStyle];
lineBox.shrink.y ← -NodeStyle.GetBottomLeadingShrink[nodeStyle];
lineBox.stretch.y ← -NodeStyle.GetBottomLeadingStretch[nodeStyle];
};
boxes[insertID] ← AppendBox[boxes[insertID], lineBox];
lastNode[insertID] ← loc.node;
h ← h - lineBox.escapement.y;
};
IF skip OR loc.where = nodeSize THEN NextNode[];
firstTime ← FALSE;
ENDLOOP;
IF loc.node = NIL THEN {
keepStretch ← keepStretch + NodeStyle.PointsPerFil;
};
FOR i: InsertID IN [top..foot] DO
IF boxes[i].last # NIL AND lastNode[i] # NIL THEN {
lastBox: Box ← boxes[i].last.first;
NodeStyleOps.ApplyAll[nodeStyle, lastNode[i], kind];
lastBox.escapement.y ← -NodeStyle.GetBottomIndent[nodeStyle];
lastBox.stretch.y ← -GetStyleParam[nodeStyle, $bottomIndentStretch, 0, kind];
IF i = normal THEN {
lastBox.stretch.y ← lastBox.stretch.y - keepStretch;
boxToStretch ← lastBox;
};
lastBox.shrink.y ← 0;
};
result ← AppendList[result, boxes[i].list];
ENDLOOP;
box ← BoxFromList[result.list, [], [size, -bounds.y]];
IF box.expansion.y > maxVerticalExpansion AND boxToStretch#NIL THEN {
boxToStretch.stretch.y ← boxToStretch.stretch.y - NodeStyle.PointsPerFil;
box ← BoxFromList[result.list, [], [size, -bounds.y]];
};
NodeStyleOps.Free[nodeStyle];
RETURN [[box: box, resume: loc]];
};
GetNodeStyle: PROC [node: TextNode.Ref, kind: NodeStyleOps.OfStyle] RETURNS [nodeStyle: NodeStyle.Ref] ~ {
nodeStyle ← NodeStyleOps.Alloc[];
NodeStyleOps.ApplyAll[nodeStyle, node, kind];
};
validMarks: LIST OF ATOMLIST[
$insideRectoHeader, $centerRectoHeader, $outsideRectoHeader,
$centerVersoHeader, $insideVersoHeader, $outsideVersoHeader,
$insideHeader, $centerHeader, $outsideHeader,
$insideRectoFooter, $centerRectoFooter, $outsideRectoFooter,
$outsideVersoFooter, $centerVersoFooter, $insideVersoFooter,
$insideFooter, $centerFooter, $outsideFooter,
$headSeparator, $topSeparator, $bottomSeparator, $footSeparator,
$headSep, $topSep, $bottomSep, $footSep
];
GetMarkAtom: PROC [mark: ROPE, location: TextNode.Location] RETURNS [ATOM] ~ {
atom: ATOMNIL;
realAtom: ATOMNIL;
FOR p: LIST OF ATOM ← validMarks, p.rest UNTIL p = NIL DO
atom ← p.first;
IF Rope.Equal[Atom.GetPName[atom], mark, FALSE] THEN {
realAtom ← SELECT atom FROM $headSep => $headSeparator, $topSep => $topSeparator, $bottomSep => $bottomSeparator, $footSep => $footSeparator, ENDCASE => atom;
EXIT;
};
atom ← NIL;
ENDLOOP;
IF realAtom = NIL OR realAtom # atom THEN {
msg: ROPENIL;
IF realAtom = NIL
THEN {
msg ← msg.Cat["Unrecognized mark property \"", mark, "\""];
}
ELSE {
msg ← msg.Cat["Obsolete mark property ", Atom.GetPName[atom]];
msg ← msg.Cat[" (Use ", Atom.GetPName[realAtom], ")"];
};
msg ← msg.Cat[" at location ", Convert.RopeFromInt[TextNode.LocNumber[at: location, skipCommentNodes: TRUE]]];
Msg[msg];
};
RETURN [realAtom]
};
topVersoItems: LIST OF LIST OF ATOMLIST[
LIST[$outsideVersoHeader, $outsideHeader, $PAGE],
LIST[$centerVersoHeader, $centerHeader],
LIST[$insideVersoHeader, $insideHeader]
];
botVersoItems: LIST OF LIST OF ATOMLIST[
LIST[$PAGE, $outsideVersoFooter, $outsideFooter],
LIST[$DROPFOLIO, $centerVersoFooter, $centerFooter],
LIST[$insideVersoFooter, $insideFooter]
];
topRectoItems: LIST OF LIST OF ATOMLIST[
LIST[$insideRectoHeader, $insideHeader],
LIST[$centerRectoHeader, $centerHeader],
LIST[$outsideRectoHeader, $outsideHeader, $PAGE]
];
botRectoItems: LIST OF LIST OF ATOMLIST[
LIST[$insideRectoFooter, $insideFooter],
LIST[$DROPFOLIO, $centerRectoFooter, $centerFooter],
LIST[$PAGE, $outsideRectoFooter, $outsideFooter]
];
FormatPage: PUBLIC PROC [pageCounter: INT, startLoc: TextNode.Location, filter: FilterProc ← NIL, marks: Marks ← NIL, screenStyle: BOOLFALSE] RETURNS [FormattedPage] ~ {
kind: NodeStyleOps.OfStyle ~ IF screenStyle THEN screen ELSE print;
pageNeedsNumber: BOOLTRUE;
topMarks: Atom.PropList ~ marks;
botMarks: Atom.PropList ← marks;
GetSeparator: PROC [i: InsertID] RETURNS [LIST OF Box ← NIL] ~ {
key: ATOM ~ SELECT i FROM top => $headSeparator, normal => $topSeparator, bottom => $bottomSeparator, foot => $footSeparator, ENDCASE => NIL;
IF key # NIL THEN RETURN [NARROW[Atom.GetPropFromList[botMarks, key]]]
};
root: TextNode.Ref ~ TextNode.Root[startLoc.node];
nodeStyle: NodeStyle.Ref ← GetNodeStyle[root, kind];
GetStylePoints: PROC [param: NodeStyle.RealParam] RETURNS [REAL] ~ {
RETURN [NodeStyle.GetReal[nodeStyle, param]]
};
loc: TextNode.Location ← IF startLoc.node = root THEN [TextNode.StepForward[root], 0] ELSE startLoc;
leftMargin: REAL ~ GetStylePoints[leftMargin];
rightMargin: REAL ~ GetStylePoints[rightMargin];
bindingMargin: REAL ~ GetStylePoints[bindingMargin];
topMargin: REAL ~ GetStylePoints[topMargin];
headerMargin: REAL ~ GetStylePoints[headerMargin];
bottomMargin: REAL ~ GetStylePoints[bottomMargin];
footerMargin: REAL ~ GetStylePoints[footerMargin];
nColumns: INT ~ MAX[NodeStyle.GetColumns[nodeStyle], 1];
columnGap: REAL ~ GetStyleParam[nodeStyle, $columnGap, 36, kind];
pageWidth: REAL ~ GetStylePoints[pageWidth];
pageLength: REAL ~ GetStylePoints[pageLength];
totalWidth: REAL ~ MAX[pageWidth-leftMargin-rightMargin-bindingMargin, 0.0];
lineWidth: REAL ~ MAX[(totalWidth-(nColumns-1)*columnGap)/nColumns, 0.0];
pageBodyHeight: REAL ~ MAX[pageLength-topMargin-bottomMargin-headerMargin-footerMargin, 0.0];
bodyHeight: REAL ← pageBodyHeight;
firstFolio: INT ~ Real.Round[GetStyleParam[nodeStyle, $firstFolio, 1, kind]];
firstVisibleFolio: INT ~ Real.Round[GetStyleParam[nodeStyle, $firstVisibleFolio, 1, kind]];
lastDropFolio: INT ~ Real.Round[GetStyleParam[nodeStyle, $lastDropFolio, 0, kind]];
twoSided: BOOL ~ GetStyleParam[nodeStyle, $sided, 1.0, kind] > 1.5;
firstHeaders: INT ~ Real.Round[GetStyleParam[nodeStyle, $firstHeaders, 0.0, kind]];
myFilter: FilterProc ~ {
IF filter # NIL THEN [skip: skip, stop: stop, maxLineLength: maxLineLength] ← filter[node, level, position];
IF NOT skip THEN {
filterStart: TextNode.Location ← node;
markFilter: FilterProc ~ {
IF node # filterStart THEN skip ← stop ← TRUE;
};
markProp: ROPE ~ NARROW[NodeProps.GetProp[node.node, $Mark]];
IF node.node.formatName = $pagebreak THEN skip ← stop ← TRUE;
IF Rope.Size[markProp]#0 THEN {
markAtom: ATOM ~ GetMarkAtom[markProp, filterStart];
inColumn: BOOL ~
SELECT markAtom FROM $headSeparator, $topSeparator, $bottomSeparator, $footSeparator => TRUE ENDCASE => FALSE;
width: REAL ~ IF inColumn THEN lineWidth ELSE totalWidth;
height: REAL ~ (
SELECT markAtom FROM
$insideRectoHeader, $centerRectoHeader, $outsideRectoHeader,
$centerVersoHeader, $insideVersoHeader, $outsideVersoHeader,
$insideHeader, $centerHeader, $outsideHeader => headerMargin,
$insideRectoFooter, $centerRectoFooter, $outsideRectoFooter,
$outsideVersoFooter, $centerVersoFooter, $insideVersoFooter,
$insideFooter, $centerFooter, $outsideFooter => footerMargin,
ENDCASE => pageBodyHeight
);
IF markAtom # NIL THEN {
IF TextEdit.Size[node.node] = 0 THEN {
botMarks ← Atom.PutPropOnList[botMarks, markAtom, NIL];
}
ELSE {
bx: Box ~ FormatNodes[start: node, bounds: [width, height], screenStyle: screenStyle, filter: markFilter, sep: NIL].box;
mark: LIST OF Box ~ UnBox[bx];
Duplicate[bx];
FOR p: LIST OF Box ← mark, p.rest UNTIL p=NIL DO
IF p.first = NIL THEN ERROR;
Duplicate[p.first];
ENDLOOP;
botMarks ← Atom.PutPropOnList[botMarks, markAtom, mark];
};
};
skip ← TRUE;
};
};
};
CatMarks: PROC [items: LIST OF LIST OF ATOM] RETURNS [Box] ~ {
pageNumber: INT ← pageCounter+firstFolio;
boxes: Boxes ← [];
result: Box ← NIL;
pageKey: ATOM ~ IF pageNumber <= lastDropFolio THEN $DROPFOLIO ELSE $PAGE;
FOR p: LIST OF LIST OF ATOM ← items, p.rest UNTIL p=NIL DO
b: Box ← NIL;
FOR q: LIST OF ATOM ← p.first, q.rest UNTIL b # NIL OR q = NIL DO
key: ATOM ~ q.first;
IF key = pageKey AND pageNeedsNumber AND pageNumber >= firstVisibleFolio
THEN {
pageFigure: ROPE ~ Convert.RopeFromInt[pageNumber];
b ← BoxFromRope[pageNumberFont, pageFigure];
b.escapement ← [0, -pageNumberBotIndent];
b ← BoxFromList[LIST[
EmptyBox[escapement: [x: 0, y: -pageNumberTopIndent]],
b]];
b.escapement ← [x: b.bounds.xmax, y: 0];
pageNeedsNumber ← FALSE;
}
ELSE {
list: LIST OF Box ← NARROW[Atom.GetPropFromList[botMarks, key]];
IF list#NIL THEN {
b ← BoxFromList[list];
Duplicate[b];
b.escapement ← [x: b.bounds.xmax, y: 0];
};
};
ENDLOOP;
IF b = NIL THEN b ← EmptyBox[];
b.stretch ← [x: NodeStyle.PointsPerFil, y: 0];
boxes ← AppendBox[boxes, b];
ENDLOOP;
IF boxes.last # NIL THEN boxes.last.first.stretch.x ← 0;
result ← BoxFromList[list: boxes.list, xFix: [size, totalWidth]];
RETURN [result]
};
leftMarg: REAL ~ leftMargin + (IF (NOT twoSided OR ((pageCounter+firstFolio) MOD 2 = 1)) THEN bindingMargin ELSE 0.0);
HeadersAndFooters: PROC RETURNS [Box] ~ {
topItems: LIST OF LIST OF ATOM ← topRectoItems;
botItems: LIST OF LIST OF ATOM ← botRectoItems;
b: Box ← EmptyBox[];
IF twoSided THEN SELECT (pageCounter+firstFolio) MOD 2 FROM
0 => {topItems ← topVersoItems; botItems ← botVersoItems};
1 => {topItems ← topRectoItems; botItems ← botRectoItems};
ENDCASE => NULL;
IF (pageCounter+firstFolio) >= firstHeaders THEN {
headerBox: Box ~ CatMarks[topItems];
footerBox: Box ~ CatMarks[botItems];
b ← Overlay[b, footerBox, [leftMarg, bottomMargin-footerBox.bounds.ymin]];
b ← Overlay[b, headerBox, [leftMarg, pageLength-topMargin]];
};
RETURN [b];
};
pageList: Boxes ← [NIL, NIL];
pageTitleBox: Box ← NIL;
pageBox: Box ← NIL;
pageNumberFont: Font ← NIL;
pageNumberTopIndent: REAL ← 0;
pageNumberBotIndent: REAL ← 0;
NodeStyleOps.ApplyFormat[nodeStyle, $pagenumber, $default, kind];
pageNumberFont ← NodeStyleFont.FontFromStyleParams[prefix: nodeStyle.name[fontPrefix], family: nodeStyle.name[fontFamily], face: nodeStyle.fontFace, size: NodeStyle.GetFontSize[nodeStyle], alphabets: nodeStyle.fontAlphabets];
pageNumberTopIndent ← NodeStyle.GetTopIndent[nodeStyle];
pageNumberBotIndent ← NodeStyle.GetBottomIndent[nodeStyle];
IF nColumns > 1 THEN {
haveTitle: BOOLFALSE;
titleFilter: FilterProc ~ {
NodeStyleOps.ApplyAll[nodeStyle, node.node, kind];
IF NodeStyle.GetColumns[nodeStyle] # 1 THEN { stop ← TRUE; RETURN };
[skip, stop, maxLineLength] ← myFilter[node, level, position];
IF NOT (skip OR stop) THEN haveTitle ← TRUE;
};
formattedTitle: FormattedNodes ← FormatNodes[start: loc, bounds: [totalWidth, bodyHeight], screenStyle: screenStyle, filter: titleFilter, sep: GetSeparator];
IF haveTitle THEN {
boxes: LIST OF Box ~ UnBox[formattedTitle.box];
rebox: Box ~ BoxFromList[boxes];
pageTitleBox ← rebox;
bodyHeight ← MAX[bodyHeight - ABS[rebox.escapement.y], 0.0];
loc ← formattedTitle.resume;
};
};
NodeStyleOps.Free[nodeStyle]; nodeStyle ← NIL;
FOR c: INT IN [0..nColumns) WHILE loc.node # NIL DO
formatted: FormattedNodes ← FormatNodes[start: loc, bounds: [lineWidth, bodyHeight], screenStyle: screenStyle, filter: myFilter, sep: GetSeparator];
formatted.box.escapement ← [lineWidth+columnGap, 0];
pageList ← AppendBox[pageList, formatted.box];
loc ← formatted.resume;
ENDLOOP;
pageBox ← BoxFromList[pageList.list];
IF pageTitleBox # NIL THEN {
t: Boxes ← [NIL, NIL];
t ← AppendBox[t, pageTitleBox];
t ← AppendBox[t, pageBox];
pageBox ← BoxFromList[t.list];
};
pageBox ← Overlay[HeadersAndFooters[], pageBox, [leftMarg, pageLength-topMargin-headerMargin]];
RETURN [[box: pageBox, nextLoc: loc, marks: botMarks, pageFigure: pageCounter+firstFolio]]
};
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
tiogaClass: Class ← NEW[ClassRep ← [
Composite: NIL,
UnBox: NIL,
Render: TiogaRender,
Resolve: TiogaResolve,
Destroy: TiogaDestroy
]];
TiogaRender: PROC [box: Box, context: Context, position: VEC] ~ {
lineInfo: TEditFormat.LineInfo ~ NARROW[box.data];
position.x ← position.x + Scaled.Float[lineInfo.xOffset];
Imager.SetXY[context, position];
TEditFormat.Paint[lineInfo, context];
};
realInteger: REALLAST[INTEGER];
TiogaResolve: PROC [box: Box, p: VEC] RETURNS [loc: TextNode.Location ← TextNode.nullLocation] ~ {
lineInfo: TEditFormat.LineInfo ~ NARROW[box.data];
IF NOT InBox[p, box] THEN RETURN;
loc ← TEditFormat.Resolve[lineInfo, Real.Round[MIN[MAX[p.x, -realInteger], realInteger]]].loc;
};
TiogaDestroy: PROC [box: Box] ~ {
lineInfo: TEditFormat.LineInfo ~ NARROW[box.data];
box.data ← NIL;
TEditFormat.Release[lineInfo];
};
EmptyBox: PUBLIC PROC [escapement: VEC ← [0,0], stretch: VEC ← [0,0], shrink: VEC ← [0,0]] RETURNS [box: Box] ~ {
box ← NEW[BoxRep ← [
nChars: 0,
bounds: [0, 0, 0, 0],
expansion: [0, 0],
escapement: escapement,
stretch: stretch,
shrink: shrink,
class: emptyClass,
data: NIL
]];
};
emptyClass: Class ← NEW[ClassRep ← [
UnBox: NIL,
Render: NIL,
Resolve: NIL,
Destroy: NIL
]];
BoxFromChar: PUBLIC PROC [font: Font, char: CHAR] RETURNS [Box] ~ {
RETURN [BoxFromXChar[font, [0, ORD[char]]]]
};
BoxFromXChar: PUBLIC PROC [font: Font, xchar: XChar] RETURNS [box: Box] ~ {
w: VEC ← ImagerFont.Width[font, xchar];
flex: VECIF ImagerFont.Amplified[font, xchar] THEN w ELSE [0, 0];
box ← NEW[BoxRep ← [
nChars: 1,
bounds: ImagerBox.BoxFromExtents[ImagerFont.BoundingBox[font, xchar]],
expansion: [0, 0],
escapement: w,
stretch: flex,
shrink: flex,
class: xCharClass,
data: NEW[XCharBoxDataRep ← [font, xchar]]
]];
};
XCharBoxDataRep: TYPE ~ RECORD [font: Font, xchar: XChar];
xCharClass: Class ← NEW[ClassRep ← [
UnBox: NIL,
Render: XCharRender,
Resolve: NIL,
Destroy: NIL
]];
XCharRender: PROC [box: Box, context: Context, position: VEC] ~ {
data: REF XCharBoxDataRep ~ NARROW[box.data];
Imager.SetFont[context, data.font];
Imager.SetXY[context, position];
Imager.ShowXChar[context, data.xchar];
};
BoxFromRope: PUBLIC PROC [font: Font, rope: Rope.ROPE] RETURNS [box: Box] ~ {
box ← NEW[BoxRep ← [
nChars: Rope.Size[rope],
bounds: ImagerBox.BoxFromExtents[ImagerFont.RopeBoundingBox[font, rope]],
expansion: [0, 0],
escapement: ImagerFont.RopeWidth[font, rope],
stretch: [0, 0],
shrink: [0, 0],
class: ropeClass,
data: NEW[RopeBoxDataRep ← [font, rope]]
]];
};
RopeBoxDataRep: TYPE ~ RECORD [font: Font, rope: ROPE];
ropeClass: Class ← NEW[ClassRep ← [
Composite: NIL,
UnBox: NIL,
Render: RopeRender,
Resolve: NIL,
Destroy: NIL
]];
RopeRender: PROC [box: Box, context: Context, position: VEC] ~ {
data: REF RopeBoxDataRep ~ NARROW[box.data];
Imager.SetFont[context, data.font];
Imager.SetXY[context, position];
Imager.ShowRope[context, data.rope];
};
CalculateExpansion: PROC [fix: Fix, w, stretch, shrink: REAL] RETURNS [e: REAL] ~ {
IF fix.what = expansion THEN e ← fix.value
ELSE {
denom: REALIF fix.value = 0 OR w/fix.value > 1.0 THEN shrink ELSE stretch;
e ← IF ABS[denom] > 1.0e-10 THEN (fix.value-w)/denom ELSE 0.0;
};
IF e < -1 THEN e ← -1;
};
Advance: PROC [w: VEC, by: Box, expansion: VEC] RETURNS [VEC]~ {
w ← w.Add[by.escapement];
SELECT expansion.x FROM
= 0.0 => NULL;
< 0.0 => w.x ← w.x + expansion.x*by.shrink.x;
> 0.0 => w.x ← w.x + expansion.x*by.stretch.x;
ENDCASE => NULL;
SELECT expansion.y FROM
= 0.0 => NULL;
< 0.0 => w.y ← w.y + expansion.y*by.shrink.y;
> 0.0 => w.y ← w.y + expansion.y*by.stretch.y;
ENDCASE => NULL;
RETURN [w];
};
CopyList: PROC [list: LIST OF Box] RETURNS [LIST OF Box] ~ {
boxes: Boxes ← [NIL, NIL];
FOR p: LIST OF Box ← list, list.rest UNTIL p = NIL DO
Duplicate[p.first];
boxes ← AppendBox[boxes, p.first];
ENDLOOP;
RETURN [boxes.list];
};
BoxFromList: PUBLIC PROC [list: LIST OF Box, xFix: Fix ← [], yFix: Fix ← []] RETURNS [box: Box] ~ {
w: VEC ← [0, 0];
min: VEC ← [Real.LargestNumber, Real.LargestNumber];
max: VEC ← [-Real.LargestNumber, -Real.LargestNumber];
stretch: VEC ← [0, 0];
shrink: VEC ← [0, 0];
expansion: VEC ← [0, 0];
FOR p: LIST OF Box ← list, p.rest UNTIL p=NIL DO
w ← w.Add[p.first.escapement];
stretch ← stretch.Add[p.first.stretch];
shrink ← shrink.Add[p.first.shrink];
ENDLOOP;
expansion ← [CalculateExpansion[xFix, w.x, stretch.x, shrink.x], CalculateExpansion[yFix, w.y, stretch.y, shrink.y]];
w ← [0, 0];
FOR p: LIST OF Box ← list, p.rest UNTIL p=NIL DO
min.x ← MIN[min.x, w.x + p.first.bounds.xmin];
min.y ← MIN[min.y, w.y + p.first.bounds.ymin];
max.x ← MAX[max.x, w.x + p.first.bounds.xmax];
max.y ← MAX[max.y, w.y + p.first.bounds.ymax];
w ← Advance[w, p.first, expansion];
ENDLOOP;
IF list = NIL THEN min ← max ← [0, 0];
IF xFix.what = size THEN w.x ← xFix.value;
IF yFix.what = size THEN w.y ← yFix.value;
box ← NEW[BoxRep ← [
nChars: 0,
bounds: [xmin: min.x, ymin: min.y, xmax: max.x, ymax: max.y],
expansion: expansion,
escapement: w,
stretch: [0, 0],
shrink: [0, 0],
class: listBoxClass,
data: list
]];
};
listBoxClass: Class ← NEW[ClassRep ← [
Composite: NIL,
UnBox: ListUnBox,
Render: ListRender,
Resolve: ListResolve,
Destroy: ListDestroy
]];
ListUnBox: PROC [box: Box] RETURNS [list: LIST OF Box] ~ {
list ← NARROW[box.data];
};
ListRender: PROC [box: Box, context: Context, position: VEC] ~ {
w: VEC ← position;
expansion: VEC ← box.expansion;
FOR p: LIST OF Box ← NARROW[box.data], p.rest UNTIL p=NIL DO
Render[p.first, context, w];
w ← Advance[w, p.first, expansion];
ENDLOOP;
};
InBox: PROC [p: VEC, box: Box] RETURNS [BOOL] ~ {
RETURN [p.x IN [box.bounds.xmin..box.bounds.xmax] AND p.y IN [box.bounds.ymin..box.bounds.ymax]]
};
ListResolve: PROC [box: Box, p: VEC] RETURNS [loc: TextNode.Location ← TextNode.nullLocation] ~ {
w: VEC ← [0, 0];
expansion: VEC ← box.expansion;
list: LIST OF Box ← NARROW[box.data];
IF NOT InBox[p, box] THEN RETURN;
list ← NARROW[box.data];
FOR l: LIST OF Box ← list, l.rest UNTIL l=NIL DO
pRel: VEC ~ p.Sub[w];
loc ← Resolve[l.first, pRel];
IF loc # TextNode.nullLocation THEN RETURN;
w ← Advance[w, l.first, expansion];
ENDLOOP;
};
ListDestroy: PROC [box: Box] ~ {
list: LIST OF Box ← NARROW[box.data];
rest: LIST OF Box ← NIL;
box.data ← NIL;
FOR l: LIST OF Box ← list, rest UNTIL l=NIL DO
Destroy[l.first];
rest ← l.rest;
l.first ← NIL;
l.rest ← NIL;
ENDLOOP;
};
Overlay: PUBLIC PROC [base, new: Box, offset: VEC] RETURNS [Box] ~ {
data: OverlayData ~ NEW[OverlayDataRep ← [base, new, offset]];
RETURN [NEW[BoxRep ← [
nChars: 0,
bounds: ImagerBox.BoundingBox[base.bounds, new.bounds],
expansion: [0,0],
escapement: base.escapement,
stretch: base.stretch,
shrink: base.shrink,
class: overlayBoxClass,
data: data
]]];
};
overlayBoxClass: Class ← NEW[ClassRep ← [
Composite: NIL,
UnBox: NIL,
Render: OverlayRender,
Resolve: OverlayResolve,
Destroy: OverlayDestroy
]];
OverlayData: TYPE ~ REF OverlayDataRep;
OverlayDataRep: TYPE ~ RECORD [base, new: Box, offset: VEC];
OverlayRender: PROC [box: Box, context: Context, position: VEC] ~ {
data: OverlayData ~ NARROW[box.data];
Render[data.base, context, position];
Render[data.new, context, Vector2.Add[position, data.offset]];
};
OverlayResolve: PROC [box: Box, p: VEC] RETURNS [loc: TextNode.Location ← TextNode.nullLocation] ~ {
data: OverlayData ~ NARROW[box.data];
loc ← Resolve[data.new, Vector2.Sub[p, data.offset]];
IF loc.node = NIL THEN loc ← Resolve[data.base, p];
};
OverlayDestroy: PROC [box: Box] ~ {
data: OverlayData ~ NARROW[box.data];
Destroy[data.new];
Destroy[data.base];
data.new ← NIL;
data.base ← NIL;
box.data ← NIL;
};
Composite: PUBLIC PROC [box: Box] RETURNS [BOOL] ~ {
RETURN [IF box.class.Composite = NIL THEN (box.class.UnBox # NIL) ELSE box.class.Composite[box]]
};
UnBox: PUBLIC PROC [box: Box] RETURNS [list: LIST OF Box] ~ {
list ← IF box.class.UnBox # NIL THEN box.class.UnBox[box] ELSE NIL;
};
ModifyBox: PUBLIC PROC [box: Box, m: Transformation] RETURNS [new: Box] ~ {
data: REF ModifiedBoxDataRep ~ NEW[ModifiedBoxDataRep ← [box, m]];
WITH box.data SELECT FROM
modified: REF ModifiedBoxDataRep => {
data.box ← modified.box;
data.m ← ImagerTransformation.Concat[modified.m, m];
};
ENDCASE => NULL;
new ← NEW[BoxRep ← [
nChars: box.nChars,
bounds: ImagerBox.BoxFromRect[ImagerTransformation.TransformRectangle[m, ImagerBox.RectFromBox[box.bounds]]],
expansion: box.expansion,
escapement: ImagerTransformation.TransformVec[m, box.escapement],
stretch: ImagerTransformation.TransformVec[m, box.stretch],
shrink: ImagerTransformation.TransformVec[m, box.shrink],
class: modifiedBoxClass,
data: NEW[ModifiedBoxDataRep ← [box, m]]
]];
};
ModifiedBoxDataRep: TYPE ~ RECORD [box: Box, m: Transformation];
modifiedBoxClass: Class ← NEW[ClassRep ← [
Composite: ModifiedComposite,
UnBox: ModifiedUnBox,
Render: ModifiedRender,
Resolve: ModifiedResolve,
Destroy: ModifiedDestroy
]];
ModifiedComposite: PROC [box: Box] RETURNS [BOOL] ~ {
modified: REF ModifiedBoxDataRep ~ NARROW[box.data];
RETURN [Composite[modified.box]]
};
ModifiedUnBox: PROC [box: Box] RETURNS [LIST OF Box] ~ {
modified: REF ModifiedBoxDataRep ~ NARROW[box.data];
m: Transformation ~ modified.m;
b: Boxes ← [NIL, NIL];
unmodified: LIST OF Box ← UnBox[modified.box];
FOR p: LIST OF Box ← unmodified, p.rest UNTIL p = NIL DO
b ← AppendBox[b, ModifyBox[p.first, m]];
ENDLOOP;
RETURN [b.list];
};
ModifiedRender: PROC [box: Box, context: Context, position: VEC] ~ {
modified: REF ModifiedBoxDataRep ~ NARROW[box.data];
proc: PROC ~ {
Imager.TranslateT[context, position];
Imager.ConcatT[context, modified.m];
Render[modified.box, context, [0, 0]];
};
Imager.DoSave[context, proc];
};
ModifiedResolve: PROC [box: Box, p: VEC] RETURNS [loc: TextNode.Location ← TextNode.nullLocation] ~ {
modified: REF ModifiedBoxDataRep ~ NARROW[box.data];
RETURN [Resolve[modified.box, ImagerTransformation.InverseTransform[modified.m, p]]]
};
ModifiedDestroy: PROC [box: Box] ~ {
modified: REF ModifiedBoxDataRep ~ NARROW[box.data];
Destroy[modified.box];
modified.box ← NIL;
};
Render: PUBLIC PROC [box: Box, context: Context, position: VEC] ~ {
IF box.class.Render # NIL THEN box.class.Render[box, context, position];
};
Resolve: PUBLIC PROC [box: Box, p: VEC] RETURNS [loc: TextNode.Location] ~ {
loc ← IF box.class.Resolve # NIL THEN box.class.Resolve[box, p] ELSE TextNode.nullLocation;
};
FirstLocWithin: PUBLIC PROC [box: Box] RETURNS [loc: TextNode.Location] ~ {
loc ← IF box.class.FirstLocWithin # NIL THEN box.class.FirstLocWithin[box] ELSE TextNode.nullLocation;
};
doDestroy: BOOLTRUE;
Destroy: PUBLIC PROC [box: Box] ~ {
IF doDestroy AND box.class # NIL AND box.class.Destroy # NIL AND NOT box.duplicate THEN box.class.Destroy[box];
};
Duplicate: PUBLIC PROC [box: Box] ~ {
IF box # NIL THEN box.duplicate ← TRUE;
};
AppendList: PUBLIC PROC [boxes: Boxes, list: LIST OF Box] RETURNS [Boxes] ~ {
IF list = NIL THEN RETURN [boxes]
ELSE IF boxes.list = NIL THEN boxes.list ← list
ELSE {
IF boxes.last.rest # NIL THEN ERROR;
boxes.last.rest ← list;
};
UNTIL list.rest = NIL DO list ← list.rest ENDLOOP;
boxes.last ← list;
RETURN [boxes];
};
AppendBox: PUBLIC PROC [boxes: Boxes, box: Box] RETURNS [Boxes] ~ {
list: LIST OF Box ← LIST[box];
IF boxes.list = NIL THEN boxes ← [list: list, last: list]
ELSE {
IF boxes.last.rest # NIL THEN ERROR;
boxes.last.rest ← list;
boxes.last ← list;
};
RETURN [boxes];
};
END.
← &td ← PutGet.FromFile["TiogaDoc.Tioga"]
← &c ← ImagerTerminal.BWContext[Terminal.Current[], TRUE]
← &b ← TiogaImager.FormatNodes[[&td, 0], [400, 600], TRUE, NIL]
← TiogaImager.Render[&b.box, &c, [10, 700]]