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