TEditFormatImpl.mesa, Edited by McGregor, January 14, 1983 11:24 am
Edited by Paxton, December 20, 1982 4:38 pm
Edited by Plass, August 12, 1983 10:52 am
Edited by Wyatt, October 24, 1983 4:19 pm
DIRECTORY
Ascii, Font, Imager, ImagerTransform, LooksReader, NameSymbolTable, NodeStyle, Process, Real, RefText, Rope, RopeReader, TEditDocument, TEditFormat, TextEdit, TextNode, TextLooks, Scaled;
TEditFormatImpl: CEDAR MONITOR
IMPORTS Font, Imager, ImagerTransform, LooksReader, NameSymbolTable, NodeStyle, Process, Real, RefText, Rope, RopeReader, TEditDocument, TextEdit, TextNode, Scaled
EXPORTS TEditFormat
= BEGIN OPEN TEditFormat;
FloorI: PROCEDURE [real: REAL] RETURNS [INTEGER] = INLINE {
RETURN[Real.Fix[real-FIRST[INTEGER]]+FIRST[INTEGER]];
};
CeilingI: PROCEDURE [real: REAL] RETURNS [integer: INTEGER] = INLINE {
RETURN[Real.Fix[real-LAST[INTEGER]]+LAST[INTEGER]];
};
scratchLineInfo1, scratchLineInfo2: LineInfo ← NIL;
Allocate: PUBLIC ENTRY PROC RETURNS [lineInfo: LineInfo] = {
IF scratchLineInfo1 # NIL THEN {lineInfo ← scratchLineInfo1; scratchLineInfo1 ← NIL}
ELSE IF scratchLineInfo2 # NIL THEN {lineInfo ← scratchLineInfo2; scratchLineInfo2 ← NIL}
ELSE {
lineInfo ← NEW[LineInfoRec];
lineInfo.charInfo ← NEW[CharInfoRec[125]];
lineInfo.formatInfo ← NEW[FormatInfoRec[12]];
lineInfo.positionInfo ← NEW[PositionInfoRec[126]];
};
};
Release: PUBLIC ENTRY PROC [lineInfo: LineInfo] = {
f: FormatInfo ← lineInfo.formatInfo;
WHILE f.length>0 DO
Avoid having unneeded REFs hanging around.
f.length ← f.length-1;
f[f.length].font ← NIL;
f[f.length].tab ← NIL;
ENDLOOP;
IF scratchLineInfo1 = NIL THEN {scratchLineInfo1 ← lineInfo}
ELSE scratchLineInfo2 ← lineInfo;
};
ExpandChars: PROC [lineInfo: LineInfo] = {
Expands the charInfo and positionInfo sequences by about 34%
oldCharLimit: NAT ← lineInfo.charInfo.maxLength;
charLimit: NATMIN[oldCharLimit + oldCharLimit/3 + 1, LAST[CharNumber]-1];
newCharInfo: CharInfo ← NEW[CharInfoRec[charLimit]];
newPositionInfo: PositionInfo ← NEW[PositionInfoRec[charLimit+1]];
IF charLimit <= oldCharLimit THEN ERROR TEditDocument.fatalTiogaError;
FOR i: NAT IN [0..oldCharLimit) DO
newCharInfo[i] ← lineInfo.charInfo[i];
newPositionInfo[i] ← lineInfo.positionInfo[i];
ENDLOOP;
newPositionInfo[oldCharLimit] ← lineInfo.positionInfo[oldCharLimit];
lineInfo.charInfo ← newCharInfo;
lineInfo.positionInfo ← newPositionInfo;
};
ExpandFormats: PROC [lineInfo: LineInfo] = {
Expands the formatInfo sequence by about 34%
oldFormatLimit: NAT ← lineInfo.formatInfo.maxLength;
formatLimit: NATMIN[oldFormatLimit + oldFormatLimit/3 + 1, LAST[FormatNumber]];
newFormatInfo: FormatInfo ← NEW[FormatInfoRec[formatLimit]];
IF formatLimit <= oldFormatLimit THEN ERROR TEditDocument.fatalTiogaError;
FOR i: NAT IN [0..oldFormatLimit) DO
newFormatInfo[i] ← lineInfo.formatInfo[i];
ENDLOOP;
lineInfo.formatInfo ← newFormatInfo;
};
This scratch storage will will normally be allocated only once, but in exceptional circumstances a new batch may be needed;
ScratchRefs: TYPE = RECORD [
ropeReader: RopeReader.Ref,
looksReader: LooksReader.Ref,
charStyle: NodeStyle.Ref,
tabStyle: NodeStyle.Ref
];
scratchRefsReleased: CONDITION;
scratchRefsAvailable: BOOLEANFALSE;
scratchRefs: ScratchRefs;
scratchRefsAllocCount: INT ← InitScratchRefs[333];
scratchRefsWaitCount: INT ← 0; -- stats only
InitScratchRefs: ENTRY PROC [timeout: Process.Milliseconds] RETURNS [initialCount: INT ← 1] = TRUSTED {
scratchRefs ← [RopeReader.Create[], LooksReader.Create[], NodeStyle.Create[], NodeStyle.Create[]];
scratchRefsAvailable ← TRUE;
Process.InitializeCondition[@scratchRefsReleased, Process.MsecToTicks[timeout]];
};
AllocScratchRefs: ENTRY PROC RETURNS [allocated: ScratchRefs] = {
Uses the cached readers if they are available or become available after a short wait; otherwise allocates a new batch.
IF NOT scratchRefsAvailable THEN {
scratchRefsWaitCount ← scratchRefsWaitCount + 1;
WAIT scratchRefsReleased
};
IF scratchRefsAvailable THEN {scratchRefsAvailable ← FALSE; allocated ← scratchRefs; scratchRefs ← [NIL, NIL, NIL, NIL]}
ELSE {allocated ← [RopeReader.Create[], LooksReader.Create[], NodeStyle.Create[], NodeStyle.Create[]]; scratchRefsAllocCount ← scratchRefsAllocCount + 1};
};
ReleaseScratchRefs: ENTRY PROC [allocated: ScratchRefs] = {
IF NOT scratchRefsAvailable THEN {
scratchRefs ← allocated;
scratchRefsAvailable ← TRUE;
NOTIFY scratchRefsReleased;
};
};
GetFont: PROC [style: NodeStyle.Ref] RETURNS [font: Font.FONT] = TRUSTED {
face: NodeStyle.FontFace ← NodeStyle.GetFontFace[style];
fontName: REF TEXT ← RefText.ObtainScratch[100];
s: REF TEXT ← RefText.ObtainScratch[60];
size: REAL ← NodeStyle.GetFontSize[style];
NameSymbolTable.FromName[NodeStyle.GetFontFamily[style], s];
RefText.Append[to: fontName, from: "Xerox/PressFonts/"];
RefText.Append[to: fontName, from: s];
s.length ← 0;
RefText.Append[to: s, from: "/MRR"];
IF face=Bold OR face=BoldItalic THEN s[1] ← 'B;
IF face=Italic OR face=BoldItalic THEN s[2] ← 'I;
RefText.Append[to: fontName, from: s];
font ← Font.Create[fontName: fontName, transformation: ImagerTransform.Scale[size, size], deviceType: $Screen];
RefText.ReleaseScratch[fontName];
RefText.ReleaseScratch[s];
};
FormatLine: PUBLIC PROC [
self: LineInfo,
tdd: TEditDocumentData,
node: TextEdit.RefTextNode,
startOffset: TextEdit.Offset,
nodeStyle: NodeStyle.Ref,
lineWidth: Scaled.Value,
forPaint: BOOLEANFALSE,
doLigsAndKern: BOOLEANFALSE
] = {scratch: ScratchRefs ← AllocScratchRefs[]; {OPEN scratch;
Formatting constants for the line:
realExtraIndent: REAL = (
IF startOffset = 0 THEN nodeStyle.GetFirstIndent[]
ELSE nodeStyle.GetBodyIndent[]
);
extraIndent: Scaled.Value = Scaled.FromReal[realExtraIndent];
leftIndent: Scaled.Value = Scaled.FromReal[
nodeStyle.GetLeftIndent[] + realExtraIndent
];
rightIndent: Scaled.Value = Scaled.FromReal[
nodeStyle.GetRightIndent[]
];
trimmedLineWidth: Scaled.Value = lineWidth.MINUS[leftIndent].MINUS[rightIndent];
Basic formatting variables:
fontWidths: Font.WidthArray;
char: CHAR;
curFormatNumber: FormatNumber;
width: Scaled.Value;
prevLooks: TextLooks.Looks ← TextLooks.allLooks;
looks: TextLooks.Looks ← TextLooks.noLooks;
breakIndex: CharNumber ← 0;
endX, breakX: Scaled.Value ← Scaled.zero;
lineFormatting: NodeStyle.LineFormatting;
highWaterChars: NAT ← self.charInfo.maxLength;
State info for formatting tabs:
tabStop: NodeStyle.TabStop;
tabNumber: NAT ← 0; -- tells us which tab stop to use
tabLoc, tabStart, tabWidth, tabTextStart: CARDINAL;
prevTabLooks: TextLooks.Looks ← TextLooks.allLooks;
tabCharLooks: TextLooks.Looks ← TextLooks.noLooks;
tabAlignment: NodeStyle.TabAlign;
tabAlignmentChar: CHAR;
doingTab: BOOLFALSE;
Level clipping info:
level: INTEGER ← 0;
maxLevel: INTEGER = tdd.clipLevel;
levelClipping: BOOL ← maxLevel < TEditDocument.maxClip;
NewFormattingLooks: PROC = {
realVShift: REAL;
new: FormatInfoEntry;
First a quick look through the first few formats to see if it is a common one.
FOR f: FormatNumber IN [0..MIN[5, self.formatInfo.length]) DO
IF self.formatInfo[f].looks = looks THEN {
curFormatNumber ← f;
fontWidths ← self.formatInfo[f].font.GetWidthArray[minimum: Scaled.zero, undefined: Scaled.zero];
prevLooks ← looks;
RETURN
};
ENDLOOP;
NodeStyle.Copy[dest: charStyle, source: tabStyle];
IF looks#TextLooks.noLooks THEN NodeStyle.ApplyLooks[charStyle, looks];
new.font ← GetFont[charStyle];
new.looks ← looks;
new.underlining ← NodeStyle.GetUnderlining[charStyle];
new.strikeout ← NodeStyle.GetStrikeout[charStyle];
realVShift ← NodeStyle.GetVShift[charStyle];
new.vShift ← Scaled.FromReal[realVShift];
self.ymax ← MAX[self.ymax, CeilingI[new.font.fontBoundingBox.ymax + realVShift]];
self.ymin ← MIN[self.ymin, FloorI[new.font.fontBoundingBox.ymin + realVShift]];
fontWidths ← new.font.GetWidthArray[minimum: Scaled.zero, undefined: Scaled.zero];
prevLooks ← looks;
curFormatNumber ← self.formatInfo.length;
IF curFormatNumber>=self.formatInfo.maxLength THEN ExpandFormats[self];
self.formatInfo[curFormatNumber] ← new;
self.formatInfo.length ← self.formatInfo.length + 1;
};
self.startPos ← [node, startOffset];
NodeStyle.Copy[dest: tabStyle, source: nodeStyle];
ropeReader.SetPosition[TextEdit.GetRope[node], startOffset];
looksReader.SetPosition[TextEdit.GetRuns[node], startOffset];
self.ymax ← FIRST[INTEGER];
self.ymin ← LAST[INTEGER];
self.break ← wrap;
self.nChars ← LAST[CharNumber];
self.formatInfo.length ← 0;
FOR curIndex: CharNumber IN [0..LAST[CharNumber]) DO
char ← RopeReader.Get[ropeReader ! RopeReader.ReadOffEnd => {self.break ← eon; self.nChars ← curIndex; EXIT}];
IF curIndex>=highWaterChars THEN {ExpandChars[self]; highWaterChars ← self.charInfo.maxLength};
IF (looks←LooksReader.Get[looksReader])#prevLooks THEN NewFormattingLooks[];
width ← fontWidths[char];
SELECT char FROM
Ascii.SP => {breakIndex ← curIndex + 1; breakX ← endX.PLUS[width]};
Ascii.TAB => {
};
ENDCASE => {
};
self.charInfo[curIndex] ← [char: char, formatNumber: curFormatNumber, width: width];
self.positionInfo[curIndex] ← endX.Round[];
endX ← endX.PLUS[width];
IF char=Ascii.CR THEN {self.break ← cr; self.nChars ← curIndex + 1; EXIT};
IF endX.GREATER[trimmedLineWidth] THEN {
IF breakIndex>0 THEN {self.nChars ← breakIndex; endX ← breakX}
ELSE IF curIndex>0 THEN {self.nChars ← curIndex; endX ← endX.MINUS[width]}
ELSE {self.nChars ← 1};
EXIT;
};
ENDLOOP;
self.positionInfo[self.nChars] ← self.xmax ← endX.Round[];
self.xmin ← 0;
self.xOffset ← SELECT nodeStyle.GetLineFormatting[] FROM
FlushLeft, Justified => leftIndent,
FlushRight => leftIndent.PLUS[trimmedLineWidth].MINUS[endX],
Centered => leftIndent.PLUS[trimmedLineWidth.MINUS[endX].Scale[-1]],
ENDCASE => ERROR;
self.nextPos ← self.startPos;
self.nextPos.where ← self.nextPos.where + self.nChars;
};
FOR i: NAT IN [1..self.nChars] DO
IF self.positionInfo[i]<=self.positionInfo[i-1] THEN self.positionInfo[i]←self.positionInfo[i-1]+1;
ENDLOOP;
ReleaseScratchRefs[scratch];
};
Paint: PUBLIC PROC [lineInfo: LineInfo, context: Imager.Context, whiten: BOOLEAN] = {
i: NAT ← 0;
Whiten: PROC = {
Imager.Move[context];
Imager.MaskRectangle[context, lineInfo.xmin, lineInfo.ymin, lineInfo.xmax-lineInfo.xmin, lineInfo.ymax-lineInfo.ymin];
};
IF whiten THEN Imager.DoSaveAll[context, Whiten];
WHILE i<lineInfo.nChars DO
formatNumber: FormatNumber ← lineInfo.charInfo[i].formatNumber;
len: NAT ← 0;
format: FormatInfoEntry ← lineInfo.formatInfo[formatNumber];
rope: Rope.ROPE ← TextEdit.GetRope[TextNode.NarrowToTextNode[lineInfo.startPos.node]];
ropeStart: INTMIN[MAX[lineInfo.startPos.where, 0], rope.Length];
vShift: REAL ← format.vShift.Float[];
WHILE i+len<lineInfo.nChars AND lineInfo.charInfo[i+len].formatNumber = formatNumber DO
len ← len + 1;
ENDLOOP;
Imager.SetColor[context, Imager.black];
IF vShift#0 THEN {
Imager.SetYRel[context, vShift];
};
Imager.ShowCharacters[
context: context,
characters: rope,
font: lineInfo.formatInfo[formatNumber].font,
start: i+ropeStart,
length: len
];
IF vShift#0 THEN {
Imager.SetYRel[context, -vShift];
};
i ← i + len;
ENDLOOP;
};
Resolve: PUBLIC PROC [lineInfo: LineInfo, x: INTEGER]
RETURNS [loc: TextNode.Location, xmin, width: INTEGER, rightOfLine: BOOLEAN] = {
xOffset: INTEGER ← lineInfo.xOffset.Round[];
xOfi: INTEGER ← lineInfo.positionInfo[0];
x ← x - xOffset;
FOR i: NAT IN [0..lineInfo.nChars) DO
xOfiPlusOne: INTEGER ← lineInfo.positionInfo[i+1];
IF i = lineInfo.nChars - 1 OR x<xOfiPlusOne THEN RETURN[
loc: [lineInfo.startPos.node, lineInfo.startPos.where+i],
xmin: xOfi + xOffset,
width: xOfiPlusOne - xOfi,
rightOfLine: i = lineInfo.nChars - 1 AND xOfi+xOfiPlusOne <= x+x
];
xOfi ← xOfiPlusOne;
ENDLOOP;
RETURN[loc: lineInfo.startPos, xmin: xOffset, width: 0, rightOfLine: TRUE];
};
CharPosition: PUBLIC PROC [lineInfo: LineInfo, offset: TextEdit.Offset]
RETURNS [x, width: INTEGER] = {
IF offset < lineInfo.startPos.where THEN {x ← FIRST[INTEGER]; width ← 0}
ELSE {
i: NAT ← offset - lineInfo.startPos.where;
IF i>lineInfo.nChars THEN {x ← LAST[INTEGER]; width ← 0}
ELSE IF i=lineInfo.nChars THEN {x ← lineInfo.positionInfo[i] + lineInfo.xOffset.Round[]; width ← 0}
ELSE {
x ← lineInfo.positionInfo[i];
width ← lineInfo.positionInfo[i+1] - x;
x ← x + lineInfo.xOffset.Round[];
};
};
};
END.
Michael Plass, August 9, 1983 4:42 pm: Converted to Imager.