DIRECTORY Imager, ImagerBackdoor, ImagerFont, ImagerSample, IO, Pipal, PipalInt, PipalMutate, PipalPaint, PipalReal, PipalTextMutant, Real, Rope, SF, VFonts; PipalTextMutantImpl: CEDAR PROGRAM IMPORTS Imager, ImagerBackdoor, ImagerFont, ImagerSample, IO, Pipal, PipalInt, PipalMutate, PipalPaint, PipalReal, Real, Rope, VFonts EXPORTS PipalTextMutant = BEGIN OPEN PipalTextMutant; pipalTextClass: PUBLIC Pipal.Class _ Pipal.RegisterClass[name: "PipalText", type: CODE[PipalTextRec]]; TextDescribe: Pipal.DescribeProc = { text: PipalText _ NARROW [object]; Pipal.PutIndent[out, indent, cr]; IO.PutF[out, "Text [%g]", IO.rope[text.rope]]; }; TextSize: PipalReal.SizeProc ~ { size _ NARROW [object, PipalText].size; }; fixedFont: Imager.Font _ VFonts.defaultFont; feedbackHeight: REAL _ 4.0; fontHeight: REAL _ ImagerFont.FontBoundingBox[fixedFont].descent + ImagerFont.FontBoundingBox[fixedFont].ascent; lineHeight: REAL _ fontHeight + feedbackHeight; TextPaint: PipalPaint.PaintProc ~ { bounds: Imager.Rectangle _ ImagerBackdoor.GetBounds[context]; clip: PipalReal.Rectangle _ [[bounds.x, bounds.y], [bounds.w, bounds.h]]; EachRow: RowProc = { IF NOT PipalReal.DoRectanglesIntersect[clip, [[0.0, y], [text.size.x, lineHeight]]] THEN RETURN; Imager.SetXY[context, [0.0, y]]; Imager.ShowRope[context, text.rope, interval.base, interval.size]; }; text: PipalText _ NARROW[object]; PipalPaint.SetColor[context, Imager.black]; Imager.SetFont[context, fixedFont]; [] _ EnumerateRows[text, EachRow]; }; emptySize: PipalReal.Size _ PipalReal.emptySize; CreatePipalText: PUBLIC PROC [rope: Pipal.ROPE, size: PipalReal.Size _ PipalReal.emptySize] RETURNS [text: PipalText] = { text _ NEW [PipalTextRec _ [rope, size]]; IF size=PipalReal.emptySize THEN { extents: ImagerFont.Extents _ ImagerFont.RopeBoundingBox[font: fixedFont, rope: rope]; text.size _ [extents.leftExtent+extents.rightExtent, lineHeight]; }; }; GetIntervalArea: PUBLIC PROC [text: PipalText, interval: PipalInt.Interval] RETURNS [rectangle: PipalReal.Rectangle] = { interest: PipalInt.Interval _ interval; EachRow: RowProc = { IF NOT PipalInt.DoIntervalsIntersect[interest, interval] THEN RETURN; rectangle _ PipalReal.BoundingBox[rectangle, [[0.0, y], [text.size.x, lineHeight]]]; }; rectangle _ PipalReal.emptyRectangle; [] _ EnumerateRows[text, EachRow]; }; GetSelectionInterval: PUBLIC PROC [text: PipalText, position: PipalReal.Position, grain: SelectionGrain] RETURNS [interval: PipalInt.Interval _ [0, 0], closerRight: BOOL _ FALSE] = { ropeSize: INT _ Rope.Size[text.rope]; IF ropeSize>0 THEN { leftEdge, rightEdge: REAL _ 0.0; location: Location; bbox: PipalReal.Rectangle; [location, bbox] _ CharLocation[text, position]; leftEdge _ bbox.base.x; rightEdge _ bbox.base.x+bbox.size.x; SELECT grain FROM point => { interval.base _ location; interval.size _ 0; }; char => { interval.base _ location; interval.size _ 1; }; word => { start: Location _ location; char: CHAR _ Rope.Fetch[text.rope, location]; IF CharSeparator[char] THEN interval.base _ location ELSE { UNTIL location=0 OR CharSeparator[(char _ Rope.Fetch[text.rope, location-1])] DO location _ location-1; leftEdge _ leftEdge-CharSize[char].x; ENDLOOP; interval.base _ location; location _ start; UNTIL location=ropeSize-1 OR CharSeparator[(char _ Rope.Fetch[text.rope, location+1])] DO location _ location+1; rightEdge _ rightEdge+CharSize[char].x; ENDLOOP; }; interval.size _ location-interval.base+1; }; ENDCASE => ERROR; closerRight _ ABS[rightEdge-position.x]0 THEN DO rightEdge: REAL _ 0.0; firstUnpaintedChar: Location _ start; lastSeparator: Location _ start; DO char: CHAR _ Rope.Fetch[text.rope, firstUnpaintedChar]; rightEdge _ rightEdge + CharSize[char].x; IF rightEdge>=text.size.x THEN EXIT; IF CharSeparator[char] THEN lastSeparator _ firstUnpaintedChar; firstUnpaintedChar _ firstUnpaintedChar + 1; IF firstUnpaintedChar=length THEN EXIT; ENDLOOP; IF firstUnpaintedChar=length THEN lastSeparator _ length-1; IF eachRow[[start, lastSeparator-start+1], y] THEN {quit _ TRUE; EXIT}; IF lastSeparator+1=length THEN EXIT; start _ lastSeparator+1; y _ y-lineHeight; ENDLOOP; }; CharSeparator: PROC [char: CHAR] RETURNS [separator: BOOL] = { separator _ NOT (char IN ['a..'z] OR char IN ['A..'Z] OR char IN ['0..'9]); }; textMutantClass: PUBLIC Pipal.Class _ Pipal.RegisterClass[name: "TextMutant", type: CODE [TextMutantRec]]; TextMutantDescribe: Pipal.DescribeProc = { textMutant: TextMutant _ NARROW[object]; Pipal.PutIndent[out, indent, cr]; IO.PutF[out, "Text Mutant"]; }; TextMutantSize: PipalReal.SizeProc ~ { size _ PipalReal.ObjectSize[NARROW [object, TextMutant].text]; }; black: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0FFFFH, xor: TRUE]; lightGrey: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0208H, xor: TRUE]; darkGrey: Imager.Color _ ImagerBackdoor.MakeStipple[stipple: 0A5A5H, xor: TRUE]; caretH: INTEGER = 6; caretW: INTEGER = 16; CaretBits: TYPE ~ REF CaretBitsRep; CaretBitsRep: TYPE = ARRAY [0..caretH) OF WORD; caretBits: CaretBits _ NEW[CaretBitsRep _ [ 000400B, -- 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 001600B, -- 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 003700B, -- 0 0 0 0 0 1 1 1 1 1 0 0 0 0 0 0 003300B, -- 0 0 0 0 0 1 1 0 1 1 0 0 0 0 0 0 006140B, -- 0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 004040B -- 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 ]]; caretMap: ImagerSample.SampleMap _ SampleMapFromCaretBits[caretBits]; SampleMapFromCaretBits: PROC [bits: CaretBits] RETURNS [ImagerSample.SampleMap] ~ TRUSTED { RETURN [ImagerSample.UnsafeNewSampleMap[ box: [min: [s: 0, f: 0], max: [s: caretH, f: caretW]], bitsPerSample: 1, bitsPerLine: BITS[WORD], base: [word: LOOPHOLE[bits], bit: 0], ref: bits, words: SIZE[CaretBitsRep] ]]; }; TextMutantPaint: PipalPaint.PaintProc = { EachRow: RowProc = { y _ y-feedbackHeight; FOR selection: Selections IN Selections DO IF tm.selections[selection].valid AND PipalInt.DoIntervalsIntersect[interval, tm.selections[selection].interval] THEN { sel: Selection _ tm.selections[selection]; selectInterval: PipalInt.Interval _ PipalInt.IntersectionIntervals[interval, sel.interval]; start: REAL _ ImagerFont.RopeEscapement[fixedFont, text.rope, interval.base, selectInterval.base-interval.base].x; SELECT TRUE FROM selection=primary => Imager.SetColor[context, black]; sel.pendingDelete => Imager.SetColor[context, lightGrey]; ENDCASE => Imager.SetColor[context, darkGrey]; IF selectInterval.size>0 THEN { length: REAL _ ImagerFont.RopeEscapement[fixedFont, text.rope, selectInterval.base, selectInterval.size].x; IF sel.pendingDelete THEN Imager.MaskRectangle[context, [start, y, length, fontHeight]] ELSE Imager.MaskRectangle[context, [start, y, length, feedbackHeight]]; IF (sel.caretAfter AND PipalInt.IsInsideIntervalNumber[selectInterval, sel.interval.base+sel.interval.size]) OR (NOT sel.caretAfter AND PipalInt.IsInsideIntervalNumber[selectInterval, sel.interval.base]) THEN Imager.MaskBitmap[context, caretMap, [0, 7], Imager.defaultScanMode, [IF sel.caretAfter THEN start+length ELSE start, y+feedbackHeight]]; } ELSE Imager.MaskBitmap[context, caretMap, [0, 7], Imager.defaultScanMode, [start, y+feedbackHeight]]; }; ENDLOOP; }; tm: TextMutant _ NARROW [object]; text: PipalText _ tm.text; PipalPaint.Paint[text, context]; Imager.SetStrokeWidth[context, feedbackHeight]; [] _ EnumerateRows[text, EachRow]; }; MutatePipalText: PipalMutate.MutationProc = { text: PipalText _ NARROW [object]; mutant _ NEW [TextMutantRec _ [text: text]]; }; InsertRope: PUBLIC PROC [tm: TextMutant, characters: Pipal.ROPE] RETURNS [message: Pipal.ROPE, changed: PipalInt.Interval, newtm: TextMutant] = { [changed, newtm] _ BasicInsert[tm, characters]; message _ "insert rope"; }; InsertChar: PUBLIC PROC [tm: TextMutant, character: CHAR] RETURNS [message: Pipal.ROPE, changed: PipalInt.Interval, newtm: TextMutant] = { [changed, newtm] _ BasicInsert[tm, Rope.FromChar[character]]; message _ "insert char"; }; Copy: PUBLIC PROC [tm: TextMutant] RETURNS [message: Pipal.ROPE, changed: PipalInt.Interval, newtm: TextMutant] = { IF tm.selections[secondary].valid THEN { otherChange: PipalInt.Interval; [changed, newtm] _ BasicInsert[tm, Rope.Substr[tm.text.rope, tm.selections[secondary].interval.base, tm.selections[secondary].interval.size]]; IF newtm.selections[secondary].pendingDelete THEN { [otherChange, newtm] _ BasicDelete[newtm, newtm.selections[secondary].interval]; changed _ PipalInt.UnionIntervals[changed, otherChange]; }; } ELSE ERROR Pipal.Error[$noSecondarySelection]; message _ "copy"; }; Transpose: PUBLIC PROC [tm: TextMutant] RETURNS [message: Pipal.ROPE, changed: PipalInt.Interval, newtm: TextMutant] = { text: PipalText _ tm.text; newtm _ NEW [TextMutantRec _ tm^]; IF NOT tm.selections[primary].valid THEN ERROR Pipal.Error[$noPrimarySelection]; IF NOT tm.selections[secondary].valid THEN ERROR Pipal.Error[$noSecondarySelection]; { i1: PipalInt.Interval; i2: PipalInt.Interval; switched: BOOL; [i1, i2, switched] _ DecomposeIntervals[tm.selections[primary].interval, tm.selections[secondary].interval]; newtm.text _ CreatePipalText[Rope.Cat[ Rope.Substr[text.rope, 0, i1.base], Rope.Substr[text.rope, i2.base, i2.size], Rope.Substr[text.rope, i1.base+i1.size, i2.base-(i1.base+i1.size)], Rope.Substr[text.rope, i1.base, i1.size], Rope.Substr[text.rope, i2.base+i2.size, Rope.Length[text.rope]-i2.base+i2.size]], text.size]; changed _ [i1.base, PipalInt.infinity]; { difference: INT _ tm.selections[primary].interval.size - tm.selections[secondary].interval.size; t: Location _ tm.selections[primary].interval.base; newtm.selections[primary].interval.base _ newtm.selections[secondary].interval.base; newtm.selections[secondary].interval.base _ t; IF switched THEN newtm.selections[secondary].interval.base _ newtm.selections[secondary].interval.base + difference ELSE newtm.selections[primary].interval.base _ newtm.selections[primary].interval.base - difference; }; }; message _ "transpose"; }; Delete: PUBLIC PROC [tm: TextMutant] RETURNS [message: Pipal.ROPE, changed: PipalInt.Interval, newtm: TextMutant] = { newtm _ NEW [TextMutantRec _ tm^]; IF NOT tm.selections[primary].valid THEN ERROR Pipal.Error[$noPrimarySelection]; newtm.paste _ Rope.Substr[tm.text.rope, tm.selections[primary].interval.base, tm.selections[primary].interval.size]; [changed, newtm] _ BasicDelete[newtm, tm.selections[primary].interval]; message _ "delete"; }; Paste: PUBLIC PROC [tm: TextMutant] RETURNS [message: Pipal.ROPE, changed: PipalInt.Interval, newtm: TextMutant] = { [changed, newtm] _ BasicInsert[tm, tm.paste]; message _ "paste"; }; Erase: PUBLIC PROC [tm: TextMutant] RETURNS [message: Pipal.ROPE, changed: PipalInt.Interval, newtm: TextMutant] = { sel: Selection _ tm.selections[primary]; loc: Location; IF NOT sel.valid THEN ERROR Pipal.Error[$noPrimarySelection]; loc _ IF sel.caretAfter THEN sel.interval.base+sel.interval.size ELSE sel.interval.base; IF loc=0 THEN ERROR Pipal.Error[$noCharacterToErase]; [changed, newtm] _ BasicDelete[tm, [loc-1, 1]]; message _ "erase"; }; BasicInsert: PROC [oldtm: TextMutant, characters: Pipal.ROPE] RETURNS [changed: PipalInt.Interval, newtm: TextMutant] = { size: Location _ Rope.Size[characters]; loc: Location; changed _ [PipalInt.infinity/2, 0]; IF NOT oldtm.selections[primary].valid THEN ERROR Pipal.Error[$noPrimarySelection]; IF oldtm.selections[primary].pendingDelete THEN { paste: Rope.ROPE _ Rope.Substr[oldtm.text.rope, oldtm.selections[primary].interval.base, oldtm.selections[primary].interval.size]; [changed, newtm] _ BasicDelete[oldtm, oldtm.selections[primary].interval]; newtm.paste _ paste; } ELSE newtm _ NEW [TextMutantRec _ oldtm^]; loc _ IF newtm.selections[primary].caretAfter THEN newtm.selections[primary].interval.base + newtm.selections[primary].interval.size ELSE newtm.selections[primary].interval.base; newtm.text _ CreatePipalText[Rope.Cat[ Rope.Substr[newtm.text.rope, 0, loc], characters, Rope.Substr[newtm.text.rope, loc, Rope.Size[newtm.text.rope]-loc]], newtm.text.size]; changed _ PipalInt.UnionIntervals[changed, [loc, size+Rope.Size[newtm.text.rope]-loc]]; FOR selection: Selections IN Selections DO IF newtm.selections[selection].valid AND loc<=newtm.selections[selection].interval.base THEN newtm.selections[selection].interval.base _ newtm.selections[selection].interval.base + size; ENDLOOP; }; BasicDelete: PROC [oldtm: TextMutant, interval: PipalInt.Interval] RETURNS [changed: PipalInt.Interval, newtm: TextMutant] = { oldSize: INT _ Rope.Size[oldtm.text.rope]; base: Location _ interval.base; size: INT _ interval.size; newtm _ NEW [TextMutantRec _ oldtm^]; newtm.text _ CreatePipalText[Rope.Cat[Rope.Substr[oldtm.text.rope, 0, base], Rope.Substr[oldtm.text.rope, base+size, oldSize-(base+size)]], oldtm.text.size]; changed _ [base, oldSize]; FOR sel: Selections IN Selections DO IF oldtm.selections[sel].valid THEN { oldInterv: PipalInt.Interval _ oldtm.selections[sel].interval; interv: PipalInt.Interval _ PipalInt.IntersectionIntervals[interval, oldInterv]; IF base0 THEN newtm.selections[sel].interval.size _ oldInterv.size - interv.size; }; ENDLOOP; }; SetSelection: PUBLIC PROC [tm: TextMutant, interval: PipalInt.Interval, selection: Selections _ primary, pendingDelete: BOOL _ FALSE, caretAfter: BOOL _ FALSE, granularity: SelectionGrain _ char] RETURNS [message: Pipal.ROPE, changed: PipalInt.Interval, newtm: TextMutant] = { text: PipalText _ tm.text; newtm _ NEW [TextMutantRec _ tm^]; changed _ IF newtm.selections[selection].valid THEN newtm.selections[selection].interval ELSE [0, 0]; newtm.selections[selection].valid _ TRUE; newtm.selections[selection].interval _ interval; newtm.selections[selection].pendingDelete _ pendingDelete; newtm.selections[selection].caretAfter _ caretAfter; newtm.selections[selection].granularity _ granularity; IF (tm.selections[selection].interval.size#0 AND tm.selections[selection].granularity=point) OR (caretAfter AND interval.base>=Rope.Size[text.rope]) THEN ERROR; changed _ PipalInt.UnionIntervals[changed, interval]; message _ "select"; }; CancelSelection: PUBLIC PROC [tm: TextMutant, selection: Selections _ primary] RETURNS [message: Pipal.ROPE, changed: PipalInt.Interval, newtm: TextMutant] = { newtm _ NEW [TextMutantRec _ tm^]; newtm.selections[selection].valid _ FALSE; changed _ newtm.selections[selection].interval; message _ "cancel selection"; }; DecomposeIntervals: PROC [i1, i2: PipalInt.Interval] RETURNS [i3, i4: PipalInt.Interval, switched: BOOL _ FALSE] = { IF i1.base>i2.base THEN { t: PipalInt.Interval _ i1; i1 _ i2; i2 _ t; switched _ TRUE; }; i3.base _ i1.base; i3.size _ MIN [i1.size, i2.base-i1.base]; i4.base _ MAX [i2.base, i1.base+i1.size]; i4.size _ MIN [i2.size, ABS [(i2.base+i2.size)-(i1.base+i1.size)]]; }; RectanglePointDistance: PROC [rectangle: PipalReal.Rectangle, position: PipalReal.Position] RETURNS [distance: REAL] = { distance _ MIN[ PointDistance[position, rectangle.base], PointDistance[position, [rectangle.base.x, rectangle.base.y+rectangle.size.y]], PointDistance[position, [rectangle.base.x+rectangle.size.x, rectangle.base.y]], PointDistance[position, [rectangle.base.x+rectangle.size.x, rectangle.base.y+rectangle.size.y]]]; }; PointDistance: PROC [p1, p2: PipalReal.Position] RETURNS [distance: REAL] = { x1x2: REAL _ ABS[p1.x-p2.x]; y1y2: REAL _ ABS[p1.y-p2.y]; distance _ Real.SqRt[x1x2*x1x2+y1y2*y1y2]; }; Pipal.PutClassMethod[pipalTextClass, Pipal.describeMethod, NEW [Pipal.DescribeProc _ TextDescribe]]; Pipal.PutClassMethod[pipalTextClass, PipalReal.sizeMethod, NEW [PipalReal.SizeProc _ TextSize]]; Pipal.PutClassMethod[pipalTextClass, PipalPaint.paintMethod, NEW [PipalPaint.PaintProc _ TextPaint]]; Pipal.PutClassMethod[textMutantClass, Pipal.describeMethod, NEW [Pipal.DescribeProc _ TextMutantDescribe]]; Pipal.PutClassMethod[textMutantClass, PipalReal.sizeMethod, NEW [PipalReal.SizeProc _ TextMutantSize]]; Pipal.PutClassMethod[textMutantClass, PipalPaint.paintMethod, NEW [PipalPaint.PaintProc _ TextMutantPaint]]; Pipal.PutClassMethod[pipalTextClass, PipalMutate.mutationMethod, NEW [PipalMutate.MutationProc _ MutatePipalText]]; END. <PipalTextMutantImpl.mesa Copyright ำ 1988 by Xerox Corporation. All rights reserved. Barth, February 9, 1988 10:33:36 am PST Bertrand Serlet May 19, 1988 6:34:12 pm PDT Text Class Text Mutant Class Creation Manipulation SEEMS BUGGY (BS March 1, 1988 12:09:39 pm PST) Selection PipalInt Orders i1 and i2 so that i3.baseKšœ-œ˜LKšœ˜K˜—Kš œ œœ"œœœœ˜Yš ž œœ%œœœ˜XK˜Kšœœ)˜0Kšœœ˜%šœ œ˜Kšœ œ˜Kšœ%˜%Kšœ ˜ š˜Kšœœ-˜7Kšœ)˜)Kšœœœ˜$Kšœœ$˜?Kšœ,˜,Kšœœœ˜'Kšœ˜—Kšœœ˜;Kšœ,œ œœ˜GKšœœœ˜$Kšœ˜Kšœ˜Kšœ˜—Kšœ˜K˜—š ž œœœœ œ˜>Kš œ œœ œœ œœ ˜LKšœ˜K˜——™K˜šœœ=œ˜jK˜—šžœ˜*Kšœœ ˜(Kšœ!˜!Kšœ˜K˜K˜—šžœ˜&Kšœœ˜>Kšœ˜K˜—KšœGœ˜MKšœJœ˜PšœJœ˜PK˜—Kšœœ˜šœœ˜K˜—Kšœ œœ˜#š œœœ œœ˜/K˜—šœœ˜+Kšœ ฯc"˜+Kšœ Ÿ"˜+Kšœ Ÿ"˜+Kšœ Ÿ"˜+Kšœ Ÿ"˜+Kšœ Ÿ"˜+K˜K˜—šœE˜EK˜—šžœœœœ˜[šœ"˜(Kšœ7˜7Kšœœœœ˜QKšœœ˜$Kšœ˜—K˜K˜—šžœ˜)šžœ ˜Kšœ˜šœœ ˜*šœ œLœ˜wKšœ*˜*Kšœ[˜[Kšœœg˜ršœœ˜Kšœ5˜5Kšœ9˜9Kšœ'˜.—šœœ˜Kšœœ_˜kKšœœ>˜WKšœC˜GKšœœWœœœEœGœœœ˜ฺK˜—Kšœa˜eK˜—Kšœ˜—K˜—Kšœœ ˜!Kšœ˜Kšœ ˜ Kšœ/˜/Kšœ"˜"Kšœ˜K˜——™šžœ˜-Kšœœ ˜"Kšœ œ ˜,K˜K™——™ š ž œœœ$œœœ4˜‘Kšœ/˜/Kšœ˜K˜J˜—š ž œœœœœœ4˜ŠKšœ=˜=Kšœ˜K˜J˜—š žœœœœœ4˜sšœ œ˜(Kšœ˜KšœŽ˜Žšœ+œ˜3KšœP˜PKšœ8˜8K˜—K˜—Kšœœ$˜.Kšœ˜K˜J˜—š ž œœœœœ4˜xKšะbkฯb ก ก ก™.Kšœ˜Kšœœ˜"Kšœœœœ"˜PKšœœ œœ$˜Tšœ˜Kšœ˜Kšœ˜Kšœ œ˜Kšœl˜lšœ&˜&Kšœ#˜#Kšœ)˜)KšœC˜CKšœ)˜)Kšœ]˜]—Kšœ'˜'šœ˜Kšœ œQ˜`Kšœ3˜3KšœT˜TKšœ.˜.Kšœ œc˜sKšœ`˜dK˜—K˜—Kšœ˜Kšœ˜J˜—š žœœœœœ4˜uKšœœ˜"Kšœœœœ"˜PKšœt˜tKšœG˜GKšœ˜K˜J˜—š žœœœœœ4˜tKšœ-˜-Kšœ˜K˜J˜—š žœœœœœ4˜tKšœ(˜(K˜Kšœœ œœ"˜=Kšœœœ%œ˜XKšœœœ"˜5Kšœ/˜/Kšœ˜K˜J˜—šž œœ'œœ4˜yK˜'K˜Kšœ#˜#Kšœœ!œœ"˜Sšœ)œ˜1Kšœ œr˜‚KšœJ˜JKšœ˜K˜—Kšœ œ˜*Kšœœ&œSœ)˜ฒšœ&˜&Kšœ%˜%Kšœ ˜ KšœU˜U—KšœW˜Wšœœ ˜*Kšœ#œ0œ^˜บKšœ˜—K˜J˜—šž œœ2œ4˜~Kšœ œ˜*Kšœ˜Kšœœ˜Kšœœ˜%Kšœ˜Kšœ˜šœœ ˜$šœœ˜&Kšœ>˜>KšœP˜PKš œœ'œœœ˜ƒKšœœD˜YK˜—Kšœ˜—K˜J˜——šœ ™ šž œœœ_œœœœ&œœ4˜”Kšœ˜Kšœœ˜"šœ œ#˜/Kšœ&˜*Kšœ˜ —Kšœ$œ˜)Kšœ0˜0Kšœ:˜:Kšœ4˜4Kšœ6˜6Kš œ+œ-œ œ&œœ˜ Kšœ5˜5Kšœ˜K˜K˜—š žœœœ3œœ4˜ŸKšœœ˜"Kšœ$œ˜*Kšœ/˜/Kšœ˜K˜K˜——™š žœœœ'œœ˜tKšœฉœ™ฎšœœ˜Kšœ+˜+Kšœ œ˜Kšœ˜—K˜Kšœ œ˜)Kšœ œ˜)Kšœ œ œ(˜CK˜K˜——™ šžœœ@œ œ˜xK™Kšœ œ˜Kšœ(˜(KšœO˜OKšœO˜OKšœa˜a—K˜K˜—šž œœœ œ˜MKšœœœ ˜Kšœœœ ˜Kšœ*˜*K˜K˜——™Kšœ;œ&˜dKšœ;œ"˜`Kšœ=œ%˜eK˜Kšœ<œ,˜kKšœ<œ(˜gKšœ>œ+˜lK˜KšœAœ/˜sK˜K˜—Kšœ˜K˜—…—F X