<> <> <> <> <<>> DIRECTORY Carets USING [pCaretViewer, sCaretViewer, StopCaret], EditSpan USING [afterMoved1, afterMoved2], Imager, NodeStyle, Process USING [Detach], RopeReader USING [Ref, GetRopeReader, FreeRopeReader, SetPosition, Backwards, Get, GetIndex], Scaled, TextEdit USING [Offset, RefTextNode, Size], TextNode USING [FirstChild, ForwardClipped, Level, Location, NarrowToTextNode, pZone, Ref, Root, StepForward], TEditCompile USING [minAvgLineLeading], TEditDisplay, TEditDocument USING [LineBreak, LineTable, LineTableRec, maxClip, Selection, SelectionRec, SpinAndLock, SelectionId, TEditDocumentData, Unlock], TEditFormat, TEditImpl, TEditLocks USING [LockDocAndTdd, LockRef, UnlockDocAndTdd, WaitingForWrite], TEditScrolling USING [AutoScroll], TEditSelection USING [AdjustSelStates, FixUpAfterDisplay, InsertionPoint, InvalidateLineCache, pSel, sSel, fSel, SelectionRoot, ShowSelection, TakeDownForRedisplay, TakeSelectionDown], TEditTouchup, ViewerClasses USING [PaintProc, Viewer, PaintRectangle], ViewerOps USING [UserToScreenCoords]; TEditDisplayImpl: CEDAR PROGRAM IMPORTS Carets, EditSpan, Imager, NodeStyle, Process, Scaled, TextEdit, TextNode, RopeReader, TEditDocument, TEditFormat, TEditLocks, TEditScrolling, TEditSelection, TEditTouchup, ViewerOps EXPORTS TEditImpl = BEGIN PaintTEditDocument: PUBLIC ViewerClasses.PaintProc = BEGIN tdd: TEditDocument.TEditDocumentData = NARROW[self.data]; lock: TEditLocks.LockRef; moveDownLines, moveDownDistance: INTEGER _ 0; -- used with scrolling down redisplay, refreshing: BOOL _ FALSE; Cleanup: PROC = { TEditLocks.UnlockDocAndTdd[tdd]; TEditTouchup.RefreshOver[] }; BEGIN ENABLE UNWIND => Cleanup[]; interrupt: BOOL = clear OR whatChanged=TEditTouchup.fullUpdate OR whatChanged=NIL; typescript: BOOL; IF self.destroyed OR tdd=NIL THEN RETURN; -- the viewer changed while this was forked tdd.invisible _ FALSE; -- or else we wouldn't have been called lock _ TEditLocks.LockDocAndTdd[tdd, "PaintTEditDocument", read, interrupt, TRUE]; IF lock=NIL THEN { IF NOT interrupt THEN ERROR; RETURN }; -- someone else will do the display IF Carets.pCaretViewer=self THEN Carets.StopCaret[primary]; IF Carets.sCaretViewer=self THEN Carets.StopCaret[secondary]; IF (self.ch/TEditCompile.minAvgLineLeading)+1 > tdd.lineTable.maxLines THEN BEGIN <<-- make bigger line table>> oldTable: TEditDocument.LineTable _ tdd.lineTable; tdd.lineTable _ TextNode.pZone.NEW[TEditDocument.LineTableRec[(self.ch/TEditCompile.minAvgLineLeading)+1] _ [lastLine: oldTable.lastLine, lastY: oldTable.lastY, lines: NULL]]; FOR n: INTEGER IN [0..oldTable.lastLine] DO -- copy old data tdd.lineTable[n] _ oldTable[n]; ENDLOOP; END; typescript _ tdd.tsInfo # NIL; refreshing _ whatChanged=TEditTouchup.refresh; IF clear OR refreshing OR whatChanged=TEditTouchup.fullUpdate OR whatChanged=NIL THEN { IF clear THEN TEditSelection.AdjustSelStates[self]; IF NOT refreshing THEN FOR n: INTEGER IN [0..tdd.lineTable.lastLine] DO -- invalidate all lines tdd.lineTable[n].valid _ FALSE; ENDLOOP; redisplay _ TRUE; } ELSE { SELECT whatChanged FROM $TakeDownPSel => TEditSelection.TakeSelectionDown[primary, self, context]; $ShowPSel => TEditSelection.ShowSelection[primary, self, context]; $TakeDownSSel => TEditSelection.TakeSelectionDown[secondary, self, context]; $ShowSSel => TEditSelection.ShowSelection[secondary, self, context]; $TakeDownFSel => TEditSelection.TakeSelectionDown[feedback, self, context]; $ShowFSel => TEditSelection.ShowSelection[feedback, self, context]; ENDCASE => WITH whatChanged SELECT FROM x: REF TEditTouchup.PreScrollDownRec => { <> IF clear THEN ERROR; moveDownLines _ x.lines; moveDownDistance _ x.distance; refreshing _ redisplay _ TRUE }; x: ViewerClasses.PaintRectangle => { -- fix up after Viewer blt IF x.flavor # blt -- or if width has changed -- THEN { <> FOR n: INTEGER IN [0..tdd.lineTable.lastLine] DO -- invalidate all lines tdd.lineTable[n].valid _ FALSE; ENDLOOP } ELSE { -- height has changed n: INTEGER _ 0; h: INTEGER _ ViewerOps.UserToScreenCoords[self, 0, self.ch].sy-x.y; -- new height UNTIL n > tdd.lineTable.lastLine OR tdd.lineTable[n].yOffset+tdd.lineTable[n].descent >= h DO n _ n+1; ENDLOOP; n _ MIN[n, tdd.lineTable.lastLine]; -- make sure invalidate at least one line UNTIL n > tdd.lineTable.lastLine DO tdd.lineTable[n].valid _ FALSE; n _ n+1; ENDLOOP }; refreshing _ redisplay _ TRUE; }; ENDCASE => ERROR; }; IF redisplay THEN { TEditSelection.TakeDownForRedisplay[primary, self, context]; TEditSelection.TakeDownForRedisplay[secondary, self, context]; TEditSelection.TakeDownForRedisplay[feedback, self, context]; IF typescript AND self.ch < tdd.lineTable[tdd.lineTable.lastLine].yOffset THEN { <> delta: INTEGER = tdd.lineTable[tdd.lineTable.lastLine].yOffset - self.ch; n: INTEGER _ 0; UNTIL n=tdd.lineTable.lastLine OR tdd.lineTable[n].yOffset > delta DO n _ n+1; ENDLOOP; tdd.lineTable[0] _ tdd.lineTable[MIN[tdd.lineTable.lastLine, n+1]]; tdd.lineTable[0].valid _ FALSE }; IF NOT tdd.lineTable[0].valid THEN { -- check start location startLoc: TextNode.Location; node: TextEdit.RefTextNode; CheckLocNode: PROC [loc: TextNode.Location] RETURNS [BOOL] = { startLoc _ loc; RETURN [ loc.node # NIL AND TextNode.Root[loc.node] = tdd.text ] }; IF CheckLocNode[tdd.lineTable[0].pos] THEN NULL ELSE { IF CheckLocNode[TEditSelection.InsertionPoint[]] OR CheckLocNode[EditSpan.afterMoved1] OR CheckLocNode[EditSpan.afterMoved2] THEN tdd.fixStart _ TRUE ELSE startLoc _ [TextNode.FirstChild[tdd.text],0] }; IF (node _ TextNode.NarrowToTextNode[startLoc.node]) # NIL THEN { size: TextEdit.Offset = TextEdit.Size[node]; IF startLoc.where NOT IN [0..size) THEN { tdd.lineTable[0].valid _ FALSE; tdd.fixStart _ TRUE; startLoc.where _ MAX[MIN[size-1,startLoc.where],0] } }; IF tdd.fixStart AND node # NIL AND startLoc.where > 0 THEN { OPEN RopeReader; <> rdr: Ref = GetRopeReader[]; cnt: INTEGER _ 0; foundBreak: BOOL _ FALSE; SetPosition[rdr,node.rope,startLoc.where]; FOR i:INT _ 0, i+1 DO char: CHAR; IF i=startLoc.where THEN { foundBreak _ TRUE; EXIT }; -- out of chars IF cnt > 200 THEN EXIT; -- give up char _ Backwards[rdr]; IF char = 15C THEN { foundBreak _ TRUE; [] _ Get[rdr]; EXIT }; cnt _ cnt+1; ENDLOOP; IF foundBreak THEN startLoc.where _ GetIndex[rdr]; FreeRopeReader[rdr]; }; tdd.fixStart _ FALSE; tdd.lineTable[0].pos _ startLoc; IF tdd.clipLevel < TEditDocument.maxClip THEN -- check level of startLoc tdd.clipLevel _ MAX[tdd.clipLevel, TextNode.Level[startLoc.node]]; }; TEditSelection.InvalidateLineCache; -- since selection impl keeps accelerators RefreshViewer[self, tdd, context, clear, (whatChanged=TEditTouchup.refresh AND NOT typescript), lock, moveDownLines, moveDownDistance]; }; IF whatChanged=$TakeDownPSel OR whatChanged=$TakeDownSSel OR whatChanged=$TakeDownFSel THEN NULL ELSE IF whatChanged=TEditTouchup.refresh AND NOT typescript AND TEditLocks.WaitingForWrite[lock] THEN NULL ELSE { --fix up the selections TEditSelection.FixUpAfterDisplay[primary, self, context, NOT tdd.readOnly]; TEditSelection.FixUpAfterDisplay[secondary, self, context, TRUE]; TEditSelection.FixUpAfterDisplay[feedback, self, context, FALSE] }; IF NOT tdd.dirty THEN SELECT tdd.scroll FROM no => NULL; endofdoc => { TRUSTED {Process.Detach[FORK AutoScroll[self, tdd.scrollGlitch, TRUE, primary]]}; tdd.scroll _ no }; endofsel => { sel: TEditDocument.Selection = SELECT tdd.scrollSelectionId FROM primary => TEditSelection.pSel, secondary => TEditSelection.sSel, feedback => TEditSelection.fSel, ENDCASE => ERROR; IF TEditSelection.SelectionRoot[sel]=tdd.text THEN TRUSTED {Process.Detach[FORK AutoScroll[self, tdd.scrollGlitch, FALSE, tdd.scrollSelectionId]]} }; ENDCASE => ERROR; Cleanup[]; END; END; -- of unwind and PaintProc AutoScroll: PROC [ self: ViewerClasses.Viewer, tryToGlitch, toEndOfDoc: BOOL, id: TEditDocument.SelectionId] = { ENABLE ABORTED => GOTO Quit; tdd: TEditDocument.TEditDocumentData _ NARROW[self.data]; IF tdd=NIL THEN RETURN; TEditScrolling.AutoScroll[self, tryToGlitch, toEndOfDoc, id]; [] _ TEditDocument.SpinAndLock[tdd, "TEditDisplayImplAutoScroll"]; IF ~tdd.dirty THEN tdd.scroll _ no; TEditDocument.Unlock[tdd]; TEditTouchup.RefreshOver[]; EXITS Quit => NULL }; RefreshViewer: PROCEDURE [viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData, dc: Imager.Context, displayClear, refresh: BOOL, lock: TEditLocks.LockRef, moveDownLines, moveDownDistance: INTEGER] = { n: INTEGER _ 0; start, end: INTEGER; lines: TEditDocument.LineTable _ tdd.lineTable; UNTIL n > lines.lastLine DO -- search for an invalid line IF lines[n].valid THEN { n _ n+1; LOOP }; start _ n; UNTIL n+1 > lines.lastLine DO IF lines[n+1].valid THEN EXIT; n _ n+1; ENDLOOP; end _ n; IF start > 0 AND lines[start].pos.node # lines[start-1].pos.node THEN { <<-- need to reinitialize the start pos; may have deleted start node>> lines[start].pos.where _ 0; IF (lines[start].pos.node _ TextNode.ForwardClipped[lines[start-1].pos.node,tdd.clipLevel].nx) = NIL THEN start _ start-1 --have deleted end of document-- }; n _ WhileInPosRange[viewer, tdd, dc, start, end, displayClear, refresh, lock, moveDownLines, moveDownDistance]; moveDownLines _ moveDownDistance _ 0; -- only do this the first time IF (refresh AND TEditLocks.WaitingForWrite[lock]) OR tdd.interrupt > 0 THEN { tdd.dirty _ TRUE; RETURN }; ENDLOOP; tdd.movedIn _ tdd.movedOut _ tdd.dirty _ FALSE; }; <> WhileInPosRange: PROCEDURE [ viewer: ViewerClasses.Viewer, tdd: TEditDocument.TEditDocumentData, dc: Imager.Context, start, end: INTEGER, displayClear, refresh: BOOLEAN, lock: TEditLocks.LockRef, moveDownLines, moveDownDistance: INTEGER ] RETURNS [line: INTEGER] = { lines: TEditDocument.LineTable _ tdd.lineTable; oldBottomY: INTEGER _ ComputeInitialBottomY[]; ComputeInitialBottomY: PROC RETURNS [y: INTEGER] = { <> y _ MAX[0, lines[start].yOffset - lines[start].ascent]; WHILE start>0 AND lines[start-1].yOffset + lines[start-1].descent > y DO start _ start-1; lines[start].valid _ FALSE; ENDLOOP; }; node: TextEdit.RefTextNode _ TextNode.NarrowToTextNode[lines[start].pos.node]; pos: TextEdit.Offset _ lines[start].pos.where; leading, topLeading, bottomLeading, nodeLeading: INTEGER _ 0; knowBottomLeading: BOOL _ start=0; -- otherwise need to get it y: INTEGER _ 0; nodeSize: TextEdit.Offset _ TextEdit.Size[node]; styleInit, yMatchRun: BOOL _ TRUE; yMatch, eraseToEnd: BOOL _ FALSE; lineInfo: TEditFormat.LineInfo _ TEditFormat.Allocate[]; nodeStyle: NodeStyle.Ref _ NodeStyle.Alloc[]; Cleanup: PROC = {lineInfo.Release[]; lineInfo _ NIL; nodeStyle.Free[]; nodeStyle _ NIL}; styleInfoNode: TextNode.Ref; -- node for which we were last called to get style info <> GetStyleInfo: PROC [node: TextNode.Ref] = { IF node = styleInfoNode THEN RETURN; -- already have the style info styleInfoNode _ node; IF NOT knowBottomLeading AND pos=0 THEN { -- get it from previous node NodeStyle.ApplyAll[nodeStyle, lines[line-1].pos.node]; bottomLeading _ NodeStyle.GetBottomLeadingI[nodeStyle] }; NodeStyle.ApplyAll[nodeStyle, node]; leading _ NodeStyle.GetLeadingI[nodeStyle]; topLeading _ MAX[bottomLeading, NodeStyle.GetTopLeadingI[nodeStyle]]; <> nodeLeading _ MAX[bottomLeading, topLeading]; -- use previous value of bottomLeading bottomLeading _ NodeStyle.GetBottomLeadingI[nodeStyle]; knowBottomLeading _ TRUE; }; FullyVisible: PROC RETURNS [BOOL] = INLINE { IF NOT checkedVisible THEN { fullyVisible _ dc.TestRectangle[x: 0, y: 0, w: viewer.cw, h: viewer.ch] = visible; checkedVisible _ TRUE; }; RETURN [fullyVisible] }; checkedVisible, fullyVisible: BOOL _ FALSE; found: INTEGER _ 0; -- set by FindBelow TryMoveUp: PROC RETURNS [ok: BOOL] = { <> to, from, num, dist, bottom, diff: INTEGER; dirtyScanlinesInFirstTextline: NAT _ 0; FindBelow: PROC RETURNS [ok: BOOL] = INLINE { -- returns true if finds the desired line <> FOR n: INTEGER IN [line..lines.lastLine] DO IF lines[n].valid AND lines[n].pos=[node,pos] THEN { found _ n; RETURN [lines[n].yOffset-lines[n].ascent > oldBottomY] }; ENDLOOP; RETURN [FALSE] }; IF NOT FullyVisible[] OR NOT FindBelow[] THEN RETURN [FALSE]; IF found=lines.lastLine AND lines[found].yOffset+lines[found].descent >= viewer.ch THEN RETURN [FALSE]; -- bottom is clipped IF found>0 AND ( dirtyScanlinesInFirstTextline _ MAX[0, lines[found-1].yOffset+lines[found-1].descent - lines[found].yOffset-lines[found].ascent ] ) > 0 THEN lines[found].valid _ FALSE; to _ y-lines[found].ascent + dirtyScanlinesInFirstTextline; <> from _ lines[found].yOffset-lines[found].ascent + dirtyScanlinesInFirstTextline; <> IF to < 0 THEN { -- top of line is clipped from _ from-to; -- move source down by amount to be clipped to _ 0 }; dist _ from-to; -- the distance to move IF dist < 0 THEN RETURN [FALSE]; -- skip this bizarre case bottom _ MIN[viewer.ch, lines.lastY]; num _ bottom-from; -- number of scan lines to move IF num <= 0 THEN RETURN [FALSE]; ClearLine[oldBottomY, to]; -- clear gap above destination MoveScanLines[to, from, num]; -- move the good scan lines up ClearLine[bottom-dist, bottom]; -- clear the space at the bottom oldBottomY _ bottom; lines.lastY _ lines.lastY-dist; -- lastY tells last used scan line in viewer diff _ found-line; -- number of lines skipped IF diff # 0 THEN { -- update the line table info FOR n: INTEGER IN [line..lines.lastLine-diff] DO lines[n] _ lines[n+diff]; -- move all the info up to new location ENDLOOP; ClearEntries[lines.lastLine-diff+1, lines.lastLine]; -- these lines no longer in use lines.lastLine _ lines.lastLine-diff; end _ end-diff }; FOR n: INTEGER IN [line..lines.lastLine] DO -- adjust the baselines lines[n].yOffset _ lines[n].yOffset-dist; ENDLOOP; lines[lines.lastLine].valid _ FALSE; <> RETURN [TRUE] }; ClearEntries: PROC [from, to: INTEGER] = { FOR n: INTEGER IN [from..to] DO lines[n] _ [ pos: [NIL, 0], valid: TRUE, yOffset: 0, xOffset: 0, width: 0, ascent: 0, descent: 0]; ENDLOOP }; MoveScanLines: PROC [to, from, num: INTEGER] = BEGIN bounds: Imager.IntRectangle _ Imager.GetViewBounds[dc]; yBase: INTEGER _ bounds.h + bounds.y - num; IF num > 0 THEN Imager.MoveSurfaceRectangle[context: dc, source: [x: bounds.x, y: yBase - from, w: bounds.w, h: num], dest: [x: bounds.x, y: yBase - to] ]; END; TryMoveDown: PROC [nchars: INTEGER, end: TEditDocument.LineBreak] = { <> <> next: TextNode.Location; from, lead, dist: INTEGER; IF NOT FullyVisible[] OR NOT lines[line].valid THEN RETURN; from _ lines[line].yOffset-lines[line].ascent; IF oldBottomY > from THEN RETURN; -- overwritten it already next _ IF end=eon THEN [NextNode[],0] ELSE [node,pos+nchars]; IF lines[line].pos # next THEN RETURN; IF end=eon THEN { -- need to get leading info for the next node GetStyleInfo[next.node]; lead _ nodeLeading } ELSE lead _ leading; dist _ y+lead-lines[line].yOffset; -- how far down to move it MoveLinesDown[line, 1, from, dist] }; MoveLinesDown: PROC [startLine, numLines, from, dist: INTEGER] = { to, num, bottom: INTEGER; visible: BOOL _ FALSE; dirtyScanlinesInFirstTextline: INTEGER; IF dist <= 0 THEN RETURN; -- skip this weird case to _ from+dist; bottom _ MIN[viewer.ch, lines.lastY+dist]; -- new bottom after blt num _ bottom-to; -- the number of scan lines to blt IF num <= 0 THEN RETURN; -- stuff to move down won't be visible MoveScanLines[from+dist, from, num]; IF startLine > 0 AND ( dirtyScanlinesInFirstTextline _ lines[startLine-1].yOffset+lines[startLine-1].descent - lines[startLine].yOffset-lines[startLine].ascent ) > 0 THEN { <> lines[startLine].valid _ FALSE; ClearLine[from, to + dirtyScanlinesInFirstTextline]; } ELSE ClearLine[from, to]; -- clear the gap FOR n: INTEGER DECREASING IN [startLine..lines.lastLine] DO newOffset: INTEGER; IF n+numLines >= lines.maxLines THEN LOOP; -- avoid bounds fault newOffset _ lines[n].yOffset+dist; -- the new baseline IF newOffset-lines[n].ascent > viewer.ch THEN LOOP; -- off the bottom of viewer IF NOT visible AND newOffset+lines[n].descent <= viewer.ch THEN { -- first fully visible line visible _ TRUE; lines.lastLine _ n+numLines; lines[n].valid _ FALSE; -- force repaint at bottom of viewer lines.lastY _ newOffset+lines[n].descent; ClearLine[lines.lastY, viewer.ch] }; lines[n+numLines] _ lines[n]; -- move all the info down by numLines lines[n+numLines].yOffset _ newOffset; -- adjust the baseline IF NOT visible THEN { -- line must be partly visible at bottom of screen lines.lastLine _ n+numLines; lines.lastY _ newOffset+lines[n].descent; lines[n+numLines].valid _ FALSE }; ENDLOOP; }; level: INTEGER _ 0; -- in case we are doing level clipping maxLevel: INTEGER = tdd.clipLevel; levelClipping: BOOL = maxLevel < TEditDocument.maxClip; last, next: TextNode.Ref; NextNode: PROC RETURNS [TextNode.Ref] = { -- returns next node to display <> IF node=last THEN RETURN [next]; -- 1 entry cache last _ node; IF levelClipping THEN [next,level] _ TextNode.ForwardClipped[node,maxLevel,level] ELSE next _ TextNode.StepForward[node]; RETURN [next] }; ClearLine: PROC [oldBottomY, newBottomY: INTEGER] = { IF oldBottomY >= newBottomY THEN RETURN; Imager.SetColor[dc, Imager.white]; Imager.MaskRectangle[dc, 0, viewer.ch-newBottomY, viewer.cw, newBottomY-oldBottomY]; Imager.SetColor[dc, Imager.black]; }; IF (line _ start) > lines.lastLine OR end < 0 THEN <> {Cleanup[]; RETURN}; IF moveDownLines > 0 AND FullyVisible[] THEN { <> MoveLinesDown[0, moveDownLines, 0, moveDownDistance]; }; <> <<(1) valid,>> <<(2) past the range we were told to paint,>> <<(3) matches the line table offsets (i.e. no ripple going forward), and>> <<(4) matches in the Y coord.>> <> UNTIL line>=lines.maxLines OR ( line>end AND lines[line].valid AND lines[line].pos=[node, pos] AND yMatch AND line<=lines.lastLine ) DO IF (refresh AND TEditLocks.WaitingForWrite[lock]) OR tdd.interrupt > 0 THEN { <> lines.lastLine _ MAX[line, lines.lastLine]; lines[line].pos _ [node, pos]; lines[line].valid _ FALSE; Cleanup[]; RETURN }; IF pos >= nodeSize THEN { <> DO -- stay in this loop until have a non-empty node or have reached end of document IF (pos > 0 AND (line=0 OR lines[line-1].end=cr)) OR (nodeSize=0 AND node#tdd.text) THEN { <> <> GetStyleInfo[node]; IF line=start THEN y _ IF line=0 THEN 0 ELSE lines[line-1].yOffset; -- initialize y y _ y + (IF line=0 THEN NodeStyle.GetTopIndentI[nodeStyle] ELSE IF pos=0 THEN nodeLeading ELSE leading); IF y>=viewer.ch AND line>0 THEN {node _ NIL; EXIT}; <> IF NOT displayClear AND TryMoveUp[] THEN NULL ELSE { indent: INTEGER _ IF pos=0 THEN nodeStyle.GetFirstIndentI[] ELSE nodeStyle.GetBodyIndentI[]; lineStart: INTEGER _ nodeStyle.GetLeftIndentI[] + indent; lineWidth: INTEGER _ viewer.cw - nodeStyle.GetRightIndentI[] - indent; xoffset: INTEGER _ SELECT nodeStyle.GetLineFormatting[] FROM FlushLeft, Justified => lineStart, FlushRight => lineStart + lineWidth, Centered => lineStart + lineWidth/2, ENDCASE => ERROR; IF NOT displayClear THEN TryMoveDown[0, eon]; <> lines[line] _ [ valid: TRUE, pos: [node, pos], nChars: 0, end: eon, yOffset: y, xOffset: xoffset, width: 0, ascent: 1, descent: 0 ]; }; line _ line+1; }; pos _ 0; <> node _ TextNode.NarrowToTextNode[NextNode[]]; IF node=NIL OR (nodeSize_TextEdit.Size[node]) > 0 THEN EXIT; ENDLOOP; IF NOT displayClear THEN {ClearLine[oldBottomY, y]; oldBottomY _ y}; IF node#NIL THEN {styleInit _ TRUE; LOOP} -- reapply termination test ELSE {eraseToEnd _ TRUE; EXIT}; -- off end of text } ELSE IF styleInit THEN {GetStyleInfo[node]; styleInit _ FALSE}; -- first time for this node y --baseline-- _ IF line=0 THEN NodeStyle.GetTopIndentI[nodeStyle] ELSE (IF line#start THEN y ELSE lines[line-1].yOffset) -- baseline -- + (IF pos=0 THEN nodeLeading ELSE leading) -- leading --; IF y>=viewer.ch AND line>0 THEN {eraseToEnd _ TRUE; EXIT}; <> yMatch _ y=lines[line].yOffset; <> oldBottomY _ IF line=0 THEN 0 ELSE lines[line-1].yOffset+lines[line-1].descent; <> IF NOT displayClear AND TryMoveUp[] THEN NULL ELSE { TEditFormat.FormatLine[ self: lineInfo, tdd: tdd, node: node, startOffset: pos, nodeStyle: nodeStyle, lineWidth: Scaled.FromInt[viewer.cw], forPaint: TRUE ]; IF NOT displayClear THEN TryMoveDown[lineInfo.nChars, lineInfo.break]; <> Imager.SetXY[dc, [lineInfo.xOffset.Float[], viewer.ch-y]]; IF NOT displayClear THEN ClearLine[oldBottomY, y-lineInfo.ymin]; oldBottomY _ y-lineInfo.ymin; TEditFormat.Paint[lineInfo, dc]; lines[line] _ [ valid: TRUE, pos: [node, pos], nChars: lineInfo.nChars, end: lineInfo.break, yOffset: y, xOffset: lineInfo.xOffset.Round[], width: lineInfo.xmax-lineInfo.xmin, ascent: MIN[511, MAX[0, lineInfo.ymax]], descent: MIN[127, MAX[0, -lineInfo.ymin]] ]; }; pos _ pos + lines[line].nChars; line _ line + 1; ENDLOOP; <> IF eraseToEnd OR line>=lines.maxLines THEN { <> bottomOfNewLines: INTEGER _ IF line=0 THEN 0 ELSE lines[line-1].yOffset+lines[line-1].descent; lines.lastLine _ MAX[line,1]-1; -- set end mark ClearEntries[lines.lastLine+1, lines.maxLines-1]; IF NOT displayClear AND bottomOfNewLines < lines.lastY THEN -- white out old lines ClearLine[bottomOfNewLines, lines.lastY]; lines.lastY _ bottomOfNewLines; } ELSE { <> IF y>viewer.ch OR displayClear THEN ERROR; lines.lastLine _ MAX[lines.lastLine, MAX[line,1]-1]; lines.lastY _ MAX[lines.lastY, y+lines[lines.lastLine].descent]; }; Cleanup[]; }; END. <>