<<>> <> <> <> <> <> <<>> 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; 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] 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 => { <> <> 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; <> 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 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]; lineBox.stretch.y ¬ -NodeStyle.GetReal[nodeStyle, leadingStretch]; lineBox.shrink.y ¬ -NodeStyle.GetReal[nodeStyle, leadingShrink]; stopper ¬ stopper - 1; IF boxes[insertID].list = NIL THEN { <> topIndent: REAL ¬ NodeStyle.GetReal[nodeStyle, topIndent]; 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.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 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 ]]; useCorrect: BOOL ¬ TRUE; 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]]>>