<> <> <> <<>> 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; <> 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: 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: BOOL _ FALSE; xbound: REAL _ Real.LargestNumber; <> 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 { <> 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 { <> topIndent: REAL _ NodeStyle.GetTopIndent[nodeStyle]; topIndentStretch: REAL _ GetStyleParam[nodeStyle, $topIndentStretch, 0, kind]; IF sep#NIL THEN { <> 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; <> IF hTemp <= bounds.y THEN { <> 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 { <> 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 ATOM _ LIST[ $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, screenStyle: BOOL _ FALSE] RETURNS [FormattedPage] ~ { kind: NodeStyleOps.OfStyle ~ IF screenStyle THEN screen ELSE print; 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, 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 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: BOOL _ FALSE; 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: 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, [0, ORD[char]]]] }; BoxFromXChar: PUBLIC PROC [font: Font, xchar: XChar] RETURNS [box: Box] ~ { w: VEC _ ImagerFont.Width[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.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: 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.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: 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]]>>