TEditFormatImpl.mesa
Copyright Ó 1983, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved.
Rick Beach, July 15, 1987 11:10:44 pm PDT
Bier, February 22, 1989 0:04:21 am PST
Michael Plass, February 10, 1992 3:18 pm PST
Doug Wyatt, August 6, 1992 4:25 pm PDT
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];
TEditFormatImpl: CEDAR MONITOR
IMPORTS Atom, Char, CodeTimer, Convert, Imager, ImagerBackdoor, ImagerFont, NodeReader, NodeStyle, NodeStyleOps, NodeStyleWorks, Process, Prop, Real, RefTab, Rope, RuntimeError, Scaled, SimpleFeedback, TextEdit, TextNode
EXPORTS TEditFormat
~ BEGIN
XCHAR: TYPE ~ Char.XCHAR;
nullXChar: XCHAR ~ XCHAR.LAST;
ROPE: TYPE ~ Rope.ROPE;
Font: TYPE ~ ImagerFont.Font;
Style: TYPE ~ NodeStyle.Style;
StyleKind: TYPE ~ NodeStyle.StyleKind;
ArtworkClass: TYPE ~ TEditFormat.ArtworkClass;
ArtworkClassRep: TYPE ~ TEditFormat.ArtworkClassRep;
CharacterArtwork: TYPE ~ TEditFormat.CharacterArtwork;
CharacterArtworkClass: TYPE ~ TEditFormat.CharacterArtworkClass;
minAmplifySpace: REAL ¬ 0.5;
minHyphLetters: NAT ¬ 7;
debug: BOOL ¬ FALSE;
Basic functions
FloorI: PROC [real: REAL] RETURNS [i: INTEGER] ~ {
i ¬ Real.Round[real];
IF i > real THEN i ¬ i - 1;
};
CeilingI: PROC [real: REAL] RETURNS [i: INTEGER] ~ {
i ¬ Real.Round[real];
IF i < real THEN i ¬ i + 1;
};
scratchLineInfo: TEditFormat.LineInfo ¬ NIL;
scratchLineInfoCount: INT ¬ 0;
scratchLineInfoLimit: INT ¬ 300;
scratchLineInfoAllocated: INT ¬ 0;
scratchLineInfoDestroyed: INT ¬ 0;
Allocate: PUBLIC ENTRY PROC RETURNS [lineInfo: TEditFormat.LineInfo] ~ {
IF scratchLineInfo # NIL THEN {
lineInfo ¬ scratchLineInfo;
scratchLineInfo ¬ lineInfo.link;
lineInfo.link ¬ NIL;
scratchLineInfoCount ¬ scratchLineInfoCount-1;
}
ELSE {
CodeTimer.StartInt[$AllocateNewLineInfo, $PTioga];
lineInfo ¬ NEW[TEditFormat.LineInfoRec];
lineInfo.charInfo ¬ NEW[TEditFormat.CharInfoRec[125]];
lineInfo.formatInfo ¬ NEW[TEditFormat.FormatInfoRec[12]];
lineInfo.positionInfo ¬ NEW[TEditFormat.PositionInfoRec[126]];
scratchLineInfoAllocated ¬ scratchLineInfoAllocated + 1;
CodeTimer.StopInt[$AllocateNewLineInfo, $PTioga];
};
};
Release: PUBLIC ENTRY PROC [lineInfo: TEditFormat.LineInfo] ~ {
last: TEditFormat.LineInfo ¬ NIL;
count: INT ¬ 0;
FOR i: TEditFormat.LineInfo ¬ lineInfo, i.link UNTIL i=NIL DO
f: TEditFormat.FormatInfo ¬ i.formatInfo;
FOR i: NAT IN [0..f.length) DO
Avoid having unneeded REFs hanging around.
f[i].font ¬ NIL;
f[i].color ¬ NIL;
f[i].tab ¬ NIL;
f[i].extension is retained for re-use.
ENDLOOP;
i.artworkClass ¬ NIL;
i.artworkData ¬ NIL;
i.data ¬ NIL;
i.index ¬ 0;
f.length ¬ 0;
last ¬ i;
count ¬ count + 1;
ENDLOOP;
IF count > 0 THEN {
last.link ¬ scratchLineInfo;
scratchLineInfo ¬ lineInfo;
scratchLineInfoCount ¬ scratchLineInfoCount + count;
};
WHILE scratchLineInfoCount > scratchLineInfoLimit DO
i: TEditFormat.LineInfo ¬ scratchLineInfo;
scratchLineInfo ¬ i.link;
scratchLineInfoCount ¬ scratchLineInfoCount - 1;
i.charInfo ¬ NIL;
i.formatInfo ¬ NIL;
i.positionInfo ¬ NIL;
scratchLineInfoDestroyed ¬ scratchLineInfoDestroyed + 1;
ENDLOOP;
};
Cache of character width arrays
widthCacheSize: NAT ¬ 12;
widthCache: LIST OF WidthCacheRec ¬ NIL;
widthCacheHits: INT ¬ 0;
widthCacheMisses: INT ¬ 0;
CommonCharCode: TYPE ~ XCHAR[VAL[040B]..VAL[176B]];
CommonWidths: TYPE ~ REF CommonWidthsArray;
CommonWidthsArray: TYPE ~ ARRAY CommonCharCode OF Scaled.Value;
WidthCacheRec: TYPE ~ RECORD [
font: Font,
widths: CommonWidths
];
CheckWidthCache: ENTRY PROC [font: Font] RETURNS [CommonWidths] ~ {
prev: LIST OF WidthCacheRec ¬ NIL;
FOR c: LIST OF WidthCacheRec ¬ widthCache, c.rest UNTIL c = NIL DO
IF c.first.font = font THEN {
IF prev # NIL THEN {
Move to front
prev.rest ¬ c.rest;
c.rest ¬ widthCache;
widthCache ¬ c;
};
widthCacheHits ¬ widthCacheHits + 1;
RETURN [c.first.widths];
};
prev ¬ c;
ENDLOOP;
widthCacheMisses ¬ widthCacheMisses + 1;
RETURN [NIL]
};
EnterWidthCache: ENTRY PROC [font: Font, widths: CommonWidths] ~ {
new: LIST OF WidthCacheRec ¬ NIL;
prev: LIST OF WidthCacheRec ¬ NIL;
i: NAT ¬ 2;
NB: The following code forces an effective cache size of >= 2
FOR p: LIST OF WidthCacheRec ¬ widthCache, p.rest DO
IF p = NIL THEN {new ¬ LIST[[font, widths]]; EXIT};
IF i >= widthCacheSize AND p.rest#NIL THEN {
new ¬ p.rest;
p.rest ¬ NIL;
new.rest ¬ NIL;
new.first ¬ [font, widths];
EXIT;
};
i ¬ i + 1;
ENDLOOP;
new.rest ¬ widthCache;
widthCache ¬ new;
};
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];
};
Painting
CharClass: PROC [char: XCHAR] RETURNS [charClass: NodeStyle.FontUnderlining] ~ {
IF Char.Set[char] = 0 THEN {
charClass ¬ SELECT CHAR[VAL[Char.Code[char]]] FROM
Ascii.SP, Ascii.TAB, Ascii.CR, Ascii.LF => All,
IN ['a..'z], IN ['A..'Z], IN ['0..'9] => LettersAndDigits,
ENDCASE => Visible;
}
ELSE IF Char.Code[char] MOD 128 IN [041B..177B) THEN charClass ¬ Visible
ELSE charClass ¬ All;
};
Paint: PUBLIC TEditFormat.PaintProc ~ {
class: ArtworkClass ~ lineInfo.artworkClass;
IF class # NIL AND class.paint#NIL THEN class.paint[lineInfo, context]
ELSE NormalPaint[lineInfo, context];
};
PaintBackground: PROC [lineInfo: TEditFormat.LineInfo, context: Imager.Context] ~ {
nChars: NAT ~ lineInfo.nChars+(IF lineInfo.nBlankCharsAtEnd=NAT.LAST THEN 1 ELSE -lineInfo.nBlankCharsAtEnd);
charInfo: TEditFormat.CharInfo ~ lineInfo.charInfo;
formatInfo: TEditFormat.FormatInfo ~ lineInfo.formatInfo;
lastSetColor: ImagerColor.Color ¬ Imager.black; -- assume black coming in
SetColor: PROC [color: ImagerColor.Color] ~ INLINE {IF lastSetColor # color THEN {Imager.SetColor[context, color]; lastSetColor ¬ color}};
Action: PROC ~ {
Boxes: PROC [outline: BOOL] ~ {
prevFormatNumber: NAT ¬ NAT.LAST;
x: Scaled.Value ¬ Scaled.zero;
pad: ImagerFont.Extents ¬ [0,0,0,0];
xMin: REAL ¬ 0.0;
EmitBox: PROC ~ {
xMax: REAL ~ Scaled.Float[x];
IF pad.ascent + pad.descent > 0 THEN Imager.MaskBox[context, [xmin: xMin - pad.leftExtent, ymin: -pad.descent, xmax: xMax + pad.rightExtent, ymax: pad.ascent]];
xMin ¬ xMax;
};
FOR i: NAT IN [0..nChars) DO
formatNumber: TEditFormat.FormatNumber ~ charInfo[i].formatNumber;
format: TEditFormat.FormatInfoEntry ~ formatInfo[formatNumber];
extension: ExtraFormatInfo ~ format.extension;
thickness: REAL ~ IF outline THEN extension.outlineBoxThickness ELSE 0;
something: BOOL ~ (NOT outline) OR thickness > 0.0;
IF prevFormatNumber # formatNumber THEN {
EmitBox[];
pad ¬ IF extension.background AND something
THEN [
leftExtent: extension.outlineBoxBearoff + thickness,
rightExtent: extension.outlineBoxBearoff + thickness,
ascent: extension.backgroundAscent + thickness,
descent: extension.backgroundDescent + thickness
]
ELSE [0,0,0,0];
IF extension.background THEN SetColor[IF outline THEN extension.outlineBoxColor ELSE extension.backgroundColor];
prevFormatNumber ¬ formatNumber;
};
x ¬ Scaled.PLUS[x, charInfo[i].width];
ENDLOOP;
EmitBox[];
};
Imager.Move[context];
Boxes[outline: TRUE];
Boxes[outline: FALSE];
};
Imager.DoSave[context, Action];
};
colorBug: CARDINAL ¬ 0;
NormalPaint: PUBLIC PROC [lineInfo: TEditFormat.LineInfo, context: Imager.Context] ~ {
nChars: NAT ~ lineInfo.nChars+(IF lineInfo.nBlankCharsAtEnd=NAT.LAST THEN 1 ELSE -lineInfo.nBlankCharsAtEnd);
i: NAT ¬ 0;
charInfo: TEditFormat.CharInfo ~ lineInfo.charInfo;
formatInfo: TEditFormat.FormatInfo ~ lineInfo.formatInfo;
lastSetColor: ImagerColor.Color ¬ Imager.black; -- assume black coming in
SetColor: PROC [color: ImagerColor.Color] ~ INLINE {IF lastSetColor # color THEN {Imager.SetColor[context, color]; lastSetColor ¬ color}};
lastAmplify: REAL ¬ 1.0; -- assume no amplification coming in
SetAmplifySpace: PROC [a: REAL] ~ INLINE {IF a # lastAmplify THEN {Imager.SetAmplifySpace[context, a]; lastAmplify ¬ a}};
lastSetFont: Font ¬ NIL;
IF context.state#NIL AND ImagerBackdoor.GetColor[context]#Imager.black THEN colorBug ¬ colorBug + 1;
IF lineInfo.hasBackground THEN PaintBackground[lineInfo, context];
WHILE i < nChars DO
formatNumber: TEditFormat.FormatNumber ~ charInfo[i].formatNumber;
format: TEditFormat.FormatInfoEntry ~ formatInfo[formatNumber];
extension: ExtraFormatInfo ~ format.extension;
worry: BOOL ~ NOT (format.underlining = None AND format.strikeout = None);
doUnderline: BOOLEAN ¬ FALSE;
doStrikeout: BOOLEAN ¬ FALSE;
deltaWidth: REAL ¬ 0.0;
tabFound: BOOL ¬ FALSE;
newi: NAT ¬ 0;
ShowProc: PROC [charAction: PROC [char: XCHAR]] ~ TRUSTED {
info: TEditFormat.CharInfo ~ charInfo;
end: NAT ~ nChars;
f: TEditFormat.FormatNumber ~ formatNumber;
whatMeWorry: BOOL ~ worry;
j: NAT ¬ i;
entry: LONG POINTER TO TEditFormat.CharInfoEntry ¬ NIL;
IF j < end THEN entry ¬ @(info[j]);
WHILE j < end DO
char: XCHAR ~ entry.char;
IF entry.formatNumber # f THEN EXIT;
IF whatMeWorry THEN {
charClass: NodeStyle.FontUnderlining ~ CharClass[char];
charUnderline: BOOLEAN ~ format.underlining >= charClass;
charStrikeout: BOOLEAN ~ format.strikeout >= charClass;
IF doUnderline # charUnderline OR doStrikeout # charStrikeout THEN EXIT;
};
IF char=xTAB THEN {tabFound ¬ TRUE; EXIT};
IF NOT (char=nullXChar AND entry.alteredWidth) THEN charAction[char];
j ¬ j + 1;
IF j = lineInfo.startAmplifyIndex THEN EXIT;
IF entry.alteredWidth AND char#nullXChar THEN {
deltaWidth ¬ Scaled.Float[entry.width] - ImagerFont.Escapement[lineInfo.formatInfo[entry.formatNumber].font, char].x;
EXIT;
};
entry ¬ entry + SIZE[TEditFormat.CharInfoEntry];
ENDLOOP;
newi ¬ j;
};
IF lastSetFont # format.font THEN {
Imager.SetFont[context, format.font];
lastSetFont ¬ format.font;
};
IF i = lineInfo.startAmplifyIndex THEN SetAmplifySpace[lineInfo.amplifySpace];
SetColor[format.color];
IF worry THEN {
startCharClass: NodeStyle.FontUnderlining ~ CharClass[charInfo[i].char];
doUnderline ¬ format.underlining >= startCharClass;
doStrikeout ¬ format.strikeout >= startCharClass;
};
IF doUnderline OR doStrikeout THEN {
Imager.StartUnderline[context];
};
IF format.vShift # 0.0 THEN {
Imager.SetYRel[context, format.vShift];
};
IF extension.hShift # 0.0 THEN {
Imager.SetXRel[context, extension.hShift];
};
IF extension.characterArtwork = NIL
THEN { Imager.Show[context, ShowProc]; i ¬ newi}
ELSE {
do: PROC ~ {extension.characterArtwork.paint[extension.characterArtwork, context]};
x: REAL ~ extension.characterArtwork.escapement.x;
Imager.DoSaveAll[context, do !
RuntimeError.UNCAUGHT => IF NOT debug THEN {
Complain["TEditFormatImpl: Character Artwork Paint bug"];
CONTINUE;
}
];
Imager.SetXRel[context, Scaled.Float[charInfo[i].width]];
i ¬ i + 1;
};
IF tabFound THEN {
entry: TEditFormat.CharInfoEntry ~ charInfo[i];
Imager.SetXRel[context, Scaled.Float[entry.width]];
i ¬ i + 1;
};
IF format.vShift # 0.0 THEN {
Imager.SetYRel[context, -format.vShift];
};
IF extension.hShift # 0.0 OR deltaWidth#0.0 THEN {
Imager.SetXRel[context, deltaWidth-extension.hShift];
deltaWidth ¬ 0;
};
IF doUnderline THEN {
SetColor[extension.underlineColor];
Imager.MaskUnderline[context, extension.underlineDY, extension.underlineHeight];
SetColor[format.color];
};
IF doStrikeout THEN {
SetColor[extension.strikeoutColor];
Imager.MaskUnderline[context, extension.strikeoutDY, extension.strikeoutHeight];
SetColor[format.color];
};
ENDLOOP;
SetColor[Imager.black];
SetAmplifySpace[1.0];
};
Character positions
Resolve: PUBLIC TEditFormat.ResolveProc ~ {
class: ArtworkClass ~ lineInfo.artworkClass;
[loc, xmin, width, rightOfLine] ¬
IF class # NIL AND class.resolve#NIL THEN class.resolve[lineInfo, x]
ELSE NormalResolve[lineInfo, x];
};
NormalResolve: PROC [lineInfo: TEditFormat.LineInfo, x: INTEGER] RETURNS [loc: Tioga.Location, xmin, width: INTEGER, rightOfLine: BOOLEAN] ~ {
xOffset: INT ¬ Scaled.Round[lineInfo.xOffset];
xOfi: INT ¬ lineInfo.positionInfo[0];
xx: INT ¬ INT[x] - xOffset;
FOR i: NAT IN [0..lineInfo.nChars) DO
xOfiPlusOne: INT ~ lineInfo.positionInfo[i+1];
IF i = lineInfo.nChars - 1 OR xx<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 <= xx+xx
];
xOfi ¬ xOfiPlusOne;
ENDLOOP;
RETURN[loc: lineInfo.startPos, xmin: xOffset, width: 0, rightOfLine: TRUE];
};
CharPosition: PUBLIC TEditFormat.CharPositionProc ~ {
class: ArtworkClass ~ lineInfo.artworkClass;
[x, width] ¬
IF class # NIL AND class.charPosition#NIL THEN class.charPosition[lineInfo, offset]
ELSE NormalCharPosition[lineInfo, offset];
};
NormalCharPosition: PROC [lineInfo: TEditFormat.LineInfo, offset: INT]
RETURNS [x, width: INTEGER] ~ {
IF offset < lineInfo.startPos.where THEN {x ¬ FIRST[INTEGER]; width ¬ 0}
ELSE {
i: NAT ¬ MIN[offset - lineInfo.startPos.where, LAST[NAT]];
IF i>lineInfo.nChars THEN {x ¬ LAST[INTEGER]; width ¬ 0}
ELSE IF i=lineInfo.nChars THEN {x ¬ lineInfo.positionInfo[i] + Scaled.Round[lineInfo.xOffset]; width ¬ 0}
ELSE {
x ¬ lineInfo.positionInfo[i];
width ¬ lineInfo.positionInfo[i+1] - x;
x ¬ x + Scaled.Round[lineInfo.xOffset];
};
};
};
BoundingBox: PUBLIC TEditFormat.BoundingBoxProc ~ {
class: ArtworkClass ~ lineInfo.artworkClass;
RETURN [
IF class # NIL AND class.boundingBox#NIL
THEN class.boundingBox[lineInfo, start, length]
ELSE NormalBoundingBox[lineInfo, start, length]
];
};
NormalBoundingBox: PROC [lineInfo: TEditFormat.LineInfo, start, length: INT]
RETURNS [Imager.Box] ~ {
charInfo: TEditFormat.CharInfo ~ lineInfo.charInfo;
formatInfo: TEditFormat.FormatInfo ~ lineInfo.formatInfo;
x0: REAL ¬ Real.LargestNumber;
y0: REAL ¬ Real.LargestNumber;
x1: REAL ¬ -Real.LargestNumber;
y1: REAL ¬ -Real.LargestNumber;
x: REAL ¬ 0.0;
BBPoint: PROC [p: Vector2.VEC] ~ {
IF p.x < x0 THEN x0 ¬ p.x;
IF p.x > x1 THEN x1 ¬ p.x;
IF p.y < y0 THEN y0 ¬ p.y;
IF p.y > y1 THEN y1 ¬ p.y;
};
BBox: PROC [p0, p1: Vector2.VEC] ~ {
BBPoint[p0];
BBPoint[p1];
};
FOR i: INT IN [0..MIN[start+length, lineInfo.nChars]) DO
c: TEditFormat.CharInfoEntry ~ charInfo[i];
f: TEditFormat.FormatInfoEntry ~ formatInfo[c.formatNumber];
ext: ExtraFormatInfo ~ f.extension;
w: REAL ~ Scaled.Float[c.width];
newx: REAL ~ x+w;
IF i >= start THEN {
charClass: NodeStyle.FontUnderlining ~ CharClass[charInfo[i].char];
doUnderline: BOOL ~ f.underlining >= charClass;
doStrikeout: BOOL ~ f.strikeout >= charClass;
y: REAL ~ f.vShift;
extent: ImagerFont.Extents ~ IF ext.characterArtwork # NIL THEN ext.characterArtwork.extents ELSE ImagerFont.BoundingBox[f.font, c.char];
BBox[[x-extent.leftExtent, y-extent.descent], [x+extent.rightExtent, y+extent.ascent]];
IF doUnderline THEN {
BBox[[x, -ext.underlineDY], [newx, -ext.underlineDY-ext.underlineHeight]];
};
IF doStrikeout THEN {
BBox[[x, -ext.strikeoutDY], [newx, -ext.strikeoutDY-ext.strikeoutHeight]];
};
};
x ¬ newx;
ENDLOOP;
IF x0 >= x1 THEN RETURN [[0, 0, 0, 0]]
ELSE RETURN [[xmin: x0, ymin: y0, xmax: x1, ymax: y1]];
};
Character Artwork classes
RegisterCharacterArtwork: PUBLIC PROC [class: CharacterArtworkClass] ~ {
[] ¬ RefTab.Store[characterArtworkRegistry, class.name, class];
};
UnregisterCharacterArtwork: PUBLIC PROC [name: ATOM] ~ {
[] ¬ RefTab.Delete[characterArtworkRegistry, name];
};
GetCharacterArtworkClass: PUBLIC PROC [name: ATOM] RETURNS [CharacterArtworkClass] ~ {
IF name # $Placeholder AND NOT artworkEnabled THEN RETURN [NIL];
RETURN [NARROW[RefTab.Fetch[characterArtworkRegistry, name].val]];
};
characterArtworkRegistry: RefTab.Ref ~ RefTab.Create[mod: 5];
Character Artwork example implementation
This is a sample implementation of a character artwork class. By placing a character property with key Artwork and value VRule on a vertical bar character, it can be made to format as a vertical rule with a thickness and height matching that of the outline box.
VRuleDataRep: TYPE ~ RECORD [
color: ImagerColor.Color,
ascent: REAL,
descent: REAL,
width: REAL,
bearoff: REAL
];
VRulePaint: PROC [self: CharacterArtwork, context: Imager.Context] ~ {
data: REF VRuleDataRep ~ NARROW[self.data];
Imager.Move[context];
Imager.SetColor[context, data.color];
Imager.MaskRectangle[context, [x: data.bearoff, y: -data.descent, w: data.width, h: data.ascent+data.descent]];
};
VRuleFormat: TEditFormat.CharacterFormatProc ~ {
letter: XCHAR ~ TextEdit.FetchChar[loc.node, loc.where];
IF letter = Char.Widen['|] THEN {
ascent: REAL ¬ NodeStyle.GetReal[style, backgroundAscent];
descent: REAL ¬ NodeStyle.GetReal[style, backgroundDescent];
width: REAL ~ NodeStyle.GetReal[style, outlineBoxThickness];
bearoff: REAL ~ NodeStyle.GetReal[style, outlineBoxBearoff];
IF ascent+descent <= 0.0 THEN {
font: ImagerFont.Font ~ NodeStyle.GetFont[style];
fontBoundingBox: ImagerFont.Extents ~ ImagerFont.FontBoundingBox[font];
ascent ¬ fontBoundingBox.ascent + bearoff;
descent ¬ fontBoundingBox.descent - bearoff;
};
IF width > 0.0 THEN {
data: REF VRuleDataRep ~ NEW[VRuleDataRep ¬ [
color: NodeStyle.GetColor[style, outlineBox],
ascent: ascent,
descent: descent,
width: width,
bearoff: bearoff
]];
extents: ImagerFont.Extents ~ [leftExtent: -data.bearoff, rightExtent: data.bearoff+data.width, ascent: data.ascent, descent: data.descent];
RETURN [NEW[TEditFormat.CharacterArtworkRep ¬ [paint: VRulePaint, extents: extents, amplified: TRUE, escapement: [data.width+2*data.bearoff, 0], data: data]]]
};
};
RETURN [NIL]; -- treat like normal character
};
vRuleClass: CharacterArtworkClass ~ NEW[TEditFormat.CharacterArtworkClassRep ¬ [
name: $VRule,
format: VRuleFormat,
data: NIL
]];
vRuleClass is registered below
Placeholder Artwork
PlaceholderDataRep: TYPE ~ RECORD [
color: ImagerColor.Color,
size: REAL,
which: {open, close}
];
PlaceholderPaint: PROC [self: CharacterArtwork, context: Imager.Context] ~ {
data: REF PlaceholderDataRep ~ NARROW[self.data];
path: ImagerPath.PathProc ~ {
u: REAL ~ data.size*0.1;
moveTo[[u, -1.25*u]];
lineTo[[u, 8.25*u]];
lineTo[[6.25*u, 3.5*u]];
};
IF data.which = open
THEN {
Imager.Trans[context];
}
ELSE {
Imager.SetXRel[context, self.escapement.x];
Imager.Trans[context];
Imager.Scale2T[context, [-1, 1]];
};
Imager.SetColor[context, data.color];
Imager.MaskFill[context, path];
};
PlaceholderFormat: TEditFormat.CharacterFormatProc ~ {
char: XCHAR ~ TextEdit.FetchChar[loc.node, loc.where];
color: ImagerColor.Color ~ NodeStyle.GetColor[style, outlineBox];
size: REAL ~ NodeStyle.GetReal[style, fontSize];
extents: ImagerFont.Extents ~ [leftExtent: 0, rightExtent: size*0.8, ascent: size*0.95, descent: size*0.15];
data: REF;
SELECT char FROM
placeholderOpen => data ¬ NEW[PlaceholderDataRep ¬ [color, size, open]];
placeholderClose => data ¬ NEW[PlaceholderDataRep ¬ [color, size, close]];
ENDCASE => RETURN [NIL];
RETURN [NEW[TEditFormat.CharacterArtworkRep ¬ [paint: PlaceholderPaint, extents: extents, amplified: FALSE, escapement: [size*0.7, 0], data: data]]];
};
placeholderClass: CharacterArtworkClass ~ NEW[TEditFormat.CharacterArtworkClassRep ¬ [
name: $Placeholder,
format: PlaceholderFormat,
data: NIL
]];
placeholderClass is registered below
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];
END.