DIRECTORY Angles2d, Cubic2, CubicSplines, GGBasicTypes, GGBoundBox, GGCircles, GGCoreTypes, GGModelTypes, GGParseIn, GGParseOut, GGSegment, GGSegmentTypes, GGTransform, Imager, ImagerBackdoor, ImagerPath, ImagerTransformation, IO, Lines2d, Real, RealFns, Rope, Vectors2d; GGSegmentImplB: CEDAR PROGRAM IMPORTS Angles2d, GGBoundBox, GGCircles, GGParseIn, GGParseOut, GGSegment, GGTransform, Imager, ImagerTransformation, Lines2d, Real, RealFns, Vectors2d EXPORTS GGSegment = BEGIN Arc: TYPE = GGBasicTypes.Arc; BitVector: TYPE = GGModelTypes.BitVector; BoundBox: TYPE = REF BoundBoxObj; BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj; BuildPathProc: TYPE = GGSegmentTypes.BuildPathProc; BuildPathTransformProc: TYPE = GGSegmentTypes.BuildPathTransformProc; Circle: TYPE = GGBasicTypes.Circle; ClassDef: TYPE = GGSegment.ClassDef; ClassDefRec: TYPE = GGSegment.ClassDefRec; Edge: TYPE = GGBasicTypes.Edge; Line: TYPE = GGCoreTypes.Line; PairSequence: TYPE = GGSegmentTypes.PairSequence; PairSequenceRep: TYPE = GGSegmentTypes.PairSequenceRep; Point: TYPE = GGBasicTypes.Point; Segment: TYPE = GGSegmentTypes.Segment; SegmentClass: TYPE = REF SegmentClassObj; SegmentClassObj: TYPE = GGSegmentTypes.SegmentClassObj; SegmentObj: TYPE = GGSegmentTypes.SegmentObj; SelectedObjectData: TYPE = GGSegmentTypes.SelectedObjectData; SelectionClass: TYPE = GGSegmentTypes.SelectionClass; VEC: TYPE = Imager.VEC; StrokeEnd: TYPE = Imager.StrokeEnd; Vector: TYPE = GGBasicTypes.Vector; X: NAT = CubicSplines.X; Y: NAT = CubicSplines.Y; defaultStrokeWidth: REAL ¬ 2.0; defaultStrokeEnd: StrokeEnd ¬ round; NoOpSameShapeProc: PUBLIC GGSegmentTypes.SameShapeProc = { RETURN[TRUE]; -- most of the work of the comparison is current done in GGSliceImplD.FindImagerObject. }; NoOpControlPointMoved: PUBLIC GGSegmentTypes.ControlPointMovedProc = {}; NoOpControlPointGet: PUBLIC GGSegmentTypes.ControlPointGetProc = {RETURN[ [0.0, 0.0] ]}; NoOpControlPointCount: PUBLIC GGSegmentTypes.ControlPointCountProc = {controlPointCount ¬ 0}; NoOpControlPointFieldSet: PUBLIC GGSegmentTypes.ControlPointFieldSetProc = {}; NoOpControlPointFieldGet: PUBLIC GGSegmentTypes.ControlPointFieldGetProc = { ERROR; }; NoOpClosestControlPoint: PUBLIC GGSegmentTypes.ClosestControlPointProc = { normal ¬ [0,-1]; point ¬ [0.0, 0.0]; controlPointNum ¬ 0; success ¬ FALSE; }; NoOpClosestPointAndTangent: PUBLIC GGSegmentTypes.ClosestPointAndTangentProc = { point ¬ [0.0, 0.0]; tangent ¬ [0,-1]; success ¬ FALSE; }; NoOpLineIntersection: PUBLIC GGSegmentTypes.LineIntersectionProc = { points ¬ NIL; pointCount ¬ 0; }; NoOpCircleIntersection: PUBLIC GGSegmentTypes.CircleIntersectionProc = { points ¬ NIL; pointCount ¬ 0; }; NoOpAsSimpleCurve: PUBLIC GGSegmentTypes.AsSimpleCurveProc = { simpleCurve ¬ NIL; }; NoOpAsPolyline: PUBLIC GGSegmentTypes.AsPolylineProc = { polyline ¬ NEW[PairSequenceRep[2]]; polyline.length ¬ 2; polyline[0] ¬ seg.lo; polyline[1] ¬ seg.hi; }; NoOpJointNormal: PUBLIC GGSegmentTypes.JointNormalProc = { normal ¬ [0,-1]; tangent ¬ [0,-1]; }; NoOpCPNormal: PUBLIC GGSegmentTypes.CPNormalProc = { normal ¬ [0,-1]; }; NoOpAddJoint: PUBLIC GGSegmentTypes.AddJointProc = { ERROR; }; NoOpFileOut: PUBLIC GGSegmentTypes.FileOutProc = {}; BuildCircleClass: PROC [] RETURNS [circleClass: SegmentClass] = { circleClass ¬ NEW[SegmentClassObj ¬ [ type: $Circle, boundBox: CircleBoundBox, endPointMoved: CircleEndPointMoved, fileIn: CircleFileIn ]]; }; CircleBoundBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { radius: REAL; cpHalf: REAL ¬ GGModelTypes.halfJointSize + 1; radius ¬ Vectors2d.Distance[seg.lo, seg.hi]; seg.bBox­ ¬ [seg.lo.x - radius - cpHalf, seg.lo.y - radius - cpHalf, seg.lo.x + radius + cpHalf, seg.lo.y + radius + cpHalf, FALSE, FALSE]; bBox ¬ seg.bBox; }; CircleFileIn: PROC [f: IO.STREAM, loPoint, hiPoint: Point, version: REAL] RETURNS [seg: Segment] = { seg ¬ NEW[SegmentObj ¬ [ class: GGSegment.FetchSegmentClass[$Circle], looks: NIL, strokeWidth: 1.0, strokeEnd: round, color: Imager.black, lo: loPoint, hi: hiPoint, bBox: GGBoundBox.CreateBoundBox[0,0,0,0], tightBox: GGBoundBox.CreateBoundBox[0,0,0,0], data: NIL, props: NIL]]; [] ¬ seg.class.boundBox[seg]; }; CircleEndPointMoved: PROC [seg: Segment, lo: BOOL, newPoint: Point] = { }; BuildDiscClass: PROC [] RETURNS [discClass: SegmentClass] = { discClass ¬ NEW[SegmentClassObj ¬ [ type: $Disc, boundBox: CircleBoundBox, endPointMoved: CircleEndPointMoved, fileIn: DiscFileIn ]]; }; DiscFileIn: PROC [f: IO.STREAM, loPoint, hiPoint: Point, version: REAL] RETURNS [seg: Segment] = { seg ¬ NEW[SegmentObj ¬ [ class: GGSegment.FetchSegmentClass[$Disc], looks: NIL, strokeWidth: 1.0, strokeEnd: round, color: Imager.black, lo: loPoint, hi: hiPoint, bBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets set one line down. tightBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets set one line down. data: NIL, props: NIL]]; [] ¬ seg.class.boundBox[seg]; }; LineData: TYPE = REF LineDataRec; LineDataRec: TYPE = RECORD [ edge: Edge -- so we don't have to allocate it all of the time ]; BuildLineClass: PROC [] RETURNS [lineClass: SegmentClass] = { OPEN GGSegment; lineClass ¬ NEW[SegmentClassObj ¬ [ type: $Line, copyData: LineCopyData, reverse: LineReverse, boundBox: LineBoundBox, tightBox: LineTightBox, sameShape: NoOpSameShapeProc, transform: LineTransform, endPointMoved: LineEndPointMoved, controlPointMoved: NoOpControlPointMoved, describe: LineDescribe, fileOut: GGSegment.NoOpFileOut, fileIn: LineFileIn, buildPath: LineBuildPath, buildPathTransform: LineBuildPathTransform, controlPointGet: NoOpControlPointGet, controlPointCount: NoOpControlPointCount, controlPointFieldSet: NoOpControlPointFieldSet, controlPointFieldGet: NoOpControlPointFieldGet, closestPoint: LineClosestPoint, closestControlPoint: NoOpClosestControlPoint, closestPointAndTangent: LineClosestPointAndTangent, lineIntersection: LineLineIntersection, circleIntersection: LineCircleIntersection, asSimpleCurve: LineAsSimpleCurve, asPolyline: NoOpAsPolyline, cPNormal: NoOpCPNormal, jointNormal: LineJointNormal, addJoint: LineAddJoint, setStrokeWidth: LineSetStrokeWidth ]]; }; LineSetStrokeWidth: PROC [seg: Segment, strokeWidth: REAL] = { seg.strokeWidth ¬ strokeWidth; UpdateLineBoundBox[seg]; }; MakeLine: PUBLIC PROC [p0, p1: Point, props: LIST OF REF ANY] RETURNS [seg: Segment] = { lineSegment: LineData ¬ NEW[LineDataRec ¬ [ edge: Lines2d.CreateEdge[p0, p1] ]]; seg ¬ NEW[SegmentObj ¬ [ class: GGSegment.FetchSegmentClass[$Line], looks: NIL, strokeWidth: defaultStrokeWidth, strokeEnd: defaultStrokeEnd, color: Imager.black, lo: p0, hi: p1, bBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets set one line down. tightBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets set one line down. data: lineSegment, props: props ]]; UpdateLineBoundBox[seg]; }; LineBoundBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { bBox ¬ seg.bBox; }; LineTightBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { bBox ¬ seg.tightBox; }; root2Over2: REAL = 0.707106816; UpdateLineBoundBox: PROC [seg: Segment] = { loX, loY, hiX, hiY: REAL; pad: REAL; loX ¬ MIN[seg.lo.x, seg.hi.x]; hiX ¬ MAX[seg.lo.x, seg.hi.x]; loY ¬ MIN[seg.lo.y, seg.hi.y]; hiY ¬ MAX[seg.lo.y, seg.hi.y]; GGBoundBox.UpdateBoundBox[seg.tightBox, loX, loY, hiX, hiY]; seg.bBox­ ¬ seg.tightBox­; IF seg.strokeEnd = square THEN pad ¬ seg.strokeWidth*root2Over2 + 1.0 ELSE pad ¬ seg.strokeWidth/2.0 + 1.0; GGBoundBox.EnlargeByOffset[seg.bBox, pad]; }; LineCopyData: PROC [seg: Segment] RETURNS [data: REF ANY] = { lineSegment: LineData ¬ NARROW[seg.data]; data ¬ NEW[LineDataRec ¬ [edge: Lines2d.CreateEdge[seg.lo, seg.hi] ]]; }; LineReverse: PROC [seg: Segment] = { }; LineBuildPath: PROC [seg: Segment, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = { lineTo[seg.hi]; }; LineBuildPathTransform: PROC [seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi: BOOL, controlPoints: BitVector, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = { hiPoint: Point; hiPoint ¬ IF hi OR entire THEN GGTransform.Transform[transform, seg.hi] ELSE [seg.hi.x, seg.hi.y]; lineTo[ [hiPoint.x, hiPoint.y] ]; }; LineTransform: PROC [seg: Segment, transform: ImagerTransformation.Transformation] = { lineSegment: LineData ¬ NARROW[seg.data]; Lines2d.FillEdge[seg.lo, seg.hi, lineSegment.edge]; UpdateLineBoundBox[seg]; }; LineEndPointMoved: PROC [seg: Segment, lo: BOOL, newPoint: Point] = { lineSegment: LineData ¬ NARROW[seg.data]; Lines2d.FillEdge[seg.lo, seg.hi, lineSegment.edge]; UpdateLineBoundBox[seg]; }; LineDescribe: PROC [seg: Segment, self, lo, hi: BOOL, cps: BitVector] RETURNS [rope: Rope.ROPE] = { rope ¬ "Line"; }; LineFileIn: PROC [f: IO.STREAM, loPoint, hiPoint: Point, version: REAL] RETURNS [seg: Segment] = { seg ¬ MakeLine[loPoint, hiPoint, NIL]; }; useBBox: BOOL ¬ TRUE; PointIsInBox: PROC [test: Point, box: BoundBoxObj] RETURNS [BOOL] = { RETURN[ NOT (test.x < box.loX OR test.x > box.hiX OR test.y < box.loY OR test.y > box.hiY) ]; }; LineClosestPoint: PROC [seg: Segment, testPoint: Point, tolerance: REAL] RETURNS [point: Point ¬ [0.0, 0.0], success: BOOL ¬ FALSE] = { lineData: LineData ¬ NARROW[seg.data]; IF useBBox THEN { bigBox: BoundBoxObj ¬ [seg.bBox.loX-tolerance, seg.bBox.loY-tolerance, seg.bBox.hiX+tolerance, seg.bBox.hiY+tolerance, FALSE, FALSE]; IF NOT PointIsInBox[testPoint, bigBox] THEN RETURN; }; point ¬ Lines2d.NearestPointOnEdge[testPoint, lineData.edge]; success ¬ TRUE; }; LineClosestPointAndTangent: PROC [seg: Segment, testPoint: Point, tolerance: REAL] RETURNS [point: Point ¬ [0.0, 0.0], tangent: Vector ¬ [0,-1], success: BOOL ¬ FALSE] = { lineData: LineData ¬ NARROW[seg.data]; IF useBBox THEN { bigBox: BoundBoxObj ¬ [seg.bBox.loX-tolerance, seg.bBox.loY-tolerance, seg.bBox.hiX+tolerance, seg.bBox.hiY+tolerance, FALSE, FALSE]; IF NOT PointIsInBox[testPoint, bigBox] THEN RETURN; }; point ¬ Lines2d.NearestPointOnEdge[testPoint, lineData.edge]; tangent ¬ Lines2d.DirectionOfLine[lineData.edge.line]; success ¬ TRUE; }; LineLineIntersection: PROC [seg: Segment, line: Line] RETURNS [points: LIST OF Point, pointCount: NAT] = { lineData: LineData ¬ NARROW[seg.data]; failure: BOOL; ipoint: Point; [ipoint, failure] ¬ Lines2d.LineMeetsEdge[line, lineData.edge]; IF failure THEN { pointCount ¬ 0; RETURN; }; points ¬ LIST[ipoint]; pointCount ¬ 1; }; LineCircleIntersection: PROC [seg: Segment, circle: Circle] RETURNS [points: LIST OF Point, pointCount: NAT] = { lineData: LineData ¬ NARROW[seg.data]; hitPoints: ARRAY[1..2] OF Point; [hitPoints, pointCount] ¬ GGCircles.CircleMeetsEdge[circle, lineData.edge]; points ¬ NIL; FOR i: NAT IN [1..pointCount] DO points ¬ CONS[hitPoints[i], points]; ENDLOOP; }; LineAsSimpleCurve: PROC [seg: Segment, point: Point] RETURNS [simpleCurve: REF ANY] = { lineData: LineData ¬ NARROW[seg.data]; edge: Edge ¬ lineData.edge; simpleCurve ¬ edge; }; LineJointNormal: PROC [seg: Segment, joint, point: Point, hi: BOOL] RETURNS [normal, tangent: Vector] = { normal1, normal2, direction: Vector; p0,p1 : Point; lineData: LineData ¬ NARROW[seg.data]; normal1 ¬ Vectors2d.RightNormalOfEdge[lineData.edge]; normal2 ¬ Vectors2d.LeftNormalOfEdge[lineData.edge]; direction ¬ Vectors2d.VectorFromPoints[joint, point]; IF ABS[Vectors2d.SmallestAngleBetweenVectors[normal1, direction]] < ABS[Vectors2d.SmallestAngleBetweenVectors[normal2, direction]] THEN{ normal ¬ normal1; } ELSE {normal ¬ normal2;}; IF lineData.edge.startIsFirst THEN { p0 ¬ lineData.edge.start; p1 ¬ lineData.edge.end; } ELSE { p0 ¬ lineData.edge.end; p1 ¬ lineData.edge.start; }; IF hi THEN {tangent ¬ Vectors2d.VectorFromPoints[p1,p0]; } ELSE {tangent ¬ Vectors2d.VectorFromPoints[p0,p1]; }; }; LineAddJoint: PROC [seg: Segment, pos: Point] RETURNS [seg1, seg2: Segment] = { seg1 ¬ GGSegment.CopySegment[seg]; seg1.hi ¬ pos; UpdateLineBoundBox[seg1]; seg2 ¬ GGSegment.CopySegment[seg]; seg2.lo ¬ pos; UpdateLineBoundBox[seg2]; }; ArcData: TYPE = REF ArcDataRec; ArcDataRec: TYPE = RECORD [ p1: VEC, p1Selected: SelectedObjectData, arc: Arc]; BuildArcClass: PROC [] RETURNS [class: SegmentClass] = { OPEN GGSegment; class ¬ NEW[SegmentClassObj ¬ [ type: $Arc, copyData: ArcCopyData, reverse: ArcReverse, boundBox: ArcBoundBox, tightBox: ArcTightBox, sameShape: NoOpSameShapeProc, transform: ArcTransform, endPointMoved: ArcEndPointMoved, controlPointMoved: ArcControlPointMoved, describe: ArcDescribe, fileOut: ArcFileOut, fileIn: ArcFileIn, buildPath: ArcBuildPath, buildPathTransform: ArcBuildPathTransform, controlPointGet: ArcControlPointGet, controlPointCount: ArcControlPointCount, controlPointFieldSet: ArcFieldSet, controlPointFieldGet: ArcFieldGet, closestPoint: ArcClosestPoint, closestControlPoint: ArcClosestControlPoint, closestPointAndTangent: NoOpClosestPointAndTangent, lineIntersection: NoOpLineIntersection, circleIntersection: NoOpCircleIntersection, asSimpleCurve: ArcAsSimpleCurve, asPolyline: ArcAsPolyline, cPNormal: ArcCPNormal, jointNormal: ArcJointNormal, addJoint: ArcAddJoint, setStrokeWidth: ArcSetStrokeWidth ]]; }; ArcSetStrokeWidth: PROC [seg: Segment, strokeWidth: REAL] = { seg.strokeWidth ¬ strokeWidth; UpdateBoundBoxOfArc[seg]; }; MakeArc: PUBLIC PROC [p0, p1, p2: Point, props: LIST OF REF ANY] RETURNS [seg: Segment] = { data: ArcData ¬ NEW[ArcDataRec ¬ [ p1: [p1.x,p1.y], p1Selected: [FALSE, FALSE, FALSE], arc: GGCircles.CreateArc[p0, p1, p2] ]]; seg ¬ NEW[SegmentObj ¬ [ class: GGSegment.FetchSegmentClass[$Arc], looks: NIL, strokeWidth: defaultStrokeWidth, strokeEnd: defaultStrokeEnd, color: Imager.black, lo: p0, hi: p2, bBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down. tightBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets updated one line down. data: data, props: props ]]; UpdateBoundBoxOfArc[seg]; }; ArcGetParams: PUBLIC PROC [seg: Segment] RETURNS [p0, p1, p2: Point] = { data: ArcData ¬ NARROW[seg.data]; p0 ¬ seg.lo; p2 ¬ seg.hi; p1 ¬ data.p1; }; ArcBoundBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { bBox ¬ seg.bBox; }; ArcTightBox: PROC [seg: Segment] RETURNS [bBox: BoundBox] = { bBox ¬ seg.tightBox; }; UpdateTightBoxOfArc: PROC [seg: Segment, boundBox: BoundBox] = { data: ArcData ¬ NARROW[seg.data]; arc: Arc ¬ data.arc; IF arc.edge # NIL THEN UpdateBoundBoxOfEdge[arc.edge, boundBox] ELSE { boundBox­ ¬ [loX: arc.p0.x, loY: arc.p0.y, hiX: arc.p0.x, hiY: arc.p0.y, null: FALSE, infinite: FALSE]; GGBoundBox.EnlargeByPoint[boundBox, arc.p2]; IF Angles2d.IsInCCWInterval2[0.0, arc.theta0, arc.deltaTheta] THEN GGBoundBox.EnlargeByPoint[boundBox, Vectors2d.Add[arc.circle.origin, [arc.circle.radius, 0.0]]]; IF Angles2d.IsInCCWInterval2[90.0, arc.theta0, arc.deltaTheta] THEN GGBoundBox.EnlargeByPoint[boundBox, Vectors2d.Add[arc.circle.origin, [0.0, arc.circle.radius]]]; IF Angles2d.IsInCCWInterval2[180.0, arc.theta0, arc.deltaTheta] THEN GGBoundBox.EnlargeByPoint[boundBox, Vectors2d.Add[arc.circle.origin, [-arc.circle.radius, 0.0]]]; IF Angles2d.IsInCCWInterval2[-90.0, arc.theta0, arc.deltaTheta] THEN GGBoundBox.EnlargeByPoint[boundBox, Vectors2d.Add[arc.circle.origin, [0.0, -arc.circle.radius]]]; }; }; UpdateBoundBoxOfEdge: PROC [edge: Edge, boundBox: BoundBox] = { boundBox­ ¬ [ loX: MIN[edge.start.x, edge.end.x], loY: MIN[edge.start.y, edge.end.y], hiX: MAX[edge.start.x, edge.end.x], hiY: MAX[edge.start.y, edge.end.y], null: FALSE, infinite: FALSE]; }; UpdateBoundBoxOfArc: PROC [seg: Segment] = { pad: REAL; UpdateTightBoxOfArc[seg, seg.tightBox]; seg.bBox­ ¬ seg.tightBox­; IF seg.strokeEnd = square THEN pad ¬ seg.strokeWidth*root2Over2 + 1.0 ELSE pad ¬ seg.strokeWidth/2.0 + 1.0; GGBoundBox.EnlargeByOffset[seg.bBox, pad]; }; ArcCopyData: GGSegmentTypes.CopyDataProc = { arcData: ArcData ¬ NARROW[seg.data]; new: ArcData ¬ NEW[ArcDataRec]; new.p1 ¬ arcData.p1; new.p1Selected ¬ arcData.p1Selected; new.arc ¬ GGCircles.CreateEmptyArc[]; GGCircles.CopyArc[from: arcData.arc, to: new.arc]; RETURN[new]; }; ArcReverse: PROC [seg: Segment] = { arcData: ArcData ¬ NARROW[seg.data]; GGCircles.ReverseArc[arcData.arc]; }; ArcTransform: PROC [seg: Segment, transform: ImagerTransformation.Transformation] = { data: ArcData ¬ NARROW[seg.data]; data.p1 ¬ ImagerTransformation.Transform[transform, data.p1]; GGCircles.FillArc[seg.lo, data.p1, seg.hi, data.arc]; UpdateBoundBoxOfArc[seg]; }; ArcEndPointMoved: PROC [seg: Segment, lo: BOOL, newPoint: Point] = { data: ArcData ¬ NARROW[seg.data]; p0: Point ¬ IF lo THEN newPoint ELSE seg.lo; p1: Point ¬ [data.p1.x, data.p1.y]; p2: Point ¬ IF lo THEN seg.hi ELSE newPoint; GGCircles.FillArc[seg.lo, data.p1, seg.hi, data.arc]; UpdateBoundBoxOfArc[seg]; }; ArcControlPointMoved: PROC [seg: Segment, transform: ImagerTransformation.Transformation, controlPointNum: NAT] = { p1Vec: VEC; p1: Point; data: ArcData ¬ NARROW[seg.data]; IF controlPointNum#0 THEN ERROR; p1Vec ¬ ImagerTransformation.Transform[transform, data.p1]; p1 ¬ [p1Vec.x, p1Vec.y]; data.p1 ¬ p1Vec; GGCircles.FillArc[seg.lo, data.p1, seg.hi, data.arc]; UpdateBoundBoxOfArc[seg]; }; TransformEndPoints: PROC [loPt, hiPt: Point, lo, hi: BOOL, transform: ImagerTransformation.Transformation] RETURNS [newLo, newHi: VEC] = { IF lo THEN newLo ¬ ImagerTransformation.Transform[transform, [loPt.x,loPt.y]] ELSE newLo ¬ [loPt.x,loPt.y]; IF hi THEN newHi ¬ ImagerTransformation.Transform[transform, [hiPt.x,hiPt.y]] ELSE newHi ¬ [hiPt.x,hiPt.y]; }; ArcBuildPath: PROC [seg: Segment, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = { data: ArcData ¬ NARROW[seg.data]; arcTo[data.p1, [seg.hi.x, seg.hi.y]]; }; ArcBuildPathTransform: PROC [seg: Segment, transform: ImagerTransformation.Transformation, entire, lo, hi: BOOL, controlPoints: BitVector, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = { data: ArcData ¬ NARROW[seg.data]; p0, p1, p2: VEC; IF entire THEN { [p0, p2] ¬ TransformEndPoints[seg.lo, seg.hi, TRUE, TRUE, transform]; p1 ¬ ImagerTransformation.Transform[transform, data.p1]; } ELSE { [p0, p2] ¬ TransformEndPoints[seg.lo, seg.hi, lo, hi, transform]; p1 ¬ IF controlPoints=NIL OR controlPoints[0] THEN ImagerTransformation.Transform[transform, data.p1] ELSE data.p1; }; arcTo[p1,p2]; }; ArcDescribe: PROC [seg: Segment, self, lo, hi: BOOL, cps: BitVector] RETURNS [rope: Rope.ROPE] = { data: ArcData ¬ NARROW[seg.data]; arc: Arc ¬ data.arc; IF arc.edge # NIL THEN rope ¬ "Straight Arc" ELSE rope ¬ "Arc"; }; ArcFileOut: PROC [seg: Segment, f: IO.STREAM] = { data: ArcData ¬ NARROW[seg.data]; p1: Point ¬ [data.p1.x, data.p1.y]; GGParseOut.WritePoint[f, p1]; }; ArcFileIn: PROC [f: IO.STREAM, loPoint, hiPoint: Point, version: REAL] RETURNS [seg: Segment] = { p1: Point; p1 ¬ GGParseIn.ReadPoint[f]; seg ¬ MakeArc[loPoint, p1, hiPoint, NIL]; }; ArcFieldSet: GGSegmentTypes.ControlPointFieldSetProc = { data: ArcData ¬ NARROW[seg.data]; IF controlPointNum # 0 THEN ERROR; SELECT selectClass FROM normal => data.p1Selected.normal ¬ selected; hot => data.p1Selected.hot ¬ selected; active => data.p1Selected.active ¬ selected; match => data.p1Selected.match ¬ selected; ENDCASE => ERROR; }; ArcFieldGet: GGSegmentTypes.ControlPointFieldGetProc = { data: ArcData ¬ NARROW[seg.data]; IF controlPointNum # 0 THEN ERROR; SELECT selectClass FROM normal => selected ¬ data.p1Selected.normal; hot => selected ¬ data.p1Selected.hot; active => selected ¬ data.p1Selected.active; match => selected ¬ data.p1Selected.match; ENDCASE => ERROR; }; ArcControlPointGet: PROC [seg: Segment, controlPointNum: NAT] RETURNS [point: Point] = { data: ArcData ¬ NARROW[seg.data]; IF controlPointNum#0 THEN ERROR; RETURN[ [data.p1.x, data.p1.y] ]; }; ArcControlPointCount: PROC [seg: Segment] RETURNS [controlPointCount: NAT] = { data: ArcData ¬ NARROW[seg.data]; RETURN[1]; }; ArcClosestPoint: PROC [seg: Segment, testPoint: Point, tolerance: REAL] RETURNS [point: Point, success: BOOL] = { data: ArcData ¬ NARROW[seg.data]; point ¬ GGCircles.NearestPointOnArc[testPoint, data.arc]; success ¬ TRUE; }; ArcClosestControlPoint: PROC [seg: Segment, testPoint: Point, tolerance: REAL] RETURNS [point: Point, normal: Vector ¬ [0,-1], controlPointNum: NAT, success: BOOL] = { data: ArcData ¬ NARROW[seg.data]; success ¬ TRUE; controlPointNum ¬ 0; point ¬ [data.p1.x, data.p1.y]; }; ArcAsSimpleCurve: PROC [seg: Segment, point: Point] RETURNS [simpleCurve: REF ANY] = { arcData: ArcData ¬ NARROW[seg.data]; arc: Arc ¬ arcData.arc; simpleCurve ¬ arc; }; ArcAsPolyline: PROC [seg: Segment, tolerance: REAL] RETURNS [polyline: GGSegmentTypes.PairSequence] = { arcData: ArcData ¬ NARROW[seg.data]; arc: Arc ¬ arcData.arc; IF arc.edge#NIL OR 2.0*arc.circle.radius<=tolerance THEN { -- also does "degenerate" case in which the radius of the circle is less than half the tolerance polyline ¬ NEW[GGSegmentTypes.PairSequenceRep[2]]; polyline.length ¬ 2; polyline[0] ¬ seg.lo; polyline[1] ¬ seg.hi; } ELSE { r: REAL = arc.circle.radius; minSides: REAL; n: NAT; phi, sin, cos, deltaTheta: REAL; startAngle: REAL; deltaTheta ¬ 0.01745328 * arc.deltaTheta; -- convert to radians minSides ¬ deltaTheta/ (2.0*RealFns.ArcTan[y: RealFns.SqRt[(2.0*r - tolerance)*tolerance], x: r-tolerance]); n ¬ Real.Ceiling[minSides]; phi ¬ deltaTheta/REAL[n]; IF arc.ccw THEN { startAngle ¬ 0.01745328*arc.theta0; } ELSE { phi ¬ -phi; startAngle ¬ 0.01745328*Vectors2d.AngleFromVector[Vectors2d.Sub[seg.lo, arc.circle.origin]]; }; polyline ¬ NEW[GGSegmentTypes.PairSequenceRep[n+1]]; polyline.length ¬ n+1; polyline[0] ¬ seg.lo; FOR i: NAT IN [1..n) DO sin ¬ RealFns.Sin[startAngle+phi*i]; cos ¬ RealFns.Cos[startAngle+phi*i]; polyline[i] ¬ Vectors2d.Add[arc.circle.origin, [cos*r, sin*r]]; ENDLOOP; polyline[n] ¬ seg.hi; }; }; ArcCPNormal: PROC [seg: Segment, controlPointNum: NAT, cPoint, testPoint: Point] RETURNS [normal: Vector] = { arcData: ArcData ¬ NARROW[seg.data]; origin: Point ¬ arcData.arc.circle.origin; normal ¬ Vectors2d.Normalize[Vectors2d.Sub[cPoint, origin]]; }; ArcJointNormal: PROC [seg: Segment, joint, point: Point, hi: BOOL] RETURNS [normal, tangent: Vector] = { arcData: ArcData ¬ NARROW[seg.data]; origin: Point ¬ arcData.arc.circle.origin; normal ¬ Vectors2d.Normalize[Vectors2d.Sub[joint, origin]]; tangent ¬ Vectors2d.Normalize[Vectors2d.VectorPlusAngle[normal, 90]]; }; ArcAddJoint: PROC [seg: Segment, pos: Point] RETURNS [seg1, seg2: Segment] = { data: ArcData ¬ NARROW[seg.data]; data1, data2: ArcData; p0Rel: Point ¬ Vectors2d.Sub[data.arc.p0, data.arc.circle.origin]; p1Rel: Point ¬ Vectors2d.Sub[data.p1, data.arc.circle.origin]; p2Rel: Point ¬ Vectors2d.Sub[data.arc.p2, data.arc.circle.origin]; caretRel: Point ¬ Vectors2d.Sub[pos, data.arc.circle.origin]; cpAngle: REAL ¬ Vectors2d.AngleCCWBetweenVectors[p0Rel, p1Rel]; caretAngle: REAL ¬ Vectors2d.AngleCCWBetweenVectors[p0Rel, caretRel]; rotVec: Point; seg1 ¬ GGSegment.CopySegment[seg]; seg2 ¬ GGSegment.CopySegment[seg]; data1 ¬ NARROW[seg1.data]; data2 ¬ NARROW[seg2.data]; seg2.lo ¬ seg1.hi ¬ pos; seg1.lo ¬ data.arc.p0; seg2.hi ¬ data.arc.p2; IF caretAngle < cpAngle THEN { -- following arc, caret is closer to p0 than is cp rotVec ¬ ImagerTransformation.Transform[ImagerTransformation.Rotate[-caretAngle/2.0], caretRel]; data1.p1 ¬ Vectors2d.Add[rotVec, data.arc.circle.origin]; } ELSE { -- following arc, cp is closer to p0 than is caret caretAngle ¬ Vectors2d.AngleCCWBetweenVectors[caretRel, p2Rel]; -- now we want angle from caret to p2 rotVec ¬ ImagerTransformation.Transform[ImagerTransformation.Rotate[caretAngle/2.0], caretRel]; data2.p1 ¬ Vectors2d.Add[rotVec, data.arc.circle.origin]; }; data1.arc ¬ GGCircles.CreateArc[seg1.lo, data1.p1, seg1.hi]; data2.arc ¬ GGCircles.CreateArc[seg2.lo, data2.p1, seg2.hi]; UpdateBoundBoxOfArc[seg1]; UpdateBoundBoxOfArc[seg2]; IF NOT data.arc.ccw THEN { -- p0 to p2 always counter-clockwise. Need to take into account. GGSegment.ReverseSegment[seg1]; GGSegment.ReverseSegment[seg2]; RETURN [seg2, seg1]; }; }; Init: PROC [] = { classDef: ClassDef ¬ NEW[ClassDefRec ¬ [type: $Line, class: BuildLineClass[]]]; GGSegment.RegisterSegmentClass[classDef]; classDef ¬ NEW[GGSegment.ClassDefRec ¬ [type: $Arc, class: BuildArcClass[]]]; GGSegment.RegisterSegmentClass[classDef]; classDef ¬ NEW[ClassDefRec ¬ [type: $Circle, class: BuildCircleClass[]]]; GGSegment.RegisterSegmentClass[classDef]; classDef ¬ NEW[ClassDefRec ¬ [type: $Disc, class: BuildDiscClass[]]]; GGSegment.RegisterSegmentClass[classDef]; }; Init[]; END. & GGSegmentImplB.mesa Copyright Σ 1986, 1987, 1989, 1992 by Xerox Corporation. All rights reserved. Contents: Procedures which implement the Gargoyle segment classes. Bier, May 19, 1989 12:24:32 pm PDT Pier, February 18, 1992 4:10 pm PST Kurlander, August 25, 1986 5:44:28 pm PDT Eisenman, August 5, 1987 4:20:04 pm PDT Last edited by: David Kurlander - August 20, 1987 11:53:33 pm PDT Doug Wyatt, December 19, 1989 11:19:19 am PST Auxiliary module for GGSegmentImplA, because GGSegmentImplA got too big !! Arcs, Lines, Old Circles, and NoOps. NoOpFileIn is not allowed. The Circle Class. THIS IS A VESTIGIAL CLASS KEPT FOR BACKWARD COMPATIBILITY GGSegmentTypes.BoundBoxProc Find the boundBox of the segment, allowing for the size of control points. GGSegmentTypes.FileInProc GGSegmentTypes.EndPointMovedProc Circles are simple. Nothing to do. The Disc Class. THIS IS A VESTIGIAL CLASS KEPT FOR BACKWARD COMPATIBILITY Discs are identical to circles and are filled with the parent outline fill color GGSegmentTypes.FileInProc The Line (straight line) Class Transforming Textual Description Drawing Control Point Access Hit Testing Editing GGSegmentTypes.BoundBoxProc GGSegmentTypes.TightBoxProc GGSegmentTypes.BoundBoxProc Find the boundBox of the segment, allowing for the size of control points (or stroke width, whichever is larger). GGSegmentTypes.CopyDataProc GGSegmentTypes.ReverseProc Lines are simple. Nothing to do. Line Drawing GGSegmentTypes.BuildPathProc GGSegmentTypes.BuildPathTransformProc Line Transforming GGSegmentTypes.TransformProc GGSegmentTypes.EndPointMovedProc Line Description GGSegmentTypes.FileInProc Line Hit Testing GGSegmentTypes.ClosestPointProc GGSegmentTypes.ClosestPointAndTangentProc GGSegmentTypes.LineIntersectionProc GGSegmentTypes.CircleIntersectionProc GGSegmentTypes.JointNormalProc Line Editing GGSegmentTypes.AddJointProc The Arc Class Transforming Textual Description Drawing Control Point Access Hit Testing Editing Circular Arc through the three points Used by the matchtool (allows it not to know about ArcDatas) GGSegmentTypes.BoundBoxProc GGSegmentTypes.TightBoxProc For when the arc is straight. GGSegmentTypes.CopyDataProc Copies the data in the "data" field of seg. This data is class-dependent. GGSegmentTypes.ReverseProc The "lo" end and "hi" end have switched roles. Update data structures as necessary. Arc Transforming GGSegmentTypes.TransformProc Apply the given transformation to all internal data of the segment. It is now in a new position, orientation, scaling, or skewing. GGSegmentTypes.EndPointMovedProc GGSegmentTypes.ControlPointMovedProc Arc Drawing GGSegmentTypes.BuildPathProc GGSegmentTypes.BuildPathTransformProc Like BuildPathProc but you should assume that transform has been applied to your lower joint iff lo = TRUE and to your higher joint iff hi = TRUE. This is for rubberbanding filled areas. Arc Description ELSE rope _ IO.PutFR ["Radius %g Arc", [real[arc.circle.radius]]]; GGSegmentTypes.FileOutProc GGSegmentTypes.FileInProc Arc Parts [seg: GGSegmentTypes.Segment, controlPointNum: NAT, selected: BOOL, selectClass: GGSegmentTypes.SelectionClass] [seg: GGSegmentTypes.Segment, controlPointNum: NAT, selectClass: GGSegmentTypes.SelectionClass] RETURNS [selected: BOOL] Arc Part Generators GGSegmentTypes.ControlPointGetProc GGSegmentTypes.ControlPointCountProc Arc Hit Testing GGSegmentTypes.ClosestControlPointProc argument to the following SqRt better be positive. See comment above. GGSegmentTypes.JointNormalProc GGSegmentTypes.JointNormalProc Arc Editing GGSegmentTypes.AddJointProc Initialization ΚΝ•NewlineDelimiter –(cedarcode) style™™Icodešœ ΟeœC™NKšΟnœ;™CKšœ"™"Kšœ#™#Kšœ&Οk™)Kšœ'™'K™AK™-K™K™JK™K™$—K˜šŸ ˜ KšœΩŸœ*˜…—K˜šžœŸœŸœ˜KšŸœ˜—KšŸœ Ÿ˜—˜KšœŸœ˜Kšœ Ÿœ˜)Kšœ ŸœŸœ ˜!Kšœ Ÿœ˜,KšœŸœ ˜3KšœŸœ)˜EKšœŸœ˜#Kšœ Ÿœ˜$Kšœ Ÿœ˜*KšœŸœ˜KšœŸœ˜KšœŸœ˜1KšœŸœ"˜7KšœŸœ˜!Kšœ Ÿœ˜'KšœŸœŸœ˜)KšœŸœ"˜7Kšœ Ÿœ˜-KšœŸœ%˜=KšœŸœ!˜5KšŸœŸœ Ÿœ˜Kšœ Ÿœ˜#KšœŸœ˜#KšžœŸœ˜KšžœŸœ˜—K˜KšœŸœ˜K˜$K˜šžœŸœ!˜:KšŸœŸœΟcW˜eK˜—KšžœŸœ+˜HKšžœŸœ'Ÿœ˜XKšžœŸœ@˜]KšžœŸœ.˜NšžœŸœ,˜LKšŸœ˜K˜—šžœŸœ+˜JK˜K˜K˜Kšœ Ÿœ˜K˜—šžœŸœ.˜PK˜K˜Kšœ Ÿœ˜K˜—šžœŸœ(˜DKšœ Ÿœ˜ K˜K˜—šžœŸœ*˜HKšœ Ÿœ˜ K˜K˜—šžœŸœ%˜>KšœŸœ˜K˜—šžœŸœ"˜8Kšœ Ÿœ˜#K˜K˜K˜K˜—šΟbœŸœ#˜:K˜K˜Kšœ˜—š‘ œŸœ ˜4K˜Kšœ˜—šž œŸœ ˜4KšŸœ˜K˜—Kšž œŸœ!˜4Kšœ™K˜š‘œŸœŸœŸ œŸœŸœŸœŸœŸ ™LK™—šžœŸœŸœ ˜AšœŸœ˜%K˜K˜K˜#K˜K˜—K˜K˜—šžœŸœŸœ˜@Kšœ™K™JKšœŸœ˜ KšœŸœ"˜.K˜,Kšœ}ŸœŸœ˜‹K˜K˜K˜—š ž œŸœŸœŸœ$ŸœŸœ˜dKšœ™šœŸœ˜K˜,KšœŸœ˜ Kšœ˜K˜K˜Kšœ˜Kšœ*˜*Kšœ.˜.KšœŸœ Ÿœ˜—K˜K˜K˜—šžœŸœŸœ˜GKšœ ™ K™#K˜—K™š‘œŸœŸœŸ œŸœŸœŸœŸœŸ ™JKšœP™PK™—šžœŸœŸœ˜=šœ Ÿœ˜#Kšœ ˜ K˜K˜#Kšœ˜K˜—K˜K˜—š ž œŸœŸœŸœ$ŸœŸœ˜bKšœ™šœŸœ˜K˜*KšœŸœ˜ Kšœ˜K˜K˜Kšœ˜Kšœ+ ˜EKšœ/ ˜IKšœŸœ˜ KšœŸœ˜ —K˜K˜—K˜šœ™K˜—Kšœ ŸœŸœ ˜!šœ ŸœŸœ˜Kšœ  2˜=Kšœ˜K˜—šžœŸœŸœ˜=KšΠbk‘ ˜šœ Ÿœ˜#K˜ K˜K˜K˜Kšœ˜šœ˜Kš‘ ™ —K˜K˜!šœ)˜)Kš‘™—K˜Kšœ˜˜Kš‘™—Kšœ˜šœ+˜+Kš‘™—Kšœ%˜%Kšœ)˜)Kšœ/˜/šœ/˜/Kš‘ ™ —K˜Kšœ-˜-K˜3K˜'K˜+Kšœ!˜!Kšœ˜Kšœ˜šœ˜Kš‘™—Kšœ˜Kšœ"˜"K˜—K˜K˜—šžœŸœŸœ˜>K˜Kšœ˜K˜K˜—šžœŸœŸœŸœŸœŸœŸœŸœ˜XšœŸœ˜+Kšœ ˜ Kšœ˜—šœŸœ˜K˜*KšœŸœ˜ Kšœ ˜ Kšœ˜K˜Kšœ˜Kšœ+ ˜EKšœ/ ˜IKšœ˜Kšœ ˜ Kšœ˜—Kšœ˜K˜K˜—šž œŸœŸœ˜>Kšœ™K˜K˜K˜—šž œŸœŸœ˜>Kšœ™K˜K˜K˜—Kšœ Ÿœ˜šžœŸœ˜+Kšœ™K™qKšœŸœ˜KšœŸ˜ KšœŸœ˜KšœŸœ˜KšœŸœ˜KšœŸœ˜Kšœ<˜Ÿœ<Ÿœ˜ˆK˜—KšŸœ˜šŸœŸœ˜$K˜K˜Kšœ˜—šŸœ˜K˜K˜Kšœ˜—KšŸœŸœ0˜:KšŸœ1˜5Kšœ˜—K˜šœ ™ K™—šž œŸœŸœ˜PK™K˜"K˜K˜K˜"K˜K˜K˜—K™šœ ™ K˜—Kšœ ŸœŸœ ˜šœ ŸœŸœ˜KšœŸœ˜Kšœ˜šœ ˜ K˜——šž œŸœŸœ˜8Kš’‘ ˜šœŸœ˜K˜ K˜K˜Kšœ˜K˜˜Kš‘ ™ —K˜K˜ ˜(Kš‘™—K˜K˜˜Kš‘™—Kšœ˜šœ*˜*Kš‘™—Kšœ$˜$Kšœ(˜(Kšœ"˜"šœ"˜"Kš‘ ™ —K˜K˜,Kšœ3˜3Kšœ'˜'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šœ* ˜HKšœ. ˜LKšœ ˜ Kšœ ˜ Kšœ˜—Kšœ˜K˜K˜—šž œŸœŸœŸœ˜HK™Ÿ˜DšœD˜DKšœ˜——šŸœ>Ÿ˜DšœD˜DKšœ˜——K˜—K˜K˜—šžœŸœ%˜?˜ K™KšœŸœ˜#KšœŸœ˜#KšœŸœ˜#KšœŸœ˜#KšœŸœ Ÿœ˜—K˜K˜—šžœŸœ˜,KšœŸ˜ Kšœ'˜'K˜šŸœŸœ'˜EKšŸœ!˜%—Kšœ*˜*K˜K˜—šž œ!˜,Kšœ™K™JKšœŸœ ˜$KšœŸœ ˜K˜K˜$K˜%Kšœ2˜2KšŸœ˜ K˜K˜—šž œŸœ˜#Kšœ™K™TKšœŸœ ˜$Kšœ"˜"K˜—K˜šœ™K™—šž œŸœC˜UKšœ™K™ƒKšœŸœ ˜!K˜=Kšœ5˜5Kšœ˜K˜K˜—šžœŸœŸœ˜DKšœ ™ KšœŸœ ˜!Kšœ ŸœŸœ Ÿœ˜,K˜#Kšœ ŸœŸœŸœ ˜,Kšœ5˜5Kšœ˜Kšœ˜K˜—šžœŸœQŸœ˜sKšœ$™$KšœŸœ˜ Kšœ ˜ KšœŸœ ˜!KšŸœŸœŸœ˜ K˜;K˜K˜Kšœ5˜5Kšœ˜Kšœ˜K˜—š žœŸœŸœ2ŸœŸœ˜ŠKšŸœŸœC˜MKšŸœ˜KšŸœŸœC˜MKšŸœ˜K˜—K™Kšœ ™ šž œŸœ‘˜£Kšœ™KšœŸœ ˜!Kšœ%˜%K˜K˜—šžœŸœPŸœ˜ŒKšœ%™%KšœfŸœ#Ÿœ*™»KšœŸœ ˜!Kšœ Ÿœ˜šŸœŸœ˜Kšœ.ŸœŸœ ˜EK˜8K˜—šŸœ˜K˜AKš œŸœŸœŸœŸœ3˜eKšŸœ ˜ K˜—K˜ K˜—K˜šœ™K™—š ž œŸœŸœŸœ Ÿœ˜bKšœŸœ ˜!K˜KšŸœ ŸœŸœ˜,KšŸœŸœ4™BKšŸœ˜K˜K˜—šž œŸœŸœŸœ˜1Kšœ™KšœŸœ ˜!K˜#Kšœ˜K˜K˜—š ž œŸœŸœŸœ$ŸœŸœ˜aKšœ™K˜ K˜Kšœ$Ÿœ˜)K˜—K˜šœ ™ K™—–s -- [seg: GGSegmentTypes.Segment, controlPointNum: NAT, selected: BOOL, selectClass: GGSegmentTypes.SelectionClass]šž œ-˜8Kšœ/Ÿœ Ÿœ-™oKšœŸœ ˜!KšŸœŸœŸœ˜"šŸœ Ÿ˜K˜,K˜&K˜,K˜*KšŸœŸœ˜—K˜K˜—–| -- [seg: GGSegmentTypes.Segment, controlPointNum: NAT, selectClass: GGSegmentTypes.SelectionClass] RETURNS [selected: BOOL]šž œ-˜8Kšœ/Ÿœ.Ÿœ Ÿœ™xKšœŸœ ˜!KšŸœŸœŸœ˜"šŸœ Ÿ˜K˜,K˜&K˜,K˜*KšŸœŸœ˜—K˜—K˜šœ™K™—šžœŸœ!ŸœŸœ˜XKšœ"™"KšœŸœ ˜!KšŸœŸœŸœ˜ KšŸœ˜!Kšœ˜K˜—šžœŸœŸœŸœ˜NKšœ$™$KšœŸœ ˜!KšŸœ˜ Kšœ˜—K˜šœ™K™—š žœŸœ-ŸœŸœŸœ˜qKšœŸœ ˜!K˜9Kšœ Ÿœ˜K˜K˜—š žœŸœ-ŸœŸœ:Ÿœ Ÿœ˜§Kšœ&™&KšœŸœ ˜!Kšœ Ÿœ˜K˜K˜K˜K˜—š žœŸœŸœŸœŸœ˜VKšœŸœ ˜$K˜K˜K˜K˜—šž œŸœŸœŸœ,˜gKšœŸœ ˜$K˜K˜š Ÿœ ŸœŸœ"Ÿœ `˜›Kšœ Ÿœ$˜2K˜K˜K˜K˜—šŸœ˜KšœŸœ˜Kšœ Ÿœ˜KšœŸœ˜KšœŸœ˜ Kšœ Ÿ˜K˜Kšœ* ˜?KšœF™F˜KšœU˜U—K˜KšœŸœ˜K˜šŸœ Ÿœ˜K˜#K˜—šŸœ˜K˜ K˜\K˜—Kšœ Ÿœ&˜4K˜K˜šŸœŸœŸœŸ˜K˜$K˜$K˜?KšŸœ˜—K˜K˜—K˜K˜—šž œŸœ!ŸœŸœ˜mKšœ™KšœŸœ ˜$K˜*K˜K˜BK˜=Kšœ Ÿœ2˜?Kšœ Ÿœ5˜EK˜K˜"K˜"KšœŸœ ˜KšœŸœ ˜K˜K˜-šŸœŸœ 2˜QK˜`K˜9K˜—šŸœ 2˜9Kšœ@ %˜eK˜_K˜9K˜—K˜Š1