DIRECTORY AtomButtonsTypes, Feedback, GGBasicTypes, GGBoundBox, GGInterfaceTypes, GGModelTypes, GGScene, GGOutline, GGParseIn, GGParseOut, GGSegmentTypes, GGSelect, GGSequence, GGShapes, GGSlice, GGTraj, GGUtility, Imager, ImagerColor, ImagerPath, ImagerTransformation, IO, Rope, Vectors2d, ViewerClasses; GGOutlineImplA: CEDAR PROGRAM IMPORTS Feedback, GGBoundBox, GGScene, GGOutline, GGParseIn, GGParseOut, GGSequence, GGShapes, GGSlice, GGTraj, GGUtility, Imager, ImagerColor, ImagerPath, ImagerTransformation, IO, Vectors2d EXPORTS GGOutline = BEGIN BitVector: TYPE = GGBasicTypes.BitVector; BoundBox: TYPE = GGBasicTypes.BoundBox; CameraData: TYPE = GGModelTypes.CameraData; Circle: TYPE = GGBasicTypes.Circle; Color: TYPE = Imager.Color; DefaultData: TYPE = GGModelTypes.DefaultData; FeedbackData: TYPE = AtomButtonsTypes.FeedbackData; StrokeJoint: TYPE = Imager.StrokeJoint; StrokeEnd: TYPE = Imager.StrokeEnd; ControlPointGenerator: TYPE = GGModelTypes.ControlPointGenerator; FeatureData: TYPE = GGInterfaceTypes.FeatureData; GGData: TYPE = GGInterfaceTypes.GGData; Joint: TYPE = GGSegmentTypes.Joint; JointGenerator: TYPE = GGModelTypes.JointGenerator; Line: TYPE = GGBasicTypes.Line; AlignBag: TYPE = GGInterfaceTypes.AlignBag; Outline: TYPE = REF OutlineObj; OutlineObj: TYPE = GGModelTypes.OutlineObj; OutlineData: TYPE = REF OutlineDataObj; OutlineDataObj: TYPE = GGOutline.OutlineDataObj; OutlineClass: TYPE = REF OutlineClassObj; OutlineClassObj: TYPE = GGModelTypes.OutlineClassObj; OutlineDescriptor: TYPE = GGModelTypes.OutlineDescriptor; OutlineHitData: TYPE = REF OutlineHitDataObj; OutlineHitDataObj: TYPE = GGOutline.OutlineHitDataObj; OutlineParts: TYPE = REF OutlinePartsObj; OutlinePartsObj: TYPE = GGOutline.OutlinePartsObj; OutlineSequence: TYPE = GGSelect.OutlineSequence; OutlineSequenceGenerator: TYPE = GGSelect.OutlineSequenceGenerator; Point: TYPE = GGBasicTypes.Point; PointGenerator: TYPE = GGModelTypes.PointGenerator; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; Scene: TYPE = GGModelTypes.Scene; Segment: TYPE = GGSegmentTypes.Segment; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; SegmentGeneratorObj: TYPE = GGModelTypes.SegmentGeneratorObj; SelectMode: TYPE = GGModelTypes.SelectMode; SelectionClass: TYPE = GGSegmentTypes.SelectionClass; Sequence: TYPE = GGModelTypes.Sequence; SequenceOfReal: TYPE = GGBasicTypes.SequenceOfReal; Slice: TYPE = GGModelTypes.Slice; SliceClass: TYPE = REF SliceClassObj; SliceClassObj: TYPE = GGModelTypes.SliceClassObj; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj; SliceParts: TYPE = GGModelTypes.SliceParts; Traj: TYPE = REF TrajObj; TrajEnd: TYPE = GGModelTypes.TrajEnd; TrajGenerator: TYPE = REF TrajGeneratorObj; TrajGeneratorObj: TYPE = GGModelTypes.TrajGeneratorObj; TrajObj: TYPE = GGModelTypes.TrajObj; TrajPartType: TYPE = GGModelTypes.TrajPartType; TriggerBag: TYPE = GGInterfaceTypes.TriggerBag; Vector: TYPE = GGBasicTypes.Vector; Viewer: TYPE = ViewerClasses.Viewer; WalkProc: TYPE = GGModelTypes.WalkProc; Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem; fillColor: PUBLIC Imager.Color _ Imager.MakeGray[0.5]; BuildOutlineSliceClass: PUBLIC PROC [] RETURNS [class: OutlineClass] = { class _ NEW[OutlineClassObj _ [ type: $Outline, getBoundBox: OutlineBoundBox, getTightBox: OutlineTightBox, copy: OutlineCopy, drawParts: OutlineDrawParts, drawTransform: OutlineDrawTransform, drawSelectionFeedback: OutlineDrawSelectionFeedback, drawAttractorFeedback: OutlineDrawAttractorFeedback, transform: OutlineTransform, describe: OutlineDescribe, describeHit: OutlineDescribeHit, fileout: OutlineFileout, filein: OutlineFilein, isEmptyParts: OutlineIsEmptyParts, isCompleteParts: OutlineIsCompleteParts, newParts: OutlineNewParts, unionParts: OutlineUnionParts, differenceParts: OutlineDifferenceParts, movingParts: OutlineMovingParts, augmentParts: OutlineAugmentParts, setSelectedFields: OutlineSetSelectedFields, pointsInDescriptor: OutlinePointsInDescriptor, pointPairsInDescriptor: OutlinePointPairsInDescriptor, segmentsInDescriptor: OutlineSegmentsInDescriptor, walkSegments: OutlineWalkSegmentsProc, nextPoint: OutlineNextPoint, nextPointPair: OutlineNextPointPair, nextSegment: OutlineNextSegment, closestPoint: OutlineClosestPoint, closestJointToHitData: OutlineClosestJointToHitData, closestPointAndTangent: GGSlice.NoOpClosestPointAndTangent, closestSegment: OutlineClosestSegment, lineIntersection: OutlineLineIntersection, circleIntersection: OutlineCircleIntersection, hitDataAsSimpleCurve: OutlineHitDataAsSimpleCurve, setDefaults: OutlineSetDefaults, setStrokeWidth: OutlineSetStrokeWidth, getStrokeWidth: OutlineGetStrokeWidth, setStrokeEnd: OutlineSetStrokeEnd, getStrokeEnd: OutlineGetStrokeEnd, setStrokeJoint: OutlineSetStrokeJoint, getStrokeJoint: OutlineGetStrokeJoint, setStrokeColor: OutlineSetStrokeColor, getStrokeColor: OutlineGetStrokeColor, setFillColor: OutlineSetFillColor, getFillColor: OutlineGetFillColor, setArrows: GGSlice.NoOpSetArrows, getArrows: GGSlice.NoOpGetArrows, setDashed: OutlineSetDashed, getDashed: OutlineGetDashed ]]; }; CreateOutline: PUBLIC PROC [traj: Traj, fillColor: Color] RETURNS [outline: Outline] = { boundBox: BoundBox _ GGBoundBox.CopyBoundBox[traj.boundBox]; data: OutlineData _ NEW[OutlineDataObj _ [fillColor: fillColor, children: LIST[traj]] ]; outline _ NEW[OutlineObj _ [ class: GGSlice.FetchSliceClass[$Outline], data: data, selectedInFull: [FALSE, FALSE, FALSE], boundBox: boundBox]]; outline.nullDescriptor _ GGSlice.DescriptorFromParts[outline, CreateEmptyParts[outline]]; traj.parent _ outline; IF traj.role = hole THEN traj.role _ fence; }; OutlineBoundBox: PROC [slice: Outline, parts: SliceParts] RETURNS [box: BoundBox] = { IF parts = NIL THEN box _ slice.boundBox ELSE { outlineParts: OutlineParts _ NARROW[parts]; boxList: LIST OF BoundBox _ NIL; thisBox: BoundBox; FOR list: LIST OF Sequence _ outlineParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN { thisBox _ GGSequence.ComputeBoundBox[list.first]; boxList _ CONS[thisBox, boxList]; }; ENDLOOP; box _ GGBoundBox.BoundBoxOfBoxes[boxList]; }; }; OutlineTightBox: PROC [slice: Outline, parts: SliceParts] RETURNS [box: BoundBox] = { outlineParts: OutlineParts; boxList: LIST OF BoundBox _ NIL; thisBox: BoundBox; IF parts = NIL THEN parts _ slice.class.newParts[slice, NIL, slice].parts; outlineParts _ NARROW[parts]; FOR list: LIST OF Sequence _ outlineParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN { thisBox _ GGSequence.ComputeTightBox[list.first]; boxList _ CONS[thisBox, boxList]; }; ENDLOOP; box _ GGBoundBox.BoundBoxOfBoxes[boxList]; }; UpdateBoundBox: PUBLIC PROC [slice: Outline] = { outlineBoxes: LIST OF BoundBox _ NIL; trajGen: TrajGenerator; IF slice=NIL THEN RETURN; trajGen _ GGOutline.TrajsInOutline[slice]; FOR traj: Traj _ GGScene.NextTraj[trajGen], GGScene.NextTraj[trajGen] UNTIL traj = NIL DO outlineBoxes _ CONS[traj.boundBox, outlineBoxes]; ENDLOOP; UpdateBoundBoxFromList[slice.boundBox, outlineBoxes]; }; UpdateBoundBoxFromList: PROC [bBox: BoundBox, list: LIST OF BoundBox] = { IF list = NIL THEN ERROR; bBox.loX _ list.first.loX; bBox.hiX _ list.first.hiX; bBox.loY _ list.first.loY; bBox.hiY _ list.first.hiY; FOR bBoxList: LIST OF BoundBox _ list.rest, bBoxList.rest UNTIL bBoxList = NIL DO bBox.loX _ MIN[bBox.loX, bBoxList.first.loX]; bBox.hiX _ MAX[bBox.hiX, bBoxList.first.hiX]; bBox.loY _ MIN[bBox.loY, bBoxList.first.loY]; bBox.hiY _ MAX[bBox.hiY, bBoxList.first.hiY]; ENDLOOP; }; OutlineCopy: PROC [slice: Outline] RETURNS [copy: Outline] = { holeGen: TrajGenerator; newTraj, fence: Traj; newData: OutlineData; fence _ GGOutline.FenceOfOutline[slice]; newTraj _ GGTraj.CopyTraj[fence]; copy _ CreateOutline[newTraj, NARROW[slice.data, OutlineData].fillColor]; newData _ NARROW[copy.data]; holeGen _ GGOutline.HolesOfOutline[slice]; FOR hole: Traj _ GGScene.NextTraj[holeGen], GGScene.NextTraj[holeGen] UNTIL hole = NIL DO newTraj _ GGTraj.CopyTraj[hole]; newData.children _ AppendTrajList[newData.children, LIST[newTraj]]; newTraj.parent _ copy; ENDLOOP; }; OutlineDrawParts: PROC [slice: Outline, parts: SliceParts, dc: Imager.Context, camera: CameraData, quick: BOOL] = { IF parts # NIL THEN SIGNAL Problem[msg: "OutlineDrawParts NYI"]; DrawOutline[dc, slice] }; OutlineDrawTransform: PROC [sliceD: OutlineDescriptor, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = { BuildOutline: Imager.PathProc = { BuildPath: Imager.PathProc = { seg: Segment; firstPoint: Point _ GGTraj.FetchJointPos[traj, 0]; moveTo[ [firstPoint.x, firstPoint.y] ]; FOR i: INT IN [0..GGTraj.HiSegment[traj]] DO seg _ GGTraj.FetchSegment[traj, i]; seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo]; ENDLOOP; }; BuildPathTransformSeq: Imager.PathProc = { seg: Segment; firstPoint: Point _ GGTraj.FetchJointPos[traj, 0]; IF thisSeq.segments[0] OR thisSeq.joints[0] THEN firstPoint _ ImagerTransformation.Transform[transform, firstPoint]; moveTo[ [firstPoint.x, firstPoint.y] ]; FOR i: INT IN [0..GGTraj.HiSegment[traj]] DO seg _ GGTraj.FetchSegment[traj, i]; seg.class.buildPathTransform[seg, transform, thisSeq.segments[i], thisSeq.joints[i], thisSeq.joints[(i+1) MOD thisSeq.traj.segCount], thisSeq.controlPoints[i], lineTo, curveTo, conicTo, arcTo]; ENDLOOP; }; imagerHole: ImagerPath.Trajectory; cc, holeCC: BOOL; traj _ fence; thisSeq _ FindSequenceInList[traj, outlineParts.seqs]; IF thisSeq # NIL THEN { BuildPathTransformSeq[moveTo, lineTo, curveTo, conicTo, arcTo]; cc _ GGTraj.IsClockwiseTrajTransformSeq[thisSeq, transform]; } ELSE { BuildPath[moveTo, lineTo, curveTo, conicTo, arcTo]; cc _ GGTraj.IsClockwiseTraj[traj]; }; FOR holeList: LIST OF Traj _ outlineData.children.rest, holeList.rest UNTIL holeList = NIL DO traj _ holeList.first; thisSeq _ FindSequenceInList[traj, outlineParts.seqs]; IF thisSeq # NIL THEN { imagerHole _ ImagerPath.TrajectoryListFromPath[BuildPathTransformSeq].first; holeCC _ GGTraj.IsClockwiseTrajTransformSeq[thisSeq, transform]; } ELSE { imagerHole _ ImagerPath.TrajectoryListFromPath[BuildPath].first; holeCC _ GGTraj.IsClockwiseTraj[traj]; }; IF cc=holeCC THEN ImagerPath.MapTrajectoryBackward[imagerHole, moveTo, lineTo, curveTo, conicTo, arcTo] ELSE ImagerPath.MapTrajectory[imagerHole, moveTo, lineTo, curveTo, conicTo, arcTo]; ENDLOOP; }; traj: Traj; thisSeq: Sequence; outline: Outline _ sliceD.slice; outlineData: OutlineData _ NARROW[outline.data]; outlineParts: OutlineParts _ NARROW[sliceD.parts]; fence: Traj _ outlineData.children.first; IF fence.role = fence AND outlineData.fillColor#NIL THEN { transformedColor: Color; WITH outlineData.fillColor SELECT FROM cc: ImagerColor.ConstantColor => { Imager.SetColor[dc, cc]; }; sc: ImagerColor.SampledColor => { IF OutlineIsCompleteParts[sliceD] THEN { transformedColor _ ImagerColor.MakeSampledColor[pa: sc.pa, um: ImagerTransformation.Concat[sc.um, transform], colorOperator: sc.colorOperator]; Imager.SetColor[dc, transformedColor]; }; }; ENDCASE => ERROR; Imager.MaskFill[dc, BuildOutline]; }; thisSeq _ FindSequenceInList[fence, outlineParts.seqs]; IF thisSeq # NIL THEN GGTraj.DrawTrajTransformSeq[dc, thisSeq, transform] ELSE GGTraj.DrawTraj[dc, fence]; FOR holeList: LIST OF Traj _ outlineData.children.rest, holeList.rest UNTIL holeList = NIL DO thisSeq _ FindSequenceInList[holeList.first, outlineParts.seqs]; IF thisSeq # NIL THEN GGTraj.DrawTrajTransformSeq[dc, thisSeq, transform] ELSE GGTraj.DrawTraj[dc, holeList.first]; ENDLOOP; }; OutlineDrawSelectionFeedback: PROC [slice: Outline, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = { normalOutlineParts, hotOutlineParts: OutlineParts; normalList, hotList: LIST OF Sequence; normalOutlineParts _ NARROW[selectedParts]; hotOutlineParts _ NARROW[hotParts]; IF caretIsMoving OR dragInProgress THEN RETURN; IF selectedParts = NIL AND hotParts = NIL THEN RETURN; IF selectedParts # NIL AND hotParts # NIL THEN { hotList _ hotOutlineParts.seqs; FOR normalList _ normalOutlineParts.seqs, normalList.rest UNTIL normalList = NIL DO IF normalList.first # NIL THEN GGTraj.DrawSelectionFeedback[normalList.first.traj, normalList.first, hotList.first, dc, camera, dragInProgress, caretIsMoving, hideHot, quick] ELSE IF hotList.first # NIL THEN GGTraj.DrawSelectionFeedback[hotList.first.traj, normalList.first, hotList.first, dc, camera, dragInProgress, caretIsMoving, hideHot, quick]; hotList _ hotList.rest; ENDLOOP; } ELSE IF selectedParts # NIL THEN { FOR normalList _ normalOutlineParts.seqs, normalList.rest UNTIL normalList = NIL DO IF normalList.first # NIL THEN GGTraj.DrawSelectionFeedback[normalList.first.traj, normalList.first, NIL, dc, camera, dragInProgress, caretIsMoving, hideHot, quick]; ENDLOOP; } ELSE { FOR hotList _ hotOutlineParts.seqs, hotList.rest UNTIL hotList = NIL DO IF hotList.first # NIL THEN GGTraj.DrawSelectionFeedback[hotList.first.traj, NIL, hotList.first, dc, camera, dragInProgress, caretIsMoving, hideHot, quick]; ENDLOOP; }; }; OutlineDrawAttractorFeedback: PUBLIC PROC [sliceD: OutlineDescriptor, selectedParts: SliceParts, dragInProgress: BOOL, dc: Imager.Context, camera: CameraData] = { success: BOOL; partType: TrajPartType; traj: Traj; seg: Segment; jointNum: NAT; [success, partType, traj, ----, jointNum, ----, ----, seg] _ GGOutline.UnpackSimpleDescriptor[sliceD]; SELECT partType FROM joint => { previous: Segment _ GGTraj.PreviousSegment[traj, jointNum]; this: Segment _ IF jointNum <= GGTraj.HiSegment[traj] THEN GGTraj.FetchSegment[traj, jointNum] ELSE NIL; IF previous#NIL THEN DrawCpsAndJoints[dc, previous]; IF this#NIL THEN DrawCpsAndJoints[dc, this]; }; segment, controlPoint => DrawCpsAndJoints[dc, seg]; ENDCASE; -- none }; DrawOutline: PROC [dc: Imager.Context, outline: Outline] = { BuildOutline: Imager.PathProc = { BuildPath: Imager.PathProc = { BuildTrajPath[traj, moveTo, lineTo, curveTo, conicTo, arcTo]; }; imagerHole: ImagerPath.Trajectory; cc: BOOL; traj _ fence; cc _ GGTraj.IsClockwiseTraj[traj]; BuildTrajPath[traj, moveTo, lineTo, curveTo, conicTo, arcTo]; FOR holeList: LIST OF Traj _ outlineData.children.rest, holeList.rest UNTIL holeList = NIL DO traj _ holeList.first; imagerHole _ ImagerPath.TrajectoryListFromPath[BuildPath].first; IF cc=GGTraj.IsClockwiseTraj[traj] THEN ImagerPath.MapTrajectoryBackward[imagerHole, moveTo, lineTo, curveTo, conicTo, arcTo] ELSE ImagerPath.MapTrajectory[imagerHole, moveTo, lineTo, curveTo, conicTo, arcTo]; ENDLOOP; }; fence, traj: Traj; outlineData: OutlineData _ NARROW[outline.data]; fence _ outlineData.children.first; IF fence.role = fence AND outlineData.fillColor#NIL THEN { Imager.SetColor[dc, outlineData.fillColor]; Imager.MaskFill[dc, BuildOutline]; }; GGTraj.DrawTraj[dc, fence]; FOR holeList: LIST OF Traj _ outlineData.children.rest, holeList.rest UNTIL holeList = NIL DO GGTraj.DrawTraj[dc, holeList.first]; ENDLOOP; }; FindSequenceInList: PROC [traj: Traj, seqList: LIST OF Sequence] RETURNS [seq: Sequence] = { FOR list: LIST OF Sequence _ seqList, list.rest UNTIL list = NIL DO IF list.first # NIL AND list.first.traj = traj THEN RETURN[list.first]; ENDLOOP; RETURN[NIL]; }; BuildTrajPath: PROC [traj: Traj, moveTo: ImagerPath.MoveToProc, lineTo: ImagerPath.LineToProc, curveTo: ImagerPath.CurveToProc, conicTo: ImagerPath.ConicToProc, arcTo: ImagerPath.ArcToProc] = { DoBuildTrajPath: Imager.PathProc = { seg: Segment; firstPoint: Point _ GGTraj.FetchJointPos[traj, 0]; moveTo[ [firstPoint.x, firstPoint.y] ]; FOR i: INT IN [0..GGTraj.HiSegment[traj]] DO seg _ GGTraj.FetchSegment[traj, i]; seg.class.buildPath[seg, lineTo, curveTo, conicTo, arcTo]; ENDLOOP; }; DoBuildTrajPath[moveTo, lineTo, curveTo, conicTo, arcTo]; }; DrawCpsAndJoints: PROC [dc: Imager.Context, seg: Segment] = { point: Point; GGShapes.DrawJoint[dc, seg.lo]; GGShapes.DrawJoint[dc, seg.hi]; FOR j:INT IN [0..seg.class.controlPointCount[seg]) DO point _ seg.class.controlPointGet[seg, j]; GGShapes.DrawCP[dc, point]; ENDLOOP; }; OutlineTransform: PUBLIC PROC [sliceD: OutlineDescriptor, transform: ImagerTransformation.Transformation] = { outlineParts: OutlineParts _ NARROW[sliceD.parts]; outlineData: OutlineData _ NARROW[sliceD.slice.data]; slice: Outline _ sliceD.slice; transformedColor: Color; FOR list: LIST OF Sequence _ outlineParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN GGTraj.TransformSequence[list.first, transform]; ENDLOOP; UpdateBoundBox[slice]; IF outlineData.fillColor # NIL THEN { WITH outlineData.fillColor SELECT FROM cc: ImagerColor.ConstantColor => {}; sc: ImagerColor.SampledColor => { IF OutlineIsCompleteParts[sliceD] THEN { transformedColor _ ImagerColor.MakeSampledColor[pa: sc.pa, um: ImagerTransformation.Concat[sc.um, transform], colorOperator: sc.colorOperator]; outlineData.fillColor _ transformedColor; }; }; ENDCASE => ERROR; }; }; OneSequenceOnly: PROC [realParts: OutlineParts] RETURNS [theSeq: Sequence] = { theSeq _ NIL; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN { IF theSeq # NIL THEN RETURN[NIL] ELSE theSeq _ list.first; }; ENDLOOP; }; OutlineDescribe: PROC [sliceD: OutlineDescriptor] RETURNS [rope: Rope.ROPE] = { realParts: OutlineParts; theSeq: Sequence; IF sliceD.parts = NIL THEN RETURN["an Outline"]; realParts _ NARROW[sliceD.parts]; IF (theSeq _ OneSequenceOnly[realParts]) # NIL THEN { rope _ GGSequence.Describe[theSeq]; } ELSE { FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN RETURN["several parts of a multi-trajectory outline"]; ENDLOOP; rope _ "No outline parts to speak of."; }; }; OutlineDescribeHit: PROC [slice: Outline, hitData: REF ANY] RETURNS [rope: Rope.ROPE] = { outlineHitData: OutlineHitData _ NARROW[hitData]; rope _ GGTraj.DescribeHit[outlineHitData.traj, outlineHitData.hitType, outlineHitData.segNum, outlineHitData.cpNum, outlineHitData.jointNum]; }; OutlineFileout: PROC [slice: Outline, f: IO.STREAM] = { outlineData: OutlineData _ NARROW[slice.data]; count: NAT; f.PutF["fillColor: "]; GGParseOut.WriteColor[f, outlineData.fillColor]; f.PutChar[IO.CR]; count _ 0; FOR trajList: LIST OF Traj _ outlineData.children, trajList.rest UNTIL trajList = NIL DO count _ count + 1; ENDLOOP; f.PutF["Trajectories: [%g]", [integer[count]]]; FOR trajList: LIST OF Traj _ outlineData.children, trajList.rest UNTIL trajList = NIL DO f.PutChar[IO.CR]; GGTraj.Fileout[f, trajList.first]; ENDLOOP; }; OutlineFilein: PROC [f: IO.STREAM, version: REAL, feedback: FeedbackData] RETURNS [slice: Outline] = { fillColor: Color; count: NAT; fence, hole: Traj; hasCircle: BOOL _ FALSE; GGParseIn.ReadBlankAndRope[f, "fillColor:"]; fillColor _ GGParseIn.ReadColor[f, version]; IF version < 8702.26 THEN { -- read and discard StrokeEnd from older formats GGParseIn.ReadBlankAndRope[f, "strokeEnd:"]; [] _ GGParseIn.ReadStrokeEnd[f]; }; GGParseIn.ReadBlankAndRope[f, "Trajectories:"]; GGParseIn.ReadBlankAndRope[f, "["]; count _ GGParseIn.ReadBlankAndNAT[f]; GGParseIn.ReadBlankAndRope[f, "]"]; [fence, hasCircle] _ GGTraj.Filein[f, version]; IF hasCircle THEN fillColor _ NIL; -- needed for the transition from circle/disc class slice _ GGOutline.CreateOutline[fence, fillColor]; FOR i: NAT IN [1..count-1] DO [hole, ----] _ GGTraj.Filein[f, version]; slice _ GGOutline.AddHole[slice, hole]; ENDLOOP; }; OutlineIsEmptyParts: PROC [sliceD: OutlineDescriptor] RETURNS [BOOL] = { realParts: OutlineParts _ NARROW[sliceD.parts]; RETURN[IsEmpty[realParts]]; }; IsEmpty: PROC [realParts: OutlineParts] RETURNS [BOOL] = { FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL AND NOT GGSequence.IsEmpty[list.first] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; OutlineIsCompleteParts: PROC [sliceD: OutlineDescriptor] RETURNS [BOOL] = { realParts: OutlineParts _ NARROW[sliceD.parts]; RETURN[IsComplete[realParts]]; }; IsComplete: PROC [realParts: OutlineParts] RETURNS [BOOL] = { FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first = NIL THEN RETURN[FALSE]; IF NOT GGSequence.IsComplete[list.first] THEN RETURN[FALSE]; ENDLOOP; RETURN[TRUE]; }; CreateEmptyParts: PROC [slice: Outline] RETURNS [empty: SliceParts] = { realParts: OutlineParts; realData: OutlineData _ NARROW[slice.data]; empty _ realParts _ NEW[OutlinePartsObj]; realParts.seqs _ NIL; FOR list: LIST OF Traj _ realData.children, list.rest UNTIL list = NIL DO realParts.seqs _ CONS[NIL, realParts.seqs]; ENDLOOP; }; OutlineNewParts: PROC [slice: Outline, hitData: REF ANY, mode: SelectMode] RETURNS [sliceD: OutlineDescriptor] = { realParts: OutlineParts; ptr: LIST OF Sequence; trajGen: GGModelTypes.TrajGenerator; parts: SliceParts; realParts _ NEW[OutlinePartsObj]; [realParts.seqs, ptr] _ GGUtility.StartSequenceList[]; SELECT mode FROM literal => { traj: Traj; hitType: GGModelTypes.TrajPartType; segNum, cpNum, jointNum: INT; seq: Sequence; outlineHitData: OutlineHitData _ NARROW[hitData]; [traj, hitType, segNum, cpNum, jointNum] _ GGOutline.UnpackHitData[hitData]; SELECT hitType FROM joint => seq _ GGSequence.CreateFromJoint[traj, jointNum]; controlPoint => seq _ GGSequence.CreateFromControlPoint[traj, segNum, cpNum]; segment => seq _ GGSequence.CreateSimpleFromSegment[traj, segNum]; ENDCASE => ERROR; sliceD _ GGOutline.DescriptorFromSequence[slice, seq]; }; none => { outlineHitData: OutlineHitData _ NARROW[hitData]; sliceD _ GGSlice.DescriptorFromParts[slice, CreateEmptyParts[slice]]; }; slice => { completeSeq: Sequence; trajGen _ GGOutline.TrajsInOutline[slice]; FOR traj: Traj _ GGScene.NextTraj[trajGen], GGScene.NextTraj[trajGen] UNTIL traj = NIL DO completeSeq _ GGSequence.CreateComplete[traj]; [realParts.seqs, ptr] _ GGUtility.AddSequence[completeSeq, realParts.seqs, ptr]; ENDLOOP; parts _ realParts; }; joint => { outlineHitData: OutlineHitData _ NARROW[hitData]; SELECT outlineHitData.hitType FROM joint => { -- hit a joint traj: Traj _ outlineHitData.traj; jointNum: NAT _ outlineHitData.jointNum; seq: Sequence _ GGSequence.CreateJointToJoint[traj, jointNum, jointNum]; parts _ GGOutline.PartsFromSequence[slice, seq]; }; segment => { -- we are in the middle of a segment. Select nearest joint or cp success: BOOL; segNum, cpNum, jointNum: NAT; jointPoint, cpPoint, caretPt: Point; traj: Traj; seg: Segment; seq: Sequence; traj _ outlineHitData.traj; segNum _ outlineHitData.segNum; seg _ GGTraj.FetchSegment[traj, segNum]; caretPt _ outlineHitData.hitPoint; jointNum _ NearestJointToHitSpot[caretPt, traj, -1, segNum, segment]; jointPoint _ GGTraj.FetchJointPos[traj, jointNum]; [cpPoint, cpNum, success] _ seg.class.closestControlPoint[seg, caretPt, GGUtility.plusInfinity]; IF NOT success THEN seq _ GGSequence.CreateJointToJoint[traj, jointNum, jointNum] ELSE { cpDist: REAL _ Vectors2d.DistanceSquared[cpPoint, caretPt]; jointDist: REAL _ Vectors2d.DistanceSquared[jointPoint, caretPt]; seq _ IF cpDist < jointDist THEN GGSequence.CreateFromControlPoint[traj, segNum, cpNum] ELSE GGSequence.CreateJointToJoint[traj, jointNum, jointNum]; }; parts _ GGOutline.PartsFromSequence[slice, seq]; }; controlPoint => { traj: Traj; segNum, cpNum: NAT; controlPointSeq: Sequence; traj _ outlineHitData.traj; segNum _ outlineHitData.segNum; cpNum _ outlineHitData.cpNum; controlPointSeq _ GGSequence.CreateFromControlPoint[traj, segNum, cpNum]; parts _ GGOutline.PartsFromSequence[slice, controlPointSeq]; }; ENDCASE => ERROR; }; segment => { outlineHitData: OutlineHitData _ NARROW[hitData]; SELECT outlineHitData.hitType FROM joint => { traj: Traj _ outlineHitData.traj; jointNum: NAT _ outlineHitData.jointNum; this: INT _ IF jointNum>GGTraj.HiSegment[traj] THEN -1 ELSE jointNum; previous: INT _ GGTraj.PreviousSegmentNum[traj, jointNum]; parts _ GGOutline.PartsFromSequence[slice, GGSequence.CreateFromSegment[traj, IF this#-1 THEN this ELSE previous]]; }; segment, controlPoint => { seq: Sequence _ GGSequence.CreateFromSegment[outlineHitData.traj, outlineHitData.segNum]; parts _ GGOutline.PartsFromSequence[slice, seq]; }; ENDCASE => ERROR; }; traj => { outlineHitData: OutlineHitData _ NARROW[hitData]; traj: Traj _ outlineHitData.traj; seq: Sequence _ GGSequence.CreateComplete[traj]; parts _ GGOutline.PartsFromSequence[slice, seq]; }; topLevel => { sliceD _ slice.class.newParts[slice, NIL, slice]; }; ENDCASE => ERROR; IF sliceD = NIL THEN sliceD _ GGSlice.DescriptorFromParts[slice, parts]; }; OutlineUnionParts: PROC [partsA: OutlineDescriptor, partsB: OutlineDescriptor] RETURNS [aPlusB: OutlineDescriptor] = { realPartsA: OutlineParts _ NARROW[partsA.parts]; realPartsB: OutlineParts _ NARROW[partsB.parts]; union: OutlineParts; listA: LIST OF Sequence _ realPartsA.seqs; listB: LIST OF Sequence _ realPartsB.seqs; ptr: LIST OF Sequence; IF partsA.parts = NIL THEN RETURN[partsB]; IF partsB.parts = NIL THEN RETURN[partsA]; IF IsEmpty[realPartsA] THEN RETURN[partsB]; IF IsEmpty[realPartsB] THEN RETURN[partsA]; IF IsComplete[realPartsA] THEN RETURN[partsA]; IF IsComplete[realPartsB] THEN RETURN[partsB]; union _ NEW[OutlinePartsObj]; [union.seqs, ptr] _ GGUtility.StartSequenceList[]; UNTIL listA = NIL DO IF listA.first = NIL THEN [union.seqs, ptr] _ GGUtility.AddSequence[listB.first, union.seqs, ptr] ELSE IF listB.first = NIL THEN [union.seqs, ptr] _ GGUtility.AddSequence[listA.first, union.seqs, ptr] ELSE { newSeq: Sequence _ GGSequence.Union[listA.first, listB.first]; [union.seqs, ptr] _ GGUtility.AddSequence[newSeq, union.seqs, ptr]; }; listA _ listA.rest; listB _ listB.rest; ENDLOOP; aPlusB _ GGSlice.DescriptorFromParts[partsA.slice, union]; }; OutlineDifferenceParts: PROC [partsA: OutlineDescriptor, partsB: OutlineDescriptor] RETURNS [aMinusB: OutlineDescriptor] = { realPartsA, realPartsB: OutlineParts; diff: OutlineParts; listA: LIST OF Sequence; listB: LIST OF Sequence; ptr: LIST OF Sequence; IF partsA.parts = NIL OR partsB = NIL OR partsB.parts = NIL THEN RETURN[partsA]; realPartsA _ NARROW[partsA.parts]; realPartsB _ NARROW[partsB.parts]; listA _ realPartsA.seqs; listB _ realPartsB.seqs; diff _ NEW[OutlinePartsObj]; [diff.seqs, ptr] _ GGUtility.StartSequenceList[]; UNTIL listA = NIL DO IF listA.first = NIL THEN [diff.seqs, ptr] _ GGUtility.AddSequence[NIL, diff.seqs, ptr] ELSE IF listB.first = NIL THEN [diff.seqs, ptr] _ GGUtility.AddSequence[listA.first, diff.seqs, ptr] ELSE { newSeq: Sequence _ GGSequence.Difference[listA.first, listB.first]; [diff.seqs, ptr] _ GGUtility.AddSequence[newSeq, diff.seqs, ptr]; }; listA _ listA.rest; listB _ listB.rest; ENDLOOP; aMinusB _ GGSlice.DescriptorFromParts[partsA.slice, diff]; }; OutlineMovingParts: PROC [slice: Outline, selectedParts: SliceParts] RETURNS [background, overlay, rubber, drag: OutlineDescriptor] = { OPEN GGUtility; outlineData: OutlineData; outlineParts: OutlineParts _ NARROW[selectedParts]; bkgdParts, overParts, rubberParts, dragParts: OutlineParts; bkgdSeq, overSeq, rubberSeq, dragSeq: Sequence; bkgdPtr, overPtr, rubberPtr, dragPtr: LIST OF Sequence; children: LIST OF Traj; filled: BOOL _ FALSE; nullD: OutlineDescriptor _ slice.nullDescriptor; IF IsEmpty[outlineParts] THEN { background _ overlay _ rubber _ drag _ nullD; RETURN; }; IF IsComplete[outlineParts] THEN { background _ overlay _ rubber _ nullD; drag _ GGSlice.DescriptorFromParts[slice, selectedParts]; RETURN; }; bkgdParts _ NEW[OutlinePartsObj]; overParts _ NEW[OutlinePartsObj]; rubberParts _ NEW[OutlinePartsObj]; dragParts _ NEW[OutlinePartsObj]; [bkgdParts.seqs, bkgdPtr] _ StartSequenceList[]; [overParts.seqs, overPtr] _ StartSequenceList[]; [rubberParts.seqs, rubberPtr] _ StartSequenceList[]; [dragParts.seqs, dragPtr] _ StartSequenceList[]; outlineData _ NARROW[slice.data]; filled _ outlineData.fillColor # NIL; children _ outlineData.children; FOR list: LIST OF Sequence _ outlineParts.seqs, list.rest UNTIL list = NIL DO IF list.first = NIL THEN { [rubberParts.seqs, rubberPtr] _ AddSequence[NIL, rubberParts.seqs, rubberPtr]; [dragParts.seqs, dragPtr] _ AddSequence[NIL, dragParts.seqs, dragPtr]; IF filled THEN { overSeq _ GGSequence.CreateComplete[children.first]; [overParts.seqs, overPtr] _ AddSequence[overSeq, overParts.seqs, overPtr]; [bkgdParts.seqs, bkgdPtr] _ AddSequence[NIL, bkgdParts.seqs, bkgdPtr]; } ELSE { bkgdSeq _ GGSequence.CreateComplete[children.first]; [bkgdParts.seqs, bkgdPtr] _ AddSequence[bkgdSeq, bkgdParts.seqs, bkgdPtr]; [overParts.seqs, overPtr] _ AddSequence[NIL, overParts.seqs, overPtr]; }; } ELSE { [bkgdSeq, overSeq, rubberSeq, dragSeq] _ GGSequence.TrajMovingParts[list.first]; IF filled THEN { [bkgdParts.seqs, bkgdPtr] _ AddSequence[NIL, bkgdParts.seqs, bkgdPtr]; [overParts.seqs, overPtr] _ AddSequence[overSeq, overParts.seqs, overPtr]; [rubberParts.seqs, rubberPtr] _ AddSequence[rubberSeq, rubberParts.seqs, rubberPtr]; [dragParts.seqs, dragPtr] _ AddSequence[dragSeq, dragParts.seqs, dragPtr]; } ELSE { [bkgdParts.seqs, bkgdPtr] _ AddSequence[overSeq, bkgdParts.seqs, bkgdPtr]; [overParts.seqs, overPtr] _ AddSequence[NIL, overParts.seqs, overPtr]; [rubberParts.seqs, rubberPtr] _ AddSequence[rubberSeq, rubberParts.seqs, rubberPtr]; [dragParts.seqs, dragPtr] _ AddSequence[dragSeq, dragParts.seqs, dragPtr]; }; }; children _ children.rest; ENDLOOP; background _ GGSlice.DescriptorFromParts[slice, bkgdParts]; overlay _ GGSlice.DescriptorFromParts[slice, overParts]; rubber _ GGSlice.DescriptorFromParts[slice, rubberParts]; drag _ GGSlice.DescriptorFromParts[slice, dragParts]; }; NearestJointToHitSpot: PUBLIC PROC [caretPt: Point, traj: Traj, hitJointNum: INT, hitSegNum: INT, hitType: GGOutline.HitType] RETURNS [jointNum: NAT] = { nextNum: NAT; p1, p2: Point; d1, d2: REAL; SELECT hitType FROM joint => { jointNum _ hitJointNum; }; segment, controlPoint => { nextNum _ GGTraj.FollowingJoint[traj, hitSegNum]; p1 _ GGTraj.FetchJointPos[traj, hitSegNum]; p2 _ GGTraj.FetchJointPos[traj, nextNum]; d1 _ Vectors2d.DistanceSquared[p1, caretPt]; d2 _ Vectors2d.DistanceSquared[p2, caretPt]; IF d1 <= d2 THEN jointNum _ hitSegNum ELSE jointNum _ nextNum; }; ENDCASE => ERROR Problem[msg: "NearestJointToHitSpot NYI"]; }; -- end of NearestJointToHitSpot NearestJointToHitData: PUBLIC PROC [hitData: REF ANY] RETURNS [jointNum: NAT, traj: Traj] = { outlineHitData: OutlineHitData _ NARROW[hitData]; nextNum: NAT; p1, p2: Point; d1, d2: REAL; traj _ outlineHitData.traj; SELECT outlineHitData.hitType FROM joint => { jointNum _ outlineHitData.jointNum; }; segment, controlPoint => { nextNum _ GGTraj.FollowingJoint[traj, outlineHitData.segNum]; p1 _ GGTraj.FetchJointPos[traj, outlineHitData.segNum]; p2 _ GGTraj.FetchJointPos[traj, nextNum]; d1 _ Vectors2d.DistanceSquared[p1, outlineHitData.hitPoint]; d2 _ Vectors2d.DistanceSquared[p2, outlineHitData.hitPoint]; IF d1 <= d2 THEN jointNum _ outlineHitData.segNum ELSE jointNum _ nextNum; }; ENDCASE => ERROR; }; OutlineAugmentParts: PROC [sliceD: OutlineDescriptor, selectClass: SelectionClass] RETURNS [more: OutlineDescriptor] = { sliceParts: OutlineParts _ NARROW[sliceD.parts]; FOR list: LIST OF Sequence _ sliceParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN GGSequence.AugmentParts[list.first, selectClass]; ENDLOOP; more _ GGSlice.DescriptorFromParts[sliceD.slice, sliceParts]; }; OutlineSetSelectedFields: PROC [sliceD: OutlineDescriptor, selected: BOOL, selectClass: SelectionClass] = { sliceParts: OutlineParts _ NARROW[sliceD.parts]; FOR list: LIST OF Sequence _ sliceParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN GGTraj.SetSelectedFields[list.first, selected, selectClass]; ENDLOOP; }; PointGeneratorData: TYPE = REF PointGeneratorDataObj; PointGeneratorDataObj: TYPE = RECORD [ seqPtr: LIST OF Sequence, pointGen: PointGenerator -- the point generator for the first sequence of seqPtr ]; PointPairGeneratorData: TYPE = REF PointPairGeneratorDataObj; PointPairGeneratorDataObj: TYPE = RECORD [ seqPtr: LIST OF Sequence, pointGen: PointPairGenerator -- the pair generator for first sequence of seqPtr ]; SegmentGeneratorData: TYPE = REF SegmentGeneratorDataObj; SegmentGeneratorDataObj: TYPE = RECORD [ seqPtr: LIST OF Sequence, segGen: SegmentGenerator -- the point generator for the first sequence of seqPtr ]; OutlinePointsInDescriptor: PUBLIC PROC [sliceD: OutlineDescriptor] RETURNS [pointGen: GGModelTypes.OutlinePointGenerator] = { realParts: OutlineParts _ NARROW[sliceD.parts]; seqPtr: LIST OF Sequence _ realParts.seqs; trajPointGen: GGModelTypes.PointGenerator; pointGenData: PointGeneratorData; trajPointGen _ IF seqPtr.first = NIL THEN NIL ELSE GGTraj.PointsInDescriptor[seqPtr.first]; pointGenData _ NEW[PointGeneratorDataObj _ [seqPtr: seqPtr, pointGen: trajPointGen]]; pointGen _ NEW[GGModelTypes.OutlinePointGeneratorObj _ [sliceD, 0, 0, pointGenData]]; }; OutlinePointPairsInDescriptor: PUBLIC PROC [sliceD: OutlineDescriptor] RETURNS [pointPairGen: GGModelTypes.OutlinePointPairGenerator] = { realParts: OutlineParts _ NARROW[sliceD.parts]; seqPtr: LIST OF Sequence _ realParts.seqs; trajPointGen: GGModelTypes.PointPairGenerator; pointGenData: PointPairGeneratorData; trajPointGen _ IF seqPtr.first = NIL THEN NIL ELSE GGTraj.PointPairsInDescriptor[seqPtr.first]; pointGenData _ NEW[PointPairGeneratorDataObj _ [seqPtr: seqPtr, pointGen: trajPointGen]]; pointPairGen _ NEW[GGModelTypes.OutlinePointPairGeneratorObj _ [sliceD, 0, 0, pointGenData]]; }; OutlineSegmentsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [segGen: SegmentGenerator] = { realParts: OutlineParts _ NARROW[sliceD.parts]; seqPtr: LIST OF Sequence _ realParts.seqs; IF seqPtr#NIL THEN { segGenData: SegmentGeneratorData _ NEW[SegmentGeneratorDataObj _ [ seqPtr: seqPtr, -- one sequence per traj in outline, including NILs for unincluded trajs segGen: GGSequence.SegmentsInSequence[seqPtr.first] -- segGen for first sequence ]]; segGen _ NEW[SegmentGeneratorObj _ [ toGo: 9999, -- for debugging sliceD: sliceD, completeSeq: FALSE, classSpecific: segGenData ]]; }; }; OutlineNextSegment: PROC [segGen: SegmentGenerator] RETURNS [seg: Segment, transform: ImagerTransformation.Transformation] = { IF segGen.toGo#9999 THEN ERROR ELSE { -- for debugging segGenData: SegmentGeneratorData _ NARROW[segGen.classSpecific]; seqPtr: LIST OF Sequence _ segGenData.seqPtr; WHILE TRUE DO IF seqPtr.first # NIL THEN { seg _ GGSequence.NextSegment[segGenData.segGen]; IF seg#NIL THEN { segGenData.seqPtr _ seqPtr; RETURN; }; seqPtr _ seqPtr.rest; -- on to the next sequence }; UNTIL seqPtr = NIL OR seqPtr.first # NIL DO seqPtr _ seqPtr.rest ENDLOOP; IF seqPtr = NIL THEN RETURN[NIL, NIL]; segGenData.segGen _ GGSequence.SegmentsInSequence[seqPtr.first]; ENDLOOP; }; }; SeqsForEntireOutline: PROC [outline: Slice] RETURNS [seqList: LIST OF Sequence] = { outlineData: OutlineData _ NARROW[outline.data]; FOR tList: LIST OF Traj _ outlineData.children, tList.rest UNTIL tList=NIL DO seqList _ CONS[GGSequence.CreateComplete[tList.first], seqList]; ENDLOOP; }; OutlineWalkSegmentsProc: PROC [slice: Slice, walkProc: WalkProc] RETURNS [sliceD: SliceDescriptor] = { parts: OutlineParts _ NEW[OutlinePartsObj]; outlineSeqs: LIST OF Sequence _ SeqsForEntireOutline[slice]; FOR seqList: LIST OF Sequence _ outlineSeqs, seqList.rest UNTIL seqList=NIL DO oldSeq: Sequence _ seqList.first; -- the original descriptor sequence oldSegGen: SegmentGenerator _ GGSequence.SegmentsInSequence[oldSeq]; newSeq: Sequence _ GGSequence.CreateEmpty[oldSeq.traj]; -- the new descriptor sequence FOR segAndIndex: GGSequence.SegAndIndex _ GGSequence.NextSegmentAndIndex[oldSegGen], GGSequence.NextSegmentAndIndex[oldSegGen] UNTIL segAndIndex.seg=NIL DO keep: BOOL _ walkProc[segAndIndex.seg, NIL]; IF keep THEN { newSeq.segments[segAndIndex.index] _ TRUE; newSeq.segCount _ newSeq.segCount+1; }; ENDLOOP; GGSequence.FillInJoints[newSeq]; GGSequence.FillInControlPoints[newSeq]; parts.seqs _ GGUtility.AppendSequenceList[parts.seqs, LIST[newSeq] ]; ENDLOOP; sliceD _ NEW[SliceDescriptorObj _ [slice, parts] ]; }; OutlineNextPoint: PUBLIC PROC [pointGen: GGModelTypes.OutlinePointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = { pgd: PointGeneratorData _ NARROW[pointGen.classSpecific]; seqPtr: LIST OF Sequence _ pgd.seqPtr; IF seqPtr = NIL THEN RETURN[[[0,0], TRUE]]; WHILE TRUE DO IF seqPtr.first # NIL THEN { pointAndDone _ GGTraj.NextPoint[pgd.pointGen]; IF NOT pointAndDone.done THEN { pgd.seqPtr _ seqPtr; RETURN; }; seqPtr _ seqPtr.rest; -- on to the next sequence }; UNTIL seqPtr = NIL OR seqPtr.first # NIL DO seqPtr _ seqPtr.rest ENDLOOP; IF seqPtr = NIL THEN RETURN[[[0,0], TRUE]]; pgd.pointGen _ GGTraj.PointsInDescriptor[seqPtr.first]; ENDLOOP; }; OutlineNextPointPair: PUBLIC PROC [pointPairGen: GGModelTypes.OutlinePointPairGenerator] RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = { pgd: PointPairGeneratorData _ NARROW[pointPairGen.classSpecific]; seqPtr: LIST OF Sequence _ pgd.seqPtr; IF seqPtr = NIL THEN RETURN[[[0,0], [0,0], TRUE]]; WHILE TRUE DO IF seqPtr.first # NIL THEN { pointPairAndDone _ GGTraj.NextPointPair[pgd.pointGen]; IF NOT pointPairAndDone.done THEN { pgd.seqPtr _ seqPtr; RETURN; }; seqPtr _ seqPtr.rest; -- on to the next sequence }; UNTIL seqPtr = NIL OR seqPtr.first # NIL DO seqPtr _ seqPtr.rest ENDLOOP; IF seqPtr = NIL THEN RETURN[[[0,0], [0,0], TRUE]]; pgd.pointGen _ GGTraj.PointPairsInDescriptor[seqPtr.first]; ENDLOOP; }; GoodPointType: TYPE = {joint, intersectionPoint, midpoint, controlPoint, slice, none}; OutlineClosestPoint: PROC [sliceD: OutlineDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL] = { parts: OutlineParts _ NARROW[sliceD.parts]; thisDist: REAL; thisSegNum, bestSegNum, thisCP, bestCP, thisJointNum, bestJointNum: NAT; thisPoint: Point; thisSuccess: BOOL; bestTraj: Traj; bestType: GoodPointType _ none; outlineHitData: OutlineHitData; success _ FALSE; bestDist _ GGUtility.plusInfinity; FOR list: LIST OF Sequence _ parts.seqs, list.rest UNTIL list = NIL DO [thisDist, thisSegNum, thisCP, thisPoint, thisSuccess] _ GGTraj.NearestControlPoint[testPoint, list.first, tolerance]; IF thisSuccess AND thisDist < bestDist THEN { bestType _ controlPoint; bestPoint _ thisPoint; bestDist _ thisDist; bestTraj _ list.first.traj; bestSegNum _ thisSegNum; bestCP _ thisCP; success _ TRUE; }; ENDLOOP; FOR list: LIST OF Sequence _ parts.seqs, list.rest UNTIL list = NIL DO [thisDist, thisJointNum, thisPoint, thisSuccess] _ GGTraj.NearestJoint[testPoint, list.first, tolerance]; IF thisSuccess AND thisDist < bestDist THEN { bestType _ joint; bestPoint _ thisPoint; bestDist _ thisDist; bestTraj _ list.first.traj; bestJointNum _ thisJointNum; success _ TRUE; }; ENDLOOP; IF success THEN { SELECT bestType FROM controlPoint => { hitData _ outlineHitData _ NEW[OutlineHitDataObj _ [ bestTraj, controlPoint, bestSegNum, bestCP, -1, bestPoint]]; }; joint => { hitData _ outlineHitData _ NEW[OutlineHitDataObj _ [ bestTraj, joint, -1, -1, bestJointNum, bestPoint]]; }; ENDCASE => ERROR; }; }; OutlineClosestJointToHitData: PUBLIC PROC [sliceD: OutlineDescriptor, mapPoint: Point, hitData: REF ANY] RETURNS [jointD: OutlineDescriptor, point: Point] = { traj: Traj; hitType: GGModelTypes.TrajPartType; segNum, cpNum, jointNum: INT; hitPoint: Point; jointSeq: Sequence; [traj, hitType, segNum, cpNum, jointNum, hitPoint] _ GGOutline.UnpackHitData[hitData]; SELECT hitType FROM joint => { jointSeq _ GGSequence.CreateFromJoint[traj, jointNum]; point _ mapPoint; }; controlPoint => { jointSeq _ GGSequence.CreateFromControlPoint[traj, segNum, cpNum]; point _ mapPoint; }; segment => { success: BOOL; jointPoint, cpPoint: Point; seg: Segment; seg _ GGTraj.FetchSegment[traj, segNum]; [jointNum, ----] _ GGOutline.NearestJointToHitData[hitData]; jointPoint _ GGTraj.FetchJointPos[traj, jointNum]; [cpPoint, cpNum, success] _ seg.class.closestControlPoint[seg, mapPoint, GGUtility.plusInfinity]; IF NOT success THEN { -- its a joint for sure jointSeq _ GGSequence.CreateFromJoint[traj, jointNum]; point _ jointPoint; } ELSE { -- could be a cp instead of a joint cpDist: REAL _ Vectors2d.DistanceSquared[cpPoint, mapPoint]; jointDist: REAL _ Vectors2d.DistanceSquared[jointPoint, mapPoint]; tisAJoint: BOOL _ jointDist <= cpDist; IF tisAJoint THEN { jointSeq _ GGSequence.CreateFromJoint[traj, jointNum]; point _ jointPoint; } ELSE { jointSeq _ GGSequence.CreateFromControlPoint[traj, segNum, cpNum]; point _ cpPoint; }; }; }; ENDCASE => ERROR; jointD _ GGOutline.DescriptorFromSequence[sliceD.slice, jointSeq]; }; OutlineClosestSegment: PUBLIC PROC [sliceD: OutlineDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL] = { parts: OutlineParts _ NARROW[sliceD.parts]; thisDist: REAL; thisSegNum, bestSegNum: NAT; thisPoint: Point; thisSuccess: BOOL; bestTraj: Traj; outlineHitData: OutlineHitData; success _ FALSE; bestDist _ GGUtility.plusInfinity; FOR list: LIST OF Sequence _ parts.seqs, list.rest UNTIL list = NIL DO [thisDist, thisSegNum, thisPoint, thisSuccess] _ GGTraj.NearestSegment[testPoint, list.first, tolerance]; IF thisSuccess AND thisDist < bestDist THEN { bestPoint _ thisPoint; bestDist _ thisDist; bestTraj _ list.first.traj; bestSegNum _ thisSegNum; success _ TRUE; }; ENDLOOP; IF success THEN { hitData _ outlineHitData _ NEW[OutlineHitDataObj _ [ bestTraj, segment, bestSegNum, -1, -1, bestPoint]]; }; }; OutlineLineIntersection: PUBLIC PROC [sliceD: OutlineDescriptor, line: Line] RETURNS [points: LIST OF Point, pointCount: NAT] = { segGen: GGModelTypes.SegmentGenerator; thesePoints: LIST OF Point; thisCount: NAT; seq: Sequence; parts: OutlineParts _ NARROW[sliceD.parts]; points _ NIL; pointCount _ 0; FOR list: LIST OF Sequence _ parts.seqs, list.rest UNTIL list = NIL DO seq _ list.first; IF seq = NIL THEN LOOP; segGen _ GGSequence.SegmentsInSequence[seq]; FOR seg: Segment _ GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen] UNTIL seg = NIL DO [thesePoints, thisCount] _ seg.class.lineIntersection[seg, line]; FOR list: LIST OF Point _ thesePoints, list.rest UNTIL list = NIL DO points _ CONS[list.first, points]; ENDLOOP; pointCount _ pointCount + thisCount; ENDLOOP; ENDLOOP; }; OutlineCircleIntersection: PROC [sliceD: OutlineDescriptor, circle: Circle] RETURNS [points: LIST OF Point, pointCount: NAT] = { segGen: GGModelTypes.SegmentGenerator; thesePoints: LIST OF Point; thisCount: NAT; seq: Sequence; parts: OutlineParts _ NARROW[sliceD.parts]; points _ NIL; pointCount _ 0; FOR list: LIST OF Sequence _ parts.seqs, list.rest UNTIL list = NIL DO seq _ list.first; IF seq = NIL THEN LOOP; segGen _ GGSequence.SegmentsInSequence[seq]; FOR seg: Segment _ GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen] UNTIL seg = NIL DO [thesePoints, thisCount] _ seg.class.circleIntersection[seg, circle]; FOR list: LIST OF Point _ thesePoints, list.rest UNTIL list = NIL DO points _ CONS[list.first, points]; ENDLOOP; ENDLOOP; pointCount _ pointCount + thisCount; ENDLOOP; }; OutlineHitDataAsSimpleCurve: PROC [slice: Outline, hitData: REF ANY] RETURNS [simpleCurve: REF ANY] = { outlineHitData: OutlineHitData _ NARROW[hitData]; traj: Traj _ outlineHitData.traj; SELECT outlineHitData.hitType FROM joint, controlPoint => RETURN[NIL]; segment => { segNum: INT _ outlineHitData.segNum; seg: Segment _ GGTraj.FetchSegment[traj, segNum]; simpleCurve _ seg.class.asSimpleCurve[seg, outlineHitData.hitPoint]; }; ENDCASE => ERROR; }; OutlineSetDefaults: PROC [slice: Outline, parts: SliceParts, defaults: DefaultData] = { realParts: OutlineParts _ NARROW[parts]; outlineData: OutlineData _ NARROW[slice.data]; outlineData.fillColor _ defaults.fillColor; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN { seq: Sequence _ list.first; GGSequence.SetStrokeWidth[seq, defaults.strokeWidth]; GGTraj.SetTrajStrokeJoint[seq.traj, defaults.strokeJoint]; GGSequence.SetStrokeEnd[seq, defaults.strokeEnd]; GGSequence.SetStrokeDashed[seq, defaults.dashed, GGUtility.CopyPattern[defaults.pattern], defaults.offset, defaults.length]; GGSequence.SetStrokeColor[seq, defaults.strokeColor]; }; ENDLOOP; }; OutlineSetStrokeWidth: PROC [slice: Outline, parts: SliceParts, strokeWidth: REAL] RETURNS [box: BoundBox] = { realParts: OutlineParts _ NARROW[parts]; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN GGSequence.SetStrokeWidth[list.first, strokeWidth]; ENDLOOP; UpdateBoundBox[slice]; box _ slice.boundBox; }; OutlineGetStrokeWidth: PROC [slice: Outline, parts: SliceParts] RETURNS [strokeWidth: REAL] = { realParts: OutlineParts _ NARROW[parts]; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN RETURN[GGSequence.GetStrokeWidth[list.first]]; ENDLOOP; RETURN[-1.0]; }; OutlineSetStrokeEnd: PROC [slice: Outline, parts: SliceParts, strokeEnd: StrokeEnd] = { realParts: OutlineParts _ NARROW[parts]; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN GGSequence.SetStrokeEnd[list.first, strokeEnd]; ENDLOOP; }; OutlineGetStrokeEnd: PROC [slice: Outline, parts: SliceParts] RETURNS [strokeEnd: StrokeEnd] = { realParts: OutlineParts _ NARROW[parts]; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN RETURN[GGSequence.GetStrokeEnd[list.first]]; ENDLOOP; RETURN[round]; }; OutlineSetStrokeJoint: PROC [slice: Outline, parts: SliceParts, strokeJoint: StrokeJoint] = { realParts: OutlineParts _ NARROW[parts]; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN GGTraj.SetTrajStrokeJoint[list.first.traj, strokeJoint]; ENDLOOP; }; OutlineGetStrokeJoint: PROC [slice: Outline, parts: SliceParts] RETURNS [strokeJoint: StrokeJoint] = { realParts: OutlineParts _ NARROW[parts]; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN RETURN[GGTraj.GetTrajStrokeJoint[list.first.traj]]; ENDLOOP; RETURN[round]; }; OutlineSetStrokeColor: PROC [slice: Outline, parts: SliceParts, color: Color] = { realParts: OutlineParts _ NARROW[parts]; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN GGSequence.SetStrokeColor[list.first, color]; ENDLOOP; }; OutlineGetStrokeColor: PROC [slice: Outline, parts: SliceParts] RETURNS [color: Color] = { realParts: OutlineParts _ NARROW[parts]; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN RETURN[GGSequence.GetStrokeColor[list.first]]; ENDLOOP; RETURN[NIL]; }; OutlineSetFillColor: PROC [slice: Outline, color: Color] = { outlineData: OutlineData _ NARROW[slice.data]; outlineData.fillColor _ color; }; OutlineGetFillColor: PROC [slice: Outline] RETURNS [color: Color] = { outlineData: OutlineData _ NARROW[slice.data]; color _ outlineData.fillColor; }; OutlineSetDashed: PUBLIC PROC [slice: Outline, parts: SliceParts, dashed: BOOL, pattern: SequenceOfReal _ NIL, offset: REAL _ 0.0, length: REAL _ -1.0] = { realParts: OutlineParts _ NARROW[parts]; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN GGSequence.SetStrokeDashed[list.first, dashed, pattern, offset, length]; ENDLOOP; }; OutlineGetDashed: PROC [slice: Outline, parts: SliceParts] RETURNS [dashed: BOOL, pattern: SequenceOfReal, offset, length: REAL] = { realParts: OutlineParts _ NARROW[parts]; FOR list: LIST OF Sequence _ realParts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN { [dashed, pattern, offset, length] _ GGSequence.GetStrokeDashed[list.first]; RETURN; }; ENDLOOP; RETURN[FALSE, NIL, 0.0, 0.0]; }; AppendTrajList: PUBLIC PROC [list1, list2: LIST OF Traj] RETURNS [result: LIST OF Traj] = { pos: LIST OF Traj; newCell: LIST OF Traj; IF list1 = NIL THEN RETURN[list2]; result _ CONS[list1.first, NIL]; pos _ result; FOR l: LIST OF Traj _ list1.rest, l.rest UNTIL l = NIL DO newCell _ CONS[l.first, NIL]; pos.rest _ newCell; pos _ newCell; ENDLOOP; pos.rest _ list2; }; END. ÚGGOutlineImplA.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last edited by Bier on June 2, 1986 5:10:01 pm PDT Contents: Procedures to implement the Outline Slice Class in Gargoyle. Outlines consist of Trajectories which in turn consist of Segments. Outline is the most important slice class. OPEN GGOutline; OutlineDescribeProc: TYPE = GGModelTypes.OutlineDescribeProc; OutlineFileoutProc: TYPE = GGModelTypes.OutlineFileoutProc; OutlineFileinProc: TYPE = GGModelTypes.OutlineFileinProc; OutlineUnionPartsProc: TYPE = GGModelTypes.OutlineUnionPartsProc; OutlineDifferencePartsProc: TYPE = GGModelTypes.OutlineDifferencePartsProc; OutlineAugmentPartsProc: TYPE = GGModelTypes.OutlineAugmentPartsProc; OutlinePointsInDescriptorProc: TYPE = GGModelTypes.OutlinePointsInDescriptorProc; OutlinePointPairsInDescriptorProc: TYPE = GGModelTypes.OutlinePointPairsInDescriptorProc; OutlineNextPointProc: TYPE = GGModelTypes.OutlineNextPointProc; OutlineNextPointPairProc: TYPE = GGModelTypes.OutlineNextPointPairProc; OutlineClosestPointProc: TYPE = GGModelTypes.OutlineClosestPointProc; OutlineClosestPointAndTangentProc: TYPE = GGModelTypes.OutlineClosestPointAndTangentProc; OutlineClosestSegmentProc: TYPE = GGModelTypes.OutlineClosestSegmentProc; OutlineSetArrowsProc: TYPE = GGModelTypes.OutlineSetArrowsProc; OutlineGetArrowsProc: TYPE = GGModelTypes.OutlineGetArrowsProc; The Outline Class Fundamentals Drawing Transforming Textual Description Parts Part Generators Hit Testing Style FetchSliceClass: PUBLIC PROC [name: ATOM] RETURNS [class: OutlineClass] = { class _ globalOutlineClass; }; FetchOutlineClass: PROC [] RETURNS [class: OutlineClass] = { class _ globalOutlineClass; }; Fundamentals Computes it from traj boxes. Drawing This proc is not really complete PathProc: TYPE ~ PROC[moveTo: MoveToProc, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc]; Fill in the outline if necessary. Draw the strokes. Drawing Utilities Fill the outline if necessary. Someday this will be a call to path.class.buildPath[traj, moveTo, lineTo, curveTo, conicTo, arcTo]; path.class.buildPath[traj, moveTo, lineTo, curveTo, conicTo, arcTo]; Draw the strokes. Will be path.class.drawBorder[fence, dc, ggData]; Will be path.class.drawBorder[holeList.first, dc, ggData]; Transforming Textual Description Parts Pass on the real work to GGSequence. Filled outlines have the property that if any of their subparts are on the overlay plane, then all of their parts are on the overlay plane. This trajectory is not at all selected. It will not move. If this outline is filled, put it on the overlay. Otherwise, leave it in the background. If the outline is filled, it has no background parts. Otherwise, all of its non-moving parts can be left on the background. If selectClass = normal, then for each segment mentioned in parts, place it, its adjacent joints, and its control points in more. If selectClass # normal then just place each segment and its control points in more. This mutates the OutlineDescriptor which may mean trouble. -- Bier, March 9, 1987 Set the selected fields of all of the joints and segments mentioned in sliceD. Hit Testing The SliceWalkSegmentsProc takes a slice and calls the walkProc for each segment in the slice with the segment data and possibly an associated transformation. It returns a slice descriptor which describes all segments of the slice for which the walkProc returned TRUE. Finds the intersection of the line with those slice parts mentioned in sliceD. simpleCurve will be of type Edge, Arc, etc. There will be Conic and Bezier types as well. Style maybe should implement parts=NIL to mean all parts this is inefficient but easy to implement Sets the stroke width of the named parts of slice to be strokeWidth. The following is very conservative and wasteful Get the stroke width of the first segment you come to. Sets the stroke end of the named parts of slice to be strokeEnd. Get the stroke end of the first segment you come to. Sets the stroke Joint of the named parts of slice to be strokeEnd. Get the stroke joint of the first segment you come to. Sets the stroke color of the named parts of slice to be color. Get the stroke color of the named parts of slice. Sets the fill color of the slice to be color. Get the fill color of the slice. Get the dash pattern of the named parts of slice. Utility Routines Non-destructive (copies the first list). globalOutlineClass: OutlineClass; Init: PROC [] = { globalOutlineClass _ MakeOutlineClass[]; }; Init[]; Ê6¶˜J˜Icodešœ™Kšœ Ïmœ1™K˜Kšœ˜Kšœ˜Kšœ(˜(Kšœ!˜!Kšœžœ%˜IKšœ žœ ˜Kšœ*˜*šžœCžœžœž˜YKšœ ˜ Kšœ4žœ ˜CKšœ˜Kšžœ˜—K˜K˜—K™Kšœ™K˜šŸœžœTžœ˜sK™ Kšžœ žœžœžœ&˜@Kšœ˜K˜K˜—K™šŸœžœx˜’šÐbn œ˜!Kšœ žœžœh™}š¡ œ˜K˜ Kšœ2˜2Kšœ'˜'šžœžœžœž˜,Kšœ#˜#Kšœ:˜:—Kšžœ˜K˜—š¡œ˜*K˜ Kšœ2˜2KšžœžœžœD˜tKšœ'˜'šžœžœžœž˜,Kšœ#˜#KšœjžœT˜ÁKšžœ˜—K˜—K˜Kšœ"˜"Kšœ žœ˜Kšœ ˜ K˜Kšœ6˜6šžœ žœžœ˜Kšœ?˜?Kšœ=˜=K˜—šžœ˜Kšœ3˜3Kšœ#˜#K˜—š žœ žœžœ1žœ žœž˜]Kšœ˜Kšœ6˜6šžœ žœžœ˜KšœL˜LKšœ@˜@K˜—šžœ˜Kšœ@˜@Kšœ&˜&K˜—Kšžœ žœV˜gKšžœO˜SKšžœ˜—K˜—K˜Kšœ ˜ K˜Kšœ ˜ Kšœžœ˜0Kšœžœ˜2šœ)˜)Kš !™!—šžœžœžœžœ˜:Kšœ˜šžœžœž˜&šœ"˜"Kšœ˜Kšœ˜—šœ!˜!šžœ žœ˜(Kšœ˜Kšœ&˜&K˜—K˜—Kšžœžœ˜—K˜"K˜Kš ™—Kšœ7˜7Kšžœ žœžœ5žœ˜jš žœ žœžœ1žœ žœž˜]Kšœ@˜@Kšžœ žœžœ5žœ%˜sKšžœ˜—K˜K˜—K˜šŸœžœšžœ˜ÅK˜2Kšœžœžœ ˜&Kšœžœ˜+Kšœžœ ˜#Kšžœžœžœžœ˜/Kš žœžœžœ žœžœžœ˜6š žœžœžœ žœžœ˜0Kšœ˜šžœ7žœžœž˜SKšžœžœžœ˜®KšžœžœžœžœŽ˜®Kšœ˜Kšžœ˜—K˜—šžœžœžœžœ˜"šžœ7žœžœž˜SKšžœžœžœGžœ=˜¥Kšžœ˜—K˜—šžœ˜šžœ.žœ žœž˜GKšžœžœžœ2žœL˜œKšžœ˜—K˜—K˜K˜—K˜š¡œžœžœHžœ-˜¢Jšœ žœ˜Jšœ˜Jšœ ˜ Jšœ ˜ Jšœ žœ˜JšœÏcœ ¢œ¢œ2˜fšžœ ž˜šœ ˜ Jšœ;˜;Jš œžœ$žœ%žœžœ˜hJšžœ žœžœ ˜4Jšžœžœžœ˜,J˜—Jšœ4˜4Jšžœ¢˜—K˜—K™K™K˜šŸ œžœ+˜˜Qšžœ˜Kšœžœ/˜;Kšœ žœ2˜AKšœžœžœ7˜WKšžœ9˜=K˜—Kšœ0˜0K˜—˜Kšœ ˜ Kšœžœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜KšœI˜IKšœ<˜˜>KšœC˜CK˜—Kšœ˜K˜Kšžœ˜—Kšœ:˜:K˜K˜—šŸœžœ8žœ!˜|Kšœ%˜%Kšœ˜Kšœžœžœ ˜Kšœžœžœ ˜Kšœžœžœ ˜K˜Kšžœžœžœ žœžœžœžœžœ ˜PKšœ žœ˜"Kšœ žœ˜"Kšœ˜Kšœ˜Kšœžœ˜Kšœ1˜1šžœ žœž˜Kšžœžœžœ*žœ˜WKšžœžœžœžœF˜dšžœ˜KšœC˜CKšœA˜AK˜—Kšœ˜K˜Kšžœ˜—Kšœ:˜:K˜K˜—šŸœžœ-žœ;˜‡KšÐbk  ˜K™±K˜Kšœžœ˜3Kšœ;˜;Kšœ/˜/Kšœ&žœžœ ˜7Kšœ žœžœ˜Kšœžœžœ˜Kšœ0˜0K˜šžœžœ˜Kšœ-˜-Kšžœ˜K˜—šžœžœ˜"Kšœ&˜&Kšœ9˜9Kšžœ˜K˜—Kšœ žœ˜!Kšœ žœ˜!Kšœžœ˜#Kšœ žœ˜!K˜Kšœ0˜0Kšœ0˜0Kšœ4˜4Kšœ0˜0K˜Kšœžœ ˜!Kšœ!žœ˜%Kšœ ˜ š žœžœžœ)žœžœž˜Mšžœžœžœ˜J™•Jšœ,žœ˜NJšœ(žœ˜Fšžœžœ˜Jšœ4˜4JšœJ˜JJšœ(žœ˜FJ˜—šžœ˜Jšœ4˜4JšœJ˜JJšœ(žœ˜FJ˜—J˜—šžœ˜J™|JšœP˜Pšžœžœ˜Jšœ(žœ˜FJšœJ˜JJšœT˜TJšœJ˜JJ˜—šžœ˜JšœJ˜JJšœ(žœ˜FJšœT˜TJšœJ˜JJ˜—J˜—J˜Jšžœ˜—Jšœ;˜;Jšœ8˜8Jšœ9˜9Jšœ5˜5J˜J˜—šŸœžœžœ+žœ žœžœ žœ˜™Kšœ žœ˜ K˜Kšœžœ˜ šžœ ž˜šœ ˜ Kšœ˜K˜—šœ˜Kšœ1˜1Kšœ+˜+Kšœ)˜)Kšœ,˜,Kšœ,˜,Kšžœ žœ˜%Kšžœ˜K˜—Kšžœžœ+˜;—Kšœ¢˜"K˜—šŸœžœžœ žœžœžœ žœ˜]Kšœ!žœ ˜1Kšœ žœ˜ Kšœ˜Kšœžœ˜ Kšœ˜šžœž˜"˜ Kšœ#˜#K˜—šœ˜Kšœ=˜=Kšœ7˜7Kšœ)˜)Kšœ<˜˜WKšœ@™@Kšœžœ˜(š žœžœžœ&žœžœž˜JJšžœžœžœ0˜HJšžœ˜—K˜K˜—š¡œžœ%žœ˜`Kšœ4™4Kšœžœ˜(š žœžœžœ&žœžœž˜JJšžœžœžœžœ&˜EJšžœ˜—Jšžœ˜K˜K˜—š¡œžœB˜]KšœB™BKšœžœ˜(š žœžœžœ&žœžœž˜JJšžœžœžœ9˜QJšžœ˜—K˜K˜—š¡œžœ%žœ˜fKšœ6™6Kšœžœ˜(š žœžœžœ&žœžœž˜JJšžœžœžœžœ-˜LJšžœ˜—Jšžœ˜K˜K˜—š¡œžœ6˜QKšœ>™>Kšœžœ˜(š žœžœžœ&žœžœž˜JJšžœžœžœ.˜FJšžœ˜—K˜K˜—š¡œžœ%žœ˜ZKšœ1™1Kšœžœ˜(š žœžœžœ&žœžœž˜JJšžœžœžœžœ(˜GJšžœ˜—Jšžœžœ˜ K˜K˜K˜—šŸ¡ œžœ#˜