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; 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. τTEditFormatImpl.mesa Copyright c 1983, 1985 by Xerox Corporation. All rights reserved. Michael Plass, April 28, 1986 4:17:45 pm PDT Doug Wyatt, May 25, 1985 2:09:03 pm PDT The 8191 comes from the stupid limit on nChars in TEditDocument.LineRec Basic functions Avoid having unneeded REFs hanging around. f[i].charProps is retained for re-use. Cache of character width arrays Move to front NB: The following code forces an effective cache size of >= 2 Formatting Expands the charInfo and positionInfo sequences by about 34% Expands the formatInfo sequence by about 34% This scratch storage will will normally be allocated only once, but in exceptional circumstances a new batch may be needed; Uses the cached readers if they are available or become available after a short wait; otherwise allocates a new batch. Note that duplicate entries might be cached, but eventually one will get flushed. 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 Formatting constants for the line: Basic formatting variables: tabs: TabState; First a quick look through the first few formats to see if it is a common one. NodeStyleOps.Copy[dest: my.tabStyle, source: nodeStyle]; -- may need for fancy tabs. Uses curIndex, lineInfo, endX, width, trimmedLineWidth; modifies breakIndex, breakX This can be speeded up. N.B. sets above parameters Painting Character positions Character Artwork classes 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. vRuleClass is registered below Artwork classes Κ2Ύ˜codešœ™Kšœ Οmœ7™BKšœ,™,Kšœ'™'—K™šΟk ˜ Kš œžœžœžœžœ˜Kšœžœ'˜1Kšœ˜Kšœ˜Kšœ žœ˜-Kšœ žœ=˜MKšœ žœ'˜8K˜Kšœ žœ ˜Kšœ ˜ Kšœžœ˜*Kšœ ˜ Kšœžœ2˜?Kšœ˜Kšœžœ%˜1Kšœžœžœ˜Kšœ žœ*˜:K˜ Kšœ˜Kšœžœ˜&Kšœ žœŠ˜›Kšœ˜Kšœ žœR˜`Kšœ žœ˜+Kšœ žœ˜Kšœžœžœ˜Kšœ ˜ —K˜KšΠblœžœž˜Kšžœβ˜ιKšžœ˜&šœž˜K˜Kšœžœ˜Kšœžœ˜šžœžœžœ˜K˜—Kšœžœ˜.šœžœ˜4K˜—Kšœžœ&˜Kšœ8˜8K˜—K˜K˜—š œžœžœžœ%˜?Kšœžœ˜!Kšœžœ˜šžœ,žœžœž˜=Kšœ)˜)šžœžœžœž˜Kšœ*™*Kšœ žœ˜Kšœ žœ˜Kšœ žœ˜Kšœ&™&Kšžœ˜—Kšœžœ˜Kšœžœ˜Kšœ žœ˜ Kšœ ˜ K˜ Kšœ ˜ Kšœ˜Kšžœ˜—šžœ žœ˜Kšœ˜Kšœ˜Kšœ4˜4Kšœ˜—šžœ-ž˜4Kšœ*˜*Kšœ˜Kšœ0˜0Kšœ žœ˜Kšœžœ˜Kšœžœ˜Kšœ8˜8Kšžœ˜—K˜K˜——™Kšœžœ˜Kšœ žœžœžœ˜(Kšœžœ˜šœžœ˜K˜—Kšœžœ˜$Kšœžœžœ˜+šœžœžœžœ˜?K˜—šœžœžœ˜K˜ Kšœ˜Kšœ˜K˜—š œžœžœžœ˜CKšœžœžœžœ˜"š žœžœžœ$žœžœž˜Bšžœžœ˜šžœžœžœ˜K™ Kšœ˜Kšœ˜Kšœ˜Kšœ˜—Kšœ$˜$Kšžœ˜Kšœ˜—Kšœ ˜ Kšžœ˜—Kšœ(˜(Kšžœžœ˜ Kšœ˜K˜—š œžœžœ'˜BKšœžœžœžœ˜!Kšœžœžœžœ˜"Kšœžœ˜ Kšœ=™=šžœžœžœ$ž˜4Kš žœžœžœžœžœ˜3šžœžœžœžœ˜,Kšœ ˜ Kšœ žœ˜ Kšœ žœ˜Kšœ˜Kšžœ˜Kšœ˜—K˜ Kšžœ˜—Kšœ˜Kšœ˜Kšœ˜K˜——™ š  œžœ%˜6Kšœ<™˜>K˜ K˜(K˜K˜—š  œžœ%˜8Kšœ,™,Kšœ<˜Kšœžœ#˜@Kšžœžœžœžœ!˜JKšžœžœžœ˜Kšœ˜—š œžœ˜Kšœ žœ˜K˜!Kšœ žœ˜$K˜$K˜%Kšœ žœ4˜DKšœ žœ4˜DKšœN™NKšœ$žœ˜*šžœžœžœ˜šžœžœžœ!ž˜MKšœ<˜Kšœ(˜(Kšœ˜—Kšœ˜—šžœžœ˜$Kšœ˜Kšœ-˜-Kšœ˜—Kšœ.˜.Kšžœžœ˜-šžœžœ˜KšœA˜AKšœžœ˜Kšœ˜Kšžœ&žœžœ˜SKšœ˜—šžœžœ ž˜šœ ˜ Kšœ žœ˜Kšœ˜Kšœ˜Kšœžœ˜Kšœžœ˜Kšœ˜—šœ ˜ Kšœ˜Kšœžœ˜&Kšœ˜Kšœžœ˜Kšœžœ˜$Kšœžœ˜Kšœ˜—šžœ ˜ Kšœ˜Kšœ˜Kšœ˜Kšœ˜—šœ˜Kšœ˜Kšœ(˜(Kšœf˜fKšœ˜Kšœžœ˜Kšœžœ˜$Kšœ˜Kšœžœ˜Kšœ˜—KšžœN˜Pšžœ˜ Kšœ7˜7Kšœ9˜9Kšœ˜šžœž˜KšœE˜Ešœ˜Kšœ˜Kšœ˜Kšœ˜—Kšžœžœ˜—Kšœ˜——šžœžœ˜šžœžœžœžœ˜>Kšœ$žœ"˜JKšœ=˜=KšœJžœ˜TKšœ žœ˜K˜—Jšœ(˜(Kšœ˜—Jšœ"˜"KšœZžœ žœ˜Kšœ žœ˜šžœ žœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšž˜Kšœ˜—šžœžœ žœžœ˜:Kšœ&žœ žœ ˜FK˜šžœžœ0žœ˜UKšœl˜lšžœ žœ˜Kšœ žœžœžœ˜Kšœ#˜#Kšœ&˜&Kšœ žœžœ˜š  œžœžœžœ˜=KšΟbœ™Kšœžœ%˜+šžœžœ˜Kšœ:˜:Kšœžœ˜3Kšœ ˜ KšœD˜DKšœ%žœ˜)Kšœ˜Kšœ˜—Kšžœ ˜Kšœ˜—Kšœžœ˜ Kšœ˜Kšœžœ˜Kšœžœ˜ šžœžœžœ žœžœžœžœžœ ž˜Žšžœ žœ˜Kšœ˜Kšœ žœ ˜Kšœ žœ˜K˜ Kšœ˜—Kšœžœ˜)K˜ Kšžœ˜—šžœ žœ˜Kšœ˜šœ!˜!Kšœ˜Kšœ˜Kšœžœ˜Kšœ žœ˜Kšœ˜Kšœ˜—Kšœ˜—Kšœ˜—Kšœ˜—Kšžœžœ.˜BKšžœžœ žœ*žœ˜NKšžœ˜Kšžœ˜K˜—Kšžœ˜—Kšžœžœžœ˜SKšžœžœ5˜Zšžœžœ˜Kšœ˜š žœžœž œžœž˜0Kšœ0˜0šžœž˜ šœ˜Kšœ1˜1Kšœžœ˜'Kšœ"žœ˜.Kšœ:˜:Kšœ˜—šœ ˜ Kšœ"žœ˜CKšœ:˜:Kšœ˜—Kšžœžœ˜—Kšžœ˜—Kšœ˜—š žœžœžœžœ#ž˜nKšœ-˜-šœ˜Kšœ˜šžœ žœžœžœ˜2Kšœ*žœžœ˜MKšœžœ.˜JKš œ žœžœžœžœT˜†Kšœžœ˜(Kšœžœ-˜5šžœ žœ˜Kšœžœžœžœ9˜OK˜(K˜+šžœžœžœž˜šžœžœ žœžœ˜Hšžœžœ˜#Kšœ˜KšœD˜DKšœ˜—Kšœ˜Kšœžœ˜Kšœ˜—Kšœžœ˜0Kšžœ˜—Kšœ˜—Kšœ ˜ Kšœ˜—Kšœ˜—Kšœ-žœžœžœ˜hšœ ˜ Kšœžœžœžœ˜bKšœ˜—Kšžœžœ˜—Kšžœ žœžœžœ˜8Kšžœžœ.˜QK˜%K˜BK˜ K˜Kšœ7˜7K˜K˜K˜—Kšœžœ*˜DKšœ žœ˜,š œžœžœ4žœ˜ŒKšœžœ$˜:Kšœžœ'˜0Kšœ,žœ˜3šžœžœžœ˜Kšœ+žœ ˜5Kšœ žœžœ ˜Kšœžœ˜ Kšœžœ˜ šžœž˜Kšœžœ˜ Kšœžœ ˜K•StartOfExpansion,[text: TextEdit.RefTextNode, index: INT]šœžœžœ˜KšœN˜Nšœž œž˜Kš œ žœ žœžœ žœžœ‘ œ˜OKšœ‘ œ‘ œžœ˜&Kšœ‘˜,Kšžœžœ˜—Kšžœžœžœžœ˜Kšœ˜Kšžœ˜—Kšžœ žœžœ žœF˜fKšœ˜—Kšžœ˜ K˜K˜—šœ žœžœ žœ˜;K˜—–#[mod: RefTab.SeqIndex _ 21B (17)]˜,K˜—š œžœ4žœžœ˜_Kšœžœžœ˜Kšœn˜nšžœžœž˜Kšœžœžœ˜Kšžœžœžœ˜—Kšœ˜K˜—š   œžœžœžœžœžœ ˜HKšžœžœžœ˜Kšžœžœ.˜˜SKš œžœžœžœžœžœžœ˜mK˜3Kšœ9˜9Kšœ+‘˜DKš  œžœžœžœžœ:˜…š œžœ˜š œžœ žœ˜Kšœžœžœžœ˜!K˜K˜$Kšœžœ˜š œžœ˜Kšœžœ˜Kšžœžœ|˜ Kšœ ˜ Kšœ˜—šžœžœžœ ž˜KšœB˜BK˜?Kšœžœ˜6Kš œ žœžœ žœžœ˜GKšœ žœžœ žœ˜3šžœ!žœ˜)Kšœ ˜ šœžœžœ ˜+šžœ˜Kšœ4˜4Kšœ5˜5Kšœ/˜/Kšœ0˜0Kšœ˜—Kšžœ ˜—Kš žœžœ žœ žœžœ˜pKšœ ˜ Kšœ˜—Kšœžœ˜Kšžœ˜—Kšœ ˜ Kšœ˜—K˜Kšœžœ˜Kšœžœ˜Kšœ˜—Kšœ˜Kšœ˜K˜—Kšœ žœ˜š  œžœžœ>˜VKš œžœžœžœžœžœžœ˜mKšœžœ˜ K˜3Kšœ9˜9Kšœ+‘˜DKš  œžœžœžœžœ:˜…Kšœ žœ‘$˜=Kš  œžœžœžœžœžœ8˜yKšœžœ˜Kšžœžœžœ/žœ˜dKšžœžœ$˜Bšžœ ž˜K˜BK˜?Kšœžœ˜6Kšœžœžœžœ˜JKšœ žœžœ˜Kšœ žœžœ˜Kšœ žœ˜Kšœ žœžœ˜Kšœžœ˜š œžœžœžœ˜;K˜&Kšœžœ ˜Kšœ+˜+Kšœ žœ ˜Kšœžœ˜ Kšœžœžœžœžœ žœ žœžœ˜Wšžœ ž˜Kšœ˜Kšžœžœžœ˜$šžœ žœ˜Kšœ7˜7Kšœžœ#˜9Kšœžœ!˜7Kšžœžœžœžœ˜HKšœ˜—Kšžœ žœ žœžœ˜*Kšžœ žœ˜$Kšœ ˜ Kšžœ žœžœ˜,šžœžœ˜Kšœ&žœG˜pKšžœ˜K˜—Kšœžœ˜0Kšžœ˜—Kšœ ˜ Kšœ˜—šžœžœ˜#Kšœ%˜%Kšœ˜Kšœ˜—Kšžœ žœ(˜NKšœ˜šžœžœ˜KšœH˜HKšœ3˜3Kšœ1˜1Kšœ˜—šžœ žœ žœ˜$K˜Kšœ˜—šžœžœ˜Kšœ'˜'Kšœ˜—šžœžœ˜ Kšœ*˜*Kšœ˜—šžœž˜#Kšžœ,˜0šžœ˜KšœžœK˜SKšœžœ+˜2šœ˜š œ žœžœžœžœ˜,KšœFžœ˜LKšœ˜K˜—K˜—Kšœ9˜9Kšœ ˜ Kšœ˜——šžœ žœ˜Kšœ/˜/Kšœ3˜3K˜ Kšœ˜—šžœžœ˜Kšœ(˜(Kšœ˜—šžœžœžœ˜2Kšœ5˜5Kšœ˜Kšœ˜—šžœ žœ˜K˜#K˜PK˜Kšœ˜—šžœ žœ˜K˜#K˜PK˜Kšœ˜—Kšžœ˜—K˜Kšœ˜K˜K˜——™š œžœ˜+Kšœ,˜,šœ!˜!Kš žœ žœžœžœžœ˜DKšžœ˜ —Kšœ˜K˜—š   œžœ%žœžœ'žœžœ˜‘Kšœ žœ˜(Kšœžœ˜%Kšœžœžœ˜šžœžœžœž˜%Kšœ žœ˜.šžœžœžœžœ˜9K˜9K˜K˜Kšœ%žœ˜BK˜—K˜Kšžœ˜—Kšžœ?žœ˜KK˜K˜—š  œžœ!˜5Kšœ,˜,šœ ˜ Kš žœ žœžœžœžœ%˜SKšžœ&˜*—Kšœ˜K˜—Kš œžœ:˜Ršžœ žœ˜Kšžœ"žœžœžœ ˜Hšžœ˜Kš œžœžœ#žœžœ˜:Kšžœžœžœžœ ˜8KšžœžœžœE˜cšžœ˜K˜K˜'K˜!K˜—K˜—K˜K˜—š  œžœ ˜3Kšœ,˜,šžœ˜Kšžœ žœžœž˜(Kšžœ+˜/Kšžœ+˜/Kšœ˜—Kšœ˜K˜—š œžœ1žœžœ˜fKšœ3˜3Kšœ9˜9Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜š œžœ žœ˜"Kšžœ žœ ˜Kšžœ žœ ˜Kšžœ žœ ˜Kšžœ žœ ˜Kšœ˜—š œžœžœ˜$Kšœ ˜ Kšœ ˜ Kšœ˜—š žœžœžœžœ!ž˜8Kšœ+˜+Kšœ<˜Kšžœžœ˜—Kšžœžœ˜ K˜K˜—šœ4˜4K˜—šœ žœ˜7Kšœžœ˜ K˜Kšœ˜Kšœ˜Kšœ!˜!Kšœ˜Kšœ˜K˜—Kšœžœžœ˜Kš œžœžœžœžœžœžœ˜Mš œžœžœžœ žœžœžœ5˜|K˜—š œ˜Kšœ@˜@Kšžœ˜ Kšœ˜K˜—Kšœ!˜!Kšœ%˜%šœ'žœ˜-K˜——K˜Kšžœ˜J˜—…—°Μλ~