DIRECTORY NodeStyle USING [Ref, Alloc, Free, ApplyAll, GetTopLeadingI, GetTopIndentI, GetLeadingI, GetBottomLeadingI], RopeReader USING [Ref, SetPosition, Backwards, GetRopeReader, FreeRopeReader], Scaled USING [FromInt], TEditDisplay USING [EstablishLine], TEditDocument USING [LineTable, maxClip, Selection, SelectionId, SelectionPoint, TEditDocumentData], TEditFormat USING [Allocate, FormatLine, LineInfo, Release], TEditInput USING [MaxLevelShown], TEditLocks USING [LockDocAndTdd, UnlockDocAndTdd], TEditOps USING [RememberCurrentPosition], TEditProfile USING [scrollBottomOffset, scrollTopOffset], TEditSelection USING [InsertionPoint, pSel, sSel, fSel], TEditScrolling, TEditTouchup USING [LockAfterRefresh, PreScrollDownRec, refresh, UnlockAfterRefresh], TextEdit USING [FetchChar, Offset, RefTextNode, Size], TextNode USING [Backward, BackwardClipped, BadArgs, FirstChild, Forward, ForwardClipped, LastLocWithin, Level, Location, LocNumber, LocOffset, LocWithin, NarrowToTextNode, Parent, pZone, Ref, RefTextNode, Root, StepForward], ViewerOps USING [PaintViewer], ViewerClasses USING [ScrollProc, Viewer]; TEditScrollingImpl: CEDAR PROGRAM IMPORTS NodeStyle, RopeReader, Scaled, TEditDisplay, TEditFormat, TEditLocks, TEditSelection, TextEdit, TEditInput, TEditOps, TEditProfile, TEditTouchup, TextNode, ViewerOps EXPORTS TEditScrolling = BEGIN OPEN TEditDocument, TextNode; BackUp: PROC [viewer: ViewerClasses.Viewer, tdd: TEditDocumentData, pos: Location, goal: INTEGER] RETURNS [newPos: Location, lines, totalLeading, topIndent: INTEGER] = BEGIN maxLevel: INTEGER _ tdd.clipLevel; parent: TextNode.Ref; level: INTEGER _ 0; -- in case we are doing level clipping levelClipping: BOOLEAN _ maxLevel < maxClip; IncrBackUp: PROC [pos: Location, goal, prevTopLeading: INTEGER] RETURNS [prev: Location, totalLeading, lines, topIndent, topLeading: INTEGER] = BEGIN tPos: Location; leading, bottomLeading: INTEGER; lastBreak: TextEdit.Offset; breakList: LIST OF TextEdit.Offset; -- breaks between first and last textNode: RefTextNode; where, endOffset, size: TextEdit.Offset; lineInfo: TEditFormat.LineInfo; IF pos.where=0 THEN DO -- back up to previous text node tempNode: TextNode.Ref; IF levelClipping THEN [tempNode, parent, level] _ BackwardClipped[pos.node, maxLevel, parent, level] ELSE [tempNode, parent, ----] _ Backward[pos.node, parent]; IF tempNode=NIL OR parent=NIL THEN RETURN[pos, 0, 0, 0, 0]; IF (textNode _ NarrowToTextNode[tempNode])=NIL THEN LOOP; size _ endOffset _ where _ TextEdit.Size[textNode]; EXIT; ENDLOOP ELSE { -- back up past last char before pos textNode _ NarrowToTextNode[pos.node]; size _ TextEdit.Size[textNode]; endOffset _ where _ pos.where-1 }; IF where < 4*MAX[12,goal] THEN where _ 0 -- don't bother to search backwards for CR ELSE { stop: TextEdit.Offset _ MAX[0, where-5000]; -- limit reading to 5000 characters RopeReader.SetPosition[rdr, textNode.rope, where]; UNTIL (where _ where-1)<=stop DO IF RopeReader.Backwards[rdr]=15C THEN {where _ where+1; EXIT}; ENDLOOP }; IF styleNode # textNode THEN NodeStyle.ApplyAll[style, styleNode _ textNode]; leading _ NodeStyle.GetLeadingI[style]; topLeading _ IF where=0 THEN NodeStyle.GetTopLeadingI[style] ELSE leading; bottomLeading _ totalLeading _ IF pos.node = textNode THEN leading ELSE MAX[prevTopLeading, NodeStyle.GetBottomLeadingI[style]]; topIndent _ NodeStyle.GetTopIndentI[style]; -- in case this line appears at top of viewer IF where=size THEN { -- no more characters in the node. shows as blank line lines _ 1; prev _ [textNode, where]; RETURN }; tPos _ [textNode, where]; lines _ 0; lineInfo _ TEditFormat.Allocate[]; DO -- format lines from tPos to starting pos lastBreak _ tPos.where; TEditFormat.FormatLine[lineInfo, tdd, TextNode.NarrowToTextNode[tPos.node], tPos.where, style, Scaled.FromInt[viewer.cw], FALSE]; tPos _ lineInfo.nextPos; IF lines > 0 THEN totalLeading _ totalLeading+leading; lines _ lines+1; IF tPos.node#textNode OR tPos.where>=endOffset THEN EXIT; IF lastBreak # where THEN breakList _ TextNode.pZone.CONS[lastBreak, breakList]; ENDLOOP; lineInfo.Release[]; lineInfo _ NIL; IF totalLeading+topIndent >= goal THEN { -- have enough. find correct line discardLines: INTEGER _ (totalLeading+topIndent-goal)/leading; -- too many lines SELECT discardLines FROM <= 0 => NULL; -- don't discard any >= lines-1 => { -- discard all but one where _ lastBreak; lines _ 1; totalLeading _ bottomLeading }; ENDCASE => { -- use breakList to find correct break count: INTEGER; -- how far to go on list to find the break lines _ lines-discardLines; count _ lines-1; -- subtract 1 because lastBreak is not on list totalLeading _ totalLeading-discardLines*leading; FOR list: LIST OF TextEdit.Offset _ breakList, list.rest DO IF (count _ count-1) = 0 THEN { where _ list.first; EXIT }; ENDLOOP; }; }; prev _ [textNode, where]; END; rdr: RopeReader.Ref _ RopeReader.GetRopeReader[]; style: NodeStyle.Ref _ NodeStyle.Alloc[]; styleNode: TextNode.Ref; remainder: INTEGER _ goal; dy: INTEGER _ LAST[INTEGER]; topLeading: INTEGER _ 0; IF pos.where=0 THEN { -- need to get topLeading for pos.node NodeStyle.ApplyAll[style, pos.node]; topLeading _ NodeStyle.GetTopLeadingI[style]; }; newPos _ pos; lines _ totalLeading _ topIndent _ 0; UNTIL remainder<=0 DO leading, newLines, newTopIndent: INTEGER; [newPos, leading, newLines, newTopIndent, topLeading] _ IncrBackUp[newPos, remainder, topLeading]; IF newLines <= 0 THEN EXIT; -- didn't get anything that time. at start of document. totalLeading _ totalLeading+leading; lines _ lines+newLines; topIndent _ newTopIndent; IF totalLeading+topIndent >= goal THEN EXIT; -- don't need to back up any farther remainder _ remainder - leading; ENDLOOP; NodeStyle.Free[style]; RopeReader.FreeRopeReader[rdr]; END; ScrollTEditDocument: PUBLIC ViewerClasses.ScrollProc = BEGIN tdd: TEditDocumentData = NARROW[self.data]; lines: LineTable; topLine: Location; paintOp: REF ANY _ TEditTouchup.refresh; iconic: BOOL = self.iconic; levelChange: BOOL _ FALSE; IF tdd = NIL THEN RETURN; IF iconic THEN { [] _ TEditLocks.LockDocAndTdd[tdd, "ScrollTEditDocument", read]; SELECT op FROM up => TEditDisplay.EstablishLine[tdd, TextNode.LastLocWithin[tdd.text], 0]; down => TEditDisplay.EstablishLine[tdd, [TextNode.FirstChild[tdd.text],0], 0]; thumb => NULL; query => NULL; ENDCASE => ERROR; TEditLocks.UnlockDocAndTdd[tdd]; RETURN }; IF NOT TEditTouchup.LockAfterRefresh[tdd, "ScrollTEditDocument"] THEN RETURN; lines _ tdd.lineTable; topLine _ lines[0].pos; SELECT op FROM up => BEGIN line: INTEGER _ 0; newLevel: INTEGER; IF amount > 0 AND lines.lastLine = 0 THEN { -- calculate next line start pos IF lines[0].end = eon THEN { -- go to the next node, unless already at end of document next: TextNode.Ref; maxLevel: INTEGER = tdd.clipLevel; next _ IF maxLevel < maxClip THEN TextNode.ForwardClipped[topLine.node,maxLevel].nx ELSE TextNode.StepForward[topLine.node]; IF next # NIL THEN topLine _ [next, 0] } ELSE topLine.where _ topLine.where+lines[0].nChars } ELSE { -- find new topLine on screen UNTIL lines[line].yOffset+lines[line].descent >= amount OR lines[line+1].pos.node=NIL DO line _ line+1; IF line >= lines.lastLine THEN EXIT; -- off end ENDLOOP; topLine _ lines[line].pos }; newLevel _ SELECT TRUE FROM control AND shift => 1, -- first level only control => maxClip, -- all levels shift => MIN[maxClip, TEditInput.MaxLevelShown[tdd]+1], -- move levels ENDCASE => tdd.clipLevel; -- no change IF newLevel # tdd.clipLevel THEN { tdd.clipLevel _ newLevel; levelChange _ TRUE } END; down => { numLines, totalLeading, topIndent: INTEGER; [topLine, numLines, totalLeading, topIndent] _ BackUp[self, tdd, topLine, MAX[amount, lines[0].yOffset]]; IF topLine # lines[0].pos THEN { -- make room for the new stuff op: REF TEditTouchup.PreScrollDownRec _ TextNode.pZone.NEW[TEditTouchup.PreScrollDownRec]; op.lines _ numLines; op.distance _ topIndent+totalLeading-lines[0].yOffset; paintOp _ op }; }; thumb => BEGIN TEditOps.RememberCurrentPosition[self]; IF amount < 5 THEN ScrollToPosition[self, [FirstChild[tdd.text],0], FALSE] ELSE BEGIN totalChars: TextEdit.Offset = LocNumber[LastLocWithin[tdd.text]]-1; pos: TextNode.Location _ LocWithin[tdd.text, (totalChars*amount)/100]; IF tdd.clipLevel < maxClip THEN { -- check level of target delta: INTEGER _ TextNode.Level[pos.node]-tdd.clipLevel; FOR i:INTEGER IN [0..delta) DO -- only do this if pos is too deep pos _ [TextNode.Parent[pos.node],0]; ENDLOOP; }; ScrollToPosition[self, pos, FALSE]; END; TEditTouchup.UnlockAfterRefresh[tdd]; RETURN; END; query => BEGIN toTop, toBottom, toEnd, totalChars, t, b: INT; ll: INTEGER = lines.lastLine; toTop _ LocOffset[[TextNode.FirstChild[tdd.text], 0], topLine ! BadArgs => GOTO Bad]; toBottom _ LocOffset[topLine, [lines[ll].pos.node, lines[ll].pos.where+lines[ll].nChars] ! BadArgs => GOTO Bad]; toEnd _ LocOffset[[lines[ll].pos.node, lines[ll].pos.where+lines[ll].nChars-1], LastLocWithin[tdd.text] ! BadArgs => GOTO Bad]; totalChars _ toTop+toBottom+toEnd; TEditTouchup.UnlockAfterRefresh[tdd]; IF totalChars<=0 THEN RETURN[0, 100]; t _ MIN[98, 100*toTop/totalChars]; b _ MAX[t+2, 100*(toTop+toBottom)/totalChars]; RETURN[t, b]; -- so compiler will coerce to short integers EXITS Bad => { TEditTouchup.UnlockAfterRefresh[tdd]; RETURN }; END; ENDCASE; IF topLine # lines[0].pos OR levelChange THEN BEGIN TEditDisplay.EstablishLine[tdd, topLine, 0]; IF levelChange THEN ViewerOps.PaintViewer[self, client] -- complete repaint ELSE ViewerOps.PaintViewer[self, client, FALSE, paintOp]; END; TEditTouchup.UnlockAfterRefresh[tdd]; END; ScrollToPosition: PUBLIC PROC [viewer: ViewerClasses.Viewer, pos: TextNode.Location, offset: BOOLEAN] = BEGIN tdd: TEditDocumentData = NARROW[viewer.data]; lines: LineTable; node: TextNode.RefTextNode _ TextNode.NarrowToTextNode[pos.node]; where: INT _ MAX[0, MIN[pos.where, TextEdit.Size[node]-1]]; start: INT _ where; icon: BOOL = viewer.iconic; repaint: BOOL _ FALSE; topLine: TextNode.Location; style: NodeStyle.Ref _ NodeStyle.Alloc[]; lineInfo: TEditFormat.LineInfo; backStop: INT _ MAX[0, where-300]; -- limit looking back too far when searching for CR's Unlock: PROC = { IF icon THEN TEditLocks.UnlockDocAndTdd[tdd] ELSE TEditTouchup.UnlockAfterRefresh[tdd] }; IF tdd = NIL THEN RETURN; IF icon THEN [] _ TEditLocks.LockDocAndTdd[tdd, "ScrollToPosition", read] ELSE IF NOT TEditTouchup.LockAfterRefresh[tdd, "ScrollToPosition"] THEN RETURN; IF tdd.clipLevel < maxClip AND tdd.clipLevel < TextNode.Level[node] THEN { repaint _ TRUE; tdd.clipLevel _ maxClip; -- turn off level clipping -- }; lines _ tdd.lineTable; UNTIL start<=backStop OR TextEdit.FetchChar[node, start-1]=15C DO start _ start - 1; ENDLOOP; topLine _ [node, start]; NodeStyle.ApplyAll[style, topLine.node]; lineInfo _ TEditFormat.Allocate[]; DO -- find the line containing [node, where] lineInfo.FormatLine[tdd, TextNode.NarrowToTextNode[topLine.node], topLine.where, style, Scaled.FromInt[viewer.cw]]; IF lineInfo.nextPos.node#node OR lineInfo.nextPos.where>where OR lineInfo.nextPos=topLine THEN EXIT; topLine _ lineInfo.nextPos; IF topLine.where+lineInfo.nChars>where THEN EXIT; ENDLOOP; IF offset THEN BEGIN -- move goal line a bit down from the top goal: INTEGER = style.GetLeadingI[] * TEditProfile.scrollTopOffset; IF goal*2 < viewer.ch THEN topLine _ BackUp[viewer, tdd, topLine, goal].newPos; END; lineInfo.Release[]; lineInfo _ NIL; NodeStyle.Free[style]; IF repaint OR topLine # lines[0].pos THEN BEGIN TEditDisplay.EstablishLine[tdd, topLine, 0]; Unlock[]; IF repaint THEN ViewerOps.PaintViewer[viewer, client] ELSE ViewerOps.PaintViewer[viewer, client, FALSE, TEditTouchup.refresh]; END ELSE Unlock[]; END; OnScreen: PROC [viewer: ViewerClasses.Viewer, point: TextNode.Location] RETURNS [BOOLEAN] = { IF viewer = NIL OR point.node = NIL THEN RETURN [FALSE]; IF viewer.destroyed OR viewer.iconic THEN RETURN [FALSE]; WITH viewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { IF TEditTouchup.LockAfterRefresh[tdd, "OnScreen"] THEN { ENABLE {UNWIND => TEditTouchup.UnlockAfterRefresh[tdd]}; lines: TEditDocument.LineTable _ tdd.lineTable; found: BOOL _ FALSE; IF lines # NIL AND lines.lastLine >= 0 THEN { first: TextNode.Location _ lines[0].pos; last: TextNode.Location _ lines[lines.lastLine].pos; each: TextNode.Ref _ first.node; last.where _ last.where + lines[lines.lastLine].nChars; IF point.node = first.node AND point.where < first.where THEN GO TO quickOut; IF point.node = last.node AND point.where > last.where THEN GO TO quickOut; WHILE each # NIL DO IF each = point.node THEN {found _ TRUE; EXIT}; IF each = last.node THEN EXIT; each _ TextNode.Forward[each].nx; ENDLOOP; EXITS quickOut => {}; }; TEditTouchup.UnlockAfterRefresh[tdd]; RETURN [found]; }; }; ENDCASE; RETURN [FALSE]; }; AutoScroll: PUBLIC PROC [ viewer: ViewerClasses.Viewer _ TEditSelection.pSel.viewer, tryToGlitch: BOOLEAN _ TRUE, toEndOfDoc: BOOLEAN _ FALSE, id: TEditDocument.SelectionId _ primary] = BEGIN tdd: TEditDocumentData; goal: TextNode.Location; lines: LineTable; onScreen: BOOLEAN; sel: Selection = SELECT id FROM primary => TEditSelection.pSel, secondary => TEditSelection.sSel, feedback => TEditSelection.fSel, ENDCASE => ERROR; TryToGlitch: PROC RETURNS [success: BOOLEAN] = BEGIN glitchLines: INTEGER = MIN[MAX[lines.lastLine/2, 1], TEditProfile.scrollBottomOffset]; tryLines: INTEGER = MAX[lines.lastLine/2-glitchLines, 2]; -- how far to search for caret newPos: TextNode.Location; style: NodeStyle.Ref; lineInfo: TEditFormat.LineInfo; lineCount: INTEGER _ 0; pos: TextNode.Location _ lines[lines.lastLine].pos; foundNode: BOOLEAN; NextNode: PROC [node: TextNode.Ref] RETURNS [next: TextNode.Ref] = { next _ IF tdd.clipLevel < maxClip THEN TextNode.ForwardClipped[node,tdd.clipLevel].nx ELSE TextNode.StepForward[node] }; IF TEditProfile.scrollBottomOffset<=0 THEN { TEditTouchup.UnlockAfterRefresh[tdd]; RETURN [TRUE] }; -- never glitch in this case success _ FALSE; style _ NodeStyle.Alloc[]; IF lines[lines.lastLine].end = eon THEN { pos _ [NextNode[lines[lines.lastLine].pos.node],0]; IF pos.node=NIL THEN { NodeStyle.Free[style]; RETURN [FALSE] }} ELSE pos.where _ pos.where+lines[lines.lastLine].nChars; foundNode _ pos.node=goal.node; NodeStyle.ApplyAll[style, pos.node]; lineInfo _ TEditFormat.Allocate[]; THROUGH [0..MIN[tryLines, lines.lastLine-glitchLines]) DO IF pos.node=NIL THEN { NodeStyle.Free[style]; lineInfo.Release[]; RETURN [FALSE] }; lineInfo.FormatLine[tdd, TextNode.NarrowToTextNode[pos.node], pos.where, style, Scaled.FromInt[viewer.cw]]; newPos _ lineInfo.nextPos; IF newPos.node=goal.node THEN foundNode _ TRUE; IF foundNode AND (newPos.node#goal.node OR newPos.where>goal.where) THEN {success _ TRUE; EXIT}; lineCount _ lineCount+1; IF newPos.node#pos.node THEN NodeStyle.ApplyAll[style, newPos.node]; pos _ newPos; ENDLOOP; NodeStyle.Free[style]; lineInfo.Release[]; lineInfo _ NIL; IF success THEN BEGIN TEditDisplay.EstablishLine[tdd, lines[lineCount+glitchLines].pos, 0]; TEditTouchup.UnlockAfterRefresh[tdd]; ViewerOps.PaintViewer[viewer, client, FALSE, TEditTouchup.refresh]; END; END; IF viewer=NIL OR viewer.iconic OR viewer.destroyed THEN RETURN; onScreen _ OnScreen[viewer, sel.start.pos]; -- determines if selection is on the screen tdd _ IF viewer=sel.viewer THEN sel.data ELSE NARROW[viewer.data]; IF tdd = NIL THEN RETURN; IF NOT TEditTouchup.LockAfterRefresh[tdd, "AutoScroll"] THEN RETURN; lines _ tdd.lineTable; IF toEndOfDoc THEN BEGIN -- scroll to end of document; used in typescripts lines: LineTable = tdd.lineTable; goal _ TextNode.LastLocWithin[TextNode.FirstChild[tdd.text]]; IF lines[lines.lastLine].pos.where+lines[lines.lastLine].nChars >= goal.where THEN {TEditTouchup.UnlockAfterRefresh[tdd]; RETURN}; goal.where _ goal.where-1; -- last char correction IF tryToGlitch AND TryToGlitch[] THEN RETURN; END ELSE IF sel.viewer # viewer THEN {TEditTouchup.UnlockAfterRefresh[tdd]; RETURN} ELSE BEGIN -- scroll to end of selection selPoint: TEditDocument.SelectionPoint = IF sel.insertion=before THEN sel.start ELSE sel.end; clipped: BOOL = selPoint.clipped; IF NOT clipped AND onScreen THEN {TEditTouchup.UnlockAfterRefresh[tdd]; RETURN}; -- visible on screen goal _ TEditSelection.InsertionPoint[sel]; IF TextNode.Root[goal.node] # tdd.text THEN -- make sure that selection didn't leave {TEditTouchup.UnlockAfterRefresh[tdd]; RETURN}; IF sel.insertion=before AND goal.where>0 THEN goal.where _ goal.where-1; IF NOT clipped AND tryToGlitch AND selPoint.line>0 AND TryToGlitch[] THEN RETURN; END; TEditTouchup.UnlockAfterRefresh[tdd]; ScrollToPosition[viewer, goal, TRUE]; END; END. TEditScrollingImpl.mesa; Edited by McGregor on January 13, 1983 2:47 pm Edited by Paxton on December 29, 1982 8:45 am Last Edited by: Maxwell, January 4, 1983 3:53 pm Last Edited by: Plass, August 12, 1983 10:56 am pos argument is the current top line in the viewer. goal argument is the distance to back up. algorithm works by incrementally formatting lines prior to pos until reaches goal height. The incremental backup procedure preserves the following invariants: newPos is the current line start totalLeading is the leading from the baseline of newPos to the baseline of pos topIndent is the distance from the viewer top to baseline of newPos if newPos goes at top topLeading is the style value for topLeading corresponding to newPos When reach here, have found all the line breaks from [textNode, where] to initial pos. where holds the offset for the first one lastBreak holds the offset for the last one breakList holds the offsets for the previous ones Once implement minGaps between lines, will also need topAscent for this line. Should be able to get that from the line table. (Except if continue to use this for top offset in ScrollToPosition... perhaps can just change that to back up a fixed number of lines.) -- make sure there's always something shown for big documents This ugly hack is to protect again messed up line table. OnScreen determines whether or not the given location is visible for the given viewer. Now we know that we have a Tioga document At this point tdd is really and truly locked up. We must release it at the end of the block or bad things will happen. Need to have the MAX or will fail to scroll in 1 or 2 line viewers. Needs to be bigger than glitchLines since burst input may move the caret several lines. -- glitch failed; use general scroll ÊݘJšœG™GJšœ-™-Jšœ0™0Jšœ/™/šÏk ˜ Jšœ œ]˜lJšœ œ>˜NJšœœ ˜Jšœ œ˜#JšœœQ˜dJšœ œ+˜œ˜OJš˜J˜Jšœœ˜ Jšœ˜Jšœ œœŸ ˜DJ˜J˜(J˜šœ œœŸ ˜7J˜šœ˜JšœN˜N—JšœŸœ˜;Jš œ œœœœœ˜;Jšœ)œœœ˜9Jšœ3˜3Jšœ˜Jš˜—šœŸ$˜+Jšœ&˜&Jšœ˜Jšœ"˜"—J˜Jšœ œ œ Ÿ*˜Sšœ˜JšœœŸ#˜OJšœ2˜2šœ˜ Jšœœœ˜>Jšœ˜ —J˜—Jšœœ1˜MJ˜Jšœ'˜'Jšœ œ œ!œ ˜Jšœœœ˜BJšœœ5˜=—Jšœ,Ÿ-˜YJ˜šœ œŸ7˜LJšœ%œ˜.—J˜Jšœ ˜ Jšœ"˜"šœŸ)˜,Jšœ˜Jšœzœ˜Jšœ˜Jšœ œ%˜6J˜Jšœœœœ˜9Jšœœœ˜PJšœ˜—Jšœœ˜#J™™VJšœ(™(Jšœ+™+Jšœ2™2J˜—šœ œŸ"˜KJšœœ*Ÿ˜Pšœ˜JšœœŸ˜"šœŸ˜&Jšœ=˜=—šœŸ&˜3JšœœŸ*˜:Jšœ˜JšœŸ.˜?Jšœ1˜1šœœœ(˜;Jšœœœ˜;Jšœ˜—J˜——J˜J˜—Jšœ˜Jšœ˜J˜—Jšœ1˜1J˜)J˜Jšœ œ˜Jšœœœœ˜Jšœ œ˜J˜šœ œŸ&˜J™8—Jšœ˜J˜—Jšœ˜—J˜šœœ œ˜3J˜,Jšœ œ%Ÿ˜KJšœ%œ ˜9Jšœ˜—J˜Jšœ%˜%J˜Jšœ˜J˜—š žœœœ@œ˜mJšœœ˜-J˜J˜AJšœœœœ$˜;Jšœœ ˜Jšœœ˜Jšœ œœ˜J˜J˜)Jšœ˜Jšœ œœŸ5˜Xšžœœ˜Jšœœ ˜,Jšœ(˜,—J˜Jšœœœœ˜Jšœœ=˜IJš œœœ8œœ˜OJ˜šœœ&œ˜JJšœ œŸœ˜IJ˜—J˜J˜šœœ'˜AJ˜Jšœ˜J˜—J˜J˜(Jšœ"˜"šœŸ)˜,Jšœs˜sJš œœœœœ˜dJšœ˜Jšœ%œœ˜1Jšœ˜—J˜šœœœŸ)˜>Jšœœ6˜CJšœœ5˜OJšœ˜—Jšœœ˜#J˜J˜šœ œœ˜/J˜,J˜ Jšœ œ&˜5Jšœ'œ˜HJš˜—šœ ˜J˜—Jšœ˜—J˜šžœœ:œœ˜]JšœV™VJšœ œœœœœœ˜8Jš œœœœœ˜9šœ œ˜šœ)˜)Jšœ)™)šœ0œ˜8Jšœw™wJšœœ*˜8Jšœ/˜/Jšœœœ˜šœ œœœ˜-Jšœ(˜(Jšœ4˜4Jšœ ˜ Jšœ7˜7Jš œœœœœ ˜MJš œœœœœ ˜Kšœœ˜Jšœœ œœ˜/Jšœœœ˜Jšœ!˜!Jšœ˜—Jšœ˜J˜—Jšœ%˜%Jšœ ˜J˜—J˜—Jšœ˜—Jšœœ˜Jšœ˜J˜Jšž œœœ˜Jšœ:˜:Jšœ œœ˜Jšœ œœ˜Jšœ+˜0J˜J˜J˜Jšœ œ˜šœœ˜Jšœ˜Jšœ!˜!Jšœ ˜ Jšœœ˜—J˜š ž œœœ œ˜4šœ œœœ8˜VJ™C—šœ œœ#Ÿ˜XJšœW™W—J˜J˜Jšœ˜Jšœ œ˜J˜3Jšœ œ˜šžœœœ˜Dšœœ˜&Jšœ.˜.Jšœ˜"——J˜šœ$œ˜,Jšœ%˜%JšœœŸ˜-—J˜Jšœ œ˜J˜šœ!œ˜)Jšœ3˜3Jš œ œœœœ˜?—Jšœ4˜8Jšœ˜J˜$Jšœ"˜"šœœ(˜9Jš œ œœ.œœ˜SJšœk˜kJšœ˜Jšœœ œ˜/šœ œœ˜CJšœ œœ˜—J˜Jšœœ(˜DJ˜ Jšœ˜—J˜Jšœœ˜#šœ œ˜J˜EJšœ%˜%Jšœ&œ˜CJšœ˜—Jšœ˜J˜—Jš œœœœœœ˜?J˜Jšœ,Ÿ+˜WJ˜Jš œœœ œœ˜BJšœœœœ˜Jšœœ2œœ˜DJ˜J˜J˜šœ œœŸ1˜JJ˜!J˜=šœK˜MJšœ(œ˜4—JšœŸ˜2Jšœ œœœ˜-Jš˜—Jšœœœ(œ˜OšœœŸ˜(Jšœ)œœ œ ˜]Jšœ œ˜!šœœ œ ˜ Jšœ'œŸ˜D—Jšœ*˜*šœ%œŸ(˜TJšœ'œ˜/—Jšœœœ˜HJšœœ œ œœœœ˜QJšœ˜J˜—Jšœ%˜%J˜JšŸ$™$Jšœœ˜%J˜Jšœ˜Icode˜—Jšœ˜J˜—…—AYé