TiogaImagerImpl.mesa
Copyright Ó 1985, 1986, 1987, 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved.
Michael Plass, March 10, 1992 2:29 pm PST
JKF February 24, 1989 1:21:18 pm PST
Doug Wyatt, March 23, 1992 3:55 pm PST
DIRECTORY
Atom USING [GetPName, GetPropFromList, PropList, PutPropOnList],
Char USING [Widen],
Commander USING [Handle],
Convert USING [RopeFromInt],
Imager USING [Box, ConcatT, Context, Correct, DoSave, SetCorrectMeasure, SetCorrectTolerance, SetFont, SetXY, ShowRope, ShowXChar, TranslateT],
ImagerBox USING [BoundingBox, BoxFromExtents, BoxFromRectangle, RectangleFromBox],
ImagerFont USING [Amplified, BoundingBox, Font, RopeBoundingBox, RopeEscapement, Escapement, XChar],
ImagerTransformation USING [Concat, InverseTransform, Transformation, TransformRectangle, TransformVec],
IO USING [PutRope, STREAM],
NodeProps USING [GetProp],
NodeStyle USING [Ref, RealParam, StyleKind, GetReal, GetInt, PointsPerFil],
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, PLUS, Value, zero],
SimpleFeedback USING [Append],
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, Char, Convert, Imager, ImagerBox, ImagerFont, IO, Process, ProcessProps, ImagerTransformation, Real, Rope, NodeProps, NodeStyle, NodeStyleFont, NodeStyleOps, Scaled, SimpleFeedback, 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;
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]
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];
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.GetReal[style, leading]],
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];
SimpleFeedback.Append[$TiogaImager, begin, $Error, "TiogaImagerImpl: "];
SimpleFeedback.Append[$TiogaImager, end, $Error, rope];
};
};
GetStyleParam:
PROC [style: NodeStyle.Ref, param:
ATOM, default:
REAL, kind: NodeStyle.StyleKind]
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]
};
insertOrder:
ARRAY [0..4)
OF InsertID ¬ [top, normal, foot, bottom];
FormatNodes:
PUBLIC
PROC [start: TextNode.Location, bounds:
VEC, styleKind: NodeStyle.StyleKind, 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, styleKind];
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: BOOL ¬ TRUE; -- 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: NodeStyle.StyleKind ~ styleKind;
keepStretch: REAL ¬ 0.0;
box: Box ¬ NIL;
boxToStretch: Box ¬ NIL;
maxVerticalExpansion: REAL ¬ 0;
NodeStyleOps.ApplyAll[nodeStyle, loc.node, kind];
maxVerticalExpansion ¬ NodeStyle.GetReal[nodeStyle, $maxVerticalExpansion];
h ¬ 0;
WHILE firstTime
OR (loc.node #
NIL
AND h <= bounds.y)
DO
skip, stop: BOOL ¬ FALSE;
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];
lineBox.stretch.y ¬ -NodeStyle.GetReal[nodeStyle, leadingStretch];
lineBox.shrink.y ¬ -NodeStyle.GetReal[nodeStyle, leadingShrink];
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.GetReal[nodeStyle, topIndent];
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.GetReal[nodeStyle, topLeading]];
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.GetReal[nodeStyle, topLeadingShrink];
prevBox.stretch.y ¬ prevBox.stretch.y-NodeStyle.GetReal[nodeStyle, topLeadingStretch];
lineBox.stretch.y ¬ -NodeStyle.GetReal[nodeStyle, leadingStretch];
};
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.GetReal[nodeStyle, bottomLeading];
lineBox.shrink.y ¬ -NodeStyle.GetReal[nodeStyle, bottomLeadingShrink];
lineBox.stretch.y ¬ -NodeStyle.GetReal[nodeStyle, bottomLeadingStretch];
};
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 insertNumber:
NAT
IN [0..
LENGTH[insertOrder])
DO
i: InsertID ~ insertOrder[insertNumber];
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.GetReal[nodeStyle, bottomIndent];
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
ATOM ¬
LIST[
$invisible,
$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: ATOM ¬ NIL;
realAtom: ATOM ¬ NIL;
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: ROPE ¬ NIL;
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
ATOM ¬
LIST[
LIST[$outsideVersoHeader, $outsideHeader, $PAGE],
LIST[$centerVersoHeader, $centerHeader],
LIST[$insideVersoHeader, $insideHeader]
];
botVersoItems:
LIST
OF
LIST
OF
ATOM ¬
LIST[
LIST[$PAGE, $outsideVersoFooter, $outsideFooter],
LIST[$DROPFOLIO, $centerVersoFooter, $centerFooter],
LIST[$insideVersoFooter, $insideFooter]
];
topRectoItems:
LIST
OF
LIST
OF
ATOM ¬
LIST[
LIST[$insideRectoHeader, $insideHeader],
LIST[$centerRectoHeader, $centerHeader],
LIST[$outsideRectoHeader, $outsideHeader, $PAGE]
];
botRectoItems:
LIST
OF
LIST
OF
ATOM ¬
LIST[
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, styleKind: NodeStyle.StyleKind]
RETURNS [FormattedPage] ~ {
pageNeedsNumber: BOOL ¬ TRUE;
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, styleKind];
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.GetInt[nodeStyle, columns], 1];
columnGap: REAL ~ GetStyleParam[nodeStyle, $columnGap, 36, styleKind];
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, styleKind]];
firstVisibleFolio: INT ~ Real.Round[GetStyleParam[nodeStyle, $firstVisibleFolio, 1, styleKind]];
lastDropFolio: INT ~ Real.Round[GetStyleParam[nodeStyle, $lastDropFolio, 0, styleKind]];
twoSided: BOOL ~ GetStyleParam[nodeStyle, $sided, 1.0, styleKind] > 1.5;
firstHeaders: INT ~ Real.Round[GetStyleParam[nodeStyle, $firstHeaders, 0.0, styleKind]];
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], styleKind: styleKind, 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, styleKind];
pageNumberFont ¬ NodeStyleFont.FontFromStyleParams[prefix: nodeStyle.name[fontPrefix], family: nodeStyle.name[fontFamily], face: nodeStyle.fontFace, size: NodeStyle.GetReal[nodeStyle, fontSize], alphabets: nodeStyle.fontAlphabets];
pageNumberTopIndent ¬ NodeStyle.GetReal[nodeStyle, topIndent];
pageNumberBotIndent ¬ NodeStyle.GetReal[nodeStyle, bottomIndent];
IF nColumns > 1
THEN {
haveTitle: BOOL ¬ FALSE;
titleFilter: FilterProc ~ {
NodeStyleOps.ApplyAll[nodeStyle, node.node, styleKind];
IF NodeStyle.GetInt[nodeStyle, columns] # 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], styleKind: styleKind, 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], styleKind: styleKind, 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];
IF useCorrect
AND lineInfo.artworkClass =
NIL
AND lineInfo.amplifySpace # 1.0
AND lineInfo.startAmplifyIndex <= lineInfo.nChars
THEN {
Action: PROC = { TEditFormat.Paint[lineInfo, context] };
w: Scaled.Value ¬ Scaled.zero;
nChars: NAT ~ lineInfo.nChars+(IF lineInfo.nBlankCharsAtEnd=NAT.LAST THEN 1 ELSE -lineInfo.nBlankCharsAtEnd);
FOR i:
NAT
IN [0..nChars)
DO
w ¬ Scaled.PLUS[w, lineInfo.charInfo[i].width];
ENDLOOP;
Imager.SetCorrectTolerance[context: context, v: [0.1, 0.1]];
Imager.SetCorrectMeasure[context: context, v: [Scaled.Float[w], 0.0]];
Imager.Correct[context: context, action: Action];
}
ELSE {
TEditFormat.Paint[lineInfo, context];
};
};
realInteger: REAL ¬ LAST[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, Char.Widen[char]]]
};
BoxFromXChar:
PUBLIC
PROC [font: Font, xchar: XChar]
RETURNS [box: Box] ~ {
w: VEC ¬ ImagerFont.Escapement[font, xchar];
flex: VEC ¬ IF 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.RopeEscapement[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: REAL ¬ IF 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.BoxFromRectangle[ImagerTransformation.TransformRectangle[m, ImagerBox.RectangleFromBox[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: BOOL ¬ TRUE;
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]]