<<>> <> <> <> <> <> <> <<>> 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; <> 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 <> f[i].font ¬ NIL; f[i].color ¬ NIL; f[i].tab ¬ NIL; <> 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; }; <> 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 { <> 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; <= 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; }; <> ExpandChars: PROC [lineInfo: TEditFormat.LineInfo] ~ { <> 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] ~ { <> 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; }; <<>> <> 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] ~ { <> 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; <> 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[]; <> 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]; <> 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; <> 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; <> 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 ~ { <> <> 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 { <> 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; <> 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 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] ~ { <> 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]; }; <> 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]; }; <> 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 xxlineInfo.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]]; }; <<>> <> 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]; <> <> <<>> 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 ]]; <> <> 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 ]]; <> <> 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.