TEditFormatImpl.mesa
Paxton, January 3, 1983 11:21 am
Maxwell, January 6, 1983 11:30 am
Russ Atkinson, July 25, 1983 6:54 pm
DIRECTORY
GraphicsOps USING [BitmapRep],
LooksReader USING [Create, Get, Ref, SetPosition],
NameSymbolTable USING [RopeFromName],
NodeStyle USING
[ApplyLooks, Copy, Create, FontFace, FontUnderlining, GetBodyIndentI, GetFirstIndentI, GetFontFace, GetLeadingI, GetLeftIndentI, GetRightIndentI, GetFontFamily, GetFontSizeI, GetStrikeout, GetTabLeaderSpacingI, GetTabRuleWeightI, GetTabRuleVShiftI, RulesTabCount, RulesTabInfoI, GetTabLocI, GetTabStopsI, GetTopLeadingI, GetUnderlining, GetVShiftI, Ref, TabAlign, TabStop],
RopeEdit USING [AlphaNumericChar],
RopeReader USING [Create, Get, ReadOffEnd, Ref, SetPosition],
PrincOps USING [BBptr, BBTableSpace],
PrincOpsUtils USING [AlignedBBTable, BITBLT, LongZero],
TextEdit USING [GetRope, GetRuns, Offset, RefTextNode, Size],
TextLooks USING [Looks, allLooks, noLooks],
TextNode USING [ForwardClipped, Location, NarrowToTextNode, Ref, StepForward],
TEditCompile USING [bitmapWPL, maxLineAscent, maxLineDescent, maxLineHeight],
TEditDocument USING [fatalTiogaError, LineBreak, maxClip, TEditDocumentData],
TEditFormat,
VFonts USING [EstablishFont, Font],
ViewerClasses USING [Viewer];
TEditFormatImpl: MONITOR
IMPORTS
PrincOpsUtils, LooksReader, NameSymbolTable, NodeStyle, RopeEdit, RopeReader, TEditDocument, TextEdit, TextNode, VFonts
EXPORTS TEditFormat
SHARES VFonts
= BEGIN OPEN TEditDocument, TEditCompile;
endMark: CARDINAL = 32767;
-- edit both GetLineBitmap and GetLineInfo when making formatting changes!!!
GetLineBitmap: PUBLIC ENTRY SAFE PROC [viewer: ViewerClasses.Viewer, tdd: TEditDocumentData,
node: TextEdit.RefTextNode, pos: TextEdit.Offset, formatInfo: TEditFormat.FormatInfo,
trimEnd: BOOLEANFALSE]
RETURNS [bitmap: TEditFormat.Bitmap] = TRUSTED BEGIN OPEN formatInfo;
ENABLE UNWIND => NULL;
cpos, blankPos: TextEdit.Offset ← pos;
char: CHARACTER;
width: CARDINAL;
tabStop: NodeStyle.TabStop;
tabNumber: CARDINAL ← 0; -- tells us which tab stop to use
tabUnderlining: NodeStyle.FontUnderlining;
tabStrikeout: NodeStyle.FontUnderlining;
tabLoc, tabStart, tabWidth, tabTextStart: CARDINAL;
tabAlignment: NodeStyle.TabAlign ← FlushLeft;
tabAlignmentChar: CHAR ← 0C;
x, italEndX, italExtra: CARDINAL ← 0;
ascent: CARDINAL ← font.ascent;
descent: CARDINAL ← font.height - font.ascent;
bltYOffset: LONG POINTER;
blankX: CARDINAL ← endMark;
looks, tabCharLooks, prevTabLooks: TextLooks.Looks ← TextLooks.noLooks;
legalChar: BOOL;
lineWidth, lineStart, extraIndent: INTEGER;
break: LineBreak;
bitmap: REF ← oneLineBitmap;
bitmapOffset: CARDINAL;
doingTab: BOOLFALSE;
NewLooks: PROC = {
NodeStyle.Copy[dest: charStyle, source: tabStyle]; -- back to the basic style
IF looks#TextLooks.noLooks THEN NodeStyle.ApplyLooks[charStyle, looks];
font ← GetFont[charStyle];
IF (ascent ← MAX[ascent, font.ascent]) > maxLineAscent THEN
RETURN WITH ERROR fatalTiogaError;
IF (descent ← MAX[descent, font.height-font.ascent]) >= maxLineDescent THEN
RETURN WITH ERROR fatalTiogaError;
bitmapOffset ← (maxLineAscent-font.ascent-NodeStyle.GetVShiftI[charStyle])*bitmapWPL;
bltYOffset ← LOOPHOLE[bitmap, LONG POINTER] + bitmapOffset;
cbbp.height ← font.height;
cbbp.srcDesc.srcBpl ← font.raster*16;
IF font.synthItalic THEN BEGIN
italSlant: CARDINAL = 3;
italPieces ← (font.height+italSlant-1)/italSlant;
italSlice ← (font.height+italPieces-1)/italPieces;
italRem ← font.height MOD italSlice;
IF italRem=0 THEN italRem ← italSlice;
italInitDx ← 1 - ((italPieces+1)/2);
italDy ← italSlice*bitmapWPL;
italInitDy ← italPieces*italDy;
italFdy ← italSlice*font.raster;
italInitFdy ← italPieces*italFdy;
italExtra ← italPieces+italInitDx
END;
cbbp.flags.dstFunc ← IF font.synthBold THEN or ELSE null;
savedLooks ← looks };
TabFill: PROC = {
TabRule: PROC [weight, vshift: INTEGER] = {
bltYOffset: LONG POINTER = LOOPHOLE[oneLineBitmap, LONG POINTER] +
((maxLineAscent-vshift-weight+1)*bitmapWPL);
ubbp.width ← tabWidth;
ubbp.dst ← [LOOPHOLE[bltYOffset+(tabStart/16)],,tabStart MOD 16];
ubbp.height ← MAX[1,weight];
PrincOpsUtils.BITBLT[ubbp];
ubbp.height ← 1 };
saveX: CARDINAL ← x;
saveWidth: CARDINAL ← width;
x ← tabStart; width ← tabWidth;
IF tabCharLooks#savedLooks THEN { looks ← tabCharLooks; NewLooks[] };
IF tabUnderlining=All THEN Underline[];
IF tabStrikeout=All THEN Strikeout[];
IF tabStop#NIL THEN WITH ts:tabStop SELECT FROM
blank => NULL;
leaders => {
charWidth: CARDINAL ← font.width[ts.char];
spacing: INTEGER
MAX[charWidth, NodeStyle.GetTabLeaderSpacingI[@ts, nodeStyle]];
leaderStart: INTEGER
IF ts.congruent THEN ((tabStart+lineStart+spacing-1)/spacing)*spacing - lineStart
ELSE tabStart + (tabWidth-(tabWidth/spacing)*spacing)/2;
leaderQuit: INTEGER ← tabStart+tabWidth-spacing;
src: CARDINAL ← font.xInSegment[ts.char];
extra: CARDINAL ← (spacing-charWidth)/2;
leaderStart ← leaderStart + extra; -- even out the extra space
leaderQuit ← leaderQuit + extra;
cbbp.flags.dstFunc ← or; -- to take care of kerning
cbbp.width ← charWidth;
cbbp.src ← [font.bitmap+(src/16),,src MOD 16];
FOR xx: INTEGER ← leaderStart, xx+spacing UNTIL xx > leaderQuit DO
Ignore synthetic bold/italic for this
cbbp.dst ← [LOOPHOLE[bltYOffset+(xx/16)],,xx MOD 16];
PrincOpsUtils.BITBLT[cbbp];
ENDLOOP;
cbbp.flags.dstFunc ← null };
rule => {
weight: INTEGER ← NodeStyle.GetTabRuleWeightI[@ts, nodeStyle];
vshift: INTEGER ← NodeStyle.GetTabRuleVShiftI[@ts, nodeStyle];
TabRule[weight, vshift] };
rules => {
FOR i: INTEGER IN [0..NodeStyle.RulesTabCount[@ts]) DO
weight, vshift: INTEGER;
[weight, vshift] ← NodeStyle.RulesTabInfoI[@ts, i];
TabRule[weight, vshift];
ENDLOOP };
ENDCASE => ERROR;
x ← saveX; width ← saveWidth };
FinishTab: PROC = {
yOffset: CARDINAL = (maxLineAscent-ascent)*bitmapWPL;
textWidth: CARDINAL ← x-tabTextStart;
distanceToMove, dest, width: CARDINAL;
alignmentPoint: CARDINAL
SELECT tabAlignment FROM
FlushRight, Character => x, -- move right end of text to tabLoc
Centered => (x+tabTextStart)/2, -- move center of text to tabLoc
ENDCASE => ERROR; -- handle FlushLeft elsewhere
IF alignmentPoint >= tabLoc THEN distanceToMove ← 0 -- too much text to fit; leave it alone
ELSE {
distanceToMove ← MIN[tabLoc-alignmentPoint, lineWidth-x];
x ← x+distanceToMove };
tabWidth ← distanceToMove+tabTextStart-tabStart;
dest ← tabStart+distanceToMove;
width ← tabTextStart-tabStart+textWidth+ascent--for italic kerning--;
tbbp.width ← MIN[bitmapWPL*16, dest+width]-dest;
tbbp.height ← ascent+descent;
tbbp.src ← [LOOPHOLE[bltYOffset+(tabStart/16)],,tabStart MOD 16];
bltYOffset ← LOOPHOLE[bitmap ← oneLineBitmap, LONG POINTER] + bitmapOffset;
tbbp.dst ← [LOOPHOLE[bltYOffset+(dest/16)],,dest MOD 16];
PrincOpsUtils.BITBLT[tbbp];
clear the aux buffer
TrimLineBuffer[tabBitmap, tabStart, tabStart+width, ascent, descent];
TabFill[];
doingTab ← FALSE };
Underline: PROC = INLINE BEGIN
bltYOffset: LONG POINTER = LOOPHOLE[bitmap, LONG POINTER] +
((maxLineAscent+1)*bitmapWPL);
ubbp.width ← width;
ubbp.dst ← [LOOPHOLE[bltYOffset+(x/16)],,x MOD 16];
PrincOpsUtils.BITBLT[ubbp];
END;
Strikeout: PROC = INLINE BEGIN
bltYOffset: LONG POINTER = LOOPHOLE[bitmap, LONG POINTER] +
((maxLineAscent-(font.ascent/2))*bitmapWPL);
ubbp.width ← width;
ubbp.dst ← [LOOPHOLE[bltYOffset+(x/16)],,x MOD 16];
PrincOpsUtils.BITBLT[ubbp];
END;
IF cpos > TextEdit.Size[node] THEN RETURN WITH ERROR fatalTiogaError;
extraIndent ← IF cpos = 0 THEN firstIndent ELSE bodyIndent;
lineWidth ← lineMeasure - extraIndent;
lineStart ← leftIndent + extraIndent;
NodeStyle.Copy[dest: tabStyle, source: nodeStyle]; -- initialize tabStyle to be equal to nodeStyle
savedLooks ← TextLooks.allLooks; -- force initial call on NewLooks
RopeReader.SetPosition[ropeReader, TextEdit.GetRope[node], cpos];
LooksReader.SetPosition[looksReader, TextEdit.GetRuns[node], cpos];
cbbp.height ← font.height;
cbbp.srcDesc.srcBpl ← font.raster*16;
ClearLineBuffer[oneLineBitmap];
DO char ← RopeReader.Get[ropeReader ! RopeReader.ReadOffEnd => {break ← eon; EXIT}];
IF (looks←LooksReader.Get[looksReader])#savedLooks AND char#11C THEN NewLooks[];
width ← IF (legalChar ← char IN [font.min..font.max]) THEN font.width[char]
ELSE MAX[3, font.width[40C]];
SELECT char FROM
40C => BEGIN
blankX ← MAX[x, italEndX]; -- take care of italic kerning
blankPos ← cpos; -- to back up to for word breaks
IF NodeStyle.GetUnderlining[charStyle]=All THEN Underline[];
IF NodeStyle.GetStrikeout[charStyle]=All THEN Strikeout[];
END;
15C => {break ← cr; EXIT}; -- end of line
11C => BEGIN
tabLooks: TextLooks.Looks;
minSpace: CARDINAL;
IF doingTab THEN FinishTab[]; -- finish previous tab before start this one
tabNumber ← tabNumber+1;
IF tabNumber > nodeStyle.numTabStops THEN {
IF (tabStop ← nodeStyle.defaultTabStops) # NIL THEN {
tabLoc ← IF nodeStyle.tabStops=NIL THEN 0 ELSE
NodeStyle.GetTabLocI[nodeStyle.tabStops.first, nodeStyle];
tabLoc ← tabLoc + (tabNumber-nodeStyle.numTabStops)*
NodeStyle.GetTabLocI[tabStop, nodeStyle] }
ELSE NULL -- calculate tabLoc after establish looks -- }
ELSE { -- go down list until find the proper tab stop
tabStops: LIST OF NodeStyle.TabStop ← nodeStyle.tabStops;
FOR i: CARDINAL ← nodeStyle.numTabStops, i-1 UNTIL i=tabNumber DO
tabStops ← tabStops.rest; ENDLOOP;
tabStop ← tabStops.first;
tabLoc ← NodeStyle.GetTabLocI[tabStop, nodeStyle] };
IF nodeStyle.fixedTabs THEN tabLoc ← tabLoc-lineStart;
tabLooks ← IF tabStop = NIL THEN TextLooks.noLooks ELSE tabStop.looks;
IF tabLooks # prevTabLooks THEN {
NodeStyle.Copy[dest: tabStyle, source: nodeStyle];
IF tabLooks#TextLooks.noLooks THEN NodeStyle.ApplyLooks[tabStyle, tabLooks];
prevTabLooks ← tabLooks;
NewLooks[] }
ELSE IF looks#savedLooks THEN NewLooks[];
tabCharLooks ← looks;
minSpace ← font.width[40C]; -- this must come after establish looks
IF tabStop=NIL THEN { -- pick value [spaceWidth..tabWidth+spaceWidth) for tab width
maxSpace, toNextTab: CARDINAL;
tabWidth ← NodeStyle.GetTabStopsI[nodeStyle];
maxSpace ← tabWidth*minSpace;
toNextTab ← maxSpace - ((x+extraIndent) MOD maxSpace);
width ← IF toNextTab<minSpace THEN toNextTab+maxSpace
ELSE toNextTab;
tabLoc ← x+width };
blankPos ← cpos; blankX ← MAX[x, italEndX]; -- take care of italic kerning
tabStart ← x; width ← minSpace;
tabUnderlining ← NodeStyle.GetUnderlining[charStyle];
tabStrikeout ← NodeStyle.GetStrikeout[charStyle];
tabAlignment ← IF tabStop=NIL THEN FlushLeft ELSE tabStop.alignment;
IF x+minSpace > tabLoc THEN { -- we are beyond the tab stop
IF tabStop=NIL OR ~tabStop.breakIfPast THEN { -- treat like a space
tabWidth ← minSpace; TabFill[] }
ELSE -- force line break -- {break ← wrap; EXIT}}
ELSE IF tabAlignment=FlushLeft THEN { -- simple case; finish now
tabWidth ← width ← tabLoc-x; TabFill[] }
ELSE { -- will finish after have the text to be positioned
doingTab ← TRUE;
tabAlignmentChar ←
IF tabAlignment=Character THEN tabStop.alignmentChar ELSE 0C;
tabTextStart ← tabStart+width;
Redirect blt's to tabBitmap
bltYOffset ← LOOPHOLE[bitmap ← tabBitmap, LONG POINTER] + bitmapOffset };
END;
ENDCASE => BEGIN
IF char=tabAlignmentChar AND tabAlignment=Character
AND doingTab THEN FinishTab[];
IF x+width >= LOOPHOLE[lineWidth, CARDINAL] THEN {break ← wrap; EXIT};
IF ~legalChar THEN BEGIN-- illegal char for display font
color: CARDINAL ← 177777B;
oldsrcDesc: INTEGER ← cbbp.srcDesc.srcBpl;
cbbp.height ← font.ascent;
cbbp.width ← width-2; -- hack some white space between bad chars
cbbp.dst ← [LOOPHOLE[bltYOffset+((x+1)/16)],,(x+1) MOD 16];
cbbp.flags.gray ← TRUE;
cbbp.srcDesc.gray ← [0, 0, 0, 0];
cbbp.src ← [@color,,0];
PrincOpsUtils.BITBLT[cbbp];
cbbp.height ← font.height;
cbbp.flags.gray ← FALSE;
cbbp.srcDesc.srcBpl ← oldsrcDesc;
END
ELSE BEGIN
src: CARDINAL ← font.xInSegment[char];
cbbp.width ← width;
IF font.synthItalic THEN BEGIN
cbbp.src ← [font.bitmap+(src/16)+italInitFdy,,src MOD 16];
IF font.synthBold THEN BEGIN--bolditalic synth
tx: INTEGER ← x + italInitDx;
ty: CARDINAL ← italInitDy;
cbbp.height ← italRem;
THROUGH [0..italPieces) DO
cbbp.src.word ← cbbp.src.word - italFdy;
ty ← ty - italDy;
cbbp.dst ← [LOOPHOLE[bltYOffset+(MAX[0, tx]/16)+ty],,MAX[0, tx] MOD 16];
PrincOpsUtils.BITBLT[cbbp];
tx ← tx+1;
cbbp.dst ← [LOOPHOLE[bltYOffset+(MAX[0, tx]/16)+ty],,MAX[0, tx] MOD 16];
PrincOpsUtils.BITBLT[cbbp];
cbbp.height ← italSlice;
ENDLOOP;
width ← width+1; -- compensate for extra bit displayed
END
ELSE BEGIN-- italic synth
tx: INTEGER ← x + italInitDx;
ty: CARDINAL ← italInitDy;
cbbp.height ← italRem;
THROUGH [0..italPieces) DO
cbbp.src.word ← cbbp.src.word - italFdy;
ty ← ty - italDy;
cbbp.dst ← [LOOPHOLE[bltYOffset+(MAX[0, tx]/16)+ty],,MAX[0, tx] MOD 16];
PrincOpsUtils.BITBLT[cbbp];
tx ← tx + 1;
cbbp.height ← italSlice;
ENDLOOP;
END;
italEndX ← x+width+italExtra;
END
ELSE IF font.synthBold THEN BEGIN-- bold synth
cbbp.src ← [font.bitmap+(src/16),,src MOD 16];
cbbp.dst ← [LOOPHOLE[bltYOffset+(x/16)],,x MOD 16];
PrincOpsUtils.BITBLT[cbbp];
x ← x+1; -- compensate for extra bit displayed
cbbp.dst ← [LOOPHOLE[bltYOffset+(x/16)],,x MOD 16];
PrincOpsUtils.BITBLT[cbbp];
END
ELSE BEGIN-- normal face
cbbp.src ← [font.bitmap+(src/16),,src MOD 16];
cbbp.dst ← [LOOPHOLE[bltYOffset+(x/16)],,x MOD 16];
PrincOpsUtils.BITBLT[cbbp];
END;
END;
SELECT NodeStyle.GetUnderlining[charStyle] FROM
All, Visible   => Underline[];
LettersAndDigits => IF RopeEdit.AlphaNumericChar[char] THEN Underline[];
ENDCASE;
SELECT NodeStyle.GetStrikeout[charStyle] FROM
All, Visible   => Strikeout[];
LettersAndDigits => IF RopeEdit.AlphaNumericChar[char] THEN Strikeout[];
ENDCASE;
END;
cpos ← cpos+1;
x ← x+width;
ENDLOOP;
-- we fell out of the loop because we got to the right edge or because
-- we hit a logical end-of-line (return or end-of-node)
------ see if need to finish a tab
IF doingTab THEN FinishTab[];
------ fix up the character position for start of next line
IF break=cr OR break=eon THEN cpos ← cpos+1
ELSE IF blankX#endMark THEN cpos ← blankPos+1;
-- add one to avoid blank at start of next line
------ here do centering, flushing etc.
SELECT lineFormatting FROM
FlushLeft, Justified => NULL;
FlushRight => lineStart ← lineStart + (lineWidth-x);
Centered => lineStart ← lineStart + (lineWidth-x)/2;
ENDCASE => ERROR;
width ← IF break=cr THEN x+width
ELSE IF break=eon OR blankX=endMark THEN MAX[x, italEndX]
ELSE blankX;
formatBitmap^ ← [
bits: formatBitmapRep,
yOffset: maxLineAscent-ascent,
ascent: ascent,
descent: descent,
width: width,
chars: cpos-pos,
leftIndent: lineStart,
break: break
];
savedAscent ← ascent; savedDescent ← descent; -- so can clear line buffer next time
IF trimEnd AND x#width THEN TrimLineBuffer[bitmap, width, x, ascent, descent];
clear off broken word at end
RETURN [formatBitmap];
END;
savedAscent: CARDINAL ← maxLineAscent;
savedDescent: CARDINAL ← maxLineDescent;
ClearLineBuffer: PROC [bitmap: REF] = INLINE {
IF savedAscent#0 THEN BEGIN
[] ← PrincOpsUtils.LongZero[LOOPHOLE[bitmap, LONG POINTER TO UNSPECIFIED] +
((maxLineAscent-savedAscent)*bitmapWPL), (savedAscent+savedDescent)*bitmapWPL];
savedAscent ← savedDescent ← 0;
END;
};
TrimLineBuffer: PROC [bitmap: REF, start, end, ascent, descent: CARDINAL] = INLINE {
bltYOffset: LONG POINTER =
LOOPHOLE[bitmap, LONG POINTER] + ((maxLineAscent-ascent)*bitmapWPL);
bbbp.height ← ascent+descent;
bbbp.width ← MIN[bitmapWPL*16, end+1+ascent--for italic kerning--]-start;
bbbp.dst ← [LOOPHOLE[bltYOffset+(start/16)],,start MOD 16];
PrincOpsUtils.BITBLT[bbbp];
};
globalLineInfo: TEditFormat.LineInfo ← NEW[TEditFormat.LineInfoRec];
GetLineInfo: PUBLIC ENTRY SAFE PROC [viewer: ViewerClasses.Viewer, tdd: TEditDocumentData,
pos: TextNode.Location, nodeStyle: NodeStyle.Ref]
RETURNS [lineInfo: TEditFormat.LineInfo, ascent: INTEGER, descent: INTEGER,
nextPos: TextNode.Location, nChars: CARDINAL, leading: INTEGER] = TRUSTED
BEGIN ENABLE UNWIND => NULL;
cpos: TextEdit.Offset ← pos.where;
node: TextEdit.RefTextNode ← TextNode.NarrowToTextNode[pos.node];
tabStop: NodeStyle.TabStop;
tabNumber: CARDINAL ← 0; -- tells us which tab stop to use
tabLoc, tabStart, tabWidth, tabTextStart: CARDINAL;
tabAlignment: NodeStyle.TabAlign ← FlushLeft;
tabAlignmentChar: CHAR ← 0C;
level: INTEGER ← 0; -- in case we are doing level clipping
maxLevel: INTEGER ← tdd.clipLevel;
levelClipping: BOOLEAN ← maxLevel < maxClip;
char: CHARACTER;
width: CARDINAL;
font: VFonts.Font;
extraIndent: INTEGER = (IF cpos = 0 THEN NodeStyle.GetFirstIndentI[nodeStyle]
ELSE NodeStyle.GetBodyIndentI[nodeStyle]);
leftIndent: INTEGER = NodeStyle.GetLeftIndentI[nodeStyle] + extraIndent;
eon: BOOLEANFALSE;
lineWidth: CARDINAL =
MAX[0, viewer.cw - NodeStyle.GetRightIndentI[nodeStyle] - leftIndent];
x, widthPtr, italPtr, tabPtr, italEndX, italExtra: CARDINAL ← 0;
blankWidthPtr: CARDINAL ← endMark;
savedLooks: TextLooks.Looks ← TextLooks.allLooks; -- to force init
doingTab: BOOLFALSE;
looks, tabCharLooks, prevTabLooks: TextLooks.Looks ← TextLooks.noLooks;
NewLooks: PROC = {
NodeStyle.Copy[dest: lineInfoCharStyle, source: lineInfoTabStyle];
IF looks#TextLooks.noLooks THEN NodeStyle.ApplyLooks[lineInfoCharStyle, looks];
font ← GetFont[lineInfoCharStyle];
IF font.synthItalic THEN BEGIN
italSlant: CARDINAL = 3;
pieces: CARDINAL ← (font.height+italSlant-1)/italSlant;
init: INTEGER ← 1 - ((pieces+1)/2);
italExtra ← pieces+init;
END;
savedLooks ← looks;
ascent ← MAX[ascent, font.ascent];
descent ← MAX[descent, font.height-font.ascent] };
FinishTab: PROC = {
textWidth: CARDINAL ← x-tabTextStart;
distanceToMove: CARDINAL;
alignmentPoint: CARDINAL
SELECT tabAlignment FROM
FlushRight, Character => x, -- move right end of text to tabLoc
Centered => (x+tabTextStart)/2, -- move center of text to tabLoc
ENDCASE => ERROR; -- handle FlushLeft elsewhere
IF alignmentPoint >= tabLoc THEN distanceToMove ← 0 -- too much text to fit; leave it alone
ELSE {
distanceToMove ← MIN[tabLoc-alignmentPoint, lineWidth-x];
x ← x+distanceToMove };
tabWidth ← distanceToMove+tabTextStart-tabStart;
lineInfo[tabPtr] ← tabWidth;
doingTab ← FALSE };
lineInfo ← globalLineInfo; -- better performance if this isn't allocated each time
IF node=NIL OR cpos > TextEdit.Size[node] THEN RETURN WITH ERROR fatalTiogaError;
NodeStyle.Copy[dest: lineInfoTabStyle, source: nodeStyle];
RopeReader.SetPosition[ropeReader, TextEdit.GetRope[node], cpos];
LooksReader.SetPosition[looksReader, TextEdit.GetRuns[node], cpos];
ascent ← descent ← 0;
DO char ← RopeReader.Get[ropeReader ! RopeReader.ReadOffEnd => {eon ← TRUE; EXIT}];
IF (looks←LooksReader.Get[looksReader])#savedLooks THEN NewLooks[];
width ← IF char IN [font.min..font.max] THEN font.width[char]
ELSE MAX[3, font.width[40C]];
SELECT char FROM
40C => {width𡤏ont.width[' ]; blankWidthPtr ← widthPtr};
11C => BEGIN   -- tab
tabLooks: TextLooks.Looks;
minSpace: CARDINAL;
IF doingTab THEN FinishTab[]; -- finish previous tab before start this one
tabNumber ← tabNumber+1;
IF tabNumber > nodeStyle.numTabStops THEN {
IF (tabStop ← nodeStyle.defaultTabStops) # NIL THEN {
tabLoc ← IF nodeStyle.tabStops=NIL THEN 0 ELSE
NodeStyle.GetTabLocI[nodeStyle.tabStops.first, nodeStyle];
tabLoc ← tabLoc + (tabNumber-nodeStyle.numTabStops)*
NodeStyle.GetTabLocI[tabStop, nodeStyle] }
ELSE NULL -- calculate tabLoc after establish looks -- }
ELSE { -- go down list until find the proper tab stop
tabStops: LIST OF NodeStyle.TabStop ← nodeStyle.tabStops;
FOR i: CARDINAL ← nodeStyle.numTabStops, i-1 UNTIL i=tabNumber DO
tabStops ← tabStops.rest; ENDLOOP;
tabStop ← tabStops.first;
tabLoc ← NodeStyle.GetTabLocI[tabStop, nodeStyle] };
IF nodeStyle.fixedTabs THEN tabLoc ← tabLoc-leftIndent;
tabLooks ← IF tabStop = NIL THEN TextLooks.noLooks ELSE tabStop.looks;
IF tabLooks # prevTabLooks THEN {
NodeStyle.Copy[dest: lineInfoTabStyle, source: nodeStyle];
IF tabLooks#TextLooks.noLooks THEN NodeStyle.ApplyLooks[lineInfoTabStyle, tabLooks];
prevTabLooks ← tabLooks;
NewLooks[] }
ELSE IF looks#savedLooks THEN NewLooks[];
tabCharLooks ← looks;
minSpace ← font.width[40C]; -- this must come after establish looks
IF tabStop=NIL THEN { -- pick value [spaceWidth..tabWidth+spaceWidth) for tab width
maxSpace, toNextTab: CARDINAL;
tabWidth ← NodeStyle.GetTabStopsI[nodeStyle];
maxSpace ← tabWidth*minSpace;
toNextTab ← maxSpace - ((x+extraIndent) MOD maxSpace);
width ← IF toNextTab<minSpace THEN toNextTab+maxSpace
ELSE toNextTab;
tabLoc ← x+width };
tabStart ← x; tabPtr ← blankWidthPtr ← widthPtr; width ← minSpace;
tabAlignment ← IF tabStop=NIL THEN FlushLeft ELSE tabStop.alignment;
IF x+minSpace > tabLoc THEN { -- we are beyond the tab stop
IF tabStop=NIL OR ~tabStop.breakIfPast THEN { -- treat like a space
tabWidth ← minSpace }
ELSE -- force line break -- EXIT}
ELSE IF tabAlignment=FlushLeft THEN { -- simple case; finish now
tabWidth ← width ← tabLoc-x }
ELSE { -- will finish after have the text to be positioned
doingTab ← TRUE;
tabAlignmentChar ←
IF tabAlignment=Character THEN tabStop.alignmentChar ELSE 0C;
tabTextStart ← tabStart+width };
END
ENDCASE => BEGIN
IF char=tabAlignmentChar AND tabAlignment=Character
AND doingTab THEN FinishTab[];
IF font.synthBold THEN width ← width+1;
IF font.synthItalic THEN { italEndX ← x+width+italExtra; italPtr ← widthPtr };
IF x + width >= lineWidth THEN EXIT;
END;
lineInfo[widthPtr] ← width;
widthPtr ← widthPtr+1;
cpos ← cpos+1;
x ← x+width;
IF char=15C THEN EXIT; -- eol
ENDLOOP;
-- we fell out of the loop because we got to the right edge or because
-- we hit a logical end-of-line (return or end-of-node)
------ see if need to finish a tab
IF doingTab THEN FinishTab[];
IF italEndX > x THEN lineInfo[italPtr] ← lineInfo[italPtr]+italExtra; -- end of line italic kerning
IF char=15C OR eon OR blankWidthPtr=endMark THEN lineInfo[widthPtr] ← endMark
ELSE BEGIN
lineInfo[blankWidthPtr+1] ← endMark;
cpos ← pos.where+blankWidthPtr+1;
END;
IF cpos>=TextEdit.Size[node] THEN BEGIN
n: TextNode.Ref;
IF levelClipping THEN [n,level] ← TextNode.ForwardClipped[node,maxLevel,level]
ELSE n ← TextNode.StepForward[node];
nextPos ← [n,0];
nChars ← TextEdit.Size[node] - pos.where;
END
ELSE BEGIN
nextPos ← [node, cpos];
nChars ← cpos - pos.where;
END;
leading ← IF pos.where=0 THEN NodeStyle.GetTopLeadingI[nodeStyle]
ELSE NodeStyle.GetLeadingI[nodeStyle];
END;
GetFont: PROC [style: NodeStyle.Ref] RETURNS [font: VFonts.Font] = INLINE BEGIN
face: NodeStyle.FontFace ← NodeStyle.GetFontFace[style];
font ← VFonts.EstablishFont[
NameSymbolTable.RopeFromName[NodeStyle.GetFontFamily[style]],
NodeStyle.GetFontSizeI[style],
face=Bold OR face=BoldItalic,
face=Italic OR face=BoldItalic
];
END;
AllocateBitmap: PROC [nWords: CARDINAL] RETURNS [REF] = BEGIN
Words: TYPE = RECORD[SEQUENCE COMPUTED CARDINAL OF CARDINAL];
RETURN[NEW[Words[nWords]]];
END;
-- Main Program
bbbTable, cbbTable, tbbTable, ubbTable: PrincOps.BBTableSpace;
bbbp: PrincOps.BBptr ← PrincOpsUtils.AlignedBBTable[@bbbTable]; -- buffer end trimming
cbbp: PrincOps.BBptr ← PrincOpsUtils.AlignedBBTable[@cbbTable]; -- chars to line buffer
tbbp: PrincOps.BBptr ← PrincOpsUtils.AlignedBBTable[@tbbTable]; -- chars to tab line buffer
ubbp: PrincOps.BBptr ← PrincOpsUtils.AlignedBBTable[@ubbTable]; -- underline and strikeout
oneLineBitmap: REF ← AllocateBitmap[bitmapWPL*maxLineHeight];
-- line buffer; magic storage provided by AllocateBitmap
tabBitmap: REF ← AllocateBitmap[bitmapWPL*maxLineHeight];
-- line buffer for tab chars; magic storage provided by AllocateBitmap
formatBitmapRep: REF GraphicsOps.BitmapRep ← NEW[GraphicsOps.BitmapRep ← [
base: oneLineBitmap,
raster: bitmapWPL,
width: bitmapWPL*16,
height: maxLineHeight
]];
formatBitmap: TEditFormat.Bitmap ← NEW[TEditFormat.BitmapRec ← [
bits: formatBitmapRep,
chars:,
leftIndent:
]];
myWhiteWord: CARDINAL ← 0;  -- white for erasing bitmap buffers
myBlackWord: CARDINAL ← 177777B; -- black for underlining and strikeout
-- RopeReader and LooksReader Stuff
ropeReader: RopeReader.Ref ← RopeReader.Create[];
looksReader: LooksReader.Ref ← LooksReader.Create[];
lineInfoCharStyle: NodeStyle.Ref ← NodeStyle.Create[];
lineInfoTabStyle: NodeStyle.Ref ← NodeStyle.Create[];
bbbp.flags.disjoint ← TRUE;
bbbp.flags.disjointItems ← TRUE;
bbbp.flags.gray ← TRUE;
bbbp.srcDesc.gray ← [0, 0, 0, 0];
bbbp.src ← [@myWhiteWord,,0];
bbbp.dstBpl ← bitmapWPL*16;
cbbp.flags.disjoint ← TRUE;
cbbp.flags.disjointItems ← TRUE;
cbbp.dstBpl ← bitmapWPL*16;
tbbp.flags.disjoint ← TRUE;
tbbp.flags.disjointItems ← TRUE;
tbbp.flags.dstFunc ← or; -- to take care of kerning
tbbp.srcDesc.srcBpl ← bitmapWPL*16;
tbbp.dstBpl ← bitmapWPL*16;
ubbp.flags.disjoint ← TRUE;
ubbp.flags.disjointItems ← TRUE;
ubbp.flags.gray ← TRUE;
ubbp.srcDesc.gray ← [0, 0, 0, 0];
ubbp.src ← [@myBlackWord,,0];
ubbp.dstBpl ← bitmapWPL*16;
ubbp.height ← 1;
ClearLineBuffer[oneLineBitmap];
savedAscent ← maxLineAscent;
savedDescent ← maxLineDescent;
ClearLineBuffer[tabBitmap];
END.