DIRECTORY
Ascii USING [CR, SP, TAB],
Atom USING [GetPropFromList, MakeAtom, PropList],
ImagerBackdoor,
Imager,
ImagerColor USING [ColorFromRGB, RGBFromHSV],
ImagerFont USING [BoundingBox, Extents, Font, FontBoundingBox, Width, XChar, nullXChar],
LooksReader USING [Create, InlineGet, Ref, SetPosition],
MessageWindow,
NodeProps USING [GetProp],
NodeStyle,
NodeStyleFont USING [FontFromStyleParams],
NodeStyleOps,
Process USING [InitializeCondition, Milliseconds, MsecToTicks],
Real,
RefTab USING [Create, Delete, Fetch, Ref, Store],
Rope USING [ROPE],
RopeReader USING [Create, Get, GetRope, Ref, SetPosition],
RuntimeError,
Scaled,
TEditDocument USING [fatalTiogaError],
TEditFormat USING [ArtworkClass, ArtworkClassRep, BoundingBoxProc, CharInfo, CharInfoEntry, CharInfoRec, CharNumber, CharPositionProc, FormatInfo, FormatInfoEntry, FormatInfoRec, FormatNumber, FormatProc, LineInfo, LineInfoRec, PaintProc, PositionInfo, PositionInfoRec, ResolveProc],
TEditFormatExtras,
TextEdit USING [Fetch, FetchChar, GetCharPropList, GetRope, GetRuns, Offset, RefTextNode, Size],
TextLooks USING [allLooks, Looks, noLooks],
TextNode USING [Location, Ref],
Vector2 USING [VEC],
ViewerOps;
Formatting
ExpandChars:
PROC [lineInfo: TEditFormat.LineInfo] ~ {
Expands the charInfo and positionInfo sequences by about 34%
oldCharInfo: TEditFormat.CharInfo ~ lineInfo.charInfo;
oldPositionInfo: TEditFormat.PositionInfo ~ lineInfo.positionInfo;
oldCharLimit: NAT ~ oldCharInfo.maxLength;
charLimit: NAT ~ MIN[oldCharLimit + oldCharLimit/3 + 1, LAST[TEditFormat.CharNumber]-1];
newCharInfo: TEditFormat.CharInfo ~ NEW[TEditFormat.CharInfoRec[charLimit]];
newPositionInfo: TEditFormat.PositionInfo ~ NEW[TEditFormat.PositionInfoRec[charLimit+1]];
IF charLimit <= oldCharLimit THEN ERROR TEditDocument.fatalTiogaError;
FOR i:
NAT
IN [0..oldCharLimit)
DO
newCharInfo[i] ← oldCharInfo[i];
newPositionInfo[i] ← oldPositionInfo[i];
ENDLOOP;
newPositionInfo[oldCharLimit] ← oldPositionInfo[oldCharLimit];
lineInfo.charInfo ← newCharInfo;
lineInfo.positionInfo ← newPositionInfo;
};
ExpandFormats:
PROC [lineInfo: TEditFormat.LineInfo] ~ {
Expands the formatInfo sequence by about 34%
oldFormatInfo: TEditFormat.FormatInfo ~ lineInfo.formatInfo;
oldFormatLimit: NAT ~ oldFormatInfo.maxLength;
formatLimit: NAT ~ MIN[oldFormatLimit + oldFormatLimit/3 + 1, LAST[TEditFormat.FormatNumber]];
newFormatInfo: TEditFormat.FormatInfo ~ NEW[TEditFormat.FormatInfoRec[formatLimit]];
newFormatInfo.length ← oldFormatInfo.length;
IF formatLimit <= oldFormatLimit THEN ERROR TEditDocument.fatalTiogaError;
FOR i:
NAT
IN [0..oldFormatLimit)
DO
newFormatInfo[i] ← oldFormatInfo[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: BOOLEAN ← FALSE;
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[], NodeStyleOps.Create[], NodeStyleOps.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[], NodeStyleOps.Create[], NodeStyleOps.Create[]]; scratchRefsAllocCount ← scratchRefsAllocCount + 1};
};
ReleaseScratchRefs:
ENTRY
PROC [allocated: ScratchRefs] ~ {
IF
NOT scratchRefsAvailable
THEN {
scratchRefs ← allocated;
scratchRefsAvailable ← TRUE;
NOTIFY scratchRefsReleased;
};
};
GetFont:
PUBLIC
PROC [style: NodeStyle.Ref]
RETURNS [font: Font ←
NIL] ~ {
font ← style.font;
IF font = NIL THEN font ← NodeStyleFont.FontFromStyleParams[prefix: style.name[fontPrefix], family: style.name[fontFamily], face: style.fontFace, size: NodeStyle.GetReal[style, fontSize], alphabets: style.fontAlphabets];
};
GetColor:
PUBLIC
PROC [style: NodeStyle.Ref, h: NodeStyle.RealParam ← textHue, s: NodeStyle.RealParam ← textSaturation, b: NodeStyle.RealParam ← textBrightness]
RETURNS [color: Imager.Color] ~ {
brightness: REAL ~ NodeStyle.GetReal[style, b];
IF brightness = 0.0
THEN {
color ← Imager.black;
}
ELSE {
saturation: REAL ~ NodeStyle.GetReal[style, s];
IF saturation = 0.0
THEN {
color ← Imager.MakeGray[1.0-brightness];
}
ELSE {
hue: REAL ~ NodeStyle.GetReal[style, h];
color ← ImagerColor.ColorFromRGB[ImagerColor.RGBFromHSV[[H: hue, S: saturation, V: brightness]]];
};
};
};
GetWidthArray:
PROC [font: Font]
RETURNS [widths: CommonWidths] ~ {
widths ← CheckWidthCache[font];
IF widths =
NIL
THEN {
widths ← NEW[CommonWidthsArray];
FOR c: CommonCharCode
IN CommonCharCode
DO
xchar: XChar ~ [set: 0, code: c];
widths[c] ← Scaled.FromReal[font.Width[xchar].x];
ENDLOOP;
Note that duplicate entries might be cached, but eventually one will get flushed.
EnterWidthCache[font, widths];
};
};
TabState:
TYPE ~
RECORD [
tabAlignmentChar: CHAR,
tabAlignment: NodeStyle.TabAlign,
doingTab: BOOL ← FALSE,
tabStop: NodeStyle.TabStop,
tabNumber: NAT ← 0,
tabLoc, tabStart, tabWidth, tabTextStart: CARDINAL,
prevTabLooks: TextLooks.Looks ← TextLooks.allLooks,
tabCharLooks: TextLooks.Looks ← TextLooks.noLooks
];
ComputeTabWidth:
PROC [style: NodeStyle.Ref, spaceWidth: Scaled.Value, endX: Scaled.Value, nTabs:
NAT]
RETURNS [width: Scaled.Value] ~ {
tabWidth: Scaled.Value ← Scaled.FromReal[MAX[NodeStyle.GetTabStops[style], 1.0]];
toNextTab: Scaled.Value ← tabWidth.MINUS[Scaled.ValRep[Scaled.IntRep[endX] MOD Scaled.IntRep[tabWidth]]];
width ← toNextTab;
IF spaceWidth.GREATER[toNextTab] THEN width ← width.PLUS[tabWidth];
};
ComputePositionVector:
PROC [lineInfo: TEditFormat.LineInfo] ~ {
x: INTEGER ← lineInfo.positionInfo[0] ← 0;
sx: Scaled.Value ← Scaled.half;
IF lineInfo.nChars > 0
THEN
TRUSTED {
charEntry: LONG POINTER TO TEditFormat.CharInfoEntry ← @(lineInfo.charInfo[0]);
FOR i:
NAT
IN [0..lineInfo.nChars)
DO
xNext: INTEGER ← Scaled.Floor[sx ← sx.PLUS[charEntry.width]];
IF xNext <= x THEN xNext ← x+1;
x ← lineInfo.positionInfo[i+1] ← xNext;
charEntry ← charEntry + SIZE[TEditFormat.CharInfoEntry];
ENDLOOP;
};
};
FormatLine:
PUBLIC TEditFormat.FormatProc ~ {
lineInfo.startPos ← [node, startOffset];
lineInfo.ymax ← INTEGER.FIRST;
lineInfo.ymin ← INTEGER.LAST;
lineInfo.break ← eon;
lineInfo.hasBackground ← FALSE;
lineInfo.nChars ← 0;
lineInfo.nBlankCharsAtEnd ← 0;
lineInfo.startAmplifyIndex ← 0;
lineInfo.amplifySpace ← 1.0;
lineInfo.formatInfo.length ← 0;
lineInfo.artworkClass ← NIL;
lineInfo.artworkData ← NIL;
IF node.hasartwork
THEN {
class: ArtworkClass ~ GetArtworkClassForNode[node];
IF class#
NIL
AND class.format#
NIL
THEN {
lineInfo.artworkClass ← class;
class.format[lineInfo, node, startOffset, nodeStyle, lineWidth, doLigsAndKern, kind];
RETURN;
};
};
NormalFormatLine[lineInfo, node, startOffset, nodeStyle, lineWidth, doLigsAndKern, kind];
IF lineInfo.ymax < lineInfo.ymin
THEN {
lineInfo.ymax ← NodeStyle.GetLeadingI[nodeStyle];
lineInfo.ymin ← 0;
};
};
xCR: XChar ~ [set: 0, code: ORD[Ascii.CR]];
xSP: XChar ~ [set: 0, code: ORD[Ascii.SP]];
xTAB: XChar ~ [set: 0, code: ORD[Ascii.TAB]];
xHyphen: XChar ~ [set: 41B, code: 76B];
xEnDash: XChar ~ [set: 357B, code: 44B];
xEmDash: XChar ~ [set: 357B, code: 45B];
xFigDash: XChar ~ [set: 357B, code: 46B];
xOldDash: XChar ~ [set: 0B, code: 30B];
xDiscHyphen: XChar ~ [set: 357B, code: 43B];
GetHyphenChar:
PROC [style: NodeStyle.Ref, kind: NodeStyleOps.OfStyle]
RETURNS [ImagerFont.XChar] ~ {
param: REAL ← 45.0;
param ← NodeStyleOps.GetStyleParam[s: style, name: $hyphenCode, styleName: style.name[style], kind: kind ! NodeStyleOps.nonNumeric => CONTINUE];
IF param < 0.0 OR param > 65278 THEN param ← 45.0;
RETURN [LOOPHOLE[Real.RoundC[param]]]
};
GetMaxHorizontalExpansion:
PROC [style: NodeStyle.Ref, kind: NodeStyleOps.OfStyle]
RETURNS [
REAL] ~ {
param: REAL ← NodeStyle.PointsPerFil;
param ← NodeStyleOps.GetStyleParam[s: style, name: $maxHorizontalExpansion, styleName: style.name[style], kind: kind ! NodeStyleOps.nonNumeric => CONTINUE];
RETURN [param]
};
NOTE - The charProps REF ANY of the FormatInfoEntry record type is usurped to be used as a catch-all extension mechanism. Instead of keeping track of the character property list, it keeps track of this plus extra relevant information that we forgot to put in the interface. The field should be renamed to "extension" when we get a chance. - MFP
ExtraFormatInfo: TYPE ~ REF ExtraFormatInfoRep;
ExtraFormatInfoRep:
TYPE ~
RECORD [
charPropList: Atom.PropList,
charPostfix: REF,
characterArtwork: CharacterArtwork,
underlineDY: REAL,
underlineHeight: REAL,
underlineColor: Imager.Color,
strikeoutDY: REAL,
strikeoutHeight: REAL,
strikeoutColor: Imager.Color,
letterspace: Scaled.Value,
hShift: REAL,
background: BOOL,
backgroundAscent: REAL,
backgroundDescent: REAL,
backgroundColor: Imager.Color,
outlineboxBearoff: REAL,
outlineboxThickness: REAL,
outlineboxColor: Imager.Color,
hyphenChar: ImagerFont.XChar
];
GetPropFromList:
PROC [list: Atom.PropList, key:
ATOM]
RETURNS [
REF] ~
INLINE {
RETURN [IF list = NIL THEN NIL ELSE Atom.GetPropFromList[list, key]]
};
NormalFormatLine:
PROC [
lineInfo: TEditFormat.LineInfo,
node: TextEdit.RefTextNode,
startOffset: TextEdit.Offset,
nodeStyle: NodeStyle.Ref,
lineWidth: Scaled.Value,
doLigsAndKern: BOOLEAN ← FALSE,
kind: NodeStyleOps.OfStyle
] ~ {
my: ScratchRefs ← AllocScratchRefs[];
Formatting constants for the line:
realExtraIndent:
REAL ~ (
IF startOffset = 0 THEN nodeStyle.GetFirstIndent[]
ELSE nodeStyle.GetRestIndent[]
);
realExtraIndentRight:
REAL ~ (
IF startOffset = 0 THEN nodeStyle.GetReal[firstIndentRight] ELSE 0
);
extraIndent: Scaled.Value ~ Scaled.FromReal[realExtraIndent];
leftIndent: Scaled.Value ~ Scaled.FromReal[nodeStyle.GetLeftIndent + realExtraIndent];
rightIndent: Scaled.Value ~ Scaled.FromReal[nodeStyle.GetRightIndent + realExtraIndentRight];
lineLength: Scaled.Value ~ Scaled.FromReal[nodeStyle.GetLineLength];
trimmedLineWidth: Scaled.Value ~
(IF lineLength.GREATER[Scaled.zero] AND lineLength.LESS[lineWidth] THEN lineLength ELSE lineWidth).MINUS[leftIndent].MINUS[rightIndent];
Basic formatting variables:
fontWidths: CommonWidths ← NIL;
xchar: XChar ← [0, 0];
curFormatNumber: TEditFormat.FormatNumber ← LAST[TEditFormat.FormatNumber];
prevLooks: TextLooks.Looks ← TextLooks.allLooks;
looks: TextLooks.Looks ← TextLooks.noLooks;
breakIndex: TEditFormat.CharNumber ← 0;
endX: Scaled.Value ← Scaled.zero;
breakX: Scaled.Value ← Scaled.zero;
spaceAtEnd: Scaled.Value ← Scaled.zero;
haveLetterspace: BOOL ← FALSE; -- set to true if any non-zero letterspace is found.
prevLetterspace: Scaled.Value ← Scaled.zero;
prevDoLetterspace: BOOL ← FALSE;
breakSpaceAtEnd: Scaled.Value ← Scaled.zero;
spaceWidths: Scaled.Value ← Scaled.zero;
highWaterChars: NAT ← lineInfo.charInfo.maxLength;
extension: ExtraFormatInfo ← NIL;
characterPropertyList: Atom.PropList ← NIL;
haveCharacterArtwork: BOOL ← FALSE;
characterArtworkIndex: INT ← 0;
hyphenation: TEditFormatExtras.HyphProc ← NIL;
hyphenationData: REF ← NIL;
tabs: TabState;
NodeStyleExtents:
PROC
RETURNS [ymax, ymin:
INTEGER] ~ {
font: Font ~ GetFont[nodeStyle];
fontBoundingBox: ImagerFont.Extents ← ImagerFont.FontBoundingBox[font];
ymax ← CeilingI[fontBoundingBox.ascent];
ymin ← FloorI[-fontBoundingBox.descent];
};
mayAmplArt: BOOL ← FALSE;
AmplArt:
PROC [f: TEditFormat.FormatNumber]
RETURNS [
BOOL] ~ {
ext: ExtraFormatInfo ~ NARROW[lineInfo.formatInfo[f].charProps];
IF ext.characterArtwork # NIL THEN RETURN [ext.characterArtwork.amplified]
ELSE RETURN [FALSE];
};
NewFormattingLooks:
PROC ~ {
realVShift: REAL;
new: TEditFormat.FormatInfoEntry;
newExtension: ExtraFormatInfo ← NIL;
fontBoundingBox: ImagerFont.Extents;
charStyle: NodeStyle.Ref ← nodeStyle;
charPostfix: REF ~ GetPropFromList[characterPropertyList, $Postfix];
charArtwork: REF ~ GetPropFromList[characterPropertyList, $Artwork];
First a quick look through the first few formats to see if it is a common one.
new.unique ← haveCharacterArtwork ← FALSE;
IF charArtwork =
NIL
THEN {
FOR f: TEditFormat.FormatNumber
IN [0..
MIN[9, lineInfo.formatInfo.length])
DO
finfo: TEditFormat.FormatInfoEntry ~ lineInfo.formatInfo[f];
e: ExtraFormatInfo ~ NARROW[finfo.charProps];
IF finfo.looks = looks
AND charPostfix = e.charPostfix
AND NOT finfo.unique
THEN {
curFormatNumber ← f;
extension ← e;
fontWidths ← GetWidthArray[finfo.font];
prevLooks ← looks;
RETURN
};
ENDLOOP;
};
curFormatNumber ← lineInfo.formatInfo.length;
IF curFormatNumber>=lineInfo.formatInfo.maxLength THEN ExpandFormats[lineInfo];
new.charProps ← newExtension ← extension ←
WITH lineInfo.formatInfo[curFormatNumber].charProps
SELECT
FROM
extra: ExtraFormatInfo => extra,
ENDCASE => NEW[ExtraFormatInfoRep];
newExtension.charPropList ← characterPropertyList;
newExtension.charPostfix ← charPostfix;
newExtension.characterArtwork ← NIL;
IF looks#TextLooks.noLooks
THEN {
charStyle ← my.charStyle;
NodeStyleOps.Copy[dest: charStyle, source: nodeStyle];
NodeStyleOps.ApplyLooks[charStyle, looks, kind];
};
IF charPostfix #
NIL
THEN {
IF charStyle = nodeStyle
THEN {
charStyle ← my.charStyle;
NodeStyleOps.Copy[dest: charStyle, source: nodeStyle];
};
NodeStyleOps.ApplyObject[charStyle, charPostfix, kind];
};
WITH charArtwork
SELECT
FROM
r:
ROPE => {
key: ATOM ~ Atom.MakeAtom[r];
class: CharacterArtworkClass ~ GetCharacterArtworkClass[key];
IF class #
NIL
THEN {
newExtension.characterArtwork ← class.format[
class, [node, characterArtworkIndex], charStyle, kind !
RuntimeError.
UNCAUGHT =>
IF
NOT debug
THEN {
MessageWindow.Append[" TEditFormatImpl: Character Artwork Format bug", TRUE];
ViewerOps.BlinkDisplay[];
CONTINUE;
}
];
IF newExtension.characterArtwork #
NIL
THEN {
vShift: REAL ~ NodeStyle.GetVShift[charStyle];
new.unique ← TRUE;
haveCharacterArtwork ← TRUE;
IF newExtension.characterArtwork.amplified THEN mayAmplArt ← TRUE;
lineInfo.ymax ← MAX[lineInfo.ymax, CeilingI[newExtension.characterArtwork.extents.ascent+vShift]];
lineInfo.ymin ← MIN[lineInfo.ymin, FloorI[vShift-newExtension.characterArtwork.extents.descent]];
};
};
};
ENDCASE => NULL;
newExtension.letterspace ← Scaled.FromReal[charStyle.GetReal[letterspacing]];
IF newExtension.letterspace#Scaled.zero THEN haveLetterspace ← TRUE;
new.font ← GetFont[charStyle];
new.color ← GetColor[charStyle];
fontBoundingBox ← ImagerFont.FontBoundingBox[new.font];
new.looks ← looks;
new.underlining ← NodeStyle.GetUnderlining[charStyle];
IF new.underlining # None
THEN {
underlineThickness: REAL ~ NodeStyle.GetReal[charStyle, underlineThickness];
underlineDescent: REAL ~ NodeStyle.GetReal[charStyle, underlineDescent];
newExtension.underlineHeight ← underlineThickness;
newExtension.underlineDY ← underlineDescent;
lineInfo.ymax ← MAX[lineInfo.ymax, CeilingI[-newExtension.underlineDY]];
lineInfo.ymin ← MIN[lineInfo.ymin, FloorI[-(newExtension.underlineHeight+newExtension.underlineDY)]];
newExtension.underlineColor ← GetColor[charStyle, underlineHue, underlineSaturation, underlineBrightness];
};
new.strikeout ← NodeStyle.GetStrikeout[charStyle];
IF new.strikeout # None
THEN {
strikeoutThickness: REAL ~ NodeStyle.GetReal[charStyle, strikeoutThickness];
strikeoutAscent: REAL ~ NodeStyle.GetReal[charStyle, strikeoutAscent];
newExtension.strikeoutHeight ← strikeoutThickness;
newExtension.strikeoutDY ← -strikeoutAscent;
lineInfo.ymax ← MAX[lineInfo.ymax, CeilingI[-newExtension.strikeoutDY]];
lineInfo.ymin ← MIN[lineInfo.ymin, FloorI[-(newExtension.strikeoutHeight+newExtension.strikeoutDY)]];
newExtension.strikeoutColor ← GetColor[charStyle, strikeoutHue, strikeoutSaturation, strikeoutBrightness];
};
-- Get background and outline box info -- {
backgroundAscent: REAL ~ NodeStyle.GetReal[charStyle, backgroundAscent];
backgroundDescent: REAL ~ NodeStyle.GetReal[charStyle, backgroundDescent];
outlineboxThickness: REAL ~ MAX[NodeStyle.GetReal[charStyle, outlineboxThickness],0];
backgroundHeight: REAL ~ backgroundAscent+backgroundDescent;
IF (newExtension.background ← (backgroundHeight > 0.0
OR outlineboxThickness > 0.0))
THEN {
lineInfo.hasBackground ← TRUE;
newExtension.backgroundAscent ← backgroundAscent;
newExtension.backgroundDescent ← backgroundDescent;
newExtension.backgroundColor ← GetColor[charStyle, backgroundHue, backgroundSaturation, backgroundBrightness];
newExtension.outlineboxBearoff ← NodeStyle.GetReal[charStyle, outlineboxBearoff];
IF backgroundHeight <= 0.0
THEN {
newExtension.backgroundAscent ← fontBoundingBox.ascent + newExtension.outlineboxBearoff;
newExtension.backgroundDescent ← fontBoundingBox.descent + newExtension.outlineboxBearoff;
newExtension.backgroundColor ← Imager.white;
};
newExtension.outlineboxThickness ← outlineboxThickness;
newExtension.outlineboxColor ← GetColor[charStyle, outlineboxHue, outlineboxSaturation, outlineboxBrightness];
lineInfo.ymax ← MAX[lineInfo.ymax, CeilingI[newExtension.backgroundAscent+outlineboxThickness]];
lineInfo.ymin ← MIN[lineInfo.ymin, FloorI[-(newExtension.backgroundDescent+outlineboxThickness)]];
};
};
newExtension.hShift ← NodeStyle.GetReal[charStyle, hshift];
realVShift ← NodeStyle.GetVShift[charStyle];
new.vShift ← realVShift;
newExtension.hyphenChar ← GetHyphenChar[charStyle, kind];
lineInfo.ymax ← MAX[lineInfo.ymax, CeilingI[fontBoundingBox.ascent + realVShift]];
lineInfo.ymin ← MIN[lineInfo.ymin, FloorI[-fontBoundingBox.descent + realVShift]];
fontWidths ← GetWidthArray[new.font];
prevLooks ← looks;
lineInfo.formatInfo[curFormatNumber] ← new;
lineInfo.formatInfo.length ← curFormatNumber + 1;
};
nodeChars: INT ~ TextEdit.Size[node]-startOffset;
maxNChars: TEditFormat.CharNumber ~ MIN[nodeChars, maxCharsPerLine];
nTabs: NAT ← 0;
hascharsets: BOOL ~ node.hascharsets;
hascharprops: BOOL ~ node.hascharprops;
hyphenated: BOOL ← FALSE;
NodeStyleOps.Copy[dest: my.tabStyle, source: nodeStyle]; -- may need for fancy tabs.
my.ropeReader.SetPosition[TextEdit.GetRope[node], startOffset];
my.looksReader.SetPosition[TextEdit.GetRuns[node], startOffset];
lineInfo.nChars ← maxNChars;
FOR curIndex:
NAT
IN [0..maxNChars)
DO
crBreak: BOOL ← FALSE;
ch: CHAR ~ RopeReader.Get[my.ropeReader];
doLetterspace: BOOL ← TRUE;
alteredWidth: BOOL ← FALSE;
width: Scaled.Value ← Scaled.zero;
TryBreakAfterDash:
PROC ~ {
Uses curIndex, lineInfo, endX, width, trimmedLineWidth;
modifies breakIndex, breakX
IsLetter:
PROC [xChar: XChar]
RETURNS [
BOOL] ~
INLINE {
RETURN [Ord[xChar] IN [ORD['a]..ORD['z]] OR Ord[xChar] IN [ORD['A]..ORD['Z]]]
};
IF curIndex > 0
AND IsLetter[lineInfo.charInfo[curIndex-1].char]
AND NOT endX.PLUS[width].GREATER[trimmedLineWidth]
AND curIndex < maxNChars-1
AND IsLetter[XFetch[node, startOffset+(curIndex+1)]]
THEN {
breakIndex ← curIndex + 1;
breakX ← endX.PLUS[width];
};
};
xchar.code ← ORD[ch];
IF hascharsets
THEN xchar.set ← TextEdit.Fetch[node, startOffset+curIndex].charSet;
This can be speeded up.
IF hascharprops
THEN {
newCharPropList: Atom.PropList ~ TextEdit.GetCharPropList[node, startOffset+curIndex];
characterArtworkIndex ← startOffset+curIndex;
IF newCharPropList#characterPropertyList
OR haveCharacterArtwork
OR Atom.GetPropFromList[newCharPropList, $Artwork]#
NIL
THEN {
prevLooks ← TextLooks.allLooks; -- to force NewFormattingLooks
characterPropertyList ← newCharPropList;
};
};
IF curIndex >= highWaterChars
THEN {
ExpandChars[lineInfo];
highWaterChars ← lineInfo.charInfo.maxLength;
};
looks ← LooksReader.InlineGet[my.looksReader];
IF looks#prevLooks THEN NewFormattingLooks[];
IF haveCharacterArtwork
THEN {
width ← Scaled.FromReal[extension.characterArtwork.escapement.x];
doLetterspace ← FALSE;
spaceAtEnd ← Scaled.zero;
IF extension.characterArtwork.amplified THEN spaceWidths ← spaceWidths.PLUS[width];
}
ELSE
SELECT Ord[xchar]
FROM
Ord[xCR] => {
crBreak ← TRUE;
width ← fontWidths[Ord[xSP]];
breakIndex ← curIndex + 1;
breakX ← endX.PLUS[width];
doLetterspace ← FALSE;
};
Ord[xSP] => {
width ← fontWidths[Ord[xSP]];
spaceWidths ← spaceWidths.PLUS[width];
breakIndex ← curIndex + 1;
breakX ← endX.PLUS[width];
spaceAtEnd ← spaceAtEnd.PLUS[width];
doLetterspace ← FALSE;
};
ORD['-] => {
width ← fontWidths[Ord[xchar]];
spaceAtEnd ← Scaled.zero;
TryBreakAfterDash[];
};
Ord[xTAB] => {
spaceWidths ← Scaled.zero;
lineInfo.startAmplifyIndex ← curIndex+1;
width ← ComputeTabWidth[style: nodeStyle, spaceWidth: fontWidths[xSP.code], endX: endX, nTabs: nTabs];
breakIndex ← curIndex + 1;
breakX ← endX.PLUS[width];
spaceAtEnd ← spaceAtEnd.PLUS[width];
nTabs ← nTabs + 1;
doLetterspace ← FALSE;
};
IN CommonCharCode => {width ← fontWidths[Ord[xchar]]; spaceAtEnd ← Scaled.zero};
ENDCASE => {
font: Font ~ lineInfo.formatInfo[curFormatNumber].font;
width ← Scaled.FromReal[ImagerFont.Width[font, xchar].x];
spaceAtEnd ← Scaled.zero;
SELECT xchar
FROM
xHyphen, xEnDash, xEmDash, xFigDash, xOldDash => TryBreakAfterDash[];
xDiscHyphen => {
xchar ← nullXChar;
width ← Scaled.zero;
alteredWidth ← TRUE;
};
ENDCASE => NULL;
};
IF haveLetterspace
THEN {
IF prevDoLetterspace
AND doLetterspace
AND curIndex > 0
THEN {
pad: Scaled.Value ~ prevLetterspace.PLUS[extension.letterspace].Scale[-1];
lineInfo.charInfo[curIndex-1].alteredWidth ← pad#Scaled.zero;
lineInfo.charInfo[curIndex-1].width ← lineInfo.charInfo[curIndex-1].width.PLUS[pad];
endX ← endX.PLUS[pad];
};
prevLetterspace ← extension.letterspace;
};
prevDoLetterspace ← doLetterspace;
lineInfo.charInfo[curIndex] ← [char: xchar, formatNumber: curFormatNumber,
alteredWidth: alteredWidth, amplified: FALSE, width: width];
endX ← endX.PLUS[width];
IF crBreak
THEN {
lineInfo.nBlankCharsAtEnd ← 1;
breakSpaceAtEnd ← width;
lineInfo.break ← cr;
lineInfo.nChars ← curIndex + 1;
EXIT
};
IF endX.
MINUS[spaceAtEnd].
GREATER[trimmedLineWidth]
THEN {
partialWordWidth: Scaled.Value ~ endX.MINUS[spaceAtEnd].MINUS[breakX];
lineInfo.break ← wrap;
IF spaceAtEnd = Scaled.zero
AND (lineInfo.nChars-breakIndex) >= minHyphLetters
THEN {
h: TEditFormatExtras.HyphenationPositions ~ FindHyphenations[node, startOffset+breakIndex, nodeStyle, kind];
IF h[0] > 0
THEN {
formatNum: NAT ← NAT.LAST;
hyphChar: ImagerFont.XChar ← [0,0];
hyphWidth: Scaled.Value ← Scaled.zero;
artChar: BOOL ← FALSE;
GetHyphWidth:
PROC [k:
NAT]
RETURNS [hyphW: Scaled.Value] ~ {
N.B. sets above parameters
f: NAT ~ lineInfo.charInfo[k].formatNumber;
IF f # formatNum
THEN {
fie: TEditFormat.FormatInfoEntry ~ lineInfo.formatInfo[f];
extension: ExtraFormatInfo ~ NARROW[fie.charProps];
hyphChar ← extension.hyphenChar;
hyphWidth ← Scaled.FromReal[ImagerFont.Width[fie.font, hyphChar].x];
artChar ← extension.characterArtwork#NIL;
formatNum ← f;
};
RETURN [hyphWidth];
};
i: NAT ← 0;
w: Scaled.Value ← breakX;
b: NAT ~ breakIndex;
j: NAT ← 0;
WHILE b+j < curIndex
AND i < TEditFormatExtras.maxHyph
AND h[i] > 0
AND trimmedLineWidth.
GREATER[w.
PLUS[GetHyphWidth[b+j]]]
AND
NOT artChar
DO
IF j = h[i]
THEN {
breakIndex ← b+j;
breakX ← w.PLUS[hyphWidth];
hyphenated ← TRUE;
i ← i + 1;
};
w ← w.PLUS[lineInfo.charInfo[b+j].width];
j ← j + 1;
ENDLOOP;
IF hyphenated
THEN {
[] ← GetHyphWidth[breakIndex];
lineInfo.charInfo[breakIndex] ← [
char: hyphChar,
formatNumber: formatNum,
alteredWidth: FALSE,
amplified: FALSE,
width: hyphWidth
];
};
};
};
IF breakIndex>0 THEN {lineInfo.nChars ← breakIndex; endX ← breakX}
ELSE IF curIndex>0 THEN {lineInfo.nChars ← curIndex; endX ← endX.MINUS[width]}
ELSE {lineInfo.nChars ← 1};
EXIT;
};
ENDLOOP;
IF lineInfo.break = eon AND lineInfo.nChars < nodeChars THEN lineInfo.break ← wrap;
IF lineInfo.ymax < lineInfo.ymin THEN [lineInfo.ymax, lineInfo.ymin] ← NodeStyleExtents[];
IF lineInfo.break = wrap
THEN {
breakSpaceAtEnd ← Scaled.zero;
FOR j:
NAT
DECREASING
IN [1..lineInfo.nChars)
DO
c: ImagerFont.XChar ~ lineInfo.charInfo[j].char;
SELECT c
FROM
xSP => {
width: Scaled.Value ~ lineInfo.charInfo[j].width;
spaceWidths ← spaceWidths.MINUS[width];
breakSpaceAtEnd ← breakSpaceAtEnd.PLUS[width];
lineInfo.nBlankCharsAtEnd ← lineInfo.nBlankCharsAtEnd + 1;
};
xTAB => {
breakSpaceAtEnd ← breakSpaceAtEnd.PLUS[lineInfo.charInfo[j].width];
lineInfo.nBlankCharsAtEnd ← lineInfo.nBlankCharsAtEnd + 1;
};
ENDCASE => EXIT;
ENDLOOP;
};
SELECT
IF lineInfo.break = wrap
THEN nodeStyle.GetLineFormatting[]
ELSE nodeStyle.GetLastLineFormatting[]
FROM
FlushLeft => {lineInfo.xOffset ← leftIndent};
Justified => {
lineInfo.xOffset ← leftIndent;
IF spaceWidths.
GREATER[Scaled.zero]
THEN
TRUSTED {
residual: Scaled.Value ~ trimmedLineWidth.MINUS[endX.MINUS[breakSpaceAtEnd]];
maxHorizontalExpansion: REAL ~ GetMaxHorizontalExpansion[nodeStyle, kind];
amplify: REAL ~ MAX[MIN[Scaled.Float[residual.PLUS[spaceWidths]]/Scaled.Float[spaceWidths], maxHorizontalExpansion], minAmplifySpace];
start: NAT ~ lineInfo.startAmplifyIndex;
end: NAT ~ lineInfo.nChars-lineInfo.nBlankCharsAtEnd;
IF start < end
THEN {
entry: LONG POINTER TO TEditFormat.CharInfoEntry ← @(lineInfo.charInfo[start]);
cachedWidth: Scaled.Value ← Scaled.zero;
amplifiedWidth: Scaled.Value ← Scaled.zero;
FOR i:
NAT
IN [start..end)
DO
IF entry.char=xSP
OR (mayAmplArt
AND AmplArt[entry.formatNumber])
THEN {
IF entry.width # cachedWidth
THEN {
cachedWidth ← entry.width;
amplifiedWidth ← Scaled.FromReal[Scaled.Float[cachedWidth]*amplify];
};
entry.width ← amplifiedWidth;
entry.amplified ← TRUE;
};
entry ← entry + SIZE[TEditFormat.CharInfoEntry];
ENDLOOP;
};
lineInfo.amplifySpace ← amplify;
};
};
FlushRight => {lineInfo.xOffset ← leftIndent.PLUS[trimmedLineWidth].MINUS[endX.MINUS[breakSpaceAtEnd]]};
Centered => {
lineInfo.xOffset ← leftIndent.PLUS[trimmedLineWidth.MINUS[endX.MINUS[breakSpaceAtEnd]].Scale[-1]];
};
ENDCASE => ERROR;
IF hyphenated THEN {lineInfo.nBlankCharsAtEnd←NAT.LAST};
IF lineInfo.amplifySpace = 1.0 THEN lineInfo.startAmplifyIndex ← lineInfo.nChars;
lineInfo.nextPos ← lineInfo.startPos;
lineInfo.nextPos.where ← lineInfo.nextPos.where + lineInfo.nChars;
ComputePositionVector[lineInfo];
lineInfo.xmin ← 0;
lineInfo.xmax ← lineInfo.positionInfo[lineInfo.nChars];
ReleaseScratchRefs[my];
};
HyphenationPositions: TYPE ~ TEditFormatExtras.HyphenationPositions;
HyphProc: TYPE ~ TEditFormatExtras.HyphProc;
FindHyphenations:
PROC [node: TextNode.Ref, index:
INT, style: NodeStyle.Ref, kind: NodeStyleOps.OfStyle]
RETURNS [HyphenationPositions] ~ {
hyphenationClass: ATOM ~ GetHyphenationClass[style, kind];
hyph: REF HyphRep ~ FetchHyph[hyphenationClass];
h: TEditFormatExtras.HyphenationPositions ← ALL[0];
IF hyph #
NIL
THEN {
hyphProc: TEditFormatExtras.HyphProc ← hyph.hyphProc;
hyphData: REF ← hyph.hyphData;
size: INT ~ TextEdit.Size[node];
len: INT ← 0;
WHILE index+len < size
DO
set: NAT ← 0;
char: CHAR ← '\000;
alpha: BOOL ← FALSE;
[charSet: set, char: char] ← TextEdit.FetchChar[text: node, index: index+len];
alpha
← SELECT set
FROM
0 => char IN ['a..'z] OR char IN ['A..'Z] OR char IN ['\301..'\321]--accents--,
46B--greek--, 47B--cyrillic-- => TRUE,
357B => (char=43C), --discretionary hyphen--
ENDCASE => FALSE;
IF NOT alpha THEN EXIT;
len ← len + 1;
ENDLOOP;
IF hyphProc#NIL AND len > 0 THEN h ← hyphProc[node: node, start: index, len: len, hyphData: hyphData];
};
RETURN [h]
};
HyphRep:
TYPE ~
RECORD [hyphProc: HyphProc, hyphData:
REF];
hyphTab: RefTab.Ref ~ RefTab.Create[mod: 3];
GetHyphenationClass:
PROC [style: NodeStyle.Ref, kind: NodeStyleOps.OfStyle]
RETURNS [
ATOM] ~ {
param: REF ← NIL;
param ← NodeStyleOps.GetStyleParamObj[s: style, name: $hyphenation, styleName: style.name[style], kind: kind];
WITH param
SELECT
FROM
atom: ATOM => RETURN [atom];
ENDCASE => RETURN [NIL];
};
FetchHyph:
ENTRY
PROC [hyphenationClass:
ATOM]
RETURNS [
REF HyphRep] ~ {
ENABLE UNWIND => NULL;
RETURN [NARROW[RefTab.Fetch[hyphTab, hyphenationClass].val]]
};
RegisterHyphenation:
PUBLIC
ENTRY
PROC [hyphenationClass:
ATOM, hyphProc: HyphProc, hyphData:
REF]
RETURNS [oldProc: HyphProc←
NIL, oldData:
REF←
NIL] ~ {
ENABLE UNWIND => NULL;
old: REF HyphRep ~ NARROW[RefTab.Fetch[hyphTab, hyphenationClass].val];
[] ← RefTab.Store[hyphTab, hyphenationClass, IF hyphProc = NIL THEN NIL ELSE NEW[HyphRep ← [hyphProc, hyphData]]];
IF old # NIL THEN {oldProc ← old.hyphProc; oldData ← old.hyphData};
};