TiogaFormatImpl.mesa, Written by S. McGregor
Edited by McGregor, March 8, 1983 4:22 pm
Edited by Paxton, December 20, 1982 4:38 pm
Edited by Plass, March 2, 1983 11:44 am
DIRECTORY
Ascii, Graphics, LooksReader, NameSymbolTable, NodeStyle, Process, Real, RefText, Rope, RopeReader, TiogaDocument, TiogaFormat, TextEdit, TiogaNode, TiogaLooks, UnifiedFonts, Scaled;
TiogaFormatImpl: CEDAR MONITOR
IMPORTS Graphics, LooksReader, NameSymbolTable, NodeStyle, Process, Real, RefText, RopeReader, TiogaDocument, TextEdit, UnifiedFonts, Scaled
EXPORTS TiogaFormat
= BEGIN OPEN TiogaFormat;
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 TiogaDocument.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 TiogaDocument.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: UnifiedFonts.FONT] = TRUSTED {
face: NodeStyle.FontFace ← NodeStyle.GetFontFace[style];
fontName: REF TEXT ← RefText.ObtainScratch[100];
s: REF TEXT ← RefText.ObtainScratch[60];
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 ← UnifiedFonts.Create[fontName: fontName, transformation: [scale: NodeStyle.GetFontSize[style]], deviceType: $Screen];
RefText.ReleaseScratch[fontName];
RefText.ReleaseScratch[s];
};
breakpointCount, dummy: INT ← 0;
InitFalse: TYPE = BOOLEANFALSE;
breakOn: ARRAY [0..5] OF InitFalse;
SetBreak: PROC [b: NAT] = {
breakOn[b] ← TRUE;
breakpointCount ← breakpointCount + 1;
};
ClearBreaks: PROC = {
breakOn ← [FALSE, FALSE, FALSE, FALSE, FALSE, FALSE];
breakpointCount ← 0;
};
BreakPoint: PROC [b: NAT] = {
IF breakpointCount>0 THEN {
breakpointCount ← breakpointCount - 1;
IF breakOn[b] THEN
Safe place to set a breakpoint when not world-swap debugging
dummy ← dummy + 1;
};
};
FormatLine: PUBLIC PROC [
self: LineInfo,
tdd: TiogaDocumentData,
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: UnifiedFonts.WidthArray;
char: CHAR;
curFormatNumber: FormatNumber;
width: Scaled.Value;
prevLooks: TiogaLooks.Looks ← TiogaLooks.allLooks;
looks: TiogaLooks.Looks ← TiogaLooks.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: TiogaLooks.Looks ← TiogaLooks.allLooks;
tabCharLooks: TiogaLooks.Looks ← TiogaLooks.noLooks;
tabAlignment: NodeStyle.TabAlign;
tabAlignmentChar: CHAR;
doingTab: BOOLFALSE;
Level clipping info:
level: INTEGER ← 0;
maxLevel: INTEGER = tdd.clipLevel;
levelClipping: BOOL ← maxLevel < TiogaDocument.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#TiogaLooks.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];
BreakPoint[1];
};
Paint: PUBLIC PROC [lineInfo: LineInfo, context: Graphics.Context, whiten: BOOLEAN] = TRUSTED {
i: NAT ← 0;
zero: Scaled.Value ← Scaled.zero;
x, y: REAL;
BreakPoint[2];
[x, y] ← Graphics.GetCP[context];
IF whiten THEN {
Graphics.SetColor[context, Graphics.white];
Graphics.DrawBox[context, [x+lineInfo.xmin, y+lineInfo.ymin, x+lineInfo.xmax, y+lineInfo.ymax]];
};
WHILE i<lineInfo.nChars DO
formatNumber: FormatNumber ← lineInfo.charInfo[i].formatNumber;
len: NAT ← 0;
format: FormatInfoEntry ← lineInfo.formatInfo[formatNumber];
WHILE i+len<lineInfo.nChars AND lineInfo.charInfo[i+len].formatNumber = formatNumber DO
len ← len + 1;
ENDLOOP;
Graphics.SetColor[context, Graphics.black];
IF format.vShift#Scaled.zero THEN {
x ← Graphics.GetCP[context].x;
Graphics.SetCP[context, x, y+format.vShift.Float[]];
};
UnifiedFonts.DrawCharSeq[
font: lineInfo.formatInfo[formatNumber].font,
context: context,
count: len,
charPtr: @(lineInfo.charInfo[i].char), charIncrement: SIZE[CharInfoEntry],
deltaXptr: @(lineInfo.charInfo[i].width), deltaXincrement: SIZE[CharInfoEntry],
deltaYptr: @zero, deltaYincrement: 0
];
IF format.vShift#Scaled.zero THEN {
x ← Graphics.GetCP[context].x;
Graphics.SetCP[context, x, y];
};
i ← i + len;
ENDLOOP;
};
Resolve: PUBLIC PROC [lineInfo: LineInfo, x: INTEGER]
RETURNS [loc: TiogaNode.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.