DIRECTORY
Ascii USING [CR, LF, SP, TAB],
Atom USING [MakeAtom],
Char,
CodeTimer,
Convert USING [RopeFromInt],
Imager USING [black, Box, Context, DoSave, DoSaveAll, MaskBox, MaskFill, MaskRectangle, MaskUnderline, Move, SetAmplifySpace, SetColor, SetFont, SetXRel, SetYRel, Show, StartUnderline, white, Trans, Scale2T],
ImagerBackdoor USING [GetColor],
ImagerColor USING [Color],
ImagerFont USING [BoundingBox, Escapement, Extents, Font, FontBoundingBox],
NodeReader USING [Ref, New, Size, CharInfo, Fetch, FetchChar],
NodeStyle USING [Style, StyleKind, FontUnderlining, GetColor, GetFont, GetHyphenChar, GetInt, GetLastLineFormatting, GetLineFormatting, GetName, GetReal, GetStrikeout, GetTabLoc, GetUnderlining, TabAlign, TabStop],
NodeStyleOps USING [ApplyLooks, ApplyObject, Copy, Create],
NodeStyleWorks USING [Where],
ImagerPath USING [PathProc],
Process USING [InitializeCondition, Milliseconds, MsecToTicks],
Prop USING [Get],
Real USING [Round, LargestNumber],
RefTab USING [Create, Delete, Fetch, Ref, Store, Update, UpdateAction],
Rope USING [ROPE, Literal, Size, Fetch],
RuntimeError USING [UNCAUGHT],
Scaled USING [Float, Floor, FromReal, GREATER, half, IntRep, LESS, MINUS, PLUS, Round, Scale, ValRep, Value, zero],
SimpleFeedback USING [Append, Blink],
TEditFormat USING [ArtworkClass, ArtworkClassRep, BoundingBoxProc, CharacterArtwork, CharacterArtworkClass, CharacterArtworkClassRep, CharacterArtworkRep, CharacterFormatProc, CharInfo, CharInfoEntry, CharInfoRec, CharNumber, CharPositionProc, FormatInfo, FormatInfoEntry, FormatInfoRec, FormatNumber, FormatProc, HyphenationPositions, HyphProc, LineInfo, LineInfoRec, maxHyph, PaintProc, PositionInfo, PositionInfoRec, ResolveProc],
TextEdit USING [FetchChar, GetProp, GetNewlineDelimiter],
TextNode USING [LocNumber, Root],
Tioga USING [Node, Location, Looks, allLooks, noLooks, PropList],
Vector2 USING [VEC];
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;
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;
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 [
nodeReader: NodeReader.Ref,
charStyle: Style,
tabStyle: Style
];
scratchRefsReleased: CONDITION;
scratchRefsAvailable: BOOLEAN ¬ FALSE;
scratchRefs: ScratchRefs;
scratchRefsAllocCount: INT ¬ InitScratchRefs[100];
scratchRefsWaitCount: INT ¬ 0; -- stats only
InitScratchRefs:
ENTRY
PROC [timeout: Process.Milliseconds]
RETURNS [initialCount:
INT ¬ 1] ~
TRUSTED {
scratchRefs ¬ [NodeReader.New[], 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]}
ELSE {allocated ¬ [NodeReader.New[], NodeStyleOps.Create[], NodeStyleOps.Create[]]; scratchRefsAllocCount ¬ scratchRefsAllocCount + 1};
};
ReleaseScratchRefs:
ENTRY
PROC [allocated: ScratchRefs] ~ {
IF
NOT scratchRefsAvailable
THEN {
scratchRefs ¬ allocated;
scratchRefsAvailable ¬ TRUE;
NOTIFY scratchRefsReleased;
};
};
GetWidthArray:
PROC [font: Font]
RETURNS [widths: CommonWidths] ~ {
widths ¬ CheckWidthCache[font];
IF widths =
NIL
THEN {
widths ¬ NEW[CommonWidthsArray];
FOR c: CommonCharCode
IN CommonCharCode
DO
widths[c] ¬ Scaled.FromReal[font.Escapement[c].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: Tioga.Looks ¬ Tioga.allLooks,
tabCharLooks: Tioga.Looks ¬ Tioga.noLooks
];
oops: CARDINAL ¬ 0; -- should stay 0 unless there is a bug
GetTabStop:
PROC [style: Style, i:
NAT]
RETURNS [NodeStyle.TabStop] ~ {
tabStop: NodeStyle.TabStop ¬ style.defaultTabStops;
IF i < style.numTabStops
THEN {
t: LIST OF NodeStyle.TabStop ¬ style.tabStops;
FOR j:
NAT
DECREASING
IN (i..style.numTabStops)
DO
IF t = NIL THEN oops ¬ oops + 1 ELSE t ¬ t.rest;
ENDLOOP;
IF t = NIL THEN oops ¬ oops + 1 ELSE tabStop ¬ t.first;
};
RETURN [tabStop]
};
ComputeTabWidth:
PROC [style: Style, spaceWidth: Scaled.Value, endX: Scaled.Value, nTabs:
NAT]
RETURNS [width: Scaled.Value] ~ {
tabStop: NodeStyle.TabStop ~ GetTabStop[style, nTabs];
IF tabStop =
NIL
THEN {
tabWidth: Scaled.Value ¬ Scaled.FromReal[MAX[NodeStyle.GetReal[style, tabStops], 1.0]];
toNextTab: Scaled.Value ¬ Scaled.MINUS[tabWidth, Scaled.ValRep[Scaled.IntRep[endX] MOD Scaled.IntRep[tabWidth]]];
width ¬ toNextTab;
IF Scaled.GREATER[spaceWidth, toNextTab] THEN width ¬ Scaled.PLUS[width, tabWidth];
}
ELSE {
tabLoc: Scaled.Value ~ Scaled.FromReal[NodeStyle.GetTabLoc[tabStop, style]];
minLoc: Scaled.Value ~ Scaled.PLUS[endX, spaceWidth];
newLoc: Scaled.Value ~ IF Scaled.GREATER[minLoc, tabLoc] THEN minLoc ELSE tabLoc;
width ¬ Scaled.MINUS[newLoc, endX];
};
};
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 ¬ Scaled.PLUS[sx, 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 ~ {
CodeTimer.StartInt[$FormatLine, $PTioga];
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];
CodeTimer.StopInt[$FormatLine, $PTioga];
RETURN;
};
};
NormalFormatLine[lineInfo, node, startOffset, nodeStyle, lineWidth, doLigsAndKern];
IF lineInfo.ymax < lineInfo.ymin
THEN {
lineInfo.ymax ¬ NodeStyle.GetInt[nodeStyle, leading];
lineInfo.ymin ¬ 0;
};
CodeTimer.StopInt[$FormatLine, $PTioga];
};
xCR: XCHAR ~ Char.Widen[Ascii.CR];
xLF: XCHAR ~ Char.Widen[Ascii.LF];
xSP: XCHAR ~ Char.Widen[Ascii.SP];
xTAB: XCHAR ~ Char.Widen[Ascii.TAB];
xDash: XCHAR ~ Char.Widen['-];
xHyphen: XCHAR ~ Char.Make[set: 41B, code: 76B];
xEnDash: XCHAR ~ Char.Make[set: 357B, code: 44B];
xEmDash: XCHAR ~ Char.Make[set: 357B, code: 45B];
xFigDash: XCHAR ~ Char.Make[set: 357B, code: 46B];
xOldDash: XCHAR ~ Char.Make[set: 0B, code: 30B];
xDiscHyphen: XCHAR ~ Char.Make[set: 357B, code: 43B];
placeholderOpen: XCHAR ~ VAL[1];
placeholderClose: XCHAR ~ VAL[2];
placeholderMax:
XCHAR ~
MAX[placeholderOpen, placeholderClose];
ExtraFormatInfo: TYPE ~ REF ExtraFormatInfoRep;
ExtraFormatInfoRep:
PUBLIC
TYPE ~
RECORD [
-- export to TEditFormat
charPropList: Tioga.PropList,
charPostfix: REF,
characterArtwork: CharacterArtwork,
underlineDY: REAL,
underlineHeight: REAL,
underlineColor: ImagerColor.Color,
strikeoutDY: REAL,
strikeoutHeight: REAL,
strikeoutColor: ImagerColor.Color,
letterspace: Scaled.Value,
hShift: REAL,
background: BOOL,
backgroundAscent: REAL,
backgroundDescent: REAL,
backgroundColor: ImagerColor.Color,
outlineBoxBearoff: REAL,
outlineBoxThickness: REAL,
outlineBoxColor: ImagerColor.Color,
hyphenChar: XCHAR
];
GetPropFromList:
PROC [list: Tioga.PropList, key:
ATOM]
RETURNS [
REF] ~
INLINE {
RETURN [IF list = NIL THEN NIL ELSE Prop.Get[list, key]]
};
Complain:
PROC [msg:
ROPE] ~ {
SimpleFeedback.Append[$Tioga, oneLiner, $Error, msg];
SimpleFeedback.Blink[$Tioga, $Error];
};
NormalFormatLine:
PROC [
lineInfo: TEditFormat.LineInfo,
node: Tioga.Node,
startOffset: INT,
nodeStyle: Style,
lineWidth: Scaled.Value,
doLigsAndKern: BOOLEAN ¬ FALSE
] ~ {
my: ScratchRefs ~ AllocScratchRefs[];
Formatting constants for the line:
leftExtra: REAL ~ (IF startOffset=0 THEN nodeStyle.GetReal[firstIndent] ELSE nodeStyle.GetReal[restIndent]);
rightExtra: REAL ~ (IF startOffset=0 THEN nodeStyle.GetReal[firstIndentRight] ELSE 0);
leftIndent: Scaled.Value ~ Scaled.FromReal[nodeStyle.GetReal[leftIndent]+leftExtra];
rightIndent: Scaled.Value ~ Scaled.FromReal[nodeStyle.GetReal[rightIndent]+rightExtra];
lineLength: Scaled.Value ~ Scaled.FromReal[nodeStyle.GetReal[lineLength]];
trimmedLineWidth: Scaled.Value ~ Scaled.MINUS[Scaled.MINUS[(IF Scaled.GREATER[lineLength, Scaled.zero] AND Scaled.LESS[lineLength, lineWidth] THEN lineLength ELSE lineWidth), leftIndent], rightIndent];
Basic formatting variables:
fontWidths: CommonWidths ¬ NIL;
charLooks: Tioga.Looks ¬ Tioga.noLooks;
charProps: Tioga.PropList ¬ NIL;
haveCharacterArtwork: BOOL ¬ FALSE;
curFormatNumber: TEditFormat.FormatNumber ¬ LAST[TEditFormat.FormatNumber];
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;
hyphenation: TEditFormat.HyphProc ¬ NIL;
hyphenationData: REF ¬ NIL;
tabs: TabState;
NodeStyleExtents:
PROC
RETURNS [ymax, ymin:
INTEGER] ~ {
font: Font ~ NodeStyle.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 ~ lineInfo.formatInfo[f].extension;
IF ext.characterArtwork # NIL THEN RETURN [ext.characterArtwork.amplified]
ELSE RETURN [FALSE];
};
reader: NodeReader.Ref ~ NodeReader.New[node, my.nodeReader];
nodeSize: INT ~ NodeReader.Size[reader];
nodeChars: INT ~ nodeSize-MIN[startOffset, nodeSize];
maxNChars: TEditFormat.CharNumber ~ MIN[nodeChars, TEditFormat.CharNumber.LAST];
maxIndex: INT ¬ -1;
nTabs: NAT ¬ 0;
hyphenated: BOOL ¬ FALSE;
NodeStyleOps.Copy[dest: my.tabStyle, source: nodeStyle]; -- may need for fancy tabs.
lineInfo.nChars ¬ maxNChars;
FOR curIndex:
NAT
IN [0..maxNChars)
DO
nodeIndex: INT ~ startOffset+curIndex;
cur: NodeReader.CharInfo ~ NodeReader.Fetch[reader, maxIndex ¬ nodeIndex];
xchar: XCHAR ¬ cur.char;
crBreak: BOOL ¬ FALSE;
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 [xChar IN [Char.Widen['a]..Char.Widen['z]]
OR xChar IN [Char.Widen['A]..Char.Widen['Z]]]
};
IF curIndex > 0
AND IsLetter[lineInfo.charInfo[curIndex-1].char]
AND NOT Scaled.GREATER[Scaled.PLUS[endX, width], trimmedLineWidth]
AND (maxNChars-curIndex) > 1
AND IsLetter[NodeReader.FetchChar[reader, maxIndex ¬ nodeIndex+1]]
THEN {
breakIndex ¬ curIndex + 1;
breakX ¬ Scaled.PLUS[endX, width];
};
};
IF curIndex=0
OR cur.looks#charLooks
OR cur.props#charProps
OR haveCharacterArtwork
OR xchar <= placeholderMax
THEN {
realVShift: REAL;
new: TEditFormat.FormatInfoEntry;
newExtension: ExtraFormatInfo ¬ NIL;
fontBoundingBox: ImagerFont.Extents;
charStyle: Style ¬ nodeStyle;
charPostfix, charArtwork: REF ¬ NIL;
IF (xchar = placeholderOpen
OR xchar = placeholderClose)
THEN {
Fake out the placeholder as Artwork.
charArtwork ¬ Rope.Literal["Placeholder"];
};
charLooks ¬ cur.looks;
charProps ¬ cur.props;
IF charProps#
NIL
THEN {
charPostfix ¬ Prop.Get[charProps, $Postfix];
charArtwork ¬ Prop.Get[charProps, $Artwork];
};
new.unique ¬ haveCharacterArtwork ¬ FALSE;
First a quick look through the first few formats to see if it is a common one.
IF charArtwork =
NIL
THEN {
FOR f: TEditFormat.FormatNumber
IN [0..
MIN[lineInfo.formatInfo.length, 9])
DO
finfo: TEditFormat.FormatInfoEntry ~ lineInfo.formatInfo[f];
e: ExtraFormatInfo ~ finfo.extension;
IF finfo.looks=charLooks
AND e.charPostfix=charPostfix
AND
NOT finfo.unique
THEN {
curFormatNumber ¬ f;
extension ¬ e;
fontWidths ¬ GetWidthArray[finfo.font];
GOTO QuickExit;
};
ENDLOOP;
};
curFormatNumber ¬ lineInfo.formatInfo.length;
IF curFormatNumber>=lineInfo.formatInfo.maxLength THEN ExpandFormats[lineInfo];
extension ¬ lineInfo.formatInfo[curFormatNumber].extension;
IF extension=NIL THEN extension ¬ NEW[ExtraFormatInfoRep];
new.extension ¬ newExtension ¬ extension;
newExtension.charPropList ¬ charProps;
newExtension.charPostfix ¬ charPostfix;
newExtension.characterArtwork ¬ NIL;
IF charLooks#Tioga.noLooks
THEN {
charStyle ¬ my.charStyle;
NodeStyleOps.Copy[dest: charStyle, source: nodeStyle];
NodeStyleOps.ApplyLooks[charStyle, charLooks, charStyle.kind !
NodeStyleWorks.Where => {
RESUME [Convert.RopeFromInt[from: TextNode.LocNumber[[node, nodeIndex]]]]
}
];
};
IF charPostfix #
NIL
THEN {
IF charStyle = nodeStyle
THEN {
charStyle ¬ my.charStyle;
NodeStyleOps.Copy[dest: charStyle, source: nodeStyle];
};
NodeStyleOps.ApplyObject[charStyle, charPostfix, charStyle.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, nodeIndex], charStyle !
RuntimeError.
UNCAUGHT =>
IF
NOT debug
THEN {
Complain["TEditFormatImpl: Character Artwork Format bug"];
CONTINUE;
}
];
IF newExtension.characterArtwork #
NIL
THEN {
vShift: REAL ~ NodeStyle.GetReal[charStyle, vshift];
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 ¬ NodeStyle.GetFont[charStyle];
new.color ¬ NodeStyle.GetColor[charStyle, text];
fontBoundingBox ¬ ImagerFont.FontBoundingBox[new.font];
new.looks ¬ charLooks;
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 ¬ NodeStyle.GetColor[charStyle, underline];
};
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 ¬ NodeStyle.GetColor[charStyle, strikeout];
};
-- 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 ¬ NodeStyle.GetColor[charStyle, background];
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 ¬ NodeStyle.GetColor[charStyle, outlineBox];
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.GetReal[charStyle, vshift];
new.vShift ¬ realVShift;
newExtension.hyphenChar ¬ NodeStyle.GetHyphenChar[charStyle];
lineInfo.ymax ¬ MAX[lineInfo.ymax, CeilingI[fontBoundingBox.ascent + realVShift]];
lineInfo.ymin ¬ MIN[lineInfo.ymin, FloorI[-fontBoundingBox.descent + realVShift]];
fontWidths ¬ GetWidthArray[new.font];
lineInfo.formatInfo[curFormatNumber] ¬ new;
lineInfo.formatInfo.length ¬ curFormatNumber + 1;
EXITS QuickExit => NULL;
};
IF curIndex >= highWaterChars
THEN {
ExpandChars[lineInfo];
highWaterChars ¬ lineInfo.charInfo.maxLength;
};
IF haveCharacterArtwork
THEN {
width ¬ Scaled.FromReal[extension.characterArtwork.escapement.x];
doLetterspace ¬ FALSE;
spaceAtEnd ¬ Scaled.zero;
IF extension.characterArtwork.amplified THEN spaceWidths ¬ Scaled.PLUS[spaceWidths, width];
}
ELSE
SELECT xchar
FROM
xCR, xLF => {
crBreak ¬ TRUE;
width ¬ fontWidths[xSP];
breakIndex ¬ curIndex + 1;
breakX ¬ Scaled.PLUS[endX, width];
doLetterspace ¬ FALSE;
};
xSP => {
width ¬ fontWidths[xSP];
spaceWidths ¬ Scaled.PLUS[spaceWidths, width];
breakIndex ¬ curIndex + 1;
breakX ¬ Scaled.PLUS[endX, width];
spaceAtEnd ¬ Scaled.PLUS[spaceAtEnd, width];
doLetterspace ¬ FALSE;
};
xDash => {
width ¬ fontWidths[xDash];
spaceAtEnd ¬ Scaled.zero;
TryBreakAfterDash[];
};
xTAB => {
spaceWidths ¬ Scaled.zero;
lineInfo.startAmplifyIndex ¬ curIndex+1;
width ¬ ComputeTabWidth[style: nodeStyle, spaceWidth: fontWidths[xSP], endX: endX, nTabs: nTabs];
breakIndex ¬ curIndex + 1;
breakX ¬ Scaled.PLUS[endX, width];
spaceAtEnd ¬ Scaled.PLUS[spaceAtEnd, width];
nTabs ¬ nTabs + 1;
doLetterspace ¬ FALSE;
};
IN CommonCharCode => {width ¬ fontWidths[xchar]; spaceAtEnd ¬ Scaled.zero};
ENDCASE => {
font: Font ~ lineInfo.formatInfo[curFormatNumber].font;
width ¬ Scaled.FromReal[ImagerFont.Escapement[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 ~ Scaled.Scale[Scaled.PLUS[prevLetterspace, extension.letterspace], -1];
lineInfo.charInfo[curIndex-1].alteredWidth ¬ pad#Scaled.zero;
lineInfo.charInfo[curIndex-1].width ¬ Scaled.PLUS[lineInfo.charInfo[curIndex-1].width, pad];
endX ¬ Scaled.PLUS[endX, pad];
};
prevLetterspace ¬ extension.letterspace;
};
prevDoLetterspace ¬ doLetterspace;
lineInfo.charInfo[curIndex] ¬ [char: xchar, formatNumber: curFormatNumber,
alteredWidth: alteredWidth, amplified: FALSE, width: width];
endX ¬ Scaled.PLUS[endX, width];
IF crBreak
THEN {
nlr: ROPE ~ TextEdit.GetNewlineDelimiter[TextNode.Root[node]];
newline: XCHAR ~ Char.Widen[IF Rope.Size[nlr]>0 THEN Rope.Fetch[nlr, 0] ELSE '\r];
lineInfo.nBlankCharsAtEnd ¬ (IF xchar = newline THEN 1 ELSE 0);
breakSpaceAtEnd ¬ width;
lineInfo.break ¬ cr;
lineInfo.nChars ¬ curIndex + 1;
EXIT
};
IF Scaled.
GREATER[Scaled.
MINUS[endX, spaceAtEnd], trimmedLineWidth]
THEN {
partialWordWidth: Scaled.Value ~ Scaled.MINUS[Scaled.MINUS[endX, spaceAtEnd], breakX];
lineInfo.break ¬ wrap;
IF spaceAtEnd=Scaled.zero
AND (lineInfo.nChars-breakIndex)>=minHyphLetters
THEN {
hyphenationClass: ATOM ~ NodeStyle.GetName[nodeStyle, hyphenation];
hyph: Hyph ~ FetchHyph[hyphenationClass];
IF hyph#
NIL
THEN {
h: TEditFormat.HyphenationPositions ¬ ALL[0];
index0: INT ~ startOffset+breakIndex;
index1: INT ¬ index0;
WHILE index1<nodeSize
DO
c: XCHAR ~ NodeReader.FetchChar[reader, index1];
alpha:
BOOL ~
SELECT Char.Set[c]
FROM
0 =>
SELECT Char.Narrow[c]
FROM
IN['a..'z], IN['A..'Z], IN['\301..'\321]--accents-- => TRUE,
ENDCASE => FALSE,
46B--greek--, 47B--cyrillic-- => TRUE,
357B => (Char.Code[c]=43B), --discretionary hyphen--
ENDCASE => FALSE;
maxIndex ¬ MAX[maxIndex, index1];
IF alpha THEN index1 ¬ index1+1 ELSE EXIT;
ENDLOOP;
IF index1>index0 THEN h ¬ hyph.proc[node: node,
start: index0, len: index1-index0, hyphData: hyph.data];
IF h[0] > 0
THEN {
formatNum: NAT ¬ NAT.LAST;
hyphChar: XCHAR ¬ VAL[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 ~ fie.extension;
hyphChar ¬ extension.hyphenChar;
hyphWidth ¬ Scaled.FromReal[ImagerFont.Escapement[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 < TEditFormat.maxHyph
AND h[i] > 0
AND Scaled.
GREATER[trimmedLineWidth, Scaled.
PLUS[w, GetHyphWidth[b+j]]]
AND
NOT artChar
DO
IF j = h[i]
THEN {
breakIndex ¬ b+j;
breakX ¬ Scaled.PLUS[w, hyphWidth];
hyphenated ¬ TRUE;
i ¬ i + 1;
};
w ¬ Scaled.PLUS[w, 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 ¬ Scaled.MINUS[endX, 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: XCHAR ~ lineInfo.charInfo[j].char;
SELECT c
FROM
xSP => {
width: Scaled.Value ~ lineInfo.charInfo[j].width;
spaceWidths ¬ Scaled.MINUS[spaceWidths, width];
breakSpaceAtEnd ¬ Scaled.PLUS[breakSpaceAtEnd, width];
lineInfo.nBlankCharsAtEnd ¬ lineInfo.nBlankCharsAtEnd + 1;
};
xTAB => {
breakSpaceAtEnd ¬ Scaled.PLUS[breakSpaceAtEnd, 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 Scaled.
GREATER[spaceWidths, Scaled.zero]
THEN
TRUSTED {
residual: Scaled.Value ~ Scaled.MINUS[trimmedLineWidth, Scaled.MINUS[endX, breakSpaceAtEnd]];
maxHorizontalExpansion: REAL ~ NodeStyle.GetReal[nodeStyle, maxHorizontalExpansion];
amplify: REAL ~ MAX[MIN[Scaled.Float[Scaled.PLUS[residual, 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 ¬ Scaled.MINUS[Scaled.PLUS[leftIndent, trimmedLineWidth], Scaled.MINUS[endX, breakSpaceAtEnd]]};
Centered => {
lineInfo.xOffset ¬ Scaled.PLUS[leftIndent, Scaled.Scale[Scaled.MINUS[trimmedLineWidth, Scaled.MINUS[endX, breakSpaceAtEnd]], -1]];
};
ENDCASE => ERROR;
IF hyphenated THEN { lineInfo.nBlankCharsAtEnd ¬ NAT.LAST };
IF lineInfo.amplifySpace = 1.0 THEN lineInfo.startAmplifyIndex ¬ lineInfo.nChars;
lineInfo.nextPos ¬ [node, startOffset+lineInfo.nChars];
lineInfo.index--nCharsExamined-- ¬ MAX[startOffset, maxIndex+1]-startOffset;
ComputePositionVector[lineInfo];
lineInfo.xmin ¬ 0;
lineInfo.xmax ¬ lineInfo.positionInfo[lineInfo.nChars];
ReleaseScratchRefs[my];
};
HyphenationPositions: TYPE ~ TEditFormat.HyphenationPositions;
HyphProc:
TYPE ~ TEditFormat.HyphProc;
Hyph: TYPE ~ REF HyphRep;
HyphRep:
TYPE ~
RECORD [proc: HyphProc, data:
REF];
hyphTab: RefTab.Ref ~ RefTab.Create[mod: 3];
FetchHyph:
PROC [hyphenationClass:
ATOM]
RETURNS [Hyph] ~ {
WITH RefTab.Fetch[hyphTab, hyphenationClass].val
SELECT
FROM
hyph: Hyph => RETURN[hyph];
ENDCASE => RETURN[NIL];
};
RegisterHyphenation:
PUBLIC
PROC [hyphenationClass:
ATOM, hyphProc: HyphProc, hyphData:
REF]
RETURNS [oldProc: HyphProc
¬
NIL, oldData:
REF ¬
NIL] ~ {
action: RefTab.UpdateAction ~ {
WITH val SELECT FROM old: Hyph => [oldProc, oldData] ← old ENDCASE;
IF hyphProc=NIL THEN RETURN[delete]
ELSE RETURN[store, NEW[HyphRep ¬ [hyphProc, hyphData]]];
};
RefTab.Update[hyphTab, hyphenationClass, action];
};
Artwork classes
RegisterArtwork:
PUBLIC
PROC [a: ArtworkClass] ~ {
[] ¬ RefTab.Store[artworkRegistry, a.name, a];
};
UnRegisterArtwork:
PUBLIC
PROC [a:
ATOM] ~ {
[] ¬ RefTab.Delete[artworkRegistry, a];
};
GetArtworkClass:
PUBLIC
PROC [a:
ATOM]
RETURNS [ArtworkClass] ~ {
RETURN [NARROW[RefTab.Fetch[artworkRegistry, a].val]];
};
GetArtworkClassForNode:
PROC [node: Tioga.Node]
RETURNS [ArtworkClass] ~ {
className: ATOM ¬ NIL;
prop: REF ~ TextEdit.GetProp[node, $Artwork];
WITH prop
SELECT
FROM
a: ATOM => className ¬ a;
r: ROPE => className ¬ Atom.MakeAtom[r];
ENDCASE => NULL;
IF className#
NIL
THEN
WITH RefTab.Fetch[artworkRegistry, className].val
SELECT
FROM
a: ArtworkClass => RETURN [IF artworkEnabled THEN a ELSE NIL];
ENDCASE => NULL;
RETURN [NIL];
};
artworkRegistry: RefTab.Ref ~ RefTab.Create[mod: 5];
nonArtworkClass: ArtworkClass ~
NEW[ArtworkClassRep ¬ [
name: NIL,
format: NormalFormatLine,
paint: NormalPaint,
resolve: NormalResolve,
charPosition: NormalCharPosition,
boundingBox: NormalBoundingBox
]];
artworkEnabled: BOOL ¬ FALSE;
ArtworkEnabled: PUBLIC ENTRY PROC RETURNS [BOOL] ~ {RETURN [artworkEnabled]};
SetArtworkEnabled:
PUBLIC
ENTRY
PROC [enabled:
BOOL]
RETURNS [was:
BOOL] ~ {was ¬ artworkEnabled; artworkEnabled ¬ enabled};
Silly: HyphProc ~ {
h: HyphenationPositions ¬ [3,6,9,12,15,18,21,0,0,0,0,0,0,0,0,0];
RETURN [h];
};
RegisterArtwork[nonArtworkClass];
RegisterCharacterArtwork[vRuleClass];
RegisterCharacterArtwork[placeholderClass];
[] ¬ RegisterHyphenation[$Silly, Silly,
NIL];