TEditFormatImpl.mesa
Copyright © 1983, 1985, 1986 by Xerox Corporation. All rights reserved.
Michael Plass, June 26, 1986 9:33:40 am PDT
Doug Wyatt, September 2, 1986 3:33:54 pm PDT
DIRECTORY
Ascii USING [CR, SP, TAB],
Atom USING [GetPropFromList, MakeAtom, PropList],
Imager USING [black, Box, Color, Context, DoSave, DoSaveAll, MakeGray, MaskBox, MaskRectangle, MaskUnderline, Move, SetAmplifySpace, SetColor, SetFont, SetXRel, SetYRel, Show, StartUnderline, white],
ImagerBackdoor USING [GetColor],
ImagerColor USING [ColorFromRGB, RGBFromHSV],
ImagerFont USING [BoundingBox, Extents, Font, FontBoundingBox, Width, XChar, nullXChar],
LooksReader USING [Create, InlineGet, Ref, SetPosition],
MessageWindow USING [Append],
NodeProps USING [GetProp],
NodeStyle USING [FontUnderlining, GetFirstIndent, GetLastLineFormatting, GetLeadingI, GetLeftIndent, GetLineFormatting, GetLineLength, GetReal, GetRestIndent, GetRightIndent, GetStrikeout, GetTabStops, GetUnderlining, GetVShift, PointsPerFil, RealParam, Style, TabAlign, TabStop],
NodeStyleFont USING [FontFromStyleParams],
NodeStyleOps USING [ApplyLooks, ApplyObject, Copy, Create, GetStyleParam, GetStyleParamObj, nonNumeric, OfStyle],
Process USING [InitializeCondition, Milliseconds, MsecToTicks],
Real USING [LargestNumber, RoundC, RoundI],
RefTab USING [Create, Delete, Fetch, Ref, Store],
Rope USING [ROPE],
RopeReader USING [Create, Get, GetRope, Ref, SetPosition],
RuntimeError USING [UNCAUGHT],
Scaled USING [Float, Floor, FromReal, GREATER, half, IntRep, LESS, MINUS, PLUS, Round, Scale, ValRep, Value, zero],
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 USING [CharacterArtwork, CharacterArtworkClass, CharacterArtworkClassRep, CharacterArtworkRep, HyphenationPositions, HyphProc, maxHyph],
TextEdit USING [Fetch, FetchChar, GetCharPropList, GetRope, GetRuns, Size],
TextLooks USING [allLooks, Looks, noLooks],
TextNode USING [Location, Node],
Vector2 USING [VEC],
ViewerOps USING [BlinkDisplay];
TEditFormatImpl: CEDAR MONITOR
IMPORTS Atom, Imager, ImagerBackdoor, ImagerColor, ImagerFont, LooksReader, MessageWindow, NodeProps, NodeStyle, NodeStyleFont, NodeStyleOps, Process, Real, RefTab, RopeReader, RuntimeError, Scaled, TEditDocument, TextEdit, ViewerOps
EXPORTS TEditFormat, TEditFormatExtras
~ BEGIN
XChar: TYPE ~ ImagerFont.XChar;
Font: TYPE ~ ImagerFont.Font;
ROPE: TYPE ~ Rope.ROPE;
ArtworkClass: TYPE ~ TEditFormat.ArtworkClass;
ArtworkClassRep: TYPE ~ TEditFormat.ArtworkClassRep;
CharacterArtwork: TYPE ~ TEditFormatExtras.CharacterArtwork;
CharacterArtworkClass: TYPE ~ TEditFormatExtras.CharacterArtworkClass;
maxCharsPerLine: INT ~ MIN[LAST[TEditFormat.CharNumber], 8191];
The 8191 comes from the stupid limit on nChars in TEditDocument.LineRec
minAmplifySpace: REAL ← 0.5;
minHyphLetters: NAT ← 7;
nullXChar: XChar ~ ImagerFont.nullXChar;
debug: BOOLFALSE;
Basic functions
FloorI: PROC [real: REAL] RETURNS [i: INTEGER] ~ {
i ← Real.RoundI[real];
IF i > real THEN i ← i - 1;
};
CeilingI: PROC [real: REAL] RETURNS [i: INTEGER] ~ {
i ← Real.RoundI[real];
IF i < real THEN i ← i + 1;
};
Ord: PROC [char: XChar] RETURNS [CARDINAL] ~ INLINE {RETURN [LOOPHOLE[char]]};
XFetch: PROC [text: TextNode.Node, index: INT] RETURNS [XChar] ~ {
charSet: [0..256); char: CHAR;
[charSet, char] ← TextEdit.FetchChar[text, index];
RETURN [[charSet, ORD[char]]]
};
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 {
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;
};
};
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].charProps 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 ~ [040B..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 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.Style,
tabStyle: NodeStyle.Style
];
scratchRefsReleased: CONDITION;
scratchRefsAvailable: BOOLEANFALSE;
scratchRefs: ScratchRefs;
scratchRefsAllocCount: INT ← InitScratchRefs[333];
scratchRefsWaitCount: INT ← 0; -- stats only
InitScratchRefs: ENTRY PROC [timeout: Process.Milliseconds] RETURNS [initialCount: INT ← 1] ~ TRUSTED {
scratchRefs ← [RopeReader.Create[], LooksReader.Create[], 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.Style] 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.Style, 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: BOOLFALSE,
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.Style, 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.Style, 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.Style, 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: TextNode.Node,
startOffset: INT,
nodeStyle: NodeStyle.Style,
lineWidth: Scaled.Value,
doLigsAndKern: BOOLEANFALSE,
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: BOOLFALSE; -- set to true if any non-zero letterspace is found.
prevLetterspace: Scaled.Value ← Scaled.zero;
prevDoLetterspace: BOOLFALSE;
breakSpaceAtEnd: Scaled.Value ← Scaled.zero;
spaceWidths: Scaled.Value ← Scaled.zero;
highWaterChars: NAT ← lineInfo.charInfo.maxLength;
extension: ExtraFormatInfo ← NIL;
characterPropertyList: Atom.PropList ← NIL;
haveCharacterArtwork: BOOLFALSE;
characterArtworkIndex: INT ← 0;
hyphenation: TEditFormatExtras.HyphProc ← NIL;
hyphenationData: REFNIL;
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: BOOLFALSE;
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.Style ← 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: BOOLFALSE;
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: BOOLFALSE;
ch: CHAR ~ RopeReader.Get[my.ropeReader];
doLetterspace: BOOLTRUE;
alteredWidth: BOOLFALSE;
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: NATNAT.LAST;
hyphChar: ImagerFont.XChar ← [0,0];
hyphWidth: Scaled.Value ← Scaled.zero;
artChar: BOOLFALSE;
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.Node, index: INT, style: NodeStyle.Style, 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: BOOLFALSE;
[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.Style, kind: NodeStyleOps.OfStyle] RETURNS [ATOM] ~ {
param: REFNIL;
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: REFNIL] ~ {
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};
};
Painting
CharClass: PROC [char: XChar] RETURNS [charClass: NodeStyle.FontUnderlining] ~ {
IF char.set = 0 THEN {
charClass ← SELECT CHAR[VAL[char.code]] FROM
Ascii.SP, Ascii.TAB, Ascii.CR => All,
IN ['a..'z], IN ['A..'Z], IN ['0..'9] => LettersAndDigits,
ENDCASE => Visible;
}
ELSE IF char.code 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: Imager.Color ← Imager.black; -- assume black coming in
SetColor: PROC [color: Imager.Color] ~ INLINE {IF lastSetColor # color THEN {Imager.SetColor[context, color]; lastSetColor ← color}};
Action: PROC ~ {
Boxes: PROC [outline: BOOL] ~ {
prevFormatNumber: NATNAT.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 ~ NARROW[format.charProps];
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 ← x.PLUS[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: Imager.Color ← Imager.black; -- assume black coming in
SetColor: PROC [color: Imager.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 ~ NARROW[format.charProps];
worry: BOOL ~ NOT (format.underlining = None AND format.strikeout = None);
doUnderline: BOOLEANFALSE;
doStrikeout: BOOLEANFALSE;
deltaWidth: REAL ← 0.0;
tabFound: BOOLFALSE;
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 ← IF j < end THEN @(info[j]) ELSE NIL;
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 THEN {
deltaWidth ← Scaled.Float[entry.width] - ImagerFont.Width[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 {
MessageWindow.Append[" TEditFormatImpl: Character Artwork Paint bug", TRUE];
ViewerOps.BlinkDisplay[];
}
];
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: TextNode.Location, xmin, width: INTEGER, rightOfLine: BOOLEAN] ~ {
xOffset: INT ← lineInfo.xOffset.Round[];
xOfi: INT ← lineInfo.positionInfo[0];
xx: INTINT[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: NATMIN[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] + lineInfo.xOffset.Round[]; width ← 0}
ELSE {
x ← lineInfo.positionInfo[i];
width ← lineInfo.positionInfo[i+1] - x;
x ← x + lineInfo.xOffset.Round[];
};
};
};
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 ~ NARROW[f.charProps];
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 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: Imager.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: PROC [class: CharacterArtworkClass, loc: TextNode.Location, style: NodeStyle.Style, kind: NodeStyleOps.OfStyle] RETURNS [CharacterArtwork] ~ {
letter: CHAR ~ TextEdit.FetchChar[loc.node, loc.where].char;
IF letter = '| 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 {
fontBoundingBox: ImagerFont.Extents ~ ImagerFont.FontBoundingBox[GetFont[style]];
ascent ← fontBoundingBox.ascent + bearoff;
descent ← fontBoundingBox.descent - bearoff;
};
IF width > 0.0 THEN {
data: REF VRuleDataRep ~ NEW[VRuleDataRep ← [
color: GetColor[style, outlineboxHue, outlineboxSaturation, outlineboxBrightness],
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[TEditFormatExtras.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[TEditFormatExtras.CharacterArtworkClassRep ← [
name: $VRule,
format: VRuleFormat,
data: NIL
]];
vRuleClass 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: TextNode.Node] RETURNS [ArtworkClass] ~ {
className: ATOMNIL;
prop: REF ~ NodeProps.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: BOOLTRUE;
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];
[] ← RegisterHyphenation[$Silly, Silly, NIL];
END.