DIRECTORY Convert USING [RopeFromReal], FS USING [Error], Graph USING [BackgroundIndex, BWLineState, CaretIndex, CaretSpec, ColorMode, Entity, EntityColor, EntityList, FontIndex, Fonts, GraphHandle, JustifX, JustifY, LastEntityColor, LineStateRec, Mark, MaxStep, NullVec, OutputType, PaintAction, PaintInfo, PaintInfoRec, ROPE, SegmentData, SegmentDataList, StepCount, Text, Texts, UnitLineWidth, ValueList, XY], GraphCarets USING [Resume, Suspend], GraphConvert USING [CharRopeFromMark, RopeOfPlace, RopeOfSlope], GraphOps USING [LabelFont, LegendFont, Math8], GraphPrivate USING [systemColor], GraphUtil USING [Almost, BackgroundColor, BlinkMsg, TextPosToChartPos, DataBounds, FontHeight, ForegroundColor, FullName, HandleNotNil, NotANan, NotANumber, RealToScreen, RealToScreenI, RealVecToScreenVec, RopeSize], Imager USING [Box, ClipRectangle, Color, Context, DoSaveAll, Error, MaskRectangle, MaskStroke, MaskVector, PathProc, Rectangle, RotateT, ScaleT, SetColor, SetFont, SetStrokeEnd, SetStrokeJoint, SetStrokeWidth, SetXY, ShowRope, TranslateT, VEC], ImagerBackdoor USING [GetBounds], ImagerFont USING [Extents, Find, Font, RopeBoundingBox, Scale], ImagerInterpress USING [Close, Create, DoPage, Ref], Real USING [FixI, RoundI, RoundLI], RealFns USING [Log, Power, SqRt], Rope USING [Cat, Concat, IsEmpty]; GraphDraw: CEDAR PROGRAM IMPORTS Convert, Imager, ImagerBackdoor, ImagerFont, ImagerInterpress, FS, GraphCarets, GraphConvert, GraphPrivate, GraphUtil, Real, RealFns, Rope EXPORTS GraphPrivate = { OPEN Graph, GraphPrivate, GraphUtil; TextHeightFudge: REAL = 8.0; TextWidthFudge: REAL = 8.0; shortLegendW: REAL = 200; bwLineState: BWLineState _ NEW[LineStateRec _ []]; Draw: PUBLIC PROC [handle: GraphHandle, context: Imager.Context, info: PaintInfo] = { IF context # NIL AND handle # NIL AND info # NIL THEN { OPEN handle; GraphCarets.Suspend[handle]; WITH info SELECT FROM a: REF all PaintInfoRec => DrawGraph[handle, context, a.action, a.output, a.clear, a.updateDivBds]; a: REF allCurves PaintInfoRec => DrawMajorPart[handle, context, a.action, a.output, a.clear]; r: REF rectangle PaintInfoRec => NULL; -- for now. DrawGraph[handle, context, r.action, r.output, r.clear, r.updateDivBds]; t: REF graphText PaintInfoRec => DrawText[handle, context, t.action, t.output, t.text]; e: REF graphEntity PaintInfoRec => DrawEntity[handle, context, e.action, e.output, e.entity]; l: REF legend PaintInfoRec => DrawLegend[handle, context, l.action, l.output, l.entity, l.onlyOne]; t: REF tails PaintInfoRec => DrawTails[handle, context, t.action, t.output, t.x1, t.x2, t.v1, t.v2, t.entities]; t: REF target PaintInfoRec => DrawTarget[handle, context, t.action, t.xy]; g: REF grid PaintInfoRec => DrawTicks[handle, context, g.action, g.output, g.xy, g.long, FALSE]; -- false meaning no label. ENDCASE; GraphCarets.Resume[handle]; }; }; -- Draw DrawGraph: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, clear, updateDivBds: BOOL] = { OPEN handle; rect: Imager.Rectangle _ IF output = interpress THEN [0.0, -518.16, 670.56, 518.16] ELSE ImagerBackdoor.GetBounds[context]; axesRectSave: Imager.Rectangle; scaleSave: ARRAY XY OF REAL; dlSave: BOOL; IF output = interpress THEN {axesRectSave _ axesRect; scaleSave _ scale; dlSave _ normalLegend;} ELSE IF clear THEN { -- happens in screen painting only. clearProc: PROC = { context.SetColor[BackgroundColor[backgroundIndex, screen]]; context.MaskRectangle[rect]; }; context.DoSaveAll[clearProc]; }; normalLegend _ (output = interpress) OR rect.h >= 450; { -- setting axesRect margin1: REAL = MIN[rect.h * 0.05, 10.0]; margin2: REAL = margin1*2.0; -- MIN[rect.w * 0.05, 20.0]; xLabelH: REAL = FontHeight[imagerFonts[output][GraphOps.LabelFont]]; legendH: REAL = FontHeight[imagerFonts[output][GraphOps.LegendFont]]; -- for legend, footnote topTextsH: REAL _ MIN[rect.h * 0.17, 50.0]; -- don't calculate anything else to save time. leftTextsW: REAL = MIN[rect.h * 0.25, 70.0]; rightTextsW: REAL = MIN[rect.w * 0.35, IF normalLegend THEN leftTextsW ELSE shortLegendW]; bottomTextsH: REAL _ MIN[rect.h*0.35, xLabelH + TextHeightFudge + (IF normalLegend THEN legendH*7.0 + TextHeightFudge + TextHeightFudge ELSE 0.0)]; IF output = interpress THEN topTextsH _ topTextsH + 20; -- experimental, for now. axesRect _ [ x: rect.x + margin1 + leftTextsW, y: rect.y + margin1 + bottomTextsH, w: rect.w - margin2 - leftTextsW - rightTextsW, h: rect.h - margin2 - topTextsH - bottomTextsH ]; }; -- setting axesRect IF axesRect.w > 0.0 AND axesRect.h > 0.0 THEN { wholePicture: PROC = { ScaleAxes[handle, output, updateDivBds]; DrawAxes[handle, context, output]; -- action must be paint. DrawTicks[handle, context, action, output, x, graph.grids[x], TRUE]; DrawTicks[handle, context, action, output, y, graph.grids[y], TRUE]; DrawTexts[handle, context, output]; -- action must be paint. IF graph.entityList # NIL THEN DrawLegend[handle, context, action, output, graph.entityList.first, FALSE]; IF output = interpress THEN PrintCaretsInfo[handle, context]; -- action must be paint. IF graph.target[x].on THEN DrawTarget[handle, context, action, x]; IF graph.target[y].on THEN DrawTarget[handle, context, action, y]; DrawEntities[handle, context, output]; -- action must be paint. }; context.DoSaveAll[wholePicture]; }; IF output = interpress THEN { axesRect _ axesRectSave; scale _ scaleSave; normalLegend _ dlSave}; [chart.caretState[text].x, chart.caretState[text].y] _ TextPosToChartPos[axesRect, graph.caret[text].place]; FOR i: CaretIndex IN [primary..secondary] DO chart.caretState[i].x _ RealToScreenI[handle, graph.caret[i].place.x, x]; chart.caretState[i].y _ RealToScreenI[handle, graph.caret[i].place.y, y]; ENDLOOP; }; -- DrawGraph DrawMajorPart: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, clear: BOOL] = { OPEN handle; IF axesRect.w > 0.0 AND axesRect.h > 0.0 THEN { majorPart: PROC = { IF clear THEN { clearProc: PROC = { context.SetColor[BackgroundColor[backgroundIndex, screen]]; context.MaskRectangle[axesRect]; }; context.DoSaveAll[clearProc]; FOR i: CaretIndex IN CaretIndex DO chart.caretState[i].visible _ FALSE; ENDLOOP; }; context.ClipRectangle[axesRect]; DrawAxes[handle, context, output]; -- action must be paint. DrawTicks[handle, context, action, output, x, graph.grids[x], TRUE]; DrawTicks[handle, context, action, output, y, graph.grids[y], TRUE]; DrawTexts[handle, context, output]; -- action must be paint. IF graph.target[x].on THEN DrawTarget[handle, context, action, x]; IF graph.target[y].on THEN DrawTarget[handle, context, action, y]; DrawEntities[handle, context, output]; -- action must be paint. }; context.DoSaveAll[majorPart]; }; }; -- DrawMajorPart DrawTarget: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, xy: XY] = { OPEN handle; targetProc: PROC = { rect: Imager.Rectangle; tcolor: Imager.Color _ IF action = erase THEN BackgroundColor[backgroundIndex, screen] ELSE systemColor[graph.target[xy].colorIndex]; IF xy = x THEN { OPEN graph.target[x]; IF value < realRect.x OR value > realRect.x + realRect.w THEN RETURN; rect _ [x: RealToScreen[handle, value, x] - UnitLineWidth*width*0.5, y: axesRect.y+1.0, w: UnitLineWidth*width, h: axesRect.h-2.0]; } ELSE { OPEN graph.target[y]; IF value < realRect.y OR value > realRect.y + realRect.h THEN RETURN; rect _ [x: axesRect.x+1.0, y: RealToScreen[handle, value, y] - UnitLineWidth*width*0.5, w: axesRect.w-2.0, h: UnitLineWidth*width]; }; context.SetColor[tcolor]; context.MaskRectangle[rect]; }; context.DoSaveAll[targetProc]; }; -- DrawTarget DrawRope: PROC[context: Imager.Context, text: ROPE, x0, y0: REAL, justifX: JustifX, justifY: JustifY, color: Imager.Color, font: ImagerFont.Font, rotation: REAL] = { IF ~text.IsEmpty[] THEN { showTextProc: PROC = { context.TranslateT[[x0, y0]]; context.RotateT[rotation]; context.SetXY[[xmin, ymin]]; context.SetColor[color]; context.SetFont[font]; context.ShowRope[text]; }; -- showTextProc extents: ImagerFont.Extents; xmin, ymin, width, height: REAL; extents _ ImagerFont.RopeBoundingBox[font: font, rope: text]; width _ extents.rightExtent - extents.leftExtent; height _ extents.descent + extents.ascent; xmin_ SELECT justifX FROM right => - width - extents.leftExtent, center => - width/2.0 - extents.leftExtent, ENDCASE => - extents.leftExtent; ymin_ SELECT justifY FROM top => - height + extents.descent, center => - height/2.0 + extents.descent, ENDCASE => extents.descent; context.DoSaveAll[showTextProc]; }; }; -- DrawRope DrawTexts: PROC [handle: GraphHandle, context: Imager.Context, output: OutputType] = { OPEN handle, handle.axesRect; FOR tl: Texts _ graph.texts, tl.rest UNTIL tl = NIL DO t: Text _ tl.first; DrawRope[context, t.text, t.place.x * w + x, t.place.y * h + y, t.justifX, t.justifY, systemColor[t.colorIndex], imagerFonts[output][t.fontIndex], t.rotation]; ENDLOOP; }; -- DrawTexts DrawText: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, gt: Text] = { OPEN handle, handle.axesRect; DrawRope[context, gt.text, gt.place.x * w + x, gt.place.y * h + y, gt.justifX, gt.justifY, IF action = erase THEN BackgroundColor[backgroundIndex, output] ELSE systemColor[gt.colorIndex], imagerFonts[output][gt.fontIndex], gt.rotation]; }; -- DrawText ScaleAxes: PROC [handle: GraphHandle, output: OutputType, updateDivBds: BOOL] = { OPEN handle; useOldDivBds: BOOL _ FALSE; IF NOT updateDivBds THEN { oldOk: BOOL _ (scale[x] # 0.0 AND scale[y] # 0.0) AND (step[x] # 0.0 AND step[y] # 0.0) AND (realRect.h > 0.0 AND realRect.w > 0.0); IF oldOk THEN useOldDivBds _ TRUE; }; IF useOldDivBds THEN scale _ [axesRect.w/realRect.w, axesRect.h/realRect.h] ELSE { ScaleAxis: PROC [minDataValue, maxDataValue, axisSize: REAL, nDiv: INT] RETURNS [min, range, step, factor: REAL] = { max: REAL _ maxDataValue; min _ minDataValue; range _ max - min; step _ range/nDiv; IF graph.auto[bounds] THEN { step _ FindStepSize[range, nDiv]; min _ AlignEnd[minDataValue, step, FALSE]; max _ AlignEnd[maxDataValue, step, TRUE]; range _ max - min; IF NOT graph.auto[divisions] THEN step _ range/nDiv; }; factor _ axisSize/range; }; -- ScaleAxis nDivX, nDivY: INT; divBad: BOOL _ graph.division[x] < 1 OR graph.division[y] < 1; boundsBad: BOOL _ (graph.bounds.xmin >= graph.bounds.xmax) OR (graph.bounds.ymin >= graph.bounds.ymax); IF boundsBad OR graph.auto[bounds] THEN { graph.bounds _ DataBounds[graph.entityList, graph.bounds]; IF mergingBounds THEN { graph.bounds _ [ xmin: MIN[boundsToMerge.xmin, graph.bounds.xmin], ymin: MIN[boundsToMerge.ymin, graph.bounds.ymin], xmax: MAX[boundsToMerge.xmax, graph.bounds.xmax], ymax: MAX[boundsToMerge.ymax, graph.bounds.ymax] ]; }; IF NOT graph.auto[bounds] THEN BlinkMsg["Bounds invalid. (xmin >= xmax or ymin >= ymax.)"]; }; IF graph.auto[divisions] OR divBad THEN { labelFont: ImagerFont.Font _ imagerFonts[output][GraphOps.LabelFont]; longest: ROPE _ "0.4444444"; width, height: REAL; [width, height] _ RopeSize[longest, labelFont]; nDivX _ MAX[1, MIN[10, Real.RoundLI[axesRect.w/width*0.9]]]; -- 0.9 is arbitrary nDivY _ MAX[1, MIN[10, Real.RoundLI[axesRect.h/height*0.625]]]; -- 0.625 is arbitrary IF NOT graph.auto[divisions] THEN BlinkMsg["Number of divisions must be >= 1"]; } ELSE { nDivX _ graph.division[x]; nDivY _ graph.division[y]; }; [realRect.x, realRect.w, step[x], scale[x]] _ ScaleAxis[ graph.bounds.xmin, graph.bounds.xmax, axesRect.w, nDivX]; [realRect.y, realRect.h, step[y], scale[y]] _ ScaleAxis[ graph.bounds.ymin, graph.bounds.ymax, axesRect.h, nDivY]; }; }; -- ScaleAxes FindStepSize: PROC [range: REAL, nSteps: CARDINAL] RETURNS [step: REAL] = { logRange: REAL; logMext, minStep: REAL; exp: INTEGER; steps: ARRAY [0..6) OF REAL = [0.2, 0.5, 1.0, 2.0, 5.0, 10.0]; logRange _ RealFns.Log[10.0, range]; exp _ Real.FixI[logRange]; logMext _ logRange - exp; IF logRange < 0.0 THEN { exp _ exp - 1; logMext _ logMext + 1.0}; minStep _ RealFns.Power[10.0, logMext]/nSteps; FOR i: CARDINAL IN [0..5) DO step _ steps[i]; IF step > minStep OR Almost[step, minStep] THEN EXIT ENDLOOP; IF exp >= 0 THEN THROUGH [1..exp] DO step _ step*10.0 ENDLOOP ELSE THROUGH [1..-exp] DO step _ step/10.0 ENDLOOP; step _ MAX[1.0, step]; }; -- FindStepSize AlignEnd: PROC [e, step: REAL, roundUp: BOOL] RETURNS [ae: REAL] = { absE: REAL = ABS[e]; nSteps: INTEGER; xend: REAL; IF e = 0.0 THEN RETURN[0.0]; IF e < 0.0 THEN roundUp _ ~roundUp; nSteps _ Real.RoundLI[absE/step]; xend _ step*nSteps; IF Almost[nSteps, absE/step] THEN ae _ xend ELSE IF roundUp THEN {ae _ IF xend >= absE THEN xend ELSE step*(nSteps + 1)} ELSE {ae _ IF xend <= absE THEN xend ELSE step*(nSteps - 1)}; IF e < 0.0 THEN ae _ -ae; }; -- AlignEnd DrawAxes: PROC [handle: GraphHandle, context: Imager.Context, output: OutputType] = { axesProc: PROC = { OPEN handle; color: Imager.Color _ ForegroundColor[backgroundIndex, output]; rect: Imager.Rectangle _ axesRect; border: Imager.PathProc = { OPEN rect; xmax: REAL = x + w; ymax: REAL = y + h; moveTo[[x, y]]; lineTo[[x, ymax]]; lineTo[[xmax, ymax]]; lineTo[[xmax, y]]; }; context.SetColor[color]; context.SetStrokeEnd[square]; context.SetStrokeJoint[round]; context.SetStrokeWidth[UnitLineWidth*2.0]; context.MaskStroke[path: border, closed: TRUE]; }; context.DoSaveAll[axesProc]; }; -- DrawAxes DrawTicks: PROC[handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, xyChoice: XY _ x, longTicks, drawLabel: BOOL _ FALSE] = { OPEN handle; ticksProc: PROC = { tickCount: CARDINAL; -- max tick count, starting from 0. tickLen, textPlace, tick: REAL; stdColor: Imager.Color _ GetStandardColor[handle, action, output]; labelFont: ImagerFont.Font _ imagerFonts[output][GraphOps.LabelFont]; context.SetColor[stdColor]; context.SetStrokeEnd[butt]; context.SetStrokeWidth[UnitLineWidth]; IF xyChoice = x THEN { -- x ticks (on horizontal axis) tickLen _ IF longTicks THEN axesRect.h ELSE axesRect.h/50.0; tickCount _ Real.RoundI[realRect.w/step[x]]; textPlace _ axesRect.y - TextHeightFudge; FOR i: CARDINAL IN [0..tickCount] DO tick _ step[x]*scale[x]*i; IF drawLabel THEN DrawRope[context, Convert.RopeFromReal[step[x]*i + realRect.x], axesRect.x + tick, textPlace, center, top, stdColor, labelFont, 0.0]; IF i # 0 AND i # tickCount THEN { xPos: REAL = axesRect.x + tick; yStart: REAL _ axesRect.y; yEnd: REAL _ axesRect.y + tickLen; Tick: Imager.PathProc = {moveTo[[xPos, yStart]]; lineTo[[xPos, yEnd]]}; context.MaskStroke[path: Tick, closed: FALSE]; IF NOT longTicks THEN { yStart _ axesRect.y + axesRect.h; yEnd _ yStart - tickLen; context.MaskStroke[path: Tick, closed: FALSE]; }; }; ENDLOOP; } ELSE { tickLen _ IF longTicks THEN axesRect.w ELSE axesRect.w/50.0; tickCount _ Real.RoundI[realRect.h/step[y]]; textPlace _ axesRect.x - TextWidthFudge; FOR i: CARDINAL IN [0..tickCount] DO tick _ step[y]*scale[y]*i; IF drawLabel THEN DrawRope[context, Convert.RopeFromReal[step[y]*i + realRect.y], textPlace, axesRect.y + tick, right, center, stdColor, labelFont, 0.0]; IF i # 0 AND i # tickCount THEN { yPos: REAL = axesRect.y + tick; xStart: REAL _ axesRect.x; xEnd: REAL _ axesRect.x + tickLen; Tick: Imager.PathProc = {moveTo[[xStart, yPos]]; lineTo[[xEnd, yPos]]}; context.MaskStroke[path: Tick, closed: FALSE]; IF NOT longTicks THEN { xStart _ axesRect.x + axesRect.w; xEnd _ xStart - tickLen; context.MaskStroke[path: Tick, closed: FALSE]; }; }; ENDLOOP; }; }; context.DoSaveAll[ticksProc]; }; -- DrawTicks DrawLegend: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity, onlyOne: BOOL] = { OPEN handle; MaxEntries: INT = 12; MaxEntriesPerColumn: INT = MaxEntries/2; notYet: BOOL _ entity # NIL; irow, icol: INT _ 0; AdvanceRC: PROC [] RETURNS [ok: BOOL _ TRUE]= { IF irow = MaxEntries THEN ok _ FALSE ELSE IF icol = 2 AND irow = MaxEntriesPerColumn THEN ok _ FALSE ELSE IF icol = 0 THEN {irow _ 1; icol _ IF normalLegend THEN 1 ELSE 3} ELSE { IF normalLegend AND (irow = MaxEntriesPerColumn) THEN {icol _ icol + 1; irow _ 1} ELSE irow _ irow + 1; }; }; -- AdvanceRC FOR el: EntityList _ graph.entityList, el.rest UNTIL el = NIL DO IF AdvanceRC[] THEN { IF notYet AND entity # NIL THEN IF el.first = entity THEN notYet _ FALSE; IF NOT notYet THEN { IF el.first.colorIndex = 0 THEN -- auto color selection for white curves. lastEntityColor _ el.first.colorIndex _ IF lastEntityColor = LastEntityColor THEN 1 ELSE lastEntityColor + 1; DrawLegendEntry[handle, context, action, output, el.first, icol, irow]; IF onlyOne THEN EXIT; }; } ELSE EXIT; ENDLOOP; IF AdvanceRC[] AND NOT onlyOne THEN DrawLegendEntry[handle, context, action, output, NIL, icol, irow]; -- blank }; -- DrawLegend DrawLegendEntry: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity, icol, irow: INT] = { OPEN handle; legendProc: PROC = { legendFont: ImagerFont.Font _ imagerFonts[output][GraphOps.LegendFont]; legendH: REAL _ FontHeight[legendFont]; labelH: REAL _ FontHeight[imagerFonts[output][GraphOps.LabelFont]]; legendW: REAL _ IF normalLegend THEN axesRect.w*0.5 ELSE shortLegendW; lx1: REAL _ SELECT icol FROM 1 => axesRect.x, 2 => axesRect.x + legendW, ENDCASE => axesRect.x + axesRect.w + 2; ty: REAL _ axesRect.y - legendH*irow + (SELECT icol FROM 1, 2 => - (labelH + TextHeightFudge + TextHeightFudge), ENDCASE => axesRect.h); IF output = screen THEN { -- clear context.SetColor[BackgroundColor[backgroundIndex, output]]; context.MaskRectangle[[lx1, ty, legendW, legendH]]; }; context.ClipRectangle[[lx1, ty, legendW, legendH]]; IF action = paint AND entity # NIL THEN { lColor: Imager.Color _ GetEntityColor[handle, action, output, entity]; lineLength: REAL _ IF entity.colorIndex IN [8..9] THEN 31.0 ELSE 34.0; lx2: REAL _ IF normalLegend THEN lx1 + 34.0 ELSE lx1; tx: REAL _ lx2 + TextWidthFudge; ly: REAL _ ty + legendH*0.5; IF normalLegend THEN { context.SetColor[lColor]; IF NOT Almost[1.0 - entity.width, 1.0] THEN { context.SetStrokeEnd[butt]; context.SetStrokeWidth[UnitLineWidth*entity.width]; IF colorMode = color THEN context.MaskVector[[lx1, ly], [lx2, ly]] ELSE { bwLineState^ _ []; MaskLineSeg[context, [lx1, ly], [lx1+lineLength, ly], entity.colorIndex, bwLineState] }; }; IF entity.mark # none THEN { lFont: ImagerFont.Font _ imagerFonts[output][ IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8]; DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark], lx1, ly, left, center, lColor, lFont, 0.0]; }; }; DrawRope[context, FullName[entity], tx, ty, left, bottom, lColor, legendFont, 0.0]; }; }; -- proc context.DoSaveAll[legendProc]; }; -- DrawLegendEntry DrawEntities: PROC [handle: GraphHandle, context: Imager.Context, output: OutputType] = { OPEN handle; IF graph.entityList # NIL THEN { entitiesProc: PROC = { context.ClipRectangle[axesRect]; FOR el: EntityList _ graph.entityList, el.rest UNTIL el = NIL DO entity: Entity _ el.first; IF entity = NIL THEN LOOP; -- error ?! IF entity.colorIndex = 0 THEN -- auto color selection for white curves. lastEntityColor _ entity.colorIndex _ IF lastEntityColor = LastEntityColor THEN 1 ELSE lastEntityColor + 1; IF handle.colorMode = bw THEN DrawBWEntity[handle, context, paint, output, entity] ELSE DrawColorEntity[handle, context, paint, output, entity]; ENDLOOP; }; -- entitiesProc context.DoSaveAll[entitiesProc]; }; }; -- DrawEntities DrawBWEntity: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity] = { OPEN handle; oneCurve: PROC = { first, prevOk, currOk: BOOL _ TRUE; lastVec, newVec: Imager.VEC; -- places on screen. xseg: SegmentDataList _ entity.group.x.segments; eColor: Imager.Color _ GetEntityColor[handle, action, output, entity]; lineVisible: BOOL _ NOT Almost[1.0 - entity.width, 1.0]; IF lineVisible THEN { -- not too thin. bwLineState^ _ []; context.SetStrokeJoint[mitered]; context.SetStrokeEnd[butt]; context.SetStrokeWidth[UnitLineWidth*entity.width]; }; context.SetColor[eColor]; context.TranslateT[[axesRect.x, axesRect.y]]; FOR yseg: SegmentDataList _ entity.segments, yseg.rest UNTIL yseg = NIL DO IF xseg = NIL THEN EXIT; currOk _ NotANan[xseg.first.end] AND NotANan[yseg.first.end]; IF currOk THEN newVec _ [(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]]; IF first THEN first _ FALSE ELSE IF currOk AND prevOk AND lineVisible THEN MaskLineSeg[context, lastVec, newVec, entity.colorIndex, bwLineState]; IF (entity.mark # none) AND currOk THEN { eFont: ImagerFont.Font _ imagerFonts[output][ IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8]; DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark], newVec.x, newVec.y, center, center, eColor, eFont, 0.0]; }; lastVec _ newVec; prevOk _ currOk; xseg _ xseg.rest; ENDLOOP; }; context.DoSaveAll[oneCurve]; }; -- DrawBWEntity DrawColorEntity: PROC[handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity] = { OPEN handle; oneCurve: PROC = { xseg: SegmentDataList; eColor: Imager.Color _ GetEntityColor[handle, action, output, entity]; context.SetColor[eColor]; context.TranslateT[[axesRect.x, axesRect.y]]; context.SetXY[[axesRect.x, axesRect.y]]; IF NOT Almost[1.0 - entity.width, 1.0] THEN { curve: Imager.PathProc = { first, currOk: BOOL _ TRUE; prevOk: BOOL _ FALSE; xseg _ entity.group.x.segments; FOR yseg: SegmentDataList _ entity.segments, yseg.rest UNTIL yseg = NIL DO vec: Imager.VEC; IF xseg = NIL THEN EXIT; currOk _ NotANan[xseg.first.end] AND NotANan[yseg.first.end]; IF currOk THEN { vec _ [(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]]; IF prevOk THEN lineTo[vec] ELSE moveTo[vec]; }; prevOk _ currOk; xseg _ xseg.rest; ENDLOOP; }; context.SetStrokeJoint[mitered]; context.SetStrokeEnd[butt]; context.SetStrokeWidth[entity.width]; context.MaskStroke[path: curve, closed: FALSE]; }; IF entity.mark # none THEN { xseg _ entity.group.x.segments; FOR yseg: SegmentDataList _ entity.segments, yseg.rest UNTIL yseg = NIL DO vec: Imager.VEC; IF NotANan[xseg.first.end] AND NotANan[yseg.first.end] THEN { eFont: ImagerFont.Font _ imagerFonts[output][ IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8]; vec _ [(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]]; DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark], vec.x, vec.y, center, center, eColor, eFont, 0.0]; }; xseg _ xseg.rest; ENDLOOP; }; }; context.DoSaveAll[oneCurve]; }; -- DrawColorEntity LineShape: TYPE = {solid, dot, dash, longDash}; StepSize: ARRAY LineShape[dot..longDash] OF REAL = [3.0, 6.0, 9.0]; LineLimit: ARRAY LineShape[dot..longDash] OF REAL = [1.0, 4.0, 7.0]; -- beyond which is space. shape: ARRAY EntityColor OF ARRAY StepCount OF LineShape = [ [solid, solid, solid, solid, solid, solid], -- solid, useless [dot, dot, dot, dot, dot, dot], -- dot [dash, dash, dash, dash, dash, dash], -- dash [longDash, longDash, longDash, longDash, longDash, longDash], -- longDash [dot, dash, dot, dash, dot, dash], -- dot-dash [dot, longDash, dot, longDash, dot, longDash], -- dot-longDash [dot, dot, dash, dot, dot, dash], -- dot-dot-dash [dot, dot, longDash, dot, dot, longDash], -- dot-dot-longDash [dot, dash, dash, dot, dash, dash], -- dot-dash-dash [dot, longDash, longDash, dot, longDash, longDash], -- dot-longDash-longDash [dash, longDash, dash, longDash, dash, longDash], -- dash-longDash [dot, dash, longDash, dot, dash, longDash] -- dot-dash-longDash ]; MaskLineSeg: PROC[context: Imager.Context, v0, v1: Imager.VEC _ [0.0, 0.0], style: EntityColor _ 1, state: BWLineState _ NIL] = { IF Almost[v0.x, v1.x] AND Almost[v0.y, v1.y] THEN RETURN; IF state = NIL THEN {bwLineState^ _ []; state _ bwLineState}; IF style = 1 THEN context.MaskVector[v0, v1] ELSE { -- Note that normally state.step and state.progress will be updated. dx: REAL = v1.x - v0.x; dy: REAL = v1.y - v0.y; lengthToGo: REAL _ RealFns.SqRt[dx*dx + dy*dy]; cosine: REAL _ dx/lengthToGo; sine: REAL _ dy/lengthToGo; newVec, oldVec: Imager.VEC _ v0; stepSize, lineLimit, increment: REAL; drawIt: BOOL; UNTIL Almost[lengthToGo, 0.0] DO stepSize_ StepSize[shape[style][state.step]]; lineLimit_ LineLimit[shape[style][state.step]]; IF state.progress < lineLimit THEN { drawIt _ TRUE; increment _ MIN[lengthToGo, lineLimit - state.progress]; } ELSE { drawIt _ FALSE; increment _ MIN[lengthToGo, stepSize - state.progress]; }; lengthToGo _ lengthToGo - increment; state.progress _ state.progress + increment; newVec _ [oldVec.x + cosine*increment, oldVec.y + sine*increment]; IF drawIt THEN context.MaskVector[oldVec, newVec] ELSE IF Almost[state.progress, stepSize] THEN { state.step _ (state.step + 1) MOD MaxStep; state.progress _ 0.0; }; oldVec _ newVec; ENDLOOP; }; }; -- MaskLineSeg DrawEntity: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity] = { OPEN handle; IF entity # NIL THEN { entityProc: PROC = { IF entity.colorIndex = 0 THEN -- auto color selection for white curves. lastEntityColor _ entity.colorIndex _ IF lastEntityColor = LastEntityColor THEN 1 ELSE lastEntityColor + 1; context.ClipRectangle[axesRect]; IF colorMode = bw THEN DrawBWEntity[handle, context, action, output, entity] ELSE DrawColorEntity[handle, context, action, output, entity]; }; context.DoSaveAll[entityProc]; }; }; -- DrawEntity DrawTails: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, x1, x2: REAL, v1, v2: ValueList, entities: EntityList] = { OPEN handle; drawTails: PROC = { entity: Entity; vec1, vec2: Imager.VEC; oneSegment: PROC = { eColor: Imager.Color _ GetEntityColor[handle, action, output, entity]; context.SetColor[eColor]; IF NOT Almost[1.0 - entity.width, 1.0] THEN { context.SetStrokeWidth[UnitLineWidth*entity.width]; IF colorMode = bw THEN { bwLineState^ _ []; MaskLineSeg[context, vec1, vec2, entity.colorIndex, bwLineState]; } ELSE context.MaskVector[vec1, vec2]; }; IF entity.mark # none THEN { eFont: ImagerFont.Font _ imagerFonts[output][ IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8]; DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark], vec1.x, vec1.y, center, center, eColor, eFont, 0.0]; DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark], vec2.x, vec2.y, center, center, eColor, eFont, 0.0]; }; }; -- oneSegment context.SetStrokeEnd[square]; context.ClipRectangle[axesRect]; context.TranslateT[[axesRect.x, axesRect.y]]; IF NotANumber[x1] THEN RETURN ELSE vec1.x _ (x1 - realRect.x)*scale[x]; IF NotANumber[x2] THEN RETURN ELSE vec2.x _ (x2 - realRect.x)*scale[x]; FOR el: EntityList _ entities, el.rest UNTIL el = NIL DO ysOk: BOOL _ TRUE; entity _ el.first; IF NotANumber[v1.first] THEN ysOk _ FALSE ELSE vec1.y _ (v1.first - realRect.y)*scale[y]; IF ysOk THEN IF NotANumber[v2.first] THEN ysOk _ FALSE ELSE vec2.y _ (v2.first - realRect.y)*scale[y]; IF entity.colorIndex = 0 THEN -- auto color selection for white curves. lastEntityColor _ entity.colorIndex _ IF lastEntityColor = LastEntityColor THEN 1 ELSE lastEntityColor + 1; IF ysOk THEN context.DoSaveAll[oneSegment]; IF (v1 _ v1.rest) = NIL OR (v2 _ v2.rest) = NIL THEN EXIT; ENDLOOP; }; -- drawTails context.DoSaveAll[drawTails]; }; -- DrawTails GetEntityColor: PROC [handle: GraphHandle, action: PaintAction, output: OutputType, entity: Entity] RETURNS [Imager.Color] = { OPEN handle; RETURN[ IF action = erase THEN BackgroundColor[backgroundIndex, output] ELSE IF colorMode = bw THEN ForegroundColor[backgroundIndex, output] ELSE systemColor[entity.colorIndex] ]; }; -- GetEntityColor GetStandardColor: PROC [handle: GraphHandle, action: PaintAction, output: OutputType] RETURNS [Imager.Color] = { OPEN handle; RETURN[ IF action = paint THEN ForegroundColor[handle.backgroundIndex, output] ELSE BackgroundColor[handle.backgroundIndex, output] ]; }; -- GetStandardColor Print: PUBLIC PROC[handle: GraphHandle, file: ROPE] RETURNS [msg: ROPE _ NIL] = { IF HandleNotNil[handle] THEN IF handle.graph # NIL THEN { OPEN handle; InterpressDrawGraph: PROC[context: Imager.Context] ~ { coordRatio: REAL = 0.25/600.0; -- 0.25 m ~ 600 pixels context.RotateT[90]; context.ScaleT[coordRatio]; Draw[handle, context, info]; }; interpress: ImagerInterpress.Ref _ NIL; info: PaintInfo _ NEW[all PaintInfoRec _ [ action: paint, output: interpress, data: all[clear: FALSE]]]; InitPFonts[handle]; interpress _ ImagerInterpress.Create[file ! FS.Error => {msg _ error.explanation; CONTINUE}]; IF msg = NIL THEN { ImagerInterpress.DoPage[interpress, InterpressDrawGraph]; ImagerInterpress.Close[interpress]; }; }; }; -- Print InitPFonts: PROC [h: GraphHandle] = { EnsurePFont: PROC [i: FontIndex] RETURNS [f: ImagerFont.Font] = { f _ h.imagerFonts[interpress][i]; IF f = NIL THEN { pFontName: ROPE _ Rope.Cat[ "Xerox/PressFonts/", h.graph.font[i].family, Rope.Concat[ IF h.graph.font[i].bold THEN "-b" ELSE "-m", IF h.graph.font[i].italic THEN "ir" ELSE "rr"] ]; f _ ImagerFont.Find[pFontName ! Imager.Error => { f _ ImagerFont.Find["Xerox/PressFonts/TimesRoman-mrr"]; CONTINUE}]; f _ ImagerFont.Scale[f, h.graph.font[i].pFontScale ! Imager.Error => {f _ ImagerFont.Scale[f, 9.0]; CONTINUE}]; }; }; -- InitPFont h.imagerFonts[interpress][GraphOps.LabelFont] _ EnsurePFont[GraphOps.LabelFont]; h.imagerFonts[interpress][GraphOps.LegendFont] _ EnsurePFont[GraphOps.LegendFont]; h.imagerFonts[interpress][GraphOps.Math8] _ EnsurePFont[GraphOps.Math8]; FOR ts: Texts _ h.graph.texts, ts.rest UNTIL ts = NIL DO i: FontIndex _ ts.first.fontIndex; h.imagerFonts[interpress][i] _ EnsurePFont[i]; ENDLOOP; }; -- InitPFonts PrintCaretsInfo: PROC [handle: GraphHandle, context: Imager.Context] = { OPEN handle; caretsProc: PROC = { xh1: CaretSpec _ graph.caret[primary]; xh2: CaretSpec _ graph.caret[secondary]; xh3: CaretSpec _ graph.caret[text]; cvec: Imager.VEC; cColor: Imager.Color _ ForegroundColor[backgroundIndex, interpress]; context.SetColor[cColor]; context.SetStrokeWidth[UnitLineWidth]; context.SetStrokeEnd[butt]; IF xh1.on THEN { cvec _ RealVecToScreenVec[handle, xh1.place]; context.MaskVector[[cvec.x - 8.0, cvec.y], [cvec.x + 8.0, cvec.y]]; context.MaskVector[[cvec.x, cvec.y - 8.0], [cvec.x, cvec.y + 8.0]]; }; IF xh2.on THEN { cvec _ RealVecToScreenVec[handle, xh2.place]; bwLineState^ _ []; MaskLineSeg[context, [cvec.x - 8.0, cvec.y], [cvec.x + 8.0, cvec.y], 2, bwLineState]; bwLineState^ _ []; MaskLineSeg[context, [cvec.x, cvec.y - 8.0], [cvec.x, cvec.y + 8.0], 2, bwLineState]; }; IF xh3.on THEN { cvec _ RealVecToScreenVec[handle, xh3.place]; context.MaskVector[[cvec.x - 8.0, cvec.y], [cvec.x + 8.0, cvec.y]]; context.MaskVector[[cvec.x - 5.65, cvec.y + 5.65], [cvec.x + 5.65, cvec.y - 5.65]]; context.MaskVector[[cvec.x - 5.65, cvec.y - 5.65], [cvec.x + 5.65, cvec.y + 5.65]]; }; IF xh1.on OR xh2.on THEN { bothOn: BOOL _ xh1.on AND xh2.on; rope: ROPE _ Rope.Concat["Crosshair", IF bothOn THEN "s: " ELSE ": "]; IF xh1.on THEN rope _ rope.Concat[GraphConvert.RopeOfPlace[xh1.place]]; IF bothOn THEN rope _ rope.Concat[", "]; IF xh2.on THEN rope _ rope.Concat[GraphConvert.RopeOfPlace[xh2.place]]; IF bothOn THEN rope _ rope.Cat["; slope: ", GraphConvert.RopeOfSlope[xh1.place, xh2.place]]; DrawRope[context, rope, axesRect.x + axesRect.w, axesRect.y + axesRect.h + TextHeightFudge, right, bottom, cColor, imagerFonts[interpress][GraphOps.LegendFont], 0.0]; }; }; context.DoSaveAll[caretsProc]; }; -- PrintCaretsInfo }. CHANGE LOG. SChen, created at October 9, 1985 6:13:12 pm PDT. ΤGraphDraw.mesa, Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: Sweetsun Chen, April 23, 1986 2:29:46 pm PST constants global vars procs autoRepaint: BOOL _ paintInfo.item = all AND (NOT paintInfo.clear) AND chart.dirty; IF NOT autoRepaint THEN GraphCarets.Suspend[]; x: PaintXsegInfo => DrawCrossSegments[context, handle, c]; x: PaintPlaceInfo => DrawXhairPlaces[context, handle, x]; IF NOT autoRepaint THEN GraphCarets.Resume[]; info^ _ []; info _ NIL; All drawing procs below are called by Draw above, which gurantees context, handle, and handle.paintInfo not nil. IF output = screen, the following vars on handle are updated: axesRec, realRec, scale, step. useful values: vars to save some output device dependent values before making interpress master. FOR i: CaretIndex IN CaretIndex DO chart.caretState[i].visible _ FALSE; ENDLOOP; -- already supended. resume altered values after making interpress master. update carets places on graph viewer. -- no legend, no carets info, cf. DrawGraph. vars on handle referenced: backgroundIndex, graph.target[xy], axesRect, realRect. the same treatment for either output type. erasing action can happen only in screen painting font and color should be set before calling this proc. x0 and y0 are in screen coordinates. IF text.IsEmpty[] THEN RETURN[[x0, y0]]; RETURN[IF output = interpress THEN [x0 + width, y0 + height] ELSE ImagerBackdoor.GetCP[context]]; paint action must be paint, not erase. vars on handle referenced: graph.texts, axesRect. vars on handle referenced: axesRect. vars on handle referenced: graph.bounds, axesRect.w, axesRect.h, graph.division, graph.auto; updates vars on handle: realRect, scale, step. IF Almost[min, max] THEN {max _ max + 50.0; min _ min - 50.0}; -- won't happen, see below. bounds checking divisions scaling graph.bounds _ [realRect.x, realRect.y, realRect.x + realRect.w, realRect.y + realRect.h]; graph.division _ [Real.RoundLI[realRect.w/step[x]], Real.RoundLI[realRect.h/step[y]]]; IF Almost[range, 0.0] THEN range _ 100.0; vars on handle referenced: backgroundIndex, axesRect. must be painting instead of erasing. global consts refrenced: UnitLineWidth, TextHeightFudge. vars on handle referenced: backgroundIndex, imagerFont[output][0], axesRect, realRect, scale, step. vertical axis vars on handle referenced: graph.entityList, normalLegend, (some more by DrawLegendEntry). vars on handle updated (possibly): lastEntityColor If normalLegend {icol = 1..2, irow = 1..6} otherwise {icol = 3, irow = 1..12}. consts referenced: shortLegendW, TextHeightFudge, TextWidthFudge, UnitLineWidth vars on handle referenced: imagerFonts, normalLegend, axesRect, backgroundIndex DrawRope[context, entity.name, tx, ty, left, bottom, lColor, legendFont]; color is the foreground color for use when colormode = black-and-white. this proc is called by DrawGraph only, when paint action = paint instead of erase. Remember to clip any rectangles before calling this proc. Remember to clip any rectangles before calling this proc. If width is almost 0.0 then don't plot the lines. Remember to clip any rectangles, set the color and width before calling this proc. DrawLegend[context, handle, entity]; DrawCrossSegments: PROC[context: Imager.Context, handle: GraphHandle _ NIL, info: PaintXsegInfo _ NIL] = { OPEN handle; FOR el: EntityList _ graph.entityList, el.rest UNTIL el = NIL DO entity: Entity _ el.first; vx: ValueList _ entity.group.x; vy: ValueList _ entity.newValues; v1, v2: Imager.VEC; [v1, v2] _ CrossSegment[vx, vy, ENDLOOP; }; -- DrawCrossSegments choose the right color for painting entity and legend. choose the drawing color for axes, ticks, grids, targets, and labels on axes, whether painting or erasing. -- fonts for legend, label, and mark -- fonts for texts Κ!©˜JšΟcœ Οmœ0™Lšœ™Jšœ,™,J™—šΟk ˜ JšœŸœ˜JšŸœŸœ ˜JšœŸœύŸœRŸœ˜βJšœ Ÿœ˜$Jšœ Ÿœ.˜@Jšœ Ÿœ ˜.Jšœ Ÿœ˜!Jšœ ŸœΙ˜ΨJšœŸœγŸœ˜τJšœŸœ ˜!Jšœ Ÿœ/˜?JšœŸœ˜4JšœŸœ˜#JšœŸœ˜"JšœŸœ˜"J˜—šœ ŸœŸ˜JšŸœ@ŸœI˜’JšŸœ˜—J˜JšŸœ ˜$J˜J™ JšœŸœ˜JšœŸœ˜JšœŸœ˜J˜J™ JšœŸœ˜2J˜J™šΟnœŸœŸœD˜UšŸœ ŸœŸœ ŸœŸœŸœŸœŸœ˜DJš œ ŸœŸœŸœŸœ™TJšŸœŸœ Ÿœ™.Jšœ˜šŸœŸœŸ˜JšœŸœ]˜cJšœŸœW˜]JšœŸœŸœ œI˜{JšœŸœQ˜WJšœŸœW˜]JšœŸœ]˜cJšœŸœj˜pJšœŸœD˜JJšœŸœSŸœ˜{JšŸœ˜Jšœ:™:J™9—JšŸœŸœ Ÿœ™-Jšœ˜J™ JšœŸœ™ J˜—Jšœ˜ —J˜Jšœp™pš  œŸœnŸœŸœ˜“J™\J™J™šœŸœŸœ˜SJšŸœ#˜'—J˜J™QJšœ˜Jš œ ŸœŸœŸœŸœ˜JšœŸœ˜ JšŸœŸœE˜`šŸœŸœŸœ#˜8šœ Ÿœ˜Jšœ<˜Ÿœ˜DJšœ>Ÿœ˜DJšœ$˜<šŸœŸœŸ˜JšœDŸœ˜K—JšŸœŸœ#˜VJšŸœŸœ(˜BJšŸœŸœ(˜BJšœ'˜?J˜—Jšœ ˜ J˜—J˜šŸœŸœ˜J™5JšœC˜C—J˜J™%šœ7˜7Icodešœ5˜5—šŸœŸœŸ˜,JšœI˜IJšœI˜IJšŸœ˜—Jšœ ˜J˜—š  œŸœ`ŸœŸœ˜‰šŸœŸœŸœ˜/šœ Ÿœ˜šŸœŸœ˜šœ Ÿœ˜Jšœ<˜Ÿœ˜DJšœ>Ÿœ˜DJšœ$˜šœ Ÿœ,Ÿ˜=Jšœ)˜)—J™J™šŸœ ŸœŸœ˜)Jšœ:˜:šŸœŸœ˜šœ˜JšœŸœ(˜1JšœŸœ(˜1JšœŸœ(˜1JšœŸœ'˜0J˜—J˜—JšŸœŸœŸœ=˜[Jšœ˜J˜—Jšœ ™ šŸœŸœŸœ˜)JšœE˜EJšœ Ÿœ˜JšœŸœ˜Jšœ/˜/JšœŸœŸœ+˜PJšœŸœŸœ.˜UJšŸœŸœŸœ.˜OJ˜—šŸœ˜Jšœ˜Jšœ˜J˜—J˜J™šœ8˜8Jšœ9˜9—šœ8˜8Jšœ9˜9—J˜—J™ZJ™VJšœ ˜J˜š   œŸœ Ÿœ ŸœŸœŸœ˜KJšœ Ÿœ˜JšœŸœ˜JšœŸœ˜JšœŸœŸœŸœ#˜?J˜JšŸœŸœ™)J˜$J˜J˜šŸœŸœ˜Jšœ˜J˜—J˜.šŸœŸœŸœŸ˜Jšœ˜JšŸœŸœŸœŸ˜4JšŸœ˜—š Ÿœ ŸœŸœ ŸœŸ˜=JšŸœŸœ ŸœŸœ˜3—JšœŸœ ˜Jšœ˜J˜—š  œŸœ Ÿœ ŸœŸœŸœ˜GJšœŸœŸœ˜JšœŸœ˜JšœŸœ˜ J˜JšŸœ ŸœŸœ˜JšŸœ Ÿœ˜#J˜!J˜JšŸœŸœ ˜+šŸœŸœ˜JšŸœŸœŸœŸœ˜ ˜IJšœ# ˜.Jšœ/˜>Jšœ"˜1Jšœ*˜=Jšœ$˜4Jšœ4˜LJšœ2˜BJšœ+˜?J˜J˜—š  œŸœ˜*JšœŸœ<Ÿœ˜VJ™RJšŸœŸœŸœŸœ˜9JšŸœ ŸœŸœ*˜=JšŸœ Ÿœ˜,šŸœD˜KJšœŸœ˜JšœŸœ˜Jšœ Ÿœ˜/JšœŸœ˜JšœŸœ˜JšœŸœ˜ Jšœ Ÿœ˜%JšœŸœ˜ šŸœŸ˜ Jšœ-˜-Jšœ/˜/šŸœŸœ˜$Jšœ Ÿœ˜Jšœ Ÿœ)˜8J˜—šŸœ˜Jšœ Ÿœ˜Jšœ Ÿœ(˜7J˜—Jšœ$˜$Jšœ,˜,JšœB˜BJšŸœŸœ#˜1šŸœŸœ"Ÿœ˜/JšœŸœ ˜*J˜J˜—Jšœ˜JšŸœ˜—Jšœ˜—Jšœ˜—J˜—š  œŸœl˜|JšŸœ˜ šŸœ ŸœŸœ˜šœ Ÿœ˜šŸœŸœ)˜Gšœ&Ÿœ#Ÿœ˜QJšŸœ˜——Jšœ$™$Jšœ ˜ JšŸœŸœ6˜LJšŸœ:˜>J˜—Jšœ˜J˜—Jšœ ˜J˜—š  œŸœ0ŸœŸœŸœ™wšŸœ,ŸœŸœŸ™@J™J™J™!JšœŸœ™J™ JšŸœ™—Jšœ™J˜—š  œŸœaŸœ.˜’JšŸœ˜ šœ Ÿœ˜J˜JšœŸœ˜J˜šœ Ÿœ˜JšœF˜FJšœ˜šŸœŸœ!Ÿœ˜-Jšœ3˜3šŸœŸœ˜Jšœ˜JšœA˜AJ˜—JšŸœ ˜$J˜—šŸœŸœ˜šœ-˜-JšŸœŸœŸœ˜D—šœ=˜=Jšœ4˜4—šœ=˜=Jšœ4˜4—J˜—Jšœ ˜—J˜Jšœ˜Jšœ ˜ Jšœ-˜-JšŸœŸœŸœŸœ%˜GJšŸœŸœŸœŸœ%˜GšŸœ$ŸœŸœŸ˜8JšœŸœŸœ˜J˜JšŸœŸœŸ˜)JšŸœ+˜/šŸœŸ˜ JšŸœŸœŸ˜)JšŸœ+˜/—šŸœŸœ)˜Gšœ&Ÿœ#Ÿœ˜QJšŸœ˜——JšŸœŸœ˜+Jš ŸœŸœŸœŸœŸœŸœ˜:JšŸœ˜—Jšœ ˜—J˜Jšœ˜Jšœ ˜J˜—š œŸœPŸœ˜~J™6JšŸœ˜ šŸœ˜JšŸœŸœ)˜?JšŸœŸœŸœ)˜DJšŸœ˜#J˜—Jšœ˜J˜—š œŸœ@Ÿœ˜pJ™jJšŸœ˜ šŸœ˜JšŸœŸœ0˜FJšŸœ0˜4J˜—Jšœ˜J˜—š œŸœŸœŸœŸœŸœŸœ˜Qš ŸœŸœŸœŸœŸœŸœ˜Fš œŸœ˜6Jšœ Ÿœ˜5J˜Jšœ˜Jšœ˜Jšœ˜—J˜Jšœ#Ÿœ˜'šœŸœ˜*Jšœ4Ÿœ˜=—J˜šœ)˜)JšœŸœ$Ÿœ˜3—šŸœŸœŸœ˜Jšœ9˜9Kšœ#˜#K˜—J˜—Jšœ˜ J˜š  œŸœ˜%š  œŸœŸœ˜AJšœ!˜!šŸœŸœŸœ˜šœ Ÿœ ˜Jšœ˜Jšœ˜šœ ˜ JšŸœŸœŸœ˜,JšŸœŸœŸœ˜.—J˜—šœ˜šœ˜Jšœ7˜7JšŸœ˜ ——šœ2˜2Jšœ0Ÿ œ˜<—J˜—Jšœ ˜—J™$JšœP˜PJšœR˜RJšœH˜HJ™šŸœ$ŸœŸœŸ˜8Jšœ"˜"Jšœ.˜.JšŸœ˜—Jšœ ˜J˜—š œŸœ4Ÿœ˜Ušœ Ÿœ˜Jšœ&˜&Jšœ(˜(Jšœ#˜#Jšœ Ÿœ˜JšœD˜DJšœ˜J˜&J˜šŸœŸœ˜Jšœ-˜-JšœC˜CJšœC˜CJ˜—šŸœŸœ˜Jšœ-˜-J˜JšœU˜UJ˜JšœU˜UJ˜—šŸœŸœ˜Jšœ-˜-JšœC˜CJšœS˜SJšœS˜SJ˜—šŸœŸœŸœ˜JšœŸœ Ÿœ˜!Jš œŸœŸœŸœŸœ˜FJšŸœŸœ9˜GJšŸœŸœ˜(JšŸœŸœ9˜GšŸœŸœ˜+Jšœ0˜0—šœ˜JšœC˜CJšœ˜Jšœ;˜;—Jšœ˜—J˜—Jšœ˜Jšœ˜——J˜J˜šŸœŸœ˜ Jšœ1˜1——…—{2«―