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 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 Κπ˜™IcodešœH™HKšΟnœ;™CKšœ"™"Kšœ#™#Kšœ&Οk™)Kšœ'™'K™AK™-K™K™J—K™K™$K˜šž ˜ JšœΩžœ*˜…—K˜šœžœžœ˜Jšžœ˜—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˜3J˜'J˜+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˜3Jšœ'˜'Jšœ+˜+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šœ8˜8K˜—šžœ˜KšœA˜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šœ9˜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šœ$˜$Jšœ?˜?Jšžœ˜—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˜