<> <> <> <> <> <<>> DIRECTORY Carets USING [StopCaretsInViewer], EditSpan USING [afterMoved1, afterMoved2], Imager USING [black, Color, Context, MaskRectangleI, SetColor, SetXY, white], ImagerBackdoor USING [MoveViewRectangle, TestViewRectangle], ImagerOps USING [DoWithBuffer], InputFocus USING [GetInputFocus], NodeStyle USING [GetBottomLeadingI, GetLeadingI, GetTopIndentI, GetTopLeadingI, Ref], NodeStyleOps USING [Alloc, ApplyAll, Free, OfStyle], RopeReader USING [Backwards, FreeRopeReader, GetIndex, GetRopeReader, Ref, SetPosition], Scaled USING [Float, FromInt, Round], TEditCompile USING [minAvgLineLeading], TEditDocument USING [LineBreak, LineTable, LineTableRec, LineRec, maxClip, Selection, SelectionId, SelectionRec, TEditDocumentData], TEditFormat USING [Allocate, FormatLine, LineInfo, Paint, Release], TEditImpl USING [], TEditLocks USING [LockDocAndTdd, LockRef, UnlockDocAndTdd, WaitingForWrite], TEditScrolling USING [AutoScroll], TEditSelection USING [AdjustSelStates, FixUpAfterDisplay, fSel, InsertionPoint, pSel, SelectionRoot, ShowSelection, sSel, TakeDownForRedisplay, TakeSelectionDown], TEditSelectionPrivate USING [InvalidateLineCache], TEditTouchup USING [fullUpdate, PreScrollDownRec, refresh, RefreshOver], TextEdit USING [Offset, Size], TextNode USING [FirstChild, ForwardClipped, Level, Location, Ref, RefTextNode, Root, StepForward], ViewerClasses USING [PaintProc, PaintRectangle, Viewer], ViewerOps USING [FetchProp, UserToScreenCoords]; TEditDisplayImpl: CEDAR PROGRAM IMPORTS Carets, EditSpan, Imager, ImagerBackdoor, ImagerOps, InputFocus, NodeStyle, NodeStyleOps, Scaled, TextEdit, TextNode, RopeReader, TEditFormat, TEditLocks, TEditScrolling, TEditSelection, TEditSelectionPrivate, TEditTouchup, ViewerOps EXPORTS TEditImpl = BEGIN LineTable: TYPE ~ TEditDocument.LineTable; TEditDocumentData: TYPE ~ TEditDocument.TEditDocumentData; Viewer: TYPE ~ ViewerClasses.Viewer; <> maxAscent: NAT ~ 512-1; maxDescent: NAT ~ 128-1; PaintTEditDocument: PUBLIC ViewerClasses.PaintProc = { WITH self.data SELECT FROM tdd: TEditDocumentData => { interrupt: BOOL _ clear OR whatChanged=TEditTouchup.fullUpdate OR whatChanged=NIL OR ISTYPE[whatChanged, ViewerClasses.PaintRectangle]; lock: TEditLocks.LockRef _ NIL; Cleanup: PROC = { IF lock # NIL THEN { TEditLocks.UnlockDocAndTdd[tdd]; TEditTouchup.RefreshOver[]; lock _ NIL; }; }; lock _ TEditLocks.LockDocAndTdd[tdd, "PaintTEditDocument", read, interrupt, TRUE]; tdd.invisible _ FALSE; -- or else we wouldn't have been called IF lock=NIL THEN { IF NOT interrupt THEN ERROR } ELSE { quit _ LockedPaint[self, context, whatChanged, clear, tdd, lock ! UNWIND => Cleanup[]]; }; Cleanup[]; }; ENDCASE => NULL; }; BiggerLineTable: PROC [oldTable: LineTable, newSize: INT] RETURNS [lineTable: LineTable] ~ { lineTable _ NEW[TEditDocument.LineTableRec[newSize] _ [lastLine: oldTable.lastLine, lastY: oldTable.lastY, lines: NULL]]; FOR n: INTEGER IN [0..oldTable.lastLine] DO -- copy old data lineTable[n] _ oldTable[n]; ENDLOOP; }; InvalidateLines: PROC [lineTable: LineTable, start, end: INT] ~ { FOR i: INT IN [start..end) DO lineTable[i].valid _ FALSE; ENDLOOP; }; Rect: TYPE ~ RECORD [xmin, ymin, w, h: INT]; Intersects: PROC [a, b: Rect] RETURNS [BOOL] ~ { ymin: INT ~ MAX[a.ymin, b.ymin]; ymax: INT ~ MIN[a.ymin+a.h, b.ymin+b.h]; xmin: INT ~ MAX[a.xmin, b.xmin]; xmax: INT ~ MIN[a.xmin+a.w, b.xmin+b.w]; RETURN [xmax > xmin AND ymax > ymin]; }; Inside: PROC [a, b: Rect] RETURNS [BOOL] ~ { RETURN [ a.ymin>=b.ymin AND a.ymin+a.h=b.xmin AND a.xmin+a.w tdd.lineTable.maxLines THEN { tdd.lineTable _ BiggerLineTable[tdd.lineTable, newSize]; }; IF redisplay THEN { IF clear THEN TEditSelection.AdjustSelStates[self]; IF NOT refreshing THEN InvalidateLines[tdd.lineTable, 0, tdd.lineTable.lastLine+1]; } ELSE { WITH whatChanged SELECT FROM x: REF TEditTouchup.PreScrollDownRec => { <> IF clear THEN ERROR; moveDownLines _ x.lines; moveDownDistance _ x.distance; refreshing _ redisplay _ TRUE; }; rect: ViewerClasses.PaintRectangle => { sx, sy: INT _ 0; [sx, sy] _ ViewerOps.UserToScreenCoords[self, 0, 0]; InvalidateLinesOutsideOfRectangle[tdd.lineTable, [rect.x-sx, rect.y-sy, rect.w, rect.h], self.ch]; tdd.lineTable[tdd.lineTable.lastLine].valid _ FALSE; <> refreshing _ redisplay _ TRUE; }; atom: ATOM => { SELECT atom 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 => ERROR; }; ENDCASE => ERROR; }; IF redisplay THEN { TEditSelection.TakeDownForRedisplay[primary, self, context]; TEditSelection.TakeDownForRedisplay[secondary, self, context]; TEditSelection.TakeDownForRedisplay[feedback, self, context]; IF typescript THEN CheckBottomVisible[self, tdd]; IF NOT tdd.lineTable[0].valid THEN CheckStartLocation[self, tdd]; TEditSelectionPrivate.InvalidateLineCache[]; -- since selection impl keeps accelerators RefreshViewer[self, tdd, context, clear, (refreshing AND NOT typescript), lock, moveDownLines, moveDownDistance]; }; SELECT whatChanged FROM $TakeDownPSel, $TakeDownSSel, $TakeDownFSel => {}; ENDCASE => SELECT TRUE FROM refreshing AND NOT typescript AND TEditLocks.WaitingForWrite[lock] => {}; ENDCASE => { <> TEditSelection.FixUpAfterDisplay[primary, self, context, ~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 => AutoScroll[self, tdd.scrollGlitch, TRUE, primary]; 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 AutoScroll[self, tdd.scrollGlitch, FALSE, tdd.scrollSelectionId]; }; ENDCASE => ERROR; }; CheckBottomVisible: PROC [self: Viewer, tdd: TEditDocumentData] ~ { IF self.ch < tdd.lineTable[tdd.lineTable.lastLine].yOffset THEN { <> delta: INT = 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; }; }; CheckStartLocation: PROC [self: Viewer, tdd: TEditDocumentData] ~ { startLoc: TextNode.Location _ [NIL, 0]; CheckLocNode: PROC [loc: TextNode.Location] RETURNS [BOOL] = { startLoc _ loc; RETURN [ loc.node # NIL AND TextNode.Root[loc.node] = tdd.text ] }; <> IF NOT CheckLocNode[tdd.lineTable[0].pos] THEN { IF (InputFocus.GetInputFocus[].owner = self AND CheckLocNode[TEditSelection.InsertionPoint[]]) OR CheckLocNode[EditSpan.afterMoved1] OR CheckLocNode[EditSpan.afterMoved2] THEN tdd.fixStart _ TRUE ELSE startLoc _ [TextNode.FirstChild[tdd.text], 0] }; IF startLoc.node # NIL THEN { size: TextEdit.Offset = TextEdit.Size[startLoc.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 startLoc.node # NIL AND startLoc.where > 0 THEN { <> rdr: RopeReader.Ref = RopeReader.GetRopeReader[]; minIndex: INT ~ MAX[INT[startLoc.where]-200, 0]; fallback: INT _ startLoc.where; foundBreak: INT _ startLoc.where; RopeReader.SetPosition[rdr, startLoc.node.rope, startLoc.where]; FOR i: INT DECREASING IN [minIndex..startLoc.where) DO char: CHAR ~ RopeReader.Backwards[rdr]; IF char = 15C THEN { foundBreak _ i+1; EXIT}; IF char = ' THEN { fallback _ i+1 }; IF i = 0 THEN { foundBreak _ i; EXIT}; ENDLOOP; startLoc.where _ MIN[foundBreak, fallback]; RopeReader.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]]; }; AutoScroll: PROC [self: Viewer, tryToGlitch, toEndOfDoc: BOOL, id: TEditDocument.SelectionId] = { ENABLE ABORTED => GOTO Quit; IF self # NIL THEN WITH self.data SELECT FROM tdd: TEditDocumentData => { TEditScrolling.AutoScroll[self, tryToGlitch, toEndOfDoc, id]; IF NOT tdd.dirty THEN tdd.scroll _ no; TEditTouchup.RefreshOver[]; }; ENDCASE => NULL; EXITS Quit => NULL }; RefreshViewer: PROC [viewer: Viewer, tdd: TEditDocumentData, dc: Imager.Context, displayClear, refresh: BOOL, lock: TEditLocks.LockRef, moveDownLines, moveDownDistance: INT] = { n: INTEGER _ 0; start, end: INTEGER _ 0; lines: TEditDocument.LineTable _ tdd.lineTable; Imager.SetColor[dc, Imager.black]; UNTIL n > lines.lastLine DO -- search for an invalid line IF lines[n].valid THEN n _ n+1 ELSE { 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 { <> 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: PROC [viewer: Viewer, tdd: TEditDocumentData, dc: Imager.Context, start, end: INTEGER, displayClear, refresh: BOOL, lock: TEditLocks.LockRef, moveDownLines, moveDownDistance: INT] RETURNS [line: INTEGER] = { lines: TEditDocument.LineTable _ tdd.lineTable; oldBottomY: INT _ MAX[INT[lines[start].yOffset] - lines[start].ascent, 0]; kindOfStyle: NodeStyleOps.OfStyle ~ IF ViewerOps.FetchProp[viewer, $StyleKind] = $Print THEN print ELSE screen; IF lines[start].ascent = maxAscent THEN oldBottomY _ 0; lines[start].valid _ FALSE; WHILE start>0 AND (lines[start-1].yOffset + lines[start-1].descent > oldBottomY OR lines[start-1].descent = maxDescent) DO start _ start-1; lines[start].valid _ FALSE; ENDLOOP; { node: TextNode.RefTextNode _ lines[start].pos.node; pos: TextEdit.Offset _ lines[start].pos.where; leading: INT _ 0; topLeading: INT _ 0; bottomLeading: INT _ 0; nodeLeading: INT _ 0; knowBottomLeading: BOOL _ (start = 0); -- otherwise need to get it y: INT _ 0; nodeSize: INT _ TextEdit.Size[node]; styleInit: BOOL _ TRUE; yMatchRun: BOOL _ TRUE; yMatch: BOOL _ FALSE; eraseToEnd: BOOL _ FALSE; lineInfo: TEditFormat.LineInfo _ TEditFormat.Allocate[]; nodeStyle: NodeStyle.Ref _ NodeStyleOps.Alloc[]; Cleanup: PROC = { TEditFormat.Release[lineInfo]; lineInfo _ NIL; NodeStyleOps.Free[nodeStyle]; nodeStyle _ NIL; }; styleInfoNode: TextNode.Ref _ NIL; -- 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 NodeStyleOps.ApplyAll[nodeStyle, lines[line-1].pos.node, kindOfStyle]; bottomLeading _ NodeStyle.GetBottomLeadingI[nodeStyle]; }; NodeStyleOps.ApplyAll[nodeStyle, node, kindOfStyle]; leading _ NodeStyle.GetLeadingI[nodeStyle]; topLeading _ MAX[bottomLeading, NodeStyle.GetTopLeadingI[nodeStyle]]; <> nodeLeading _ MAX[bottomLeading, topLeading]; <> bottomLeading _ NodeStyle.GetBottomLeadingI[nodeStyle]; knowBottomLeading _ TRUE; }; checkedVisible, fullyVisible: BOOL _ FALSE; FullyVisible: PROC RETURNS [BOOL] = { IF NOT checkedVisible THEN { fullyVisible _ ImagerBackdoor.TestViewRectangle[dc, 0, 0, viewer.cw, viewer.ch]=all; checkedVisible _ TRUE; }; RETURN [fullyVisible]; }; found: INT _ 0; -- set by FindBelow TryMoveUp: PROC RETURNS [ok: BOOL] = { <> to, from, num, dist, bottom: INT; diff: INTEGER; dirtyScanlinesInFirstTextline: NAT _ 0; FindBelow: PROC RETURNS [ok: BOOL] = { <> <> 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 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: FALSE, yOffset: 0, xOffset: 0, width: 0, ascent: 0, descent: 0]; ENDLOOP }; MoveScanLines: PROC [to, from, height: INT] = { IF height>0 THEN ImagerBackdoor.MoveViewRectangle[context: dc, width: viewer.cw, -- full width of viewer height: height, -- height of source fromX: 0, fromY: viewer.ch-from-height, toX: 0, toY: viewer.ch-to-height ]; }; TryMoveDown: PROC [nchars: INTEGER, end: TEditDocument.LineBreak] = { <> <> next: TextNode.Location; from, lead, dist: INT; 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, TRUE] }; MoveLinesDown: PROC [startLine, numLines: INTEGER, from, dist: INT, clear: BOOL] = { to: INT; num: INT; bottom: INT; visible: BOOL _ FALSE; dirtyScanlinesInFirstTextline: INT; IF dist <= 0 THEN RETURN; -- skip this weird case to _ from+dist; bottom _ MIN[lines.lastY+dist, viewer.ch]; -- 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: INT; 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 { <> 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 { <> 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] = { <> <> 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: INT] = { IF oldBottomY >= newBottomY THEN RETURN; Imager.SetColor[dc, Imager.white]; Imager.MaskRectangleI[dc, 0, viewer.ch-newBottomY, viewer.cw, newBottomY-oldBottomY]; Imager.SetColor[dc, Imager.black]; }; ContinueReason: TYPE ~ {dont, inRange, invalidLine, whitewashed}; continueReason: ContinueReason _ dont; WhyGoOn: PROC [lineNumber: INTEGER] RETURNS [cr: ContinueReason] ~ { SELECT TRUE FROM (line >= lines.maxLines) => cr _ dont; (line <= end) => cr _ inRange; NOT (line <= lines.lastLine AND lines[line].valid AND lines[line].pos=[node, pos] AND yMatch) => cr _ invalidLine; ((NOT displayClear) AND line > 0 AND (lines[line].ascent = maxAscent OR lines[line-1].descent = maxDescent OR lines[line].yOffset-lines[line].ascent < lines[line-1].yOffset+lines[line-1].descent)) => cr _ whitewashed; ENDCASE => cr _ dont; continueReason _ cr; }; <
> <<>> IF node = NIL OR (line _ start) > lines.lastLine OR end < 0 THEN { <> Cleanup[]; RETURN}; checkedVisible _ FALSE; IF moveDownLines > 0 AND FullyVisible[] THEN { <> MoveLinesDown[0, moveDownLines, 0, moveDownDistance, FALSE]; }; <> <<(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.>> <<(5) not partially whitened by paint of earlier lines.>> <> UNTIL WhyGoOn[line] = dont 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; }; GetStyleInfo[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 -- ); yMatch _ y=lines[line].yOffset; <> IF y>=viewer.ch AND line>0 THEN {eraseToEnd _ TRUE; EXIT}; <> oldBottomY _ IF line=0 THEN 0 ELSE lines[line-1].yOffset+lines[line-1].descent; <> IF displayClear OR NOT TryMoveUp[] THEN { backgroundColor: Imager.Color _ NIL; painter: PROC ~ { Imager.SetXY[dc, [lineInfo.xOffset.Float[], viewer.ch-y]]; SELECT TRUE FROM displayClear, backgroundColor#NIL => {}; ENDCASE => ClearLine[oldBottomY, y-lineInfo.ymin]; oldBottomY _ y-lineInfo.ymin; TEditFormat.Paint[lineInfo, dc]; }; TEditFormat.FormatLine[lineInfo: lineInfo, node: node, startOffset: pos, nodeStyle: nodeStyle, lineWidth: Scaled.FromInt[viewer.cw], kind: kindOfStyle]; IF NOT displayClear THEN TryMoveDown[lineInfo.nChars, lineInfo.break]; <> IF oldBottomY <= y-lineInfo.ymax THEN { backgroundColor _ Imager.white; IF NOT displayClear THEN ClearLine[oldBottomY, y-lineInfo.ymax]; oldBottomY _ y-lineInfo.ymax; }; IF displayClear THEN painter[] ELSE { ymin: INT _ viewer.ch-y+lineInfo.ymin; ymax: INT _ viewer.ch-y+lineInfo.ymax; IF continueReason = whitewashed THEN { ymin _ MAX[viewer.ch-oldBottomY, ymin]; backgroundColor _ NIL; }; IF ymin < ymax THEN { ImagerOps.DoWithBuffer[ context: dc, action: painter, x: 0, y: ymin, w: viewer.cw, h: ymax-ymin, backgroundColor: backgroundColor ]; }; }; 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[maxAscent, MAX[0, lineInfo.ymax]], descent: MIN[maxDescent, MAX[0, -lineInfo.ymin]]]; }; pos _ pos + lines[line].nChars; line _ line + 1; IF continueReason = whitewashed THEN EXIT; IF pos >= nodeSize AND lines[line-1].end#cr THEN { <> node _ NextNode[]; IF node = NIL THEN {eraseToEnd _ TRUE; EXIT}; nodeSize _ TextEdit.Size[node]; pos _ 0; }; ENDLOOP; <> IF eraseToEnd OR line>=lines.maxLines THEN { <> bottomOfNewLines: INT _ 0; IF line # 0 THEN { descent: NAT _ lines[line-1].descent; IF descent = maxDescent THEN bottomOfNewLines _ viewer.ch ELSE bottomOfNewLines _ lines[line-1].yOffset + 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 <> 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[y+lines[lines.lastLine].descent, lines.lastY]; }; Cleanup[]; }; }; END.