DIRECTORY Atom, AtomButtonsTypes, Feedback, GGBasicTypes, GGBoundBox, GGInterfaceTypes, GGModelTypes, GGSegment, GGSegmentTypes, GGSequence, GGOutline, GGParseIn, GGParseOut, GGShapes, GGSlice, GGTransform, GGUtility, Imager, ImagerTransformation, IO, Lines2d, NodeStyle, Rope, Vectors2d, ViewerClasses; GGSliceImplA: CEDAR PROGRAM IMPORTS Atom, Feedback, GGBoundBox, GGOutline, GGParseIn, GGParseOut, GGSegment, GGSequence, GGShapes, GGSlice, GGTransform, Imager, ImagerTransformation, IO, Lines2d, Rope, Vectors2d EXPORTS GGSlice = BEGIN BoundBox: TYPE = GGModelTypes.BoundBox; CameraData: TYPE = GGInterfaceTypes.CameraData; Circle: TYPE = GGBasicTypes.Circle; Color: TYPE = Imager.Color; Corner: TYPE = GGSlice.Corner; DefaultData: TYPE = GGInterfaceTypes.DefaultData; Edge: TYPE = GGBasicTypes.Edge; EntityGenerator: TYPE = GGModelTypes.EntityGenerator; ExtendMode: TYPE = GGModelTypes.ExtendMode; FactoredTransformation: TYPE = ImagerTransformation.FactoredTransformation; FeedbackData: TYPE = AtomButtonsTypes.FeedbackData; Line: TYPE = GGBasicTypes.Line; Object: TYPE = Imager.Object; OutlineParts: TYPE = GGOutline.OutlineParts; Point: TYPE = GGBasicTypes.Point; PointGenerator: TYPE = GGModelTypes.PointGenerator; PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj; Scene: TYPE = GGModelTypes.Scene; Segment: TYPE = GGSegmentTypes.Segment; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; SegmentGeneratorObj: TYPE = GGModelTypes.SegmentGeneratorObj; SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData; SelectionClass: TYPE = GGInterfaceTypes.SelectionClass; SelectMode: TYPE = GGModelTypes.SelectMode; Sequence: TYPE = GGModelTypes.Sequence; SequenceOfReal: TYPE = GGBasicTypes.SequenceOfReal; Slice: TYPE = GGModelTypes.Slice; SliceClass: TYPE = GGModelTypes.SliceClass; SliceClassObj: TYPE = GGModelTypes.SliceClassObj; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj; SliceObj: TYPE = GGModelTypes.SliceObj; SliceParts: TYPE = GGModelTypes.SliceParts; StrokeEnd: TYPE = Imager.StrokeEnd; Transformation: TYPE = ImagerTransformation.Transformation; Vector: TYPE = GGBasicTypes.Vector; Viewer: TYPE = ViewerClasses.Viewer; WalkProc: TYPE = GGModelTypes.WalkProc; SliceClassDef: TYPE = REF SliceClassDefObj; SliceClassDefObj: TYPE = RECORD[type: ATOM, class: SliceClass]; BoxData: TYPE = REF BoxDataObj; BoxDataObj: TYPE = RECORD [ box: BoundBox, -- this is not used as a bounding box. It is a representation of the shape. tightBox: BoundBox, -- a Manhattan aligned box, that contains this box. tightBox does NOT allow for stroke width or control points. transform: ImagerTransformation.Transformation, inverse: ImagerTransformation.Transformation, -- inverse of transform inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s fillColor: Color, segments: ARRAY [0..4) OF Segment -- in order left, top, right, bottom ]; CornerArray: TYPE = ARRAY [0..4) OF BOOL; -- ll, ul, ur, lr. Clockwise order of corners. EdgeArray: TYPE = ARRAY [0..4) OF BOOL; -- left, top, right, bottom. Clockwise order of edges. BoxParts: TYPE = REF BoxPartsObj; BoxPartsObj: TYPE = RECORD [ corners: CornerArray, -- which corners of box are selected. edges: EdgeArray, -- which edges of box are selected. center: BOOL -- is the middle joint selected? ]; BoxHitData: TYPE = REF BoxHitDataObj; BoxHitDataObj: TYPE = RECORD [ corner: [-1..3], edge: [-1..3], center: [-1..0], hitPoint: Point ]; Problem: SIGNAL [msg: Rope.ROPE] = Feedback.Problem; BuildBoxSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = { OPEN GGSlice; class _ NEW[SliceClassObj _ [ type: $Box, getBoundBox: BoxGetBoundBox, getTightBox: BoxGetTightBox, copy: BoxCopy, drawParts: BoxDrawParts, drawTransform: BoxDrawTransform, drawSelectionFeedback: BoxDrawSelectionFeedback, drawAttractorFeedback: NoOpDrawAttractorFeedback, transform: BoxTransform, describe: BoxDescribe, describeHit: BoxDescribeHit, fileout: BoxFileout, filein: BoxFilein, isEmptyParts: BoxIsEmptyParts, isCompleteParts: BoxIsCompleteParts, newParts: BoxNewParts, unionParts: BoxUnionParts, differenceParts: BoxDiffParts, movingParts: BoxMovingParts, augmentParts: BoxAugmentParts, setSelectedFields: NoOpSetSelectedFields, pointsInDescriptor: BoxPointsInDescriptor, pointPairsInDescriptor: BoxPointPairsInDescriptor, segmentsInDescriptor: BoxSegmentsInDescriptor, walkSegments: BoxWalkSegments, nextPoint: BoxNextPoint, nextPointPair: BoxNextPointPair, nextSegment: BoxNextSegment, closestPoint: BoxClosestPoint, closestJointToHitData: NoOpClosestJointToHitData, closestPointAndTangent: NoOpClosestPointAndTangent, closestSegment: BoxClosestSegment, lineIntersection: BoxLineIntersection, circleIntersection: NoOpCircleIntersection, hitDataAsSimpleCurve: BoxHitDataAsSimpleCurve, setDefaults: BoxSetDefaults, setStrokeWidth: BoxSetStrokeWidth, getStrokeWidth: BoxGetStrokeWidth, setStrokeEnd: BoxSetStrokeEnd, getStrokeEnd: BoxGetStrokeEnd, setStrokeJoint: NoOpSetStrokeJoint, getStrokeJoint: NoOpGetStrokeJoint, setStrokeColor: BoxSetStrokeColor, getStrokeColor: BoxGetStrokeColor, setFillColor: BoxSetFillColor, getFillColor: BoxGetFillColor, setArrows: NoOpSetArrows, getArrows: NoOpGetArrows, setDashed: BoxSetDashed, getDashed: BoxGetDashed ]]; }; MakeBoxSlice: PUBLIC PROC [box: BoundBox, corner: Corner, transform: ImagerTransformation.Transformation _ GGTransform.Identity[]] RETURNS [sliceD: SliceDescriptor] = { boxSlice: Slice; inverse: ImagerTransformation.Transformation _ ImagerTransformation.Invert[transform]; boxData: BoxData _ NEW[BoxDataObj _ [box: box, tightBox: GGBoundBox.NullBoundBox[], transform: transform, inverse: inverse, inverseScale: ImagerTransformation.Factor[inverse].s, fillColor: NIL] ]; boxParts: BoxParts _ NEW[BoxPartsObj _ [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE] ]; cIndex: INTEGER _ SELECT corner FROM ll=>0, ul=>1, ur=>2, lr=>3,ENDCASE=>2; boxParts.corners[cIndex] _ TRUE; IF box.loX > box.hiX THEN {t: REAL _ box.loX; box.loX _ box.hiX; box.hiX _ t}; IF box.loY > box.hiY THEN {t: REAL _ box.loY; box.loY _ box.hiY; box.hiY _ t}; FOR i: NAT IN [0..4) DO newSeg: Segment _ GGSegment.MakeLine[p0: BoxPointFromIndex[boxData.box, i], p1: BoxPointFromIndex[boxData.box, (i+1) MOD 4], props: NIL ]; newSeg.strokeWidth _ 2.0; newSeg.strokeEnd _ round; newSeg.dashed _ FALSE; newSeg.color _ Imager.black; boxData.segments[i] _ newSeg; -- N.B. line ends in segment updated by BoxSetBoundBox ENDLOOP; boxSlice _ NEW[SliceObj _ [ class: FetchSliceClass[$Box], data: boxData, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE], boundBox: GGBoundBox.NullBoundBox[] ]]; boxSlice.nullDescriptor _ GGSlice.DescriptorFromParts[boxSlice, NIL]; BoxSetBoundBox[boxSlice]; sliceD _ NEW[SliceDescriptorObj _ [boxSlice, boxParts] ]; }; BoxMaxStrokeWidth: PROC [boxData: BoxData] RETURNS [maxWidth: REAL] = { maxWidth _ boxData.segments[0].strokeWidth; FOR i: NAT IN [1..3] DO maxWidth _ MAX[maxWidth, boxData.segments[i].strokeWidth]; ENDLOOP; }; BoxSetBoundBox: PROC [slice: Slice] = { cpHalf: REAL _ GGModelTypes.halfJointSize + 1; strokeWidth: REAL _ BoxMaxStrokeWidth[NARROW[slice.data]]; pad: REAL _ MAX[cpHalf, strokeWidth]; boxData: BoxData _ NARROW[slice.data]; GGBoundBox.UpdateBoundBoxOfBoundBox[boxData.tightBox, boxData.box, boxData.transform]; GGBoundBox.UpdateCopyBoundBox[bBox: slice.boundBox, from: boxData.tightBox]; GGBoundBox.EnlargeByOffset[slice.boundBox, pad]; FOR i: NAT IN [0..4) DO lo: Point _ BoxPointFromIndex[boxData.box, i]; hi: Point _ BoxPointFromIndex[boxData.box, (i+1) MOD 4]; IF lo#boxData.segments[i].lo THEN boxData.segments[i].class.endPointMoved[boxData.segments[i], TRUE, lo]; IF hi#boxData.segments[i].hi THEN boxData.segments[i].class.endPointMoved[boxData.segments[i], FALSE, hi]; ENDLOOP; }; BoxPointFromIndex: PROC [box: BoundBox, index: [0..4)] RETURNS [point: Point] = { RETURN[ SELECT index FROM 0 => [box.loX, box.loY], 1 => [box.loX, box.hiY], 2 => [box.hiX, box.hiY], 3 => [box.hiX, box.loY], ENDCASE => [-999.9, -999.9] ]; }; BoxGetBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { RETURN[slice.boundBox]; }; BoxGetTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { boxData: BoxData _ NARROW[slice.data]; RETURN[boxData.tightBox]; }; BoxCopy: PROC [slice: Slice] RETURNS [copy: Slice] = { copyData: BoxData; boxData: BoxData _ NARROW[slice.data]; box: BoundBox _ GGBoundBox.CopyBoundBox[boxData.box]; transform: Imager.Transformation _ ImagerTransformation.Copy[boxData.transform]; copy _ MakeBoxSlice[box, ur, transform].slice; -- does ur work here ?? copyData _ NARROW[copy.data]; FOR i: NAT IN [0..4) DO copyData.segments[i] _ GGSegment.CopySegment[seg: boxData.segments[i]]; ENDLOOP; copyData.fillColor _ boxData.fillColor; BoxSetBoundBox[copy]; }; BoxDrawParts: PROC [slice: Slice, parts: SliceParts _ NIL, dc: Imager.Context, camera: CameraData, quick: BOOL] = { DoBoxDrawParts: PROC = { boxParts: BoxParts _ NARROW[parts]; IF boxParts = NIL OR IsComplete[boxParts] THEN BoxDrawAll[dc, boxData, [boxData.box.loX, boxData.box.loY], [boxData.box.hiX, boxData.box.hiY], camera] ELSE { BoxPathProc: Imager.PathProc = { moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]]; }; BoxEdgeDraw: PROC [index: INTEGER, p1, p2: Point] = { seg: Segment _ boxData.segments[index]; IF seg.color=NIL OR seg.strokeWidth=0.0 THEN RETURN ELSE { Imager.SetStrokeWidth[dc, seg.strokeWidth ]; Imager.SetStrokeEnd[dc, seg.strokeEnd ]; Imager.SetColor[dc, seg.color ]; IF seg.dashed THEN { EdgePathProc: Imager.PathProc = {moveTo[p1]; lineTo[p2]; }; PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; pattern: SequenceOfReal _ seg.pattern; Imager.MaskDashedStroke[dc, EdgePathProc, pattern.len, PatternProc, seg.offset, seg.length]; } ELSE Imager.MaskVector[dc, p1, p2]; }; }; t: ImagerTransformation.Transformation _ boxData.transform; tLL: Point _ [MIN[boxData.box.loX, boxData.box.hiX], MIN[boxData.box.loY, boxData.box.hiY] ]; tUR: Point _ [MAX[boxData.box.loX, boxData.box.hiX], MAX[boxData.box.loY, boxData.box.hiY] ]; pts: ARRAY[0..3] OF Point; pts[0] _ ImagerTransformation.Transform[t, tLL]; pts[1] _ ImagerTransformation.Transform[t, [tLL.x, tUR.y] ]; pts[2] _ ImagerTransformation.Transform[t, tUR]; pts[3] _ ImagerTransformation.Transform[t, [tUR.x, tLL.y] ]; IF AllEdges[boxParts] AND boxData.fillColor#NIL THEN { Imager.SetColor[dc, boxData.fillColor]; Imager.MaskFill[dc, BoxPathProc ]; }; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN BoxEdgeDraw[edge, pts[edge], pts[(edge + 1) MOD 4]]; ENDLOOP; }; }; boxData: BoxData _ NARROW[slice.data]; Imager.DoSave[dc, DoBoxDrawParts]; }; BoxDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = { DoDrawFeedback: PROC = { BoxEdgeFeedback: PROC [index: INTEGER, p1, p2: Point] = { seg: Segment _ boxData.segments[index]; Imager.SetStrokeWidth[dc, 2.0*(IF seg.strokeWidth#0.0 THEN seg.strokeWidth ELSE 1.0)]; Imager.SetStrokeEnd[dc, seg.strokeEnd]; Imager.SetColor[dc, IF seg.color#NIL THEN seg.color ELSE Imager.black]; Imager.MaskVector[dc, p1, p2]; }; box: BoundBox _ boxData.box; t: ImagerTransformation.Transformation _ boxData.transform; cc: Point; thisCPisHot, thisCPisSelected: BOOL; pts: ARRAY[0..3] OF Point; pts[0] _ ImagerTransformation.Transform[t, [box.loX, box.loY] ]; pts[1] _ ImagerTransformation.Transform[t, [box.loX, box.hiY] ]; pts[2] _ ImagerTransformation.Transform[t, [box.hiX, box.hiY] ]; pts[3] _ ImagerTransformation.Transform[t, [box.hiX, box.loY] ]; FOR corner: INTEGER IN [0..4) DO thisCPisHot _ hotBoxParts#NIL AND hotBoxParts.corners[corner]; thisCPisSelected _ normalBoxParts#NIL AND normalBoxParts.corners[corner]; IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, pts[corner], hot]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[corner], normal]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[corner]]; ENDLOOP; thisCPisHot _ hotBoxParts#NIL AND hotBoxParts.center; thisCPisSelected _ normalBoxParts#NIL AND normalBoxParts.center; cc _ ImagerTransformation.Transform[t, [(box.loX+box.hiX)/2.0, (box.loY+box.hiY)/2.0]]; IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, cc, hot]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, cc, normal]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, cc]; IF NOT quick THEN { FOR edge: INTEGER IN [0..4) DO IF hotBoxParts#NIL AND hotBoxParts.edges[edge] THEN BoxEdgeFeedback[edge, pts[edge], pts[(edge+1) MOD 4]]; ENDLOOP; }; }; normalBoxParts, hotBoxParts: BoxParts; boxData: BoxData _ NARROW[slice.data]; IF caretIsMoving OR dragInProgress THEN RETURN; IF selectedParts = NIL AND hotParts = NIL THEN RETURN; normalBoxParts _ NARROW[selectedParts]; hotBoxParts _ NARROW[hotParts]; IF camera.quality#quality THEN Imager.DoSave[dc, DoDrawFeedback]; }; BoxDrawAll: PROC [dc: Imager.Context, boxData: BoxData, from: Point, to: Point, camera: CameraData --, drawCPs: BOOL _ TRUE--] = { BoxPathProc: Imager.PathProc={moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]];}; BoxEdgeDraw: PROC [index: INTEGER, p1, p2: Point] = { seg: Segment _ boxData.segments[index]; IF seg.color=NIL OR seg.strokeWidth=0.0 THEN RETURN ELSE { Imager.SetStrokeWidth[dc, seg.strokeWidth ]; Imager.SetStrokeEnd[dc, seg.strokeEnd ]; Imager.SetColor[dc, seg.color ]; IF seg.dashed THEN { EdgePathProc: Imager.PathProc = {moveTo[p1]; lineTo[p2]; }; PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; pattern: SequenceOfReal _ seg.pattern; Imager.MaskDashedStroke[dc, EdgePathProc, pattern.len, PatternProc, seg.offset, seg.length]; } ELSE Imager.MaskVector[dc, p1, p2]; }; }; t: ImagerTransformation.Transformation _ boxData.transform; tLL: Point _ [MIN[to.x, from.x], MIN[to.y, from.y] ]; tUR: Point _ [MAX[to.x, from.x], MAX[to.y, from.y] ]; pts: ARRAY[0..3] OF Point; pts[0] _ ImagerTransformation.Transform[t, tLL]; pts[1] _ ImagerTransformation.Transform[t, [tLL.x, tUR.y] ]; pts[2] _ ImagerTransformation.Transform[t, tUR]; pts[3] _ ImagerTransformation.Transform[t, [tUR.x, tLL.y] ]; IF boxData.fillColor#NIL THEN { Imager.SetColor[dc, boxData.fillColor]; Imager.MaskFill[dc, BoxPathProc ]; }; FOR edge: INTEGER IN [0..4) DO BoxEdgeDraw[edge, pts[edge], pts[(edge + 1) MOD 4]]; ENDLOOP; }; BoxDrawTransform: PROC [sliceD: SliceDescriptor, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = { HardDraw: PROC = { BoxDrawAll[dc, boxData, point, oppositePoint, camera--, FALSE--]; }; EasyDraw: PROC = { Imager.ConcatT[dc, transform]; BoxDrawAll[dc, boxData, [box.loX, box.loY], [box.hiX, box.hiY], camera--, FALSE--]; }; point, oppositePoint, tWorld, tBox: Point; boxData: BoxData _ NARROW[sliceD.slice.data]; boxParts: BoxParts _ NARROW[sliceD.parts]; box: BoundBox _ boxData.box; totalTransform, worldBox: Transformation; cornerCount, edgeCount, edgeNum, cornerNum: INTEGER _ 0; IF box.null OR box.infinite THEN ERROR; BEGIN IF IsComplete[boxParts] THEN GOTO FullTransform; IF IsEmpty[boxParts] THEN RETURN; [edgeCount, edgeNum] _ CountEdges[boxParts.edges]; [cornerCount, cornerNum] _ CountCorners[boxParts.corners]; IF edgeCount >= 2 OR cornerCount >= 3 OR boxParts.center THEN GOTO FullTransform; worldBox _ boxData.inverse; tWorld _ [transform.c, transform.f]; -- the translation components of transform tBox _ ImagerTransformation.TransformVec[worldBox, tWorld]; IF edgeCount=1 THEN { -- one edge. Transform it. lo, hi: NAT; [lo, hi] _ CornersOfEdge[edgeNum]; IF NOT (boxParts.corners[lo] AND boxParts.corners[hi]) THEN GOTO FullTransform; oppositePoint _ BoxPoint[box, OppositeCorner[lo]]; point _ BoxPoint[box, lo]; SELECT edgeNum FROM 0, 2 => -- left and right -- point _ [point.x + tBox.x, point.y]; 1, 3 => -- top and bottom -- point _ [point.x, point.y + tBox.y]; ENDCASE => ERROR; } ELSE IF cornerCount = 2 THEN GOTO FullTransform -- not quite right if corners adjacent ELSE { -- one corner. point _ BoxPoint[box, cornerNum]; oppositePoint _ BoxPoint[box, OppositeCorner[cornerNum]]; totalTransform _ ImagerTransformation.Cat[boxData.transform, transform, worldBox]; point _ ImagerTransformation.Transform[m: totalTransform, v: point]; }; Imager.DoSave[dc, HardDraw]; EXITS FullTransform => { Imager.DoSave[dc, EasyDraw]; }; END; }; BoxTransform: PROC [sliceD: SliceDescriptor, transform: ImagerTransformation.Transformation] = { point, oppositePoint: ImagerTransformation.VEC; -- really points, not vectors boxData: BoxData _ NARROW[sliceD.slice.data]; boxParts: BoxParts _ NARROW[sliceD.parts]; box: BoundBox _ boxData.box; inverse, totalTransform: ImagerTransformation.Transformation; cornerCount, edgeCount: INTEGER _ 0; IF box.null OR box.infinite THEN ERROR; IF IsComplete[boxParts] THEN { boxData.transform _ ImagerTransformation.Concat[boxData.transform, transform]; boxData.inverse _ ImagerTransformation.Invert[boxData.transform]; boxData.inverseScale _ ImagerTransformation.Factor[boxData.inverse].s; BoxSetBoundBox[sliceD.slice]; RETURN; }; IF IsEmpty[boxParts] THEN RETURN; -- no parts. Legal. IF (edgeCount _ CountEdges[boxParts.edges].count) >= 2 OR (cornerCount _ CountCorners[boxParts.corners].count) >= 3 OR boxParts.center THEN { -- more than one edge or more than two corners. Full transform. boxData.transform _ ImagerTransformation.Concat[boxData.transform, transform]; boxData.inverse _ ImagerTransformation.Invert[boxData.transform]; boxData.inverseScale _ ImagerTransformation.Factor[boxData.inverse].s; BoxSetBoundBox[sliceD.slice]; RETURN; }; IF edgeCount=1 THEN { -- transform an edge f: ImagerTransformation.FactoredTransformation _ ImagerTransformation.Factor[transform]; globalTranslate: ImagerTransformation.VEC _ f.t; mInverse: ImagerTransformation.Transformation _ ImagerTransformation.Invert[boxData.transform]; localTranslate: ImagerTransformation.VEC _ ImagerTransformation.TransformVec[mInverse, globalTranslate]; totalTransform: ImagerTransformation.Transformation _ ImagerTransformation.Translate[localTranslate]; SELECT TRUE FROM boxParts.edges[0] => { point _ [ box.loX, 0 ]; oppositePoint _ [ box.hiX, box.hiY ]; point _ ImagerTransformation.Transform[m: totalTransform, v: point]; point _ [ point.x, box.loY]; -- result point is transformed in x only }; boxParts.edges[1] => { point _ [ 0, box.hiY ]; oppositePoint _ [ box.loX, box.loY ]; point _ ImagerTransformation.Transform[m: totalTransform, v: point]; point _ [ box.hiX, point.y]; -- result point is transformed in y only }; boxParts.edges[2] => { point _ [ box.hiX, 0 ]; oppositePoint _ [ box.loX, box.loY ]; point _ ImagerTransformation.Transform[m: totalTransform, v: point]; point _ [ point.x, box.hiY]; -- result point is transformed in x only }; boxParts.edges[3] => { point _ [ 0, box.loY ]; oppositePoint _ [ box.hiX, box.hiY ]; point _ ImagerTransformation.Transform[m: totalTransform, v: point]; point _ [ box.loX, point.y]; -- result point is transformed in y only }; ENDCASE => ERROR; } ELSE IF cornerCount IN [1..2] THEN { -- one or two isolated corners. Pick one and transform it. SELECT TRUE FROM boxParts.corners[0] => { point _ [ box.loX, box.loY ]; oppositePoint _ [ box.hiX, box.hiY ]; }; boxParts.corners[1] => { point _ [ box.loX, box.hiY ]; oppositePoint _ [ box.hiX, box.loY ]; }; boxParts.corners[2] => { point _ [ box.hiX, box.hiY ]; oppositePoint _ [ box.loX, box.loY ]; }; boxParts.corners[3] => { point _ [ box.hiX, box.loY ]; oppositePoint _ [ box.loX, box.hiY ]; }; ENDCASE => ERROR; inverse _ boxData.inverse; totalTransform _ ImagerTransformation.Cat[boxData.transform, transform, inverse]; point _ ImagerTransformation.Transform[m: totalTransform, v: point]; -- transform the dragging point }; box^ _ [MIN[point.x, oppositePoint.x], MIN[point.y, oppositePoint.y], MAX[point.x, oppositePoint.x], MAX[point.y, oppositePoint.y], FALSE, FALSE]; BoxSetBoundBox[sliceD.slice]; }; boxCornerRopes: ARRAY [0..4) OF Rope.ROPE = [ "lower left corner", "upper left corner", "upper right corner", "lower right corner"]; boxEdgeRopes: ARRAY [0..4) OF Rope.ROPE = [ "left edge", "top edge", "right edge", "bottom edge"]; BoxDescribeHit: PROC [slice: Slice, hitData: REF ANY] RETURNS [rope: Rope.ROPE] = { boxHitData: BoxHitData _ NARROW[hitData]; prefix: Rope.ROPE; IF boxHitData.corner#-1 THEN prefix _ boxCornerRopes[boxHitData.corner] ELSE IF boxHitData.center#-1 THEN prefix _ "center" ELSE IF boxHitData.edge#-1 THEN prefix _ boxEdgeRopes[boxHitData.edge]; rope _ Rope.Concat[prefix, " of a Box slice"]; }; BoxDescribe: PROC [sliceD: SliceDescriptor] RETURNS [rope: Rope.ROPE] = { prefix: Rope.ROPE; boxParts: BoxParts; cornerCount: INTEGER; edgeCount: INTEGER; IF sliceD.parts = NIL THEN RETURN["multiple parts of a Box slice"]; boxParts _ NARROW[sliceD.parts]; cornerCount _ CountCorners[boxParts.corners].count; edgeCount _ CountEdges[boxParts.edges].count; IF cornerCount+edgeCount>1 THEN RETURN["multiple parts of a Box slice"] ELSE { centerOnly: BOOL _ boxParts.center AND edgeCount=0 AND cornerCount=0; oneEdge: BOOL _ NOT boxParts.center AND edgeCount=1 AND cornerCount=0; oneCorner: BOOL _ NOT boxParts.center AND cornerCount=1 AND edgeCount=0; noParts: BOOL _ NOT boxParts.center AND cornerCount=0 AND edgeCount=0; SELECT TRUE FROM oneCorner => { FOR i: INTEGER IN [0..4) DO IF boxParts.corners[i] THEN { prefix _ boxCornerRopes[i]; EXIT; }; ENDLOOP; }; oneEdge => { FOR i: INTEGER IN [0..4) DO IF boxParts.edges[i] THEN { prefix _ boxEdgeRopes[i]; EXIT; }; ENDLOOP; }; centerOnly => prefix _ "center"; noParts => prefix _ "NO parts"; ENDCASE => ERROR; rope _ Rope.Concat[prefix, " of a Box slice"]; }; }; BoxFileout: PROC [slice: Slice, f: IO.STREAM] = { boxData: BoxData _ NARROW[slice.data]; GGParseOut.WritePoint[f, [ boxData.box.loX, boxData.box.loY] ]; f.PutChar[IO.SP]; GGParseOut.WritePoint[f, [ boxData.box.hiX, boxData.box.hiY] ]; f.PutChar[IO.SP]; GGParseOut.WriteTransformation[f, boxData.transform]; f.PutRope[" strokeWidths: ( "]; FOR edge: INTEGER IN [0..4) DO f.PutF["%g ", [real[boxData.segments[edge].strokeWidth]] ]; ENDLOOP; f.PutRope[") strokeEnds: ( "]; FOR edge: INTEGER IN [0..4) DO GGParseOut.WriteStrokeEnd[f, boxData.segments[edge].strokeEnd]; f.PutChar[IO.SP]; ENDLOOP; f.PutRope[") strokeColors: ( "]; FOR edge: INTEGER IN [0..4) DO GGParseOut.WriteColor[f, boxData.segments[edge].color]; f.PutChar[IO.SP]; ENDLOOP; f.PutRope[") fillColor: "]; GGParseOut.WriteColor[f, boxData.fillColor]; f.PutRope[" dashes: ( "]; FOR edge: INTEGER IN [0..4) DO seg: Segment _ boxData.segments[edge]; GGParseOut.WriteBOOL[f, seg.dashed]; f.PutChar[IO.SP]; IF seg.dashed THEN { GGParseOut.WriteArrayOfReal[f, seg.pattern]; f.PutF[" %g %g ", [real[seg.offset]], [real[seg.length]]]; }; ENDLOOP; f.PutRope[") "]; }; BoxFilein: PROC [f: IO.STREAM, version: REAL, feedback: FeedbackData] RETURNS [slice: Slice] = { boxData: BoxData; box: BoundBox; p1, p2: Point; transform: ImagerTransformation.Transformation; strokeWidths: ARRAY [0..4) OF REAL; ends: ARRAY [0..4) OF StrokeEnd; colors: ARRAY [0..4) OF Color; dashes: ARRAY [0..4) OF BOOL _ ALL[FALSE]; patterns: ARRAY [0..4) OF SequenceOfReal; offsets: ARRAY [0..4) OF REAL; lengths: ARRAY [0..4) OF REAL; fill: Color; p1 _ GGParseIn.ReadPoint[f]; p2 _ GGParseIn.ReadPoint[f]; transform _ GGParseIn.ReadTransformation[f]; GGParseIn.ReadBlankAndRope[f, "strokeWidths: ("]; FOR edge: INTEGER IN [0..4) DO strokeWidths[edge] _ GGParseIn.ReadBlankAndReal[f]; ENDLOOP; IF version>=8702.26 THEN { -- read in strokeEnds GGParseIn.ReadBlankAndRope[f, ") strokeEnds: ("]; FOR edge: INTEGER IN [0..4) DO ends[edge] _ GGParseIn.ReadStrokeEnd[f]; ENDLOOP; } ELSE ends _ [round, round, round, round]; GGParseIn.ReadBlankAndRope[f, ") strokeColors: ("]; FOR edge: INTEGER IN [0..4) DO colors[edge] _ GGParseIn.ReadColor[f, version]; ENDLOOP; GGParseIn.ReadBlankAndRope[f, ") fillColor: "]; fill _ GGParseIn.ReadColor[f, version]; IF version>=8704.03 THEN { -- read in dash patterns GGParseIn.ReadBlankAndRope[f, "dashes: ("]; FOR edge: INTEGER IN [0..4) DO dashes[edge] _ GGParseIn.ReadBOOL[f, version].truth; IF dashes[edge] THEN { patterns[edge] _ GGParseIn.ReadArrayOfReal[f]; offsets[edge] _ GGParseIn.ReadBlankAndReal[f]; lengths[edge] _ GGParseIn.ReadBlankAndReal[f]; }; ENDLOOP; GGParseIn.ReadBlankAndRope[f, ")"]; }; box _ GGBoundBox.CreateBoundBox[ p1.x, p1.y, p2.x, p2.y ]; slice _ MakeBoxSlice[box, ur, transform].slice; boxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO boxData.segments[edge].strokeWidth _ strokeWidths[edge]; boxData.segments[edge].strokeEnd _ ends[edge]; boxData.segments[edge].color _ colors[edge]; boxData.segments[edge].dashed _ dashes[edge]; IF dashes[edge] THEN { boxData.segments[edge].pattern _ patterns[edge]; boxData.segments[edge].offset _ offsets[edge]; boxData.segments[edge].length _ lengths[edge]; }; ENDLOOP; boxData.fillColor _ fill; BoxSetBoundBox[slice]; }; MakeComplete: PROC [boxParts: BoxParts] = { boxParts.corners _ ALL[TRUE]; boxParts.edges _ ALL[TRUE]; boxParts.center _ TRUE; }; IsComplete: PROC [boxParts: BoxParts] RETURNS [BOOL] = { IF boxParts = NIL THEN RETURN[FALSE]; RETURN[ boxParts.corners=ALL[TRUE] AND boxParts.edges=ALL[TRUE] AND boxParts.center ]; }; AllEdges: PROC [boxParts: BoxParts] RETURNS [BOOL] = { RETURN[boxParts.edges=ALL[TRUE] ]; }; IsEmpty: PROC [boxParts: BoxParts] RETURNS [BOOL] = { RETURN[boxParts.corners=ALL[FALSE] AND boxParts.edges=ALL[FALSE] AND boxParts.center = FALSE]; }; CountCorners: PROC [a: CornerArray] RETURNS [count: INTEGER, cornerNum: INTEGER] = { count _ 0; cornerNum _ -1; FOR corner: INTEGER IN [0..4) DO IF a[corner] THEN {count _ count+1; cornerNum _ corner}; ENDLOOP; }; CountEdges: PROC [a: EdgeArray] RETURNS [count: INTEGER, edgeNum: INTEGER] = { count _ 0; edgeNum _ -1; FOR edge: INTEGER IN [0..4) DO IF a[edge] THEN {count _ count+1; edgeNum _ edge}; ENDLOOP; }; BoxPointsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = { parts: BoxParts _ NARROW[sliceD.parts]; pointGen _ NEW[PointGeneratorObj _ [sliceD, 0, 0, NIL] ]; -- toGo and index now used FOR corner: INTEGER IN [0..4) DO IF parts.corners[corner] THEN pointGen.toGo _ pointGen.toGo + 1; ENDLOOP; IF parts.center THEN pointGen.toGo _ pointGen.toGo + 1; }; BoxPointPairsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointPairGen: PointPairGenerator] = { parts: BoxParts _ NARROW[sliceD.parts]; pointPairGen _ NEW[PointPairGeneratorObj _ [sliceD, 0, 0, NIL] ]; -- toGo and index now used FOR edge: INTEGER IN [0..4) DO IF parts.edges[edge] THEN pointPairGen.toGo _ pointPairGen.toGo + 1; ENDLOOP; }; BoxSegmentsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [segGen: SegmentGenerator] = { parts: BoxParts _ NARROW[sliceD.parts]; segGen _ NEW[SegmentGeneratorObj _ [traj: NIL, toGo: 0, index: 0, touched: 0, seq: NIL, completeSeq: FALSE, sliceD: sliceD] ]; FOR edge: INTEGER IN [0..4) DO IF parts.edges[edge] THEN segGen.toGo _ segGen.toGo + 1; ENDLOOP; }; BoxWalkSegments: PROC [slice: Slice, walkProc: WalkProc] RETURNS [sliceD: SliceDescriptor] = { boxData: BoxData _ NARROW[slice.data]; boxParts: BoxParts _ NEW[BoxPartsObj _ [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE]]; FOR i: NAT IN [0..4) DO boxParts.edges[i] _ walkProc[boxData.segments[i], NIL]; ENDLOOP; sliceD _ NEW[SliceDescriptorObj _ [slice, boxParts] ]; }; BoxNextPoint: PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = { IF pointGen=NIL OR pointGen.toGo = 0 THEN { pointAndDone.done _ TRUE; RETURN; } ELSE { sliceD: SliceDescriptor _ pointGen.sliceD; slice: Slice _ sliceD.slice; boxData: BoxData _ NARROW[slice.data]; boxParts: BoxParts _ NARROW[sliceD.parts]; t: ImagerTransformation.Transformation _ boxData.transform; index: INTEGER; pointAndDone.done _ FALSE; FOR index _ pointGen.index, index+1 UNTIL index >=4 DO IF boxParts.corners[index] THEN EXIT; -- index will point to next available point REPEAT FINISHED => { IF NOT boxParts.center THEN index _ index + 1; }; ENDLOOP; SELECT index FROM -- index in [0..4) of next available point 0 => pointAndDone.point _ ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.loY]]; 1 => pointAndDone.point _ ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.hiY]]; 2 => pointAndDone.point _ ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.hiY]]; 3 => pointAndDone.point _ ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.loY]]; 4 => pointAndDone.point _ ImagerTransformation.Transform[t, [(boxData.box.loX + boxData.box.hiX)/2.0, (boxData.box.loY + boxData.box.hiY)/2.0]]; ENDCASE => SIGNAL Feedback.Problem[msg: "Broken Invariant"]; pointGen.toGo _ pointGen.toGo-1; pointGen.index _ IF pointGen.toGo=0 THEN 99 ELSE index+1; -- bump to the next available point in the generator }; }; BoxNextPointPair: PROC [pointPairGen: PointPairGenerator] RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = { IF pointPairGen=NIL OR pointPairGen.toGo = 0 THEN { pointPairAndDone.done _ TRUE; RETURN; } ELSE { sliceD: SliceDescriptor _ pointPairGen.sliceD; slice: Slice _ sliceD.slice; boxData: BoxData _ NARROW[slice.data]; boxParts: BoxParts _ NARROW[sliceD.parts]; t: ImagerTransformation.Transformation _ boxData.transform; index: INTEGER _ -1; pointPairAndDone.done _ FALSE; FOR index _ pointPairGen.index, index+1 UNTIL index >=4 DO IF boxParts.edges[index] THEN EXIT; -- index will point to next available edge ENDLOOP; SELECT index FROM -- index in [0..4) of next available edge 0 => { pointPairAndDone.lo _ ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.loY]]; pointPairAndDone.hi _ ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.hiY]]; }; 1 => { pointPairAndDone.lo _ ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.hiY]]; pointPairAndDone.hi _ ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.hiY]]; }; 2 => { pointPairAndDone.lo _ ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.hiY]]; pointPairAndDone.hi _ ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.loY]]; }; 3 => { pointPairAndDone.lo _ ImagerTransformation.Transform[t, [boxData.box.hiX, boxData.box.loY]]; pointPairAndDone.hi _ ImagerTransformation.Transform[t, [boxData.box.loX, boxData.box.loY]]; }; ENDCASE => SIGNAL Feedback.Problem[msg: "Broken Invariant"]; pointPairGen.toGo _ pointPairGen.toGo - 1; pointPairGen.index _ IF pointPairGen.toGo=0 THEN 99 ELSE index+1; -- bump to the next available pair in the generator }; }; BoxNextSegment: PUBLIC PROC [segGen: SegmentGenerator] RETURNS [seg: Segment, transform: Transformation _ NIL] = { IF segGen=NIL OR segGen.toGo = 0 THEN RETURN[NIL, NIL] ELSE { boxData: BoxData _ NARROW[segGen.sliceD.slice.data]; boxParts: BoxParts _ NARROW[segGen.sliceD.parts]; index: INTEGER _ -1; FOR index _ segGen.index, index+1 UNTIL index >=4 DO IF boxParts.edges[index] THEN EXIT; -- index will point to next available edge ENDLOOP; IF index>=4 THEN SIGNAL Feedback.Problem[msg: "Broken Invariant"]; seg _ boxData.segments[index]; segGen.toGo _ segGen.toGo-1; segGen.index _ IF segGen.toGo=0 THEN 99 ELSE index+1; -- bump to the next available segment in the generator }; }; BoxIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = { boxParts: BoxParts _ NARROW[sliceD.parts]; RETURN[IsEmpty[boxParts]]; }; BoxIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = { boxParts: BoxParts _ NARROW[sliceD.parts]; RETURN[IsComplete[boxParts]]; }; NearestBoxPoint: PROC [slice: Slice, point: Point] RETURNS [index: [-1..3], center: BOOL] = { boxHitData: BoxHitData; success: BOOL; hitData: REF ANY; sliceD: SliceDescriptor _ BoxNewParts[slice, NIL, slice]; [----, ----, hitData, success] _ BoxClosestPoint[sliceD: sliceD, testPoint: point, tolerance: GGUtility.plusInfinity]; IF NOT success THEN ERROR; boxHitData _ NARROW[hitData]; center _ boxHitData.center > -1; index _ boxHitData.corner; IF index = -1 AND NOT center THEN ERROR; }; BoxNewParts: PROC [slice: Slice, hitData: REF ANY, mode: SelectMode] RETURNS [sliceD: SliceDescriptor] = { boxHitData: BoxHitData _ NARROW[hitData]; boxParts: BoxParts; boxParts_ NEW[BoxPartsObj _ [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE]]; SELECT mode FROM literal => { IF boxHitData.corner#-1 THEN boxParts.corners[boxHitData.corner] _ TRUE ELSE IF boxHitData.center#-1 THEN boxParts.center _ TRUE ELSE IF boxHitData.edge#-1 THEN boxParts.edges[boxHitData.edge] _ TRUE ELSE ERROR; }; joint => { IF boxHitData.corner#-1 THEN boxParts.corners[boxHitData.corner] _ TRUE ELSE IF boxHitData.center#-1 THEN boxParts.center _ TRUE ELSE IF boxHitData.edge#-1 THEN { index: [-1..3]; center: BOOL; [index, center] _ NearestBoxPoint[slice, boxHitData.hitPoint]; IF center THEN boxParts.center _ TRUE ELSE boxParts.corners[index] _ TRUE; }; }; segment => { IF boxHitData.center#-1 THEN MakeComplete[boxParts] ELSE IF boxHitData.edge#-1 THEN boxParts.edges[boxHitData.edge] _ TRUE; }; controlPoint => { boxParts.corners _ ALL[TRUE]; boxParts.center _ TRUE; }; traj, topLevel, slice => MakeComplete[boxParts]; none => { -- leave boxParts empty }; ENDCASE => ERROR; sliceD _ GGSlice.DescriptorFromParts[slice, boxParts]; }; BoxUnionParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aPlusB: SliceDescriptor] = { boxPartsA: BoxParts _ NARROW[partsA.parts]; boxPartsB: BoxParts _ NARROW[partsB.parts]; newParts: BoxParts; IF partsA.parts = NIL THEN RETURN[partsB]; IF partsB.parts = NIL THEN RETURN[partsA]; IF IsEmpty[boxPartsA] THEN RETURN[partsB]; IF IsEmpty[boxPartsB] THEN RETURN[partsA]; IF IsComplete[boxPartsA] THEN RETURN[partsA]; IF IsComplete[boxPartsB] THEN RETURN[partsB]; newParts _ NEW[BoxPartsObj _ [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE ] ]; FOR i: INTEGER IN [0..4) DO newParts.corners[i] _ boxPartsA.corners[i] OR boxPartsB.corners[i]; newParts.edges[i] _ boxPartsA.edges[i] OR boxPartsB.edges[i]; ENDLOOP; newParts.center _ boxPartsA.center OR boxPartsB.center; aPlusB _ GGSlice.DescriptorFromParts[partsA.slice, newParts]; }; BoxDiffParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aMinusB: SliceDescriptor] = { boxPartsA: BoxParts _ NARROW[partsA.parts]; boxPartsB: BoxParts _ NARROW[partsB.parts]; newParts: BoxParts; IF partsA = NIL OR partsB = NIL THEN ERROR; IF partsA.parts = NIL OR partsB.parts = NIL THEN RETURN[partsA]; newParts _ NEW[BoxPartsObj _ [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE ] ]; FOR i: INTEGER IN [0..4) DO newParts.corners[i] _ IF boxPartsB.corners[i] THEN FALSE ELSE boxPartsA.corners[i]; newParts.edges[i] _ IF boxPartsB.edges[i] THEN FALSE ELSE boxPartsA.edges[i]; ENDLOOP; newParts.center _ IF boxPartsB.center THEN FALSE ELSE boxPartsA.center; aMinusB _ GGSlice.DescriptorFromParts[partsA.slice, newParts]; }; CornersOfEdge: PROC [edge: NAT] RETURNS [lo, hi: NAT] = { SELECT edge FROM IN [0..3] => {lo _ edge; hi _ (edge + 1) MOD 4}; ENDCASE => ERROR; }; OppositeCorner: PROC [corner: NAT] RETURNS [opCorner: NAT] = { SELECT corner FROM IN [0..3] => opCorner _ (corner + 2) MOD 4; ENDCASE => ERROR; }; BoxPoint: PROC [box: BoundBox, corner: NAT] RETURNS [point: Point] = { point _ SELECT corner FROM 0 => [box.loX, box.loY], 1 => [box.loX, box.hiY], 2 => [box.hiX, box.hiY], 3 => [box.hiX, box.loY], ENDCASE => ERROR; }; BoxMovingParts: PROC [slice: Slice, selectedParts: SliceParts] RETURNS [background, overlay, rubber, drag: SliceDescriptor] = { boxParts: BoxParts _ NARROW[selectedParts]; boxData: BoxData _ NARROW[slice.data]; overlayParts: BoxParts; filled: BOOL _ boxData.fillColor # NIL; nullD: SliceDescriptor _ slice.nullDescriptor; newParts: BoxParts; cornerCount, cornerNum, edgeCount, edgeNum: INTEGER; centerCount: INTEGER _ IF boxParts.center THEN 1 ELSE 0; IF IsEmpty[boxParts] THEN { background _ overlay _ rubber _ drag _ nullD; RETURN; }; IF IsComplete[boxParts] THEN { background _ overlay _ rubber _ nullD; drag _ GGSlice.DescriptorFromParts[slice, boxParts]; RETURN; }; [cornerCount, cornerNum] _ CountCorners[boxParts.corners]; [edgeCount, edgeNum] _ CountEdges[boxParts.edges]; newParts _ NEW[BoxPartsObj _ [corners:ALL[TRUE], edges:ALL[TRUE], center:TRUE]]; BEGIN IF edgeCount >= 2 OR cornerCount >= 3 OR centerCount > 0 THEN GOTO EverythingMoves; IF edgeCount = 1 THEN { lo, hi: NAT; IF cornerCount # 2 THEN GOTO EverythingMoves; [lo, hi] _ CornersOfEdge[edgeNum]; IF NOT boxParts.corners[lo] OR NOT boxParts.corners[hi] THEN GOTO EverythingMoves; BEGIN wholeD: SliceDescriptor _ slice.class.newParts[slice, NIL, slice]; newParts.edges[(edgeNum+2) MOD 4] _ FALSE; newParts.corners[(edgeNum+2) MOD 4] _ FALSE; newParts.corners[(edgeNum+3) MOD 4] _ FALSE; background _ drag _ nullD; rubber _ GGSlice.DescriptorFromParts[slice, newParts]; overlay _ BoxDiffParts[wholeD, rubber]; IF NOT filled THEN {background _ overlay; overlay _ nullD}; END; } ELSE IF cornerCount = 1 THEN { -- all but the opposite corner newParts.corners[(cornerNum+2) MOD 4] _ FALSE; background _ drag _ nullD; rubber _ GGSlice.DescriptorFromParts[slice, newParts]; overlayParts _ NEW[BoxPartsObj _ [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE]]; overlayParts.corners[cornerNum] _ TRUE; overlay _ GGSlice.DescriptorFromParts[slice, overlayParts]; IF NOT filled THEN {background _ overlay; overlay _ nullD}; } ELSE { -- nothing is moving background _ GGSlice.DescriptorFromParts[slice, newParts]; rubber _ overlay _ drag _ nullD; }; EXITS EverythingMoves => { background _ overlay _ rubber _ nullD; drag _ GGSlice.DescriptorFromParts[slice, newParts]; }; END; }; BoxAugmentParts: PROC [sliceD: SliceDescriptor, selectClass: SelectionClass] RETURNS [more: SliceDescriptor] = { boxParts: BoxParts _ NARROW[sliceD.parts]; newParts: BoxParts; newParts _ NEW[BoxPartsObj _ [corners: boxParts.corners, edges: boxParts.edges, center: boxParts.center] ]; FOR i: INTEGER IN [0..4) DO IF boxParts.edges[i] THEN { newParts.corners[i] _ TRUE; newParts.corners[(i+1) MOD 4] _ TRUE; }; ENDLOOP; more _ GGSlice.DescriptorFromParts[sliceD.slice, newParts]; }; BoxClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL _ FALSE] = { sliceBBox: BoundBox _ sliceD.slice.boundBox; toleranceBox: GGBasicTypes.BoundBoxObj _ [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; IF GGBoundBox.PointIsInBox[testPoint, toleranceBox] THEN { index: NAT _ 9999; boxHitData: BoxHitData; boxData: BoxData _ NARROW[sliceD.slice.data]; box: BoundBox _ boxData.box; boxParts: BoxParts _ NARROW[sliceD.parts]; thisPoint: Point; thisDist: REAL; localTestPoint: Point _ GGTransform.Transform[boxData.inverse, testPoint]; localTolerance: REAL _ ABS[tolerance*MAX[boxData.inverseScale.x, boxData.inverseScale.y]]; -- MAX function is not correct when text is skewed. [bestDist, index, bestPoint, success] _ GGBoundBox.NearestPoint[box, localTestPoint, localTolerance, boxParts.corners]; IF success THEN { hitData _ boxHitData _ NEW[BoxHitDataObj _ [corner: index, edge: -1, center: -1, hitPoint: [0,0]]]; }; IF boxParts.center THEN { -- center is a candidate for closest point, too thisPoint _ [(box.loX+box.hiX)/2.0, (box.loY+box.hiY)/2.0]; -- center point thisDist _ Vectors2d.Distance[localTestPoint, thisPoint]; IF thisDist < bestDist AND thisDist < localTolerance THEN { bestDist _ thisDist; bestPoint _ thisPoint; IF success THEN boxHitData.corner _ -1 ELSE hitData _ boxHitData _ NEW[BoxHitDataObj _ [corner: -1, edge: -1, center: -1, hitPoint: [0,0]]]; boxHitData.center _ 0; success _ TRUE; }; }; IF success THEN { bestPoint _ GGTransform.Transform[boxData.transform, bestPoint]; boxHitData.hitPoint _ bestPoint; bestDist _ Vectors2d.Distance[testPoint, bestPoint]; }; }; }; BoxClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL _ FALSE] = { sliceBBox: BoundBox _ sliceD.slice.boundBox; toleranceBox: GGBasicTypes.BoundBoxObj _ [sliceBBox.loX-tolerance, sliceBBox.loY-tolerance, sliceBBox.hiX+tolerance, sliceBBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates IF GGBoundBox.PointIsInBox[testPoint, toleranceBox] THEN { seg: NAT _ 9999; boxHitData: BoxHitData; boxData: BoxData _ NARROW[sliceD.slice.data]; boxParts: BoxParts _ NARROW[sliceD.parts]; localTestpoint: Point _ GGTransform.Transform[boxData.inverse, testPoint]; localTolerance: REAL _ABS [tolerance*MAX[boxData.inverseScale.x, boxData.inverseScale.y]]; -- MAX function is not correct when box is skewed. [bestDist, seg, bestPoint, success] _ GGBoundBox.NearestSegment[boxData.box, localTestpoint, localTolerance, boxParts.edges]; IF success THEN { bestPoint _ GGTransform.Transform[boxData.transform, bestPoint]; bestDist _ Vectors2d.Distance[testPoint, bestPoint]; hitData _ boxHitData _ NEW[BoxHitDataObj _ [corner: -1, edge: seg, center: -1, hitPoint: bestPoint] ]; }; }; }; BoxLineIntersection: PUBLIC PROC [sliceD: SliceDescriptor, line: Line] RETURNS [points: LIST OF Point, pointCount: NAT] = { boxData: BoxData _ NARROW[sliceD.slice.data]; boxParts: BoxParts _ NARROW[sliceD.parts]; bBox: BoundBox _ boxData.box; noHit: BOOL; hits: ARRAY[0..3] OF BOOL; hitPoints: ARRAY[0..3] OF Point; epsilon: REAL = 0.072; localLine: Line _ Lines2d.LineTransform[line, boxData.inverse]; localPoints: LIST OF Point; localPoints _ NIL; pointCount _ 0; FOR i: NAT IN [0..3] DO hits[i] _ FALSE; ENDLOOP; FOR i: NAT IN [0..2] DO IF NOT boxParts.edges[i] THEN LOOP; -- only intersect edges mentioned in sliceD. FillBoxEdge[globalEdge, bBox, i]; [hitPoints[i], noHit] _ Lines2d.LineMeetsEdge[localLine, globalEdge]; IF noHit THEN LOOP; IF i>0 AND hits[i-1] THEN { IF ABS[hitPoints[i].x - hitPoints[i-1].x] < epsilon THEN LOOP; IF ABS[hitPoints[i].y - hitPoints[i-1].y] < epsilon THEN LOOP; }; localPoints _ CONS[hitPoints[i], localPoints]; pointCount _ pointCount + 1; hits[i] _ TRUE; REPEAT FINISHED => { IF NOT boxParts.edges[3] THEN GOTO End; -- only intersect edges mentioned in sliceD. FillBoxEdge[globalEdge, bBox, 3]; [hitPoints[3], noHit] _ Lines2d.LineMeetsEdge[localLine, globalEdge]; IF noHit THEN GOTO End; IF hits[2] THEN { IF ABS[hitPoints[3].x - hitPoints[2].x] < epsilon THEN GOTO End; IF ABS[hitPoints[3].y - hitPoints[2].y] < epsilon THEN GOTO End; }; IF hits[0] THEN { IF ABS[hitPoints[3].x - hitPoints[0].x] < epsilon THEN GOTO End; IF ABS[hitPoints[3].y - hitPoints[0].y] < epsilon THEN GOTO End; }; localPoints _ CONS[hitPoints[3], localPoints]; pointCount _ pointCount + 1; hits[3] _ TRUE; EXITS End => NULL; }; ENDLOOP; IF pointCount > 2 THEN ERROR; points _ NIL; FOR list: LIST OF Point _ localPoints, list.rest UNTIL list = NIL DO points _ CONS[ImagerTransformation.Transform[m: boxData.transform, v: list.first], points]; ENDLOOP; }; BoxHitDataAsSimpleCurve: PROC [slice: Slice, hitData: REF ANY] RETURNS [simpleCurve: REF ANY] = { boxHitData: BoxHitData _ NARROW[hitData]; boxData: BoxData _ NARROW[slice.data]; bBox: BoundBox _ boxData.box; edge: Edge; IF boxHitData.edge = -1 THEN RETURN[NIL]; edge _ Lines2d.CreateEmptyEdge[]; FillBoxEdge[edge, bBox, boxHitData.edge]; Lines2d.FillEdgeTransform[edge, boxData.transform, edge]; simpleCurve _ edge; }; FillBoxEdge: PRIVATE PROC [edge: Edge, bBox: BoundBox, index: NAT] = { IF bBox.null OR bBox.infinite THEN ERROR; SELECT index FROM 0 => Lines2d.FillEdge[[bBox.loX, bBox.loY], [bBox.loX, bBox.hiY], edge]; 1 => Lines2d.FillEdge[[bBox.loX, bBox.hiY], [bBox.hiX, bBox.hiY], edge]; 2 => Lines2d.FillEdge[[bBox.hiX, bBox.hiY], [bBox.hiX, bBox.loY], edge]; 3 => Lines2d.FillEdge[[bBox.hiX, bBox.loY], [bBox.loX, bBox.loY], edge]; ENDCASE => ERROR; }; BoxSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Color] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN boxData.segments[edge].color _ color; ENDLOOP; }; BoxGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Color] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN color _ boxData.segments[edge].color; ENDLOOP; }; BoxSetFillColor: PROC [slice: Slice, color: Color] = { boxData: BoxData _ NARROW[slice.data]; boxData.fillColor _ color; }; BoxGetFillColor: PROC [slice: Slice] RETURNS [color: Color] = { boxData: BoxData _ NARROW[slice.data]; color _ boxData.fillColor; }; BoxSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] RETURNS [box: BoundBox] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN boxData.segments[edge].strokeWidth _ strokeWidth; ENDLOOP; BoxSetBoundBox[slice]; box _ slice.boundBox; }; BoxSetDefaults: PROC [slice: Slice, parts: SliceParts, defaults: DefaultData] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN GGSegment.SetDefaults[boxData.segments[edge], defaults]; ENDLOOP; boxData.fillColor _ defaults.fillColor; BoxSetBoundBox[slice]; }; BoxGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN strokeWidth _ boxData.segments[edge].strokeWidth; ENDLOOP; }; BoxSetStrokeEnd: PROC [slice: Slice, parts: SliceParts, strokeEnd: StrokeEnd] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN boxData.segments[edge].strokeEnd _ strokeEnd; ENDLOOP; }; BoxGetStrokeEnd: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeEnd: StrokeEnd] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN strokeEnd _ boxData.segments[edge].strokeEnd; ENDLOOP; }; BoxSetDashed: PROC [slice: Slice, parts: SliceParts, dashed: BOOL _ FALSE, pattern: SequenceOfReal _ NIL, offset: REAL _ 0.0, length: REAL _ -1.0] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN { seg: Segment _ boxData.segments[edge]; seg.dashed _ dashed; seg.pattern _ pattern; seg.offset _ offset; seg.length _ length; }; ENDLOOP; }; BoxGetDashed: PROC [slice: Slice, parts: SliceParts] RETURNS [dashed: BOOL _ FALSE, pattern: SequenceOfReal, offset, length: REAL] = { boxParts: BoxParts _ NARROW[parts]; boxData: BoxData _ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN { seg: Segment _ boxData.segments[edge]; dashed _ seg.dashed; pattern _ seg.pattern; offset _ seg.offset; length _ seg.length; }; ENDLOOP; }; FetchSliceClass: PUBLIC PROC [type: ATOM] RETURNS [class: SliceClass] = { FOR l: LIST OF SliceClassDef _ sliceClasses, l.rest UNTIL l=NIL DO IF l.first.type=type THEN RETURN[l.first.class]; ENDLOOP; SIGNAL Feedback.Problem[msg: "Slice class not found."]; RETURN[NIL]; }; UpdateDescriptorBoundBoxes: PUBLIC PROC [sliceD: SliceDescriptor] = { IF sliceD.slice.class.type = $Outline THEN { parts: OutlineParts _ NARROW[sliceD.parts]; FOR list: LIST OF Sequence _ parts.seqs, list.rest UNTIL list = NIL DO IF list.first # NIL THEN GGSequence.UpdateBoundBox[list.first]; ENDLOOP; }; }; RegisterSliceClass: PUBLIC PROC [class: SliceClass] = { classDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: class.type, class: class]]; sliceClasses _ CONS[classDef, sliceClasses]; }; DescriptorFromParts: PUBLIC PROC [slice: Slice, parts: SliceParts] RETURNS [sliceD: SliceDescriptor] = { sliceD _ NEW[SliceDescriptorObj _ [slice, parts]]; }; SetDefaults: PUBLIC PROC [slice: Slice, parts: SliceParts, defaults: DefaultData] = { slice.class.setDefaults[slice, parts, defaults]; }; CopySlice: PUBLIC PROC [slice: Slice] RETURNS [copy: Slice] = { RETURN[slice.class.copy[slice]]; }; SegmentsInDescriptor: PUBLIC PROC [sliceD: SliceDescriptor] RETURNS [segGen: SegmentGenerator] = { segGen _ sliceD.slice.class.segmentsInDescriptor[sliceD]; }; NextSegment: PUBLIC PROC [segGen: SegmentGenerator] RETURNS [next: Segment] = { next _ segGen.sliceD.slice.class.nextSegment[segGen].seg; }; WalkSegments: PUBLIC PROC [slice: Slice, walkProc: WalkProc] RETURNS [sliceD: SliceDescriptor] = { sliceD _ slice.class.walkSegments[slice, walkProc]; }; EntitiesInSlice: PUBLIC PROC [slice: Slice] RETURNS [entityGenerator: EntityGenerator] = { SIGNAL Problem[msg: "GGSlice.EntitiesInSlice NYI"]; }; Init: PROC [] = { textDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $Text, class: GGSlice.BuildTextSliceClass[]]]; ipDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $IP, class: GGSlice.BuildIPSliceClass[]]]; boxDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $Box, class: GGSlice.BuildBoxSliceClass[]]]; circleDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $Circle, class: GGSlice.BuildCircleSliceClass[]]]; outlineDef: SliceClassDef _ NEW[SliceClassDefObj _ [type: $Outline, class: GGOutline.BuildOutlineSliceClass[]]]; sliceClasses _ LIST[outlineDef, circleDef, boxDef, ipDef, textDef]; printPrefix _ Atom.MakeAtom["xerox/pressfonts/"]; screenPrefix _Atom.MakeAtom ["xerox/tiogafonts/"]; globalEdge _ Lines2d.CreateEmptyEdge[]; }; sliceClasses: LIST OF SliceClassDef; printPrefix: ATOM; screenPrefix: ATOM; globalEdge: Edge; Init[]; END. GGSliceImplA.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last edited by Pier on May 13, 1987 2:28:17 pm PDT Last edited by Bier on April 30, 1987 11:38:57 pm PDT Contents: Implements various slice classes in Gargoyle. Box Slice Class Fundamentals Drawing Transforming Textual Description Parts Part Generators Hit Testing Style requires a bound box input with loX<=hiX AND loY<=hiY IF index NOT IN [0..4) THEN ERROR; Fundamentals GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceCopyProc Drawing GGModelTypes.SliceDrawPartsProc draw fill draw outline GGModelTypes.SliceDrawSelectionFeedbackProc Imager.SetStrokeJoint[dc, square]; Imager.SetStrokeEnd[dc, round]; -- set in BoxEdgeFeedback draw fill draw outline GGModelTypes.SliceDrawTransformProc This is what makes boxes behave specially. Depending on which parts are selected, the points are transformed and the box is rubberbanded properly. The box data itself is not modified. [Artwork node; type 'Artwork on' to command tool] total transformation has already occurred on point, oppositePoint tWorld is the arrow shown in the figure. Transforming GGModelTypes.SliceTransformProc Permanently transforms the box. Depending on which parts are selected, the points are transformed and the box is grown/shrunk properly. Textual Description GGModelTypes.SliceDescribeProc GGModelTypes.SliceFileoutProc Write a description of yourself onto stream f. GGModelTypes.SliceFileinProc Parts cornerNum is the number of the last corner counted, if any. Otherwise cornerNum is undefined. edgeNum is the number of the last edge counted, if any. Otherwise edgeNum is undefined. GGModelTypes.SliceWalkSegmentsProc GGModelTypes.SliceNewPartsProc GGModelTypes.SliceUnionPartsProc GGModelTypes.SliceMovingPartsProc If anything other than only one corner or only one edge is moving, everything is moving If only one corner is moving, everything except its opposite corner is moving If only one edge is moving, everything excepts its opposite edge and the opposite edge corners are moving. If the box is filled and only partly moving, its stationary parts go on the overlay plane. Otherwise, they may be left in the background. GGModelTypes.SliceAugmentPartsProc For every edge, add its corresponding corners GGModelTypes.SliceClosestPointProc GGModelTypes.SliceClosestSegmentProc hitData _ NIL; -- automatically done by compiler Find the intersections of the given line with bBox. pointCount will be at most 2. Box Looks Class registration UpdateDescriptorBoundBoxesOLDD: PUBLIC PROC [sliceD: SliceDescriptor] = { IF sliceD.slice.class.type = $Outline THEN SIGNAL Problem[msg: "Time to have this proc call GGOutline.UpdateDescriptorBoundBoxes"]; }; Slice classes other than outlines don't use descriptor bound boxes !! SegmentsInDescriptorOLDD: PUBLIC PROC [sliceD: SliceDescriptor] RETURNS [segGen: SegmentGenerator] = { IF sliceD.slice.class.type=$Outline THEN SIGNAL Problem[msg: "GGSlice.SegmentsInSlice called with Outline slice. ILLEGAL"] ELSE segGen _ sliceD.slice.class.segmentsInDescriptor[sliceD]; }; WalkSegmentsOLDD: PUBLIC PROC [slice: Slice, walkProc: WalkProc] RETURNS [sliceD: SliceDescriptor] = { Calls the slice class walkSegments proc. The walkProc is called with each segment in the slice. If walkProc returns TRUE, the part of the slice that corresponds to the segment will be in the SliceDescriptor returned by WalkSegments. IF slice.class.type=$Outline THEN SIGNAL Problem[msg: "GGSlice.WalkSegments called with Outline slice. ILLEGAL"] ELSE sliceD _ slice.class.walkSegments[slice, walkProc]; }; Calls the slice class walkSegments proc. The walkProc is called with each segment in the slice. If walkProc returns TRUE, the part of the slice that corresponds to the segment will be in the SliceDescriptor returned by WalkSegments. ÊAó˜codešœ™Kšœ<™œ˜iKšœœ>œ˜jKšœ˜—K˜K˜—–# -- [cluster: GGModelTypes.Cluster]šžœœ œ˜QKš œœœœœ™"šœœ˜K˜K˜K˜K˜Kšœ˜K˜—K˜K˜—K™Kšœ ™ –# -- [cluster: GGModelTypes.Cluster]šžœœ#œ˜RKšœ™Kšœ˜K˜K˜—–# -- [cluster: GGModelTypes.Cluster]šžœœ#œ˜RKšœ™Kšœœ ˜&Kšœ˜K˜K˜—šžœœœ˜6Kšœ™Kšœ˜Kšœœ ˜&Kšœ5˜5KšœP˜PKšœ/Ÿ˜FKšœ œ ˜šœœœœ˜K–[seg: GGSegmentTypes.Segment]šœG˜GKšœ˜—Kšœ'˜'K˜K˜K˜—K™Kšœ™šž œœ$œ1œ˜sKšœ™šžœœ˜Kšœœ˜#Kšœ œœœh˜–šœ˜Kšž œX˜cšž œœ œ˜5Kšœ'˜'Kš œ œœœ˜3šœ˜Kšœ,˜,Kšœ(˜(Kšœ ˜ šœ œ˜Kšž œ/˜;š ž œœœœœ˜-Kšœ ˜K˜—Kšœ&˜&Kšœ¡ œ4˜\Kšœ˜—Kšœ˜#K˜—K˜—Kšœ;˜;Kšœœ$œ%˜]Kšœœ$œ%˜]Kšœœœ˜Kšœ0˜0Kšœ<˜Kšœ"œœ ˜IKšœ œ2˜EKšœœ5˜MKš œœ œœœ"˜RKšœ˜—Kšœœœ˜5Kšœ"œœ˜@KšœW˜WKšœ œ)˜[context: Imager.Context, strokeJoint: Imager.StrokeJoint]šœ"™"Kšœ9™9šœœœ˜Kš œ œœœ/œ˜jKšœ˜—K˜—K˜—K˜&Kšœœ ˜&Kšœœœœ˜/Kš œœœ œœœ˜6Kšœœ˜'Kšœœ ˜Kšœœ#˜AK˜K˜—š ž œœSœ œœ˜‚Kšž œT˜_šž œœ œ˜5Kšœ'˜'Kš œ œœœ˜3šœ˜Kšœ,˜,Kšœ(˜(Kšœ ˜ šœ œ˜Kšž œ/˜;š ž œœœœœ˜-Kšœ ˜K˜—Kšœ&˜&Kšœ¡ œ4˜\Kšœ˜—Kšœ˜#K˜—K˜—Kšœ;˜;Kšœœœ˜5Kšœœœ˜5Kšœœœ˜Kšœ0˜0Kšœ<˜—¡¡¡¡™¢¯“¢°“¡¡¨ÄWÄ?Ä‚«WÄ[[GÄ)/®¡¹¢¯“¢°“¡¡¨Ä[[GÄ)/®Ä ÓĘëO¡¹¢¯“¢°“¡¡¨Ä ÓĘëOÄèëÄk=>¡¹¢¯“¢°“¡¡¨ÄèëÄk=>ÄWÄ?Ä‚«W¡¹ k x j”””£¡ˆ¡¡ÅXeroxÅResearchÅ RGBLinear£¡¡¦ ç • ” ç­“ÄWÄ?Ä‚«W™Ä[[GÄ)/®—Ä"ÖkVÄò¥ ¢—Ä´áhÄ••Y—¡¡¡¡™¢¯“¢°“¡¡¨ÄWÄ?Ä‚«WÄ[[GÄ)/®¡¹¢¯“¢°“¡¡¨Ä[[GÄ)/®Ä"ÖkVÄò¥ ¢¡¹¢¯“¢°“¡¡¨Ä"ÖkVÄò¥ ¢Ä´áhÄ••Y¡¹¢¯“¢°“¡¡¨Ä´áhÄ••YÄWÄ?Ä‚«W¡¹ k r jª ¤XÄÉ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁloX, loY– k é r jª ¤aK ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁloX, hiY– k é r jª ¤¯Ä¡÷a ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁhiX, hiY– k é r jª ¤°ÄÇ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“ÁhiX, loY– k é¢¯“¢°“¢·“¡¡¨Ä®)\Ä°é ™ÄN,ÄÃnk—˜¤¯“¢°“¢·“¡¡¨Ä´áhÄ••Y™Ä®)\Ä°é —ÄœGTÄ­ l—Ä®)\Ä°é —Ä¹psÄ@E'—˜ x j”””£¡ˆ¡¡ÅXeroxÅResearchÅ RGBLinear£¡¡¦ ç • ” ç­“o™oD—ÄûÍžD—¡¡¡¡™¢¯“¢°“¡¡¨ooD¡¹¢¯“¢°“¡¡¨oDÄûÍžD¡¹¢¯“¢°“¡¡¨ÄûÍžDÄûÍž¡¹¢¯“¢°“¡¡¨ÄûÍžo¡¹ k x j”””£¡ˆ¡¡ÅXeroxÅResearchÅ RGBLinear£¡¡¦ ç • ” ç­“o™oD—ÃD—¡¡¡¡™¢¯“¢°“¡¡¨ooD¡¹¢¯“¢°“¡¡¨oDÃD¡¹¢¯“¢°“¡¡¨ÃDá¹¢¯“¢°“¡¡¨Ão¡¹ k r jª ¤NÄ! ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“Át– k é r j¨ ¤WÄ+ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica8£¡ “Ä ¤ ” •  —¡¡¨  Š¡²“ÁWorld– k é¢¯“¢°“¢·“¡¡¨¥g™¶„òšˆ¡’˜¢¯“¢°“¢·“¡¡¨ˆ™Är9Ä´[_—ˆ—Ä‹è_ÄðÇ—˜ r jª ¤ÒÄ™ ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “Ä  ¤ ” •  —¡¡¨  Š¡²“Ábox– k é r j¨ ¤Ä}v ¢ ¥ ¨ÅXeroxÅ TiogaFontsÅ Helvetica8£¡ “Ä ¤ ” •  —¡¡¨  Š¡²“ÁWorld– k é k é k g•Artwork Interpress•Bounds:0.0 mm xmin 0.0 mm ymin 108.8741 mm xmax 56.97361 mm ymax –Ó59.79583 mm topLeading 59.79583 mm topIndent 1.411111 mm bottomLeading 0.5 0.3 1.0 backgroundColor the topLeading 6 pt .sub backgroundAscent 3 pt backgroundDescent 6 pt outlineBoxThickness 1 pt outlineBoxBearoffšž3™3šžœœ˜KšœA™AKšœ4Ÿ œ˜AK˜—šžœœ˜Kšœ˜KšœFŸ œ˜SK˜—KšœÏuœ¢œ˜*Kšœœ˜-Kšœœ˜*Kšœ˜Kšœ¢œ˜)Kšœ,œ˜8Kšœ œœœ˜'Kš˜Kšœœœ˜0Kšœœœ˜!Kšœ2˜2Kšœ:˜:Kš œœœœœ˜QKšœ¢œ˜Kšœ¢œ"™(Kšœ¢œŸ*˜OKšœ¢œ*¢œ¢œ˜;šœ œŸ˜0Kšœœ˜ Kšœ"˜"Kš œœœœœ˜OKšœ2˜2Kšœ˜šœ ˜Kšœ2¢œ ˜BKšœ Ÿœ¢œ˜BKšœœ˜—K˜—Kš œœœœŸ&˜VšœŸ˜Kšœ!˜!Kšœ9˜9KšœM¢œ˜RKšœD˜DK˜—Kšœ˜š˜˜Kšœ˜K˜——Kšœ˜K˜K˜—K™Kšœ ™ šž œœN˜`Kšœ™Kšœ‡™‡Kšœ+œŸ˜MKšœœ˜-Kšœœ˜*Kšœ˜Kšœ=˜=Kšœœ˜$Kšœ œœœ˜'šœœ˜KšœN˜NKšœA˜AK–*[m: ImagerTransformation.Transformation]šœF˜FKšœ˜Kšœ˜K˜—KšœœœŸ˜5š œ5œ;œœŸ?˜ÍKšœN˜NKšœA˜AK–*[m: ImagerTransformation.Transformation]šœF˜FKšœ˜Kšœ˜K˜—šœ œŸ˜*KšœX˜XKšœ&œ˜0Kšœ_˜_Kšœ%œ@˜hKšœe˜ešœœ˜šœ˜Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EKšœ˜—šœ˜Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EKšœ˜—šœ˜Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EKšœ˜—šœ˜Kšœ˜Kšœ%˜%KšœD˜DKšœŸ(˜EKšœ˜—Kšœœ˜—K˜—š œœ œœŸ:˜_šœœ˜Kšœ_˜_Kšœ_˜_Kšœ_˜_Kšœ_˜_Kšœœ˜—Kšœ˜KšœQ˜QKšœEŸ˜dK˜—Kš œ œœœœœœ˜“Kšœ˜K˜K˜—K™Kšœ™šœœœœ˜-K˜V—šœœœœ˜+K˜6K˜—š žœœœœœ œ˜SKšœœ ˜)Kšœ œ˜Kšœœ+˜GKšœœœ˜3Kšœœœ(˜GKšœ.˜.K˜K˜—šž œœœ œ˜IK™Kšœ œ˜Kšœ˜Kšœ ˜Kšœ ˜Kšœœœœ"˜CKšœ œ˜ Kšœ3˜3Kšœ-˜-Kšœœœ!˜Gšœ˜Kšœ œœ œ˜EKš œ œœœ œ˜FKš œ œœœœ ˜HKš œ œœœœ ˜Fšœœ˜˜šœœœ˜šœœ˜Kšœ˜Kšœ˜K˜—Kšœ˜—K˜—˜ šœœœ˜šœœ˜Kšœ˜Kšœ˜K˜—Kšœ˜—K˜—Kšœ ˜ Kšœ˜Kšœœ˜—Kšœ.˜.K˜—K˜K˜—šž œœœœ˜1Kšœ™K™.Kšœœ ˜&Kšœ?˜?Kšœ œœ˜Kšœ?˜?Kšœ œœ˜Kšœ5˜5Kšœ˜šœœœ˜Kšœ;˜;Kšœ˜—Kšœ˜šœœœ˜Kšœ?˜?Kšœ œœ˜Kšœ˜—Kšœ ˜ šœœœ˜Kšœ7˜7Kšœ œœ˜Kšœ˜—Kšœ˜K˜,Kšœ˜šœœœ˜K˜&Iprocšœ/œœ˜6šœ œ˜Mšœ,˜,Mšœ:˜:Mšœ˜—Kšœ˜—Kšœ˜K˜K˜—š ž œœœœ œœ˜`Kšœ™K˜K˜K˜Kšœ/˜/Kšœœœœ˜#Kšœœœ ˜ Kšœœœ˜Kš œœœœœœ˜*Kšœ œœ˜)Kšœ œœœ˜Kšœ œœœ˜Kšœ ˜ Kšœ˜Kšœ˜Kšœ,˜,Kšœ1˜1šœœœ˜Kšœ3˜3Kšœ˜—šœœŸ˜0Kšœ1˜1šœœœ˜Kšœ(˜(Kšœ˜—K˜—Kšœ%˜)Kšœ3˜3šœœœ˜Kšœ/˜/Kšœ˜—Kšœ/˜/Kšœ'˜'šœœŸ˜3Kšœ+˜+šœœœ˜Kšœ4˜4šœ œ˜K˜.Kšœ.˜.Kšœ.˜.K˜—Kšœ˜—Kšœ#˜#K˜—Kšœ:˜:Kšœ/˜/Kšœ œ ˜šœœœ˜Kšœ8˜8Kšœ.˜.Kšœ,˜,Kšœ-˜-šœ œ˜Kšœ0˜0Kšœ.˜.Kšœ.˜.K˜—Kšœ˜—Kšœ˜Kšœ˜K˜K˜—Kšœ™šž œœ˜,Kšœœœ˜Kšœœœ˜Kšœœ˜K˜K˜—šž œœœœ˜8š œ œœœœ˜%Kšœœœœœœœ˜V—K˜K˜—šžœœœœ˜6Kšœœœ˜"K˜K˜—šžœœœœ˜5Kšœœœœœœœœ˜^K˜K˜—š ž œœœ œ œ˜TKšœ^™^K˜ Kšœ˜šœ œœ˜ Kšœ œ'˜8Kšœ˜—Kšœ˜K˜—š ž œœœ œ œ˜NKšœX™XK˜ Kšœ ˜ šœœœ˜Kšœ œ#˜2Kšœ˜—Kšœ˜K˜—šžœœœ˜\Kšœœ˜'Kšœ œ$œŸ˜Tšœ œœ˜ Kšœœ#˜@Kšœ˜—Kšœœ#˜7K˜K˜—šžœœœ'˜hKšœœ˜'Kšœœ(œŸ˜\šœœœ˜Kšœœ+˜DKšœ˜—K˜K˜—šžœœœ˜^Kšœœ˜'Kš œ œœ&œœ˜~šœœœ˜Kšœœ˜8Kšœ˜—K˜K˜—šžœœ$œ˜^Kšœ ¡™"Kšœœ ˜&Kš œœœœ œœ œ˜`šœœœ˜Kšœ2œ˜7Kšœ˜—Kšœ œ*˜6K˜K˜—šž œœœ.˜cšœ œœœ˜+Kšœœ˜Kšœ˜K˜—šœ˜Kšœ*˜*Kšœ˜Kšœœ ˜&Kšœœ˜*Kšœ;˜;Kšœœ˜Kšœœ˜šœ!œ ˜6KšœœœŸ+˜Qš˜šœ˜ Kšœœœ˜.K˜——Kšœ˜—K˜šœœŸ*˜=Kšœ`˜`Kšœ`˜`Kšœ`˜`Kšœ`˜`Kšœ˜Kšœœ+˜<—Kšœ ˜ Kšœœœœ Ÿ4˜nK˜—K˜K˜—šžœœ$œ6˜wšœœœœ˜3Kšœœ˜Kšœ˜K˜—šœ˜Kšœ.˜.Kšœ˜Kšœœ ˜&Kšœœ˜*Kšœ;˜;Kšœœ˜Kšœœ˜šœ%œ ˜:KšœœœŸ*˜NKšœ˜—šœœŸ)˜;šœ˜Kšœ\˜\Kšœ\˜\K˜—šœ˜Kšœ\˜\Kšœ\˜\K˜—šœ˜Kšœ\˜\Kšœ\˜\K˜—šœ˜Kšœ\˜\Kšœ\˜\K˜—Kšœœ+˜<—Kšœ*˜*Kšœœœœ Ÿ3˜uK˜—K˜K˜—š žœœœœ,œ˜rKšœœœœœœœ˜6šœ˜Kšœœ˜4Kšœœ˜1Kšœœ˜šœœ ˜4KšœœœŸ*˜NKšœ˜—Kšœ œœ+˜BKšœ˜Kšœ˜Kšœœœœ Ÿ6˜lK˜—K˜K˜K˜—šžœœœœ˜BKšœœ˜*Kšœ˜K˜K˜—šžœœœœ˜EKšœœ˜*Kšœ˜K˜K˜—šžœœœœ˜]Kšœ˜Kšœ œ˜Kšœ œœ˜Kšœ-œ ˜9K–\[sliceD: GGModelTypes.SliceDescriptor, testPoint: GGBasicTypes.Point, tolerance: REAL]šœv˜vKšœœ œœ˜Kšœ œ ˜Kšœ ˜ Kšœ˜Kš œ œœœœ˜(K˜K˜—š ž œœœœœ˜jKšœ™Kšœœ ˜)Kšœ˜Kš œ œœœ œœ œ˜Ušœ˜˜ Kšœœ'˜GKšœœœ˜8Kšœœœ#˜FKšœœ˜ K˜—šœ ˜ Kšœœ'˜GKšœœœ˜8šœœœ˜!Kšœ˜Kšœœ˜ Kšœ>˜>Kšœœ˜%Kšœœ˜$K˜—K˜—šœ ˜ Kšœœ˜3Kšœœœ#œ˜GK˜—šœ˜Kšœœœ˜Kšœœ˜K˜—Kšœ0˜0šœ Ÿ˜!K˜—Kšœœ˜—Kšœ6˜6K˜K˜K˜—šž œœ4œ˜lKšœ ™ Kšœœ˜+Kšœœ˜+Kšœ˜Kšœœœœ ˜*Kšœœœœ ˜*Kšœœœ ˜*Kšœœœ ˜*Kšœœœ ˜-Kšœœœ ˜-K˜Kš œ œœœ œœ œ˜Xšœœœ˜Kšœ+œ˜CKšœ'œ˜=Kšœ˜—Kšœ#œ˜7Kšœ=˜=Kšœ˜K˜—šž œœ4œ˜lKšœœ˜+Kšœœ˜+Kšœ˜Kš œ œœ œœœ˜+Kš œœœœœœ ˜@Kš œ œœœ œœ œ˜Xšœœœ˜Kš œœœœœ˜SKš œœœœœ˜MKšœ˜—Kš œœœœœ˜GKšœ>˜>K˜K˜—š ž œœœœ œ˜9šœ˜Kšœ'œ˜0Kšœœ˜—K˜—š žœœ œœ œ˜>šœ˜Kšœ#œ˜+Kšœœ˜—K˜—šžœœœœ˜Fšœ œ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœœ˜—K˜K˜—šžœœ+œ9˜Kšœ!™!K™WK™MK™jK™ŠKšœœ˜+Kšœœ ˜&Kšœ˜Kšœœœ˜'Kšœ.˜.Kšœ˜Kšœ,˜4Kš œ œœœœ˜8K˜šœœ˜Kšœ-˜-Kšœ˜K˜—šœœ˜Kšœ&˜&Kšœ4˜4Kšœ˜K˜—Kšœ:˜:Kšœ2˜2K˜Kš œ œœœ œœ œ˜Pš˜Kš œœœœœ˜Sšœœ˜Kšœœ˜ Kšœœœ˜-Kšœ"˜"Kš œœœœœœ˜Rš˜Kšœ6œ ˜BKšœœœ˜*Kšœœœ˜,Kšœœœ˜,Kšœ˜Kšœ6˜6Kšœ'˜'Kšœœœ)˜;Kšœ˜—K˜—šœœŸ˜=Kšœœœ˜.Kšœ˜Kšœ6˜6Kš œœœœ œœ œ˜ZKšœ"œ˜'Kšœ;˜;Kšœœœ)˜;K˜—šœŸ˜Kšœ:˜:Kšœ ˜ Kšœ˜—š˜šœ˜Kšœ&˜&Kšœ4˜4K˜——Kšœ˜—K˜K˜—šžœœ8œ˜pKšœ"™"Kšœœ˜*Kšœ˜Kšœ œ]˜kK™-šœœœ˜šœœ˜Kšœœ˜Kšœœœ˜%K˜—Kšœ˜—Kšœ;˜;K˜K˜—šžœœ8œœœ œœ œœ˜ªKšœ"™"K˜,KšœŽœœ˜œšœ2œ˜:Kšœœ˜Kšœ˜Kšœœ˜-Kšœ˜Kšœœ˜*Kšœ˜Kšœ œ˜KšœJ˜JKšœœœ œ3Ÿ3˜ŽKšœw˜wšœ œ˜KšœœI˜cKšœ˜—šœœŸ/˜IKšœ<Ÿ˜KKšœ9˜9šœœœ˜;Kšœ˜Kšœ˜Kšœ œ˜&KšœœF˜eKšœ˜Kšœ œ˜K˜—K˜—šœ œ˜Kšœ@˜@Kšœ ˜ Kšœ4˜4K˜—K˜—K˜K˜—šžœœ8œœœ œœ œœ˜¬Kšœ$™$Kšœ œŸ!™1K˜,KšœŽœœŸ˜µšœ2œ˜:Kšœœ˜Kšœ˜Kšœœ˜-Kšœœ˜*KšœJ˜JKšœœœ œ3Ÿ2˜Kšœ}˜}šœ œ˜Kšœ@˜@Kšœ4˜4KšœœL˜fK˜—K˜—K˜K˜—šžœœœ'œ œœœ˜{Kšœœ˜-Kšœœ˜*Kšœ˜Kšœœ˜ Kšœœœœ˜Kšœ œœ˜ Kšœ œ ˜Kšœ?˜?Kšœ œœ˜KšœR™RKšœœ˜Kšœ˜Kš œœœœ œœ˜1šœœœ˜Kš œœœœŸ,˜PKšœ!˜!KšœE˜EKšœœœ˜šœœ œ˜Kšœœ.œœ˜>Kšœœ.œœ˜>K˜—Kšœœ˜.Kšœ˜Kšœ œ˜š˜šœ˜ Kš œœœœŸ,˜UKšœ!˜!KšœE˜EKšœœœ˜šœ œ˜Kšœœ,œœ˜@Kšœœ,œœ˜@K˜—šœ œ˜Kšœœ,œœ˜@Kšœœ,œœ˜@K˜—Kšœœ˜.Kšœ˜Kšœ œ˜š˜Kšœœ˜ —K˜——Kšœ˜—Kšœœœ˜Kšœ œ˜ š œœœ œœ˜DK–4[m: ImagerTransformation.Transformation, v: VEC]šœ œN˜[Kšœ˜—K˜K˜—šžœœœœœœœ˜aKšœœ ˜)Kšœœ ˜&Kšœ˜K˜ Kšœœœœ˜)Kšœ!˜!Kšœ)˜)Kšœ9˜9Kšœ˜K˜K˜—šž œœœ%œ˜FKšœ œœœ˜)Kšœ˜KšœH˜HKšœH˜HKšœH˜HKšœH˜HKšœœ˜K˜—K˜K™ šžœœ4˜KKšœœ˜#Kšœœ ˜&šœœœ˜Kšœœ&˜BKšœ˜—Kšœ˜K˜—šžœœ#œ˜TKšœœ˜#Kšœœ ˜&šœœœ˜Kšœœ&˜BKšœ˜—Kšœ˜K˜—šžœœ!˜6Kšœœ ˜&Kšœ˜Kšœ˜K˜—šžœœœ˜?Kšœœ ˜&Kšœ˜Kšœ˜K˜—šžœœ0œœ˜hKšœœ˜#Kšœœ ˜&šœœœ˜Kšœœ2˜NKšœ˜—Kšœ˜Kšœ˜K˜K˜—šžœœ=˜QKšœœ˜#Kšœœ ˜&šœœœ˜Kš ¡ ¡9˜UKšœ˜—K˜'Kšœ˜K˜K˜—šžœœ#œœ˜YKšœœ˜#Kšœœ ˜&šœœœ˜Kšœœ2˜NKšœ˜—K˜K˜—šžœœ<˜QKšœœ˜#Kšœœ ˜&šœœœ˜Kšœœ.˜JKšœ˜—K˜K˜—šžœœ#œ˜ZKšœœ˜#Kšœœ ˜&šœœœ˜Kšœœ.˜JKšœ˜—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™K™š žœœœœœ˜Iš œœœ&œœ˜BKšœœœ˜0Kšœ˜—Kšœ1˜7Kšœœ˜ K˜K˜—šžœœœ™IKšœ$œœR™ƒK™K™—šžœœœ˜EKš¡E™Ešœ$œ˜,Kšœœ˜+š œœœ"œœ˜FJšœœœ'˜?Jšœ˜—J˜—K˜K˜—šžœ œ˜7Kšœœ6˜SKšœœ˜,K˜K˜—šžœœœ#œ˜hKšœ œ&˜2K˜K˜—šž œœœ>˜VKšœ0˜0Kšœ˜K˜—šž œœœœ˜?Kšœ˜ Kšœ˜—K˜šžœœœœ™fKšœ"œœK™zKšœ:™>K™K™—šžœœœœ˜bKšœ9˜9K˜K˜—šž œ œœ˜OKšœ9˜9K˜K˜—šžœ œ$œ™fJ™(J™ÀKšœœœH™pKšœ4™8J™J™—šž œ œ$œ˜bJ™(J™ÀKšœ3˜3J˜J™—šžœ œœ'˜ZKšœ-˜3K˜K˜—K˜šžœœ˜KšœœI˜eKšœœE˜_KšœœG˜bKšœœM˜kKšœœ6žœ˜pKšœœ0˜CK˜Kšœ1˜1Kšœ2˜2K˜K˜'K˜—K˜Kšœœœ˜$Kšœ œ˜Kšœœ˜K˜K˜K˜K˜Kšœ˜—…—¬·