DIRECTORY Feedback, FeedbackTypes, GGBasicTypes, GGBoundBox, GGCoreOps, GGCoreTypes, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGOutline, GGParent, GGParseIn, GGParseOut, GGProps, GGScene, GGSegment, GGSegmentTypes, GGShapes, GGSlice, GGSliceOps, GGTransform, GGUtility, Imager, ImagerColor, ImagerPath, ImagerTransformation, IO, Lines2d, NodeStyle, Real, Rope, TextNode, TiogaImager, TiogaIO, Vectors2d; GGSliceImplA: CEDAR PROGRAM IMPORTS Feedback, GGBoundBox, GGCoreOps, GGParent, GGParseIn, GGParseOut, GGProps, GGScene, GGSegment, GGShapes, GGSlice, GGSliceOps, GGTransform, GGUtility, Imager, ImagerTransformation, IO, Lines2d, Rope, TextNode, TiogaImager, TiogaIO, Vectors2d EXPORTS GGSlice = BEGIN BoundBox: TYPE = REF BoundBoxObj; BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj; Camera: TYPE = GGInterfaceTypes.Camera; Circle: TYPE = GGBasicTypes.Circle; Color: TYPE = Imager.Color; Corner: TYPE = GGSlice.Corner; DefaultData: TYPE = GGInterfaceTypes.DefaultData; EditConstraints: TYPE = GGModelTypes.EditConstraints; Edge: TYPE = GGBasicTypes.Edge; SliceGenerator: TYPE = GGModelTypes.SliceGenerator; ExtendMode: TYPE = GGModelTypes.ExtendMode; FactoredTransformation: TYPE = ImagerTransformation.FactoredTransformation; MsgRouter: TYPE = FeedbackTypes.MsgRouter; HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent; Line: TYPE = GGCoreTypes.Line; Object: TYPE = Imager.Object; Orientation: TYPE = GGModelTypes.Orientation; Point: TYPE = GGBasicTypes.Point; PointGenerator: TYPE = GGModelTypes.PointGenerator; PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj; PointWalkProc: TYPE = GGModelTypes.PointWalkProc; Scene: TYPE = GGModelTypes.Scene; Segment: TYPE = GGSegmentTypes.Segment; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; SegmentGeneratorObj: TYPE = GGModelTypes.SegmentGeneratorObj; SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData; SelectionClass: TYPE = GGSegmentTypes.SelectionClass; SelectMode: TYPE = GGModelTypes.SelectMode; Sequence: TYPE = GGModelTypes.Sequence; SequenceOfReal: TYPE = GGCoreTypes.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; StrokeJoint: TYPE = Imager.StrokeJoint; Transformation: TYPE = ImagerTransformation.Transformation; VEC: TYPE = Imager.VEC; Vector: TYPE = GGBasicTypes.Vector; WalkProc: TYPE = GGModelTypes.WalkProc; MoveToProc: TYPE = ImagerPath.MoveToProc; LineToProc: TYPE = ImagerPath.LineToProc; CurveToProc: TYPE = ImagerPath.CurveToProc; ConicToProc: TYPE = ImagerPath.ConicToProc; ArcToProc: TYPE = ImagerPath.ArcToProc; BoxData: TYPE = REF BoxDataObj; BoxDataObj: TYPE = RECORD [ box: BoundBox, -- this is not used as a bounding box. It is a representation of the shape. transform: Transformation, inverse: Transformation, -- inverse of transform inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s fillColor: Color, fillPixelArray: Imager.PixelArray ¬ NIL, fillText: TextNode.Location, formattedNodes: TiogaImager.FormattedNodes, screenStyle: BOOL ¬ FALSE, -- refers to desired format of nodes isScreenStyle: BOOL ¬ FALSE, -- refers to actual format of nodes strokeJoint: Imager.StrokeJoint ¬ round, forward: BOOL ¬ TRUE, -- that is, should be traversed in the normal order ll, ul, ur, lr savedPointSelections: ARRAY [0..5) OF SelectedObjectData, -- in order, ll, ul, ur, lr, center 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. centerIndex: INTEGER = 4; 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; copyRestore: PUBLIC BOOL ¬ TRUE; -- temporary for test Restore procs. BuildBoxSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = { OPEN GGSlice; class ¬ NEW[SliceClassObj ¬ [ type: $Box, unlink: GGSlice.UnlinkSlice, -- boxData doesn't need unlinking getBoundBox: BoxGetBoundBox, getTransformedBoundBox: GGSlice.GenericTransformedBoundBox, getTightBox: BoxGetTightBox, copy: BoxCopy, restore: BoxRestore, buildPath: BoxBuildPath, drawBorder: BoxDrawBorder, drawParts: BoxDrawParts, drawTransform: BoxDrawTransform, drawSelectionFeedback: BoxDrawSelectionFeedback, drawAttractorFeedback: BoxDrawAttractorFeedback, attractorFeedbackBoundBox: BoxAttractorFeedbackBoundBox, saveSelections: BoxSaveSelections, remakeSelections: BoxRemakeSelections, transform: BoxTransform, describe: BoxDescribe, describeHit: BoxDescribeHit, fileout: BoxFileout, filein: BoxFilein, isEmptyParts: BoxIsEmptyParts, isCompleteParts: BoxIsCompleteParts, newParts: BoxNewParts, unionParts: BoxUnionParts, differenceParts: BoxDiffParts, movingParts: BoxMovingParts, augmentParts: BoxAugmentParts, alterParts: BoxAlterParts, setSelectedFields: NoOpSetSelectedFields, pointsInDescriptor: BoxPointsInDescriptor, walkPointsInDescriptor: BoxWalkPointsInDescriptor, pointPairsInDescriptor: BoxPointPairsInDescriptor, segmentsInDescriptor: BoxSegmentsInDescriptor, walkSegments: BoxWalkSegments, nextPoint: BoxNextPoint, nextPointPair: BoxNextPointPair, nextSegment: BoxNextSegment, closestPoint: BoxClosestPoint, closestJointToHitData: BoxClosestJointToHitData, closestPointAndTangent: NoOpClosestPointAndTangent, closestSegment: BoxClosestSegment, filledPathsUnderPoint: BoxFilledPathsUnderPoint, lineIntersection: BoxLineIntersection, circleIntersection: NoOpCircleIntersection, hitDataAsSimpleCurve: BoxHitDataAsSimpleCurve, setDefaults: BoxSetDefaults, setStrokeWidth: BoxSetStrokeWidth, getStrokeWidth: BoxGetStrokeWidth, setStrokeEnd: BoxSetStrokeEnd, getStrokeEnd: BoxGetStrokeEnd, setStrokeJoint: BoxSetStrokeJoint, getStrokeJoint: BoxGetStrokeJoint, setStrokeColor: BoxSetStrokeColor, getStrokeColor: BoxGetStrokeColor, setFillColor: BoxSetFillColor, getFillColor: BoxGetFillColor, setArrows: NoOpSetArrows, getArrows: NoOpGetArrows, setDashed: BoxSetDashed, getDashed: BoxGetDashed, setOrientation: BoxSetOrientation, getOrientation: BoxGetOrientation ]]; }; MakeBoxSlice: PUBLIC PROC [box: BoundBox, corner: Corner, transform: Transformation ¬ GGTransform.Identity[]] RETURNS [sliceD: SliceDescriptor] = { boxSlice: Slice; inverse: Transformation ¬ ImagerTransformation.Invert[transform]; boxData: BoxData ¬ NEW[BoxDataObj ¬ [box: box, transform: transform, inverse: inverse, inverseScale: ImagerTransformation.Factor[inverse].s, fillColor: NIL, strokeJoint: round]]; 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 BoxSetSegments ENDLOOP; boxSlice ¬ NEW[SliceObj ¬ [ class: GGSlice.FetchSliceClass[$Box], data: boxData, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE], tightBox: GGBoundBox.NullBoundBox[], boundBox: GGBoundBox.NullBoundBox[], boxValid: FALSE ]]; boxSlice.nullDescriptor ¬ GGSlice.DescriptorFromParts[boxSlice, NIL]; BoxSetSegments[boxSlice, boxParts]; -- make segment end points correspond to box vertices sliceD ¬ GGSlice.DescriptorFromParts[boxSlice, boxParts]; }; MakeBoxFromMaskPixel: PUBLIC PROC [pa: Imager.PixelArray, color: Color, router: MsgRouter, transform: Transformation ¬ NIL] RETURNS [slice: Slice] = { boxData: BoxData; boundBox: BoundBox ¬ GGBoundBox.BoundBoxOfPixelArray[pa]; slice ¬ MakeBoxSlice[boundBox, none, transform].slice; boxData ¬ NARROW[slice.data]; boxData.fillPixelArray ¬ pa; boxData.fillColor ¬ color; FOR i: NAT IN [0..4) DO boxData.segments[i].color ¬ NIL; ENDLOOP; }; BoxFetchSegment: PUBLIC PROC [slice: Slice, index: NAT] RETURNS [seg: Segment] = { boxData: BoxData ¬ NARROW[slice.data]; seg ¬ boxData.segments[index]; }; SetBoxText: PUBLIC PROC [slice: Slice, loc: TextNode.Location, screenStyle: BOOL ¬ FALSE, history: HistoryEvent] = { nilFormattedNodes: TiogaImager.FormattedNodes ¬ [NIL, [NIL, 0]]; SELECT GGSliceOps.GetType[slice] FROM $Box => { boxData: BoxData ¬ NARROW[slice.data]; box: BoundBox ¬ boxData.box; IF IsChildOfOutline[slice] THEN { -- not top level or clustered box. Format loc directly. boxData.formattedNodes ¬ IF loc.node#NIL THEN TiogaImager.FormatNodes[ loc, [box.hiX-box.loX, box.hiY-box.loY], (IF screenStyle THEN screen ELSE print)] ELSE nilFormattedNodes; } ELSE { -- root of document passed in to single box object. Format first child. firstChildNode: TextNode.Ref ¬ TextNode.FirstChild[loc.node]; boxData.formattedNodes ¬ IF firstChildNode#NIL THEN TiogaImager.FormatNodes[ [firstChildNode, 0], [box.hiX-box.loX, box.hiY-box.loY], (IF screenStyle THEN screen ELSE print)] ELSE nilFormattedNodes; }; boxData.screenStyle ¬ screenStyle; boxData.isScreenStyle ¬ screenStyle; boxData.fillText ¬ loc; }; ENDCASE => NULL; -- benign in case other slice types get passed in }; GetBoxText: PUBLIC PROC [slice: Slice] RETURNS [loc: TextNode.Location, screenStyle: BOOL ¬ FALSE] = { SELECT GGSliceOps.GetType[slice] FROM $Box => { boxData: BoxData ¬ NARROW[slice.data]; loc ¬ boxData.fillText; screenStyle ¬ boxData.screenStyle; }; $Outline => { outlineData: GGOutline.OutlineData ¬ NARROW[slice.data]; loc ¬ [outlineData.fillText, 0]; screenStyle ¬ outlineData.screenStyle; }; ENDCASE; }; GetBoxNodes: PUBLIC PROC [slice: Slice] RETURNS [nodes: TiogaImager.FormattedNodes] = { WITH slice.data SELECT FROM boxData: BoxData => nodes ¬ boxData.formattedNodes; ENDCASE => nodes ¬ [NIL, [NIL, 0] ]; }; 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; }; 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] ]; }; BoxSetSegments: PRIVATE PROC [slice: Slice, parts: SliceParts ¬ NIL] = { boxData: BoxData ¬ NARROW[slice.data]; 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 GGSegment.MoveEndPointSegment[boxData.segments[i], TRUE, lo]; IF hi#boxData.segments[i].hi THEN GGSegment.MoveEndPointSegment[boxData.segments[i], FALSE, hi]; ENDLOOP; }; IsChildOfOutline: PROC [slice: Slice] RETURNS [BOOL] = { RETURN [NOT GGScene.IsTopLevel[slice] AND GGSliceOps.GetType[GGParent.GetParent[slice]]=$Outline]; }; root2Over2: REAL = 0.707106816; BoxGetBoundBoxAux: PROC [slice: Slice, parts: SliceParts] RETURNS [tightBox, boundBox: BoundBox] = { BoxFindBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [tightBox, boundBox: BoundBox] = { strokeWidth: REAL ¬ BoxMaxStrokeWidth[NARROW[slice.data]]; pad: REAL ¬ strokeWidth*root2Over2 + 1.0; -- allows for square ends and some mitered joints boxData: BoxData ¬ NARROW[slice.data]; boxParts: BoxParts ¬ NARROW[parts]; IF boxParts # NIL AND IsEmpty[boxParts] THEN RETURN[NIL, NIL]; IF boxParts=NIL OR boxParts.center OR CountCorners[boxParts.corners].count>2 OR CountEdges[boxParts.edges].count>1 THEN { -- bound box is the whole box tightBox ¬ GGBoundBox.BoundBoxOfBoundBox[boxData.box, boxData.transform]; } ELSE { -- have to deal with corners and edges FOR i: NAT IN [0..4) DO -- better be here only if <= 1 edge is selected IF boxParts.edges[i] THEN { p1: Point ¬ BoxPoint[boxData.box, i]; p2: Point ¬ BoxPoint[boxData.box, (i+1) MOD 4]; p1 ¬ ImagerTransformation.Transform[boxData.transform, p1]; p2 ¬ ImagerTransformation.Transform[boxData.transform, p2]; IF tightBox=NIL THEN tightBox ¬ GGBoundBox.CreateBoundBox[MIN[p1.x, p2.x], MIN[p1.y, p2.y], MAX[p1.x, p2.x], MAX[p1.y, p2.y] ] ELSE SIGNAL Problem[msg: "Broken Invariant"]; }; ENDLOOP; FOR i: NAT IN [0..4) DO -- better be here only if <= 2 corners are selected IF boxParts.corners[i] THEN { p1: Point ¬ BoxPoint[boxData.box, i]; p1 ¬ ImagerTransformation.Transform[boxData.transform, p1]; IF tightBox=NIL THEN tightBox ¬ GGBoundBox.CreateBoundBox[p1.x, p1.y, p1.x+1.0, p1.y+1.0] ELSE GGBoundBox.EnlargeByPoint[tightBox, p1]; }; ENDLOOP; }; boundBox ¬ GGBoundBox.CopyBoundBox[tightBox]; GGBoundBox.EnlargeByOffset[boundBox, pad]; BoxSetSegments[slice, parts]; }; [tightBox, boundBox] ¬ BoxFindBoundBox[slice, parts]; -- do the bound box update IF parts=NIL THEN { -- set up cache for fast case next time around GGSlice.KillBoundBox[slice.parent]; -- invalidate ancestor caches slice.boundBox ¬ boundBox; slice.tightBox ¬ tightBox; slice.boxValid ¬ TRUE; slice.tightBoxValid ¬ TRUE; }; }; BoxGetBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { IF slice.boxValid AND parts=NIL THEN RETURN[slice.boundBox]; -- fast case RETURN[BoxGetBoundBoxAux[slice, parts].boundBox]; -- cache update if possible }; BoxGetTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { IF slice.boxValid AND parts=NIL THEN RETURN[slice.tightBox]; -- fast case RETURN[BoxGetBoundBoxAux[slice, parts].tightBox]; -- cache update if possible }; BoxCopy: PROC [slice: Slice, parts: SliceParts ¬ NIL] RETURNS [copy: LIST OF Slice] = { copySlice: Slice; copyData: BoxData; boxData: BoxData ¬ NARROW[slice.data]; box: BoundBox ¬ GGBoundBox.CopyBoundBox[boxData.box]; transform: Transformation ¬ ImagerTransformation.Copy[boxData.transform]; copySlice ¬ MakeBoxSlice[box, ur, transform].slice; -- does ur work here ?? copyData ¬ NARROW[copySlice.data]; FOR i: NAT IN [0..4) DO copyData.segments[i] ¬ GGSegment.CopySegment[seg: boxData.segments[i]]; copyData.savedPointSelections[i] ¬ boxData.savedPointSelections[i]; ENDLOOP; copyData.savedPointSelections[4] ¬ boxData.savedPointSelections[4]; copyData.forward ¬ boxData.forward; copyData.fillColor ¬ GGCoreOps.CopyColor[boxData.fillColor]; copyData.fillPixelArray ¬ boxData.fillPixelArray; copyData.strokeJoint ¬ boxData.strokeJoint; IF NOT IsChildOfOutline[slice] THEN SetBoxText[copySlice, boxData.fillText, boxData.screenStyle, NIL]; -- this only works because we have no way to edit the fillText so it is immutable! GGProps.CopyAll[fromSlice: slice, toSlice: copySlice]; RETURN[LIST[copySlice]]; }; BoxRestore: PROC [from: Slice, to: Slice] = { IF to=NIL OR from=NIL THEN ERROR; IF to.class#from.class THEN ERROR; IF to.class.type#$Box THEN ERROR; BEGIN fromData: BoxData ¬ NARROW[from.data]; toData: BoxData ¬ NARROW[to.data]; IF GGSlice.copyRestore THEN { toData.box­ ¬ fromData.box­; toData.transform ¬ fromData.transform; toData.inverse ¬ fromData.inverse; toData.inverseScale ¬ fromData.inverseScale; toData.fillColor ¬ fromData.fillColor; toData.fillText ¬ fromData.fillText; toData.formattedNodes ¬ fromData.formattedNodes; toData.screenStyle ¬ fromData.screenStyle; toData.isScreenStyle ¬ fromData.isScreenStyle; toData.strokeJoint ¬ fromData.strokeJoint; toData.forward ¬ fromData.forward; toData.savedPointSelections ¬ fromData.savedPointSelections; toData.segments ¬ fromData.segments; } ELSE to.data ¬ from.data; to.selectedInFull ¬ from.selectedInFull; -- RECORD of BOOL to.normalSelectedParts ¬ NIL; -- caller must reselect to.hotSelectedParts ¬ NIL; -- caller must reselect to.activeSelectedParts ¬ NIL; -- caller must reselect to.matchSelectedParts ¬ NIL; -- caller must reselect to.tightBox­ ¬ from.tightBox­; to.tightBoxValid ¬ from.tightBoxValid; to.boundBox­ ¬ from.boundBox­; to.boxValid ¬ from.boxValid; to.onOverlay ¬ from.onOverlay; to.extraPoints ¬ from.extraPoints; to.priority ¬ from.priority; to.historyTop ¬ from.historyTop; END; }; BoxBuildPath: PROC [slice: Slice, transformParts: SliceParts, transform: Transformation, moveTo: MoveToProc, lineTo: LineToProc, curveTo: CurveToProc, conicTo: ConicToProc, arcTo: ArcToProc, editConstraints: EditConstraints ¬ none] = { OPEN ImagerTransformation; DoBoxPath: PROC [boxData: BoxData, plusTransform: Transformation, useBox: BOOL, box: BoundBox] = { pts: ARRAY[0..3] OF Point; boxLo, boxHi, tLL, tUR: Point; fullTransform: Transformation; boxLo ¬ IF useBox THEN [box.loX, box.loY] ELSE point; boxHi ¬ IF useBox THEN [box.hiX, box.hiY] ELSE oppositePoint; fullTransform ¬ IF plusTransform=NIL THEN boxData.transform ELSE ImagerTransformation.Concat[m: boxData.transform, n: plusTransform]; tLL ¬ [MIN[boxLo.x, boxHi.x], MIN[boxLo.y, boxHi.y] ]; tUR ¬ [MAX[boxLo.x, boxHi.x], MAX[boxLo.y, boxHi.y] ]; pts[0] ¬ Transform[fullTransform, tLL]; pts[1] ¬ Transform[fullTransform, [tLL.x, tUR.y] ]; pts[2] ¬ Transform[fullTransform, tUR]; pts[3] ¬ Transform[fullTransform, [tUR.x, tLL.y] ]; IF boxData.forward THEN {moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]];} ELSE {moveTo[pts[3]]; lineTo[pts[2]]; lineTo[pts[1]]; lineTo[pts[0]];} }; point, oppositePoint: Point; boxData: BoxData ¬ NARROW[slice.data]; boxTransformParts: BoxParts ¬ NARROW[transformParts]; box: BoundBox ¬ boxData.box; transformEntire, none: BOOL ¬ FALSE; [point, oppositePoint, transformEntire, none] ¬ PointsForTransform[box, boxData, boxTransformParts, transform]; IF transformEntire THEN DoBoxPath[boxData, transform, TRUE, box] -- do box path with additional transformation ELSE IF none THEN DoBoxPath[boxData, NIL, TRUE, box] ELSE DoBoxPath[boxData, NIL, FALSE, box]; -- transformations already done to point, oppositePoint }; BoxDrawBorder: PROC [slice: Slice, drawParts: SliceParts, transformParts: SliceParts, transform: Transformation, dc: Imager.Context, camera: GGModelTypes.Camera, quick: BOOL, editConstraints: EditConstraints ¬ none] = { OPEN ImagerTransformation; DrawBoxPath: PROC [boxData: BoxData, boxDrawParts: BoxParts, plusTransform: Transformation, useBox: BOOL, box: BoundBox] = { pts: ARRAY[0..3] OF Point; boxLo, boxHi: Point; fullTransform: Transformation; boxLo ¬ IF useBox THEN [box.loX, box.loY] ELSE point; boxHi ¬ IF useBox THEN [box.hiX, box.hiY] ELSE oppositePoint; fullTransform ¬ IF plusTransform=NIL THEN boxData.transform ELSE Concat[m: boxData.transform, n: plusTransform]; pts ¬ TransformBoxObj[[boxLo.x, boxLo.y, boxHi.x, boxHi.y, FALSE, FALSE], fullTransform]; IF AllStrokePropsAndColorsEqual[boxData] THEN { BuildPath: Imager.PathProc = { moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]]; lineTo[pts[0]]; }; MaskStrokeBoxPath[dc, boxData, BuildPath]; } ELSE { FOR edge: INTEGER IN [0..4) DO IF drawAll OR boxDrawParts.edges[edge] THEN { seg: Segment ¬ boxData.segments[edge]; GGSegment.DrawLine[dc, pts[edge], pts[(edge + 1) MOD 4], seg]; }; ENDLOOP; }; }; DoBoxDrawBorder: PROC = { boxData: BoxData ¬ NARROW[slice.data]; boxTransformParts: BoxParts ¬ NARROW[transformParts]; box: BoundBox ¬ boxData.box; transformEntire, none: BOOL ¬ FALSE; IF boxDrawParts#NIL AND IsEmpty[boxDrawParts] THEN RETURN; [point, oppositePoint, transformEntire, none] ¬ PointsForTransform[box, boxData, boxTransformParts, transform]; IF transformEntire THEN DrawBoxPath[boxData, boxDrawParts, transform, TRUE, box] -- do box path with additional finalTransform ELSE IF none THEN DrawBoxPath[boxData, boxDrawParts, NIL, TRUE, box] ELSE DrawBoxPath[boxData, boxDrawParts, NIL, FALSE, box]; -- transformations already done to point, oppositePoint }; point, oppositePoint: Point ¬ [0.0, 0.0]; boxDrawParts: BoxParts ¬ NARROW[drawParts]; drawAll: BOOL ¬ boxDrawParts = NIL OR boxDrawParts.edges=ALL[TRUE]; Imager.DoSave[dc, DoBoxDrawBorder]; }; PointsForTransform: PROC [box: BoundBox, boxData: BoxData, boxTransformParts: BoxParts, transform: Transformation] RETURNS [point, oppositePoint: Point ¬ [0,0], transformEntire, none: BOOL ¬ FALSE] = { tWorld, tBox: Point; totalTransform, worldBox: Transformation; cornerCount, edgeCount, edgeNum, cornerNum: INTEGER ¬ 0; IF box.null OR box.infinite THEN ERROR; BEGIN IF boxTransformParts=NIL OR transform=NIL OR boxTransformParts.center OR boxTransformParts.edges=ALL[TRUE] THEN GOTO FullTransform; IF IsEmpty[boxTransformParts] THEN {none ¬ TRUE; RETURN;}; [edgeCount, edgeNum] ¬ CountEdges[boxTransformParts.edges]; IF edgeCount >= 2 THEN GOTO FullTransform; [cornerCount, cornerNum] ¬ CountCorners[boxTransformParts.corners]; IF cornerCount >= 3 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 (boxTransformParts.corners[lo] AND boxTransformParts.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 -- wrong code 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]; }; EXITS FullTransform => transformEntire ¬ TRUE; END; }; TransformedBoxPoints: PROC [box: BoundBox, t: Transformation] RETURNS [pts: ARRAY[0..3] OF Point] = { tLL: Point ¬ [MIN[box.loX, box.hiX], MIN[box.loY, box.hiY]]; tUR: Point ¬ [MAX[box.loX, box.hiX], MAX[box.loY, box.hiY]]; 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]]; }; TransformBoxObj: PROC [box: BoundBoxObj, t: Transformation] RETURNS [pts: ARRAY[0..3] OF Point] = { tLL: Point ¬ [MIN[box.loX, box.hiX], MIN[box.loY, box.hiY]]; tUR: Point ¬ [MAX[box.loX, box.hiX], MAX[box.loY, box.hiY]]; 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] ]; }; FlipBoxStyle: PROC [slice: Slice, boxData: BoxData] = { nilFormattedNodes: TiogaImager.FormattedNodes ¬ [NIL, [NIL, 0]]; loc: TextNode.Location ¬ GetBoxText[slice].loc; box: BoundBox ¬ boxData.box; IF IsChildOfOutline[slice] THEN { -- not top level or clustered box. Format loc directly. boxData.formattedNodes ¬ IF loc.node#NIL THEN TiogaImager.FormatNodes[ loc, [box.hiX-box.loX, box.hiY-box.loY], (IF boxData.isScreenStyle THEN print ELSE screen)] ELSE nilFormattedNodes; } ELSE { -- root of document passed in to single box object. Format first child. firstChildNode: TextNode.Ref ¬ TextNode.FirstChild[loc.node]; boxData.formattedNodes ¬ IF firstChildNode#NIL THEN TiogaImager.FormatNodes[ [firstChildNode, 0], [box.hiX-box.loX, box.hiY-box.loY], (IF boxData.isScreenStyle THEN print ELSE screen)] ELSE nilFormattedNodes; }; boxData.isScreenStyle ¬ NOT boxData.isScreenStyle; }; BoxDrawParts: PROC [slice: Slice, parts: SliceParts ¬ NIL, dc: Imager.Context, camera: Camera, quick: BOOL] = { DoBoxDrawParts: PROC = { BoxTextDraw: PROC = { refPoint: Point ¬ [boxData.box.loX, boxData.box.hiY]; Imager.ConcatT[dc, boxData.transform]; Imager.SetColor[dc, Imager.black]; SELECT camera.displayStyle FROM screen => IF NOT boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData]; print => SELECT boxData.screenStyle FROM TRUE => IF NOT boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData]; FALSE => IF boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData]; ENDCASE; ENDCASE; TiogaImager.Render[box: boxData.formattedNodes.box, context: dc, position: refPoint]; }; boxParts: BoxParts ¬ NARROW[parts]; drawAll: BOOL ¬ boxParts = NIL OR boxParts.edges=ALL[TRUE]; pts: ARRAY[0..3] OF Point; ptsComputed: BOOL ¬ FALSE; IF drawAll AND boxData.fillColor#NIL AND NOT quick THEN { GGCoreOps.SetColor[dc, boxData.fillColor]; IF boxData.fillPixelArray = NIL THEN { BoxPathProc: Imager.PathProc = { moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]]; }; pts ¬ TransformedBoxPoints[boxData.box, boxData.transform]; ptsComputed ¬ TRUE; Imager.MaskFill[dc, BoxPathProc]; } ELSE { DoMaskPixel: PROC = { Imager.ConcatT[dc, boxData.transform]; Imager.MaskPixel[dc, boxData.fillPixelArray]; }; Imager.DoSave[dc, DoMaskPixel]; }; }; IF drawAll AND boxData.fillText.node#NIL THEN Imager.DoSave[dc, BoxTextDraw]; IF AllStrokePropsAndColorsEqual[boxData] THEN DrawSingleStrokeBox[dc, slice] ELSE { IF NOT ptsComputed THEN pts ¬ TransformedBoxPoints[boxData.box, boxData.transform]; FOR edge: INTEGER IN [0..4) DO IF drawAll OR boxParts.edges[edge] THEN { seg: Segment ¬ boxData.segments[edge]; GGSegment.DrawLine[dc, pts[edge], pts[(edge + 1) MOD 4], seg]; }; ENDLOOP; }; }; boxData: BoxData ¬ NARROW[slice.data]; Imager.DoSave[dc, DoBoxDrawParts]; }; BoxDrawTransform: PROC [slice: Slice, parts: SliceParts ¬ NIL, dc: Imager.Context, camera: Camera, transform: Transformation, editConstraints: EditConstraints] = { point, oppositePoint: Point; boxData: BoxData ¬ NARROW[slice.data]; boxTransformParts: BoxParts ¬ NARROW[parts]; box: BoundBox ¬ boxData.box; transformColor, transformEntire, none: BOOL ¬ FALSE; IF IsComplete[boxTransformParts] THEN { transformColor ¬ TRUE; transformEntire ¬ TRUE; } ELSE IF IsEmpty[boxTransformParts] THEN { transform ¬ NIL; transformEntire ¬ TRUE; } ELSE [point, oppositePoint, transformEntire, none] ¬ PointsForTransform[box, boxData, boxTransformParts, transform]; IF transformEntire THEN { TransformWholeBox: PROC = { IF transform # NIL THEN { IF transformColor THEN { Imager.ConcatT[dc, transform]; GGCoreOps.SetColor[dc, boxData.fillColor]; } ELSE { GGCoreOps.SetColor[dc, boxData.fillColor]; Imager.ConcatT[dc, transform]; }; } ELSE GGCoreOps.SetColor[dc, boxData.fillColor]; BoxDrawAll[dc, boxData, [box.loX, box.loY], [box.hiX, box.hiY], camera]; }; Imager.DoSave[dc, TransformWholeBox]; } ELSE { RubberbandBox: PROC = { GGCoreOps.SetColor[dc, boxData.fillColor]; BoxDrawAll[dc, boxData, point, oppositePoint, camera]; }; Imager.DoSave[dc, RubberbandBox]; }; }; BoxDrawAttractorFeedback: PROC [slice: Slice, attractorParts: SliceParts, selectedParts: SliceParts, dragInProgress: BOOL, dc: Imager.Context, camera: Camera, editConstraints: EditConstraints] = { DoDrawAttractorFeedback: PROC = { boxData: BoxData ¬ NARROW[slice.data]; boxParts: BoxParts ¬ NARROW[attractorParts]; -- attracting parts drawAll: BOOL ¬ boxParts.center OR IsComplete[boxParts]; t: 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] ]; drawn: ARRAY[0..3] OF BOOL ¬ ALL[FALSE]; 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] ]; FOR corner: INTEGER IN [0..4) DO IF drawAll OR boxParts.corners[corner] THEN { nextCorner: INTEGER ¬ (corner + 1) MOD 4; prevCorner: INTEGER ¬ (corner + 3) MOD 4; IF NOT drawn[corner] THEN {GGShapes.DrawCP[dc, pts[corner], camera.cpScale]; drawn[corner] ¬ TRUE;}; IF NOT drawn[nextCorner] THEN {GGShapes.DrawCP[dc, pts[nextCorner], camera.cpScale]; drawn[nextCorner] ¬ TRUE;}; IF NOT drawn[prevCorner] THEN {GGShapes.DrawCP[dc, pts[prevCorner], camera.cpScale]; drawn[prevCorner] ¬ TRUE;}; }; ENDLOOP; IF drawAll THEN -- draw center GGShapes.DrawCP[dc, ImagerTransformation.Transform[t, [(boxData.box.loX+boxData.box.hiX)/2.0, (boxData.box.loY+boxData.box.hiY)/2.0]], camera.cpScale]; IF NOT drawAll THEN { FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN { IF NOT drawn[edge] THEN GGShapes.DrawCP[dc, pts[edge], camera.cpScale]; IF NOT drawn[(edge + 1) MOD 4] THEN GGShapes.DrawCP[dc, pts[(edge + 1) MOD 4], camera.cpScale]; }; ENDLOOP; }; }; IF NOT (dragInProgress AND selectedParts#NIL) AND camera.quality#quality THEN Imager.DoSave[dc, DoDrawAttractorFeedback]; }; BoxAttractorFeedbackBoundBox: PROC [slice: Slice, attractorParts: SliceParts, selectedParts: SliceParts, dragInProgress: BOOL, camera: Camera, editConstraints: EditConstraints] RETURNS [box: BoundBox] = { box ¬ GGBoundBox.NullBoundBox[]; IF NOT (dragInProgress AND selectedParts#NIL) AND camera.quality#quality THEN { boxData: BoxData ¬ NARROW[slice.data]; boxParts: BoxParts ¬ NARROW[attractorParts]; -- attracting parts drawAll: BOOL ¬ boxParts.center OR IsComplete[boxParts]; t: 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] ]; drawn: ARRAY[0..3] OF BOOL ¬ ALL[FALSE]; 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] ]; FOR corner: INTEGER IN [0..4) DO IF drawAll OR boxParts.corners[corner] THEN { nextCorner: INTEGER ¬ (corner + 1) MOD 4; prevCorner: INTEGER ¬ (corner + 3) MOD 4; IF NOT drawn[corner] THEN {GGBoundBox.EnlargeByPoint[box, pts[corner]]; drawn[corner] ¬ TRUE}; IF NOT drawn[nextCorner] THEN {GGBoundBox.EnlargeByPoint[box, pts[nextCorner]]; drawn[nextCorner] ¬ TRUE;}; IF NOT drawn[prevCorner] THEN {GGBoundBox.EnlargeByPoint[box, pts[prevCorner]]; drawn[prevCorner] ¬ TRUE;}; }; ENDLOOP; IF drawAll THEN -- draw center GGBoundBox.EnlargeByPoint[box, ImagerTransformation.Transform[t, [(boxData.box.loX+boxData.box.hiX)/2.0, (boxData.box.loY+boxData.box.hiY)/2.0]]]; IF NOT drawAll THEN { FOR edge: INTEGER IN [0..4) DO IF boxParts.edges[edge] THEN { IF NOT drawn[edge] THEN GGBoundBox.EnlargeByPoint[box, pts[edge]]; IF NOT drawn[(edge + 1) MOD 4] THEN GGBoundBox.EnlargeByPoint[box, pts[(edge + 1) MOD 4]]; }; ENDLOOP; }; GGBoundBox.EnlargeByOffset[box, GGModelTypes.halfJointSize*camera.cpScale +1]; }; }; BoxDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: Camera, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = { slowNormal, slowHot, completeNormal, completeHot: BOOL ¬ FALSE; firstJoint: Point; normalBoxParts, hotBoxParts: BoxParts; boxData: BoxData ¬ NARROW[slice.data]; transform: Transformation ¬ boxData.transform; IF caretIsMoving OR dragInProgress OR camera.quality=quality THEN RETURN; IF selectedParts=NIL AND hotParts=NIL THEN RETURN; normalBoxParts ¬ NARROW[selectedParts]; hotBoxParts ¬ NARROW[hotParts]; completeNormal ¬ normalBoxParts#NIL AND IsComplete[normalBoxParts]; completeHot ¬ hotBoxParts#NIL AND IsComplete[hotBoxParts]; slowNormal ¬ normalBoxParts#NIL AND (NOT quick OR (quick AND NOT completeNormal)); slowHot ¬ hotBoxParts#NIL AND (NOT quick OR (quick AND NOT completeHot)); IF slowNormal AND slowHot THEN DrawSelectionFeedbackBox[slice, boxData, normalBoxParts, hotBoxParts, dc, transform, camera] ELSE IF slowNormal THEN DrawSelectionFeedbackBox[slice, boxData, normalBoxParts, NIL, dc, transform, camera] ELSE IF slowHot THEN DrawSelectionFeedbackBox[slice, boxData, NIL, hotBoxParts, dc, transform, camera]; IF (NOT slowNormal AND completeNormal) OR (NOT slowHot AND completeHot) THEN { fullParts: BoxParts ¬ IF completeNormal THEN normalBoxParts ELSE hotBoxParts; [] ¬ GGSliceOps.GetTightBox[slice]; -- force update of internal data firstJoint ¬ ImagerTransformation.Transform[transform, [boxData.box.loX, boxData.box.loY]]; }; IF NOT slowHot AND completeHot THEN GGShapes.DrawQuickSelectedJoint[dc, firstJoint, hot, camera.cpScale]; IF NOT slowNormal AND completeNormal THEN GGShapes.DrawQuickSelectedJoint[dc, firstJoint, normal, camera.cpScale]; }; RenderText: PUBLIC PROC [dc: Imager.Context, slice: Slice, camera: Camera, quick: BOOL] = { DoRenderText: PROC [] = { refPoint: Point ¬ [boxData.box.loX, boxData.box.hiY]; Imager.ConcatT[dc, boxData.transform]; Imager.SetColor[dc, Imager.black]; SELECT camera.displayStyle FROM screen => IF NOT boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData]; print => SELECT boxData.screenStyle FROM TRUE => IF NOT boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData]; FALSE => IF boxData.isScreenStyle THEN FlipBoxStyle[slice, boxData]; ENDCASE; ENDCASE; TiogaImager.Render[box: boxData.formattedNodes.box, context: dc, position: refPoint]; }; boxData: BoxData ¬ NARROW[slice.data]; IF boxData.fillText.node#NIL THEN Imager.DoSave[dc, DoRenderText]; }; DrawSingleStrokeBox: PROC [dc: Imager.Context, slice: Slice] = { BuildPath: Imager.PathProc = { pts: ARRAY[0..3] OF Point ¬ TransformedBoxPoints[boxData.box, boxData.transform]; moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]]; lineTo[pts[0]]; }; boxData: BoxData ¬ NARROW[slice.data]; MaskStrokeBoxPath[dc, boxData, BuildPath]; }; -- end DrawSingleStrokeBox MaskStrokeBoxPath: PROC [dc: Imager.Context, boxData: BoxData, pathProc: Imager.PathProc] = { PatternProc: PROC [i: NAT] RETURNS [REAL] = { RETURN[pattern[i]]; }; firstSeg: Segment ¬ boxData.segments[0]; strokeWidth: REAL ¬ firstSeg.strokeWidth; pattern: SequenceOfReal ¬ firstSeg.pattern; IF strokeWidth=0.0 OR firstSeg.color=NIL THEN RETURN; Imager.SetStrokeWidth[dc, strokeWidth]; Imager.SetStrokeEnd[dc, firstSeg.strokeEnd]; Imager.SetStrokeJoint[dc, boxData.strokeJoint]; Imager.SetColor[dc, firstSeg.color]; IF firstSeg.dashed THEN Imager.MaskDashedStroke[dc, pathProc, pattern.len, PatternProc, firstSeg.offset, firstSeg.length] ELSE Imager.MaskStroke[dc, pathProc, TRUE]; }; AllStrokePropsAndColorsEqual: PROC [boxData: BoxData] RETURNS [BOOL ¬ FALSE] = { seg: Segment; firstSeg: Segment ¬ boxData.segments[0]; width: REAL ¬ firstSeg.strokeWidth; end: StrokeEnd ¬ firstSeg.strokeEnd; color: Color ¬ firstSeg.color; dashed: BOOL ¬ firstSeg.dashed; pattern: SequenceOfReal ¬ firstSeg.pattern; offset: REAL ¬ firstSeg.offset; length: REAL ¬ firstSeg.length; FOR i: INT IN [1..4) DO seg ¬ boxData.segments[i]; IF seg.dashed # dashed THEN RETURN[FALSE]; IF seg.strokeEnd # end THEN RETURN[FALSE]; IF seg.strokeWidth # width THEN RETURN[FALSE]; IF NOT GGCoreOps.EquivalentColors[color, seg.color] THEN RETURN[FALSE]; IF NOT dashed THEN LOOP; IF seg.offset # offset THEN RETURN[FALSE]; IF seg.length # length THEN RETURN[FALSE]; IF NOT GGUtility.EquivalentPatterns[seg.pattern, pattern] THEN RETURN[FALSE]; REPEAT FINISHED => RETURN[TRUE]; ENDLOOP; }; DrawSelectionFeedbackBox: PROC [slice: Slice, boxData: BoxData, normalBoxParts: BoxParts, hotBoxParts: BoxParts, dc: Imager.Context, t: Transformation, camera: Camera] = { DoDrawFeedback: PROC = { box: BoundBox ¬ boxData.box; 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, camera.cpScale]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[corner], normal, camera.cpScale]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[corner], camera.cpScale]; 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, camera.cpScale]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, cc, normal, camera.cpScale]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, cc, camera.cpScale]; }; Imager.DoSave[dc, DoDrawFeedback]; }; BoxDrawAll: PROC [dc: Imager.Context, boxData: BoxData, from: Point, to: Point, camera: Camera] = { BoxPath: Imager.PathProc={moveTo[pts[0]]; lineTo[pts[1]]; lineTo[pts[2]]; lineTo[pts[3]];}; t: Transformation ¬ boxData.transform; pts: ARRAY[0..3] OF Point; pts ¬ TransformBoxObj[[loX: to.x, loY: to.y, hiX: from.x, hiY: from.y, null: FALSE, infinite: FALSE], t]; IF boxData.fillColor#NIL THEN { IF boxData.fillPixelArray = NIL THEN Imager.MaskFill[dc, BoxPath] ELSE { DoMaskPixel: PROC = { Imager.ConcatT[dc, t]; Imager.MaskPixel[dc, boxData.fillPixelArray]; }; }; }; IF AllStrokePropsAndColorsEqual[boxData] THEN MaskStrokeBoxPath[dc, boxData, BoxPath] ELSE { FOR edge: INTEGER IN [0..4) DO seg: Segment ¬ boxData.segments[edge]; GGSegment.DrawLine[dc, pts[edge], pts[(edge + 1) MOD 4], seg]; ENDLOOP; }; }; BoxSaveSelections: PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass] = { SetPointField: PROC [point: INTEGER, selected: BOOL, selectClass: SelectionClass] = { SELECT selectClass FROM normal => boxData.savedPointSelections[point].normal ¬ selected; hot => boxData.savedPointSelections[point].hot ¬ selected; active => boxData.savedPointSelections[point].active ¬ selected; match => boxData.savedPointSelections[point].match ¬ selected; ENDCASE; }; SetSegmentField: PROC [seg: Segment, selected: BOOL, selectClass: SelectionClass] = { SELECT selectClass FROM normal => seg.TselectedInFull.normal ¬ selected; hot => seg.TselectedInFull.hot ¬ selected; active => seg.TselectedInFull.active ¬ selected; match => seg.TselectedInFull.match ¬ selected; ENDCASE; }; dontClear: BOOL ¬ TRUE; boxParts: BoxParts ¬ NARROW[parts]; boxData: BoxData ¬ NARROW[slice.data]; IF boxParts=NIL THEN dontClear ¬ FALSE; FOR count: INTEGER IN [0..4) DO SetPointField[count, dontClear AND boxParts.corners[count], selectClass]; SetSegmentField[boxData.segments[count], dontClear AND boxParts.edges[count], selectClass]; ENDLOOP; SetPointField[centerIndex, dontClear AND boxParts.center, selectClass]; }; BoxRemakeSelections: PROC [slice: Slice, selectClass: SelectionClass] RETURNS [parts: SliceParts] = { GetPointField: PROC [point: INTEGER, selectClass: SelectionClass] RETURNS [BOOL] = { RETURN[SELECT selectClass FROM normal => boxData.savedPointSelections[point].normal, hot => boxData.savedPointSelections[point].hot, active => boxData.savedPointSelections[point].active, match => boxData.savedPointSelections[point].match, ENDCASE => FALSE]; }; GetSegmentField: PROC [seg: Segment, selectClass: SelectionClass] RETURNS [BOOL] = { RETURN[SELECT selectClass FROM normal => seg.TselectedInFull.normal, hot => seg.TselectedInFull.hot, active => seg.TselectedInFull.active, match => seg.TselectedInFull.match, ENDCASE => FALSE]; }; boxData: BoxData ¬ NARROW[slice.data]; boxParts: BoxParts ¬ NEW[BoxPartsObj]; FOR count: INTEGER IN [0..4) DO boxParts.corners[count] ¬ GetPointField[count, selectClass]; boxParts.edges[count] ¬ GetSegmentField[boxData.segments[count], selectClass]; ENDLOOP; boxParts.center ¬ GetPointField[centerIndex, selectClass]; parts ¬ IF IsEmpty[boxParts] THEN NIL ELSE boxParts; -- KAP. September 9, 1991 }; BoxTransform: PROC [slice: Slice, parts: SliceParts ¬ NIL, transform: Transformation, editConstraints: EditConstraints, history: HistoryEvent] = { BoxFullTransform: PROC [transformColor: BOOL] = { boxData.transform ¬ ImagerTransformation.Concat[boxData.transform, transform]; boxData.inverse ¬ ImagerTransformation.Invert[boxData.transform]; boxData.inverseScale ¬ ImagerTransformation.Factor[boxData.inverse].s; IF transformColor THEN boxData.fillColor ¬ GGCoreOps.TransformColor[boxData.fillColor, transform]; BoxSetSegments[slice, parts]; GGSlice.KillBoundBox[slice]; }; point, oppositePoint: VEC; -- really points, not vectors boxData: BoxData ¬ NARROW[slice.data]; boxParts: BoxParts ¬ NARROW[parts]; box: BoundBox ¬ boxData.box; inverse, totalTransform: Transformation; cornerCount, edgeCount: INTEGER ¬ 0; IF box.null OR box.infinite THEN ERROR; IF boxParts=NIL OR IsComplete[boxParts] THEN { BoxFullTransform[TRUE]; 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. BoxFullTransform[FALSE]; RETURN; }; IF edgeCount=1 THEN { -- transform an edge f: FactoredTransformation ¬ ImagerTransformation.Factor[transform]; globalTranslate: VEC ¬ f.t; mInverse: Transformation ¬ ImagerTransformation.Invert[boxData.transform]; localTranslate: VEC ¬ ImagerTransformation.TransformVec[mInverse, globalTranslate]; totalTransform: 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=2 THEN { -- two isolated corners. Full transform. Not quite right if corners are adjacent. BoxFullTransform[FALSE]; RETURN; } ELSE { -- one corner. 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]; BoxSetSegments[slice, parts]; GGSlice.KillBoundBox[slice]; IF boxData.fillText.node#NIL AND NOT IsChildOfOutline[slice] THEN { loc: TextNode.Location ¬ GGSlice.GetBoxText[slice].loc; -- includes root firstChild: TextNode.Ref ¬ TextNode.FirstChild[loc.node]; boxData.formattedNodes ¬ TiogaImager.FormatNodes[ [firstChild, 0], [box.hiX-box.loX, box.hiY-box.loY], (IF boxData.screenStyle THEN screen ELSE print)] -- top level box 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.PutF1["%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[" pa: "]; GGParseOut.WritePixelArray[f, boxData.fillPixelArray]; 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[" ) props: ( "]; FOR edge: INTEGER IN [0..4) DO seg: Segment ¬ boxData.segments[edge]; f.PutRope["( "]; GGParseOut.WriteBool[f, seg.props#NIL]; IF seg.props#NIL THEN GGParseOut.WriteProps[f, seg.props] ELSE f.PutChar[IO.SP]; -- list of ROPE f.PutRope[") "]; ENDLOOP; f.PutRope[") fwd: "]; GGParseOut.WriteBool[f, boxData.forward]; f.PutRope[" strokeJoint: "]; GGParseOut.WriteStrokeJoint[f, boxData.strokeJoint]; BEGIN loc: TextNode.Location; screenStyle: BOOL ¬ FALSE; [loc, screenStyle] ¬ GGSlice.GetBoxText[slice]; f.PutRope["\n fillText: "]; GGParseOut.WriteText[f, IF NOT IsChildOfOutline[slice] THEN loc.node ELSE NIL, screenStyle]; END; }; BoxFilein: PROC [f: IO.STREAM, version: REAL, router: MsgRouter, camera: Camera] RETURNS [slice: Slice] = { boxData: BoxData; box: BoundBox; p1, p2: Point; transform: 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; props: ARRAY [0..4) OF LIST OF Rope.ROPE ¬ ALL[NIL]; fill: Color; pa: Imager.PixelArray ¬ NIL; fwd, truth: BOOL ¬ TRUE; strokeJoint: Imager.StrokeJoint ¬ round; p1 ¬ GGParseIn.ReadPoint[f]; p2 ¬ GGParseIn.ReadPoint[f]; transform ¬ GGParseIn.ReadTransformation[f]; IF version > 8605.12 THEN { GGParseIn.ReadRope[f, "strokeWidths: ("]; FOR edge: INTEGER IN [0..4) DO strokeWidths[edge] ¬ GGParseIn.ReadReal[f]; ENDLOOP; } ELSE strokeWidths ¬ ALL[2.0]; IF version>=8702.26 THEN { -- read in strokeEnds GGParseIn.ReadRope[f, ") strokeEnds: ("]; FOR edge: INTEGER IN [0..4) DO ends[edge] ¬ GGParseIn.ReadStrokeEnd[f]; ENDLOOP; } ELSE ends ¬ [round, round, round, round]; IF version > 8605.12 THEN { GGParseIn.ReadRope[f, ") strokeColors: ("]; FOR edge: INTEGER IN [0..4) DO colors[edge] ¬ GGParseIn.ReadColor[f, version]; ENDLOOP; GGParseIn.ReadRope[f, ") fillColor: "]; fill ¬ GGParseIn.ReadColor[f, version]; } ELSE { colors ¬ ALL[Imager.black]; fill ¬ NIL; }; IF version >= 8811.30 THEN { -- read in the pixel array GGParseIn.ReadRope[f, "pa:"]; pa ¬ GGParseIn.ReadPixelArray[f]; }; IF version>=8704.03 THEN { -- read in dash patterns GGParseIn.ReadRope[f, "dashes: ("]; FOR edge: INTEGER IN [0..4) DO dashes[edge] ¬ GGParseIn.ReadBool[f]; IF dashes[edge] THEN { patterns[edge] ¬ GGParseIn.ReadArrayOfReal[f]; offsets[edge] ¬ GGParseIn.ReadReal[f]; lengths[edge] ¬ GGParseIn.ReadReal[f]; }; ENDLOOP; GGParseIn.ReadChar[f, ')]; }; IF version>=8706.08 THEN { -- read in segment props hasProps: BOOL ¬ FALSE; GGParseIn.ReadRope[f, "props: ("]; FOR edge: INTEGER IN [0..4) DO GGParseIn.ReadRope[f, "( "]; hasProps ¬ GGParseIn.ReadBool[f]; props[edge] ¬ IF hasProps THEN GGParseIn.ReadListOfRope[f] ELSE NIL; GGParseIn.ReadChar[f, ')]; ENDLOOP; GGParseIn.ReadChar[f, ')]; }; IF version>=8802.04 THEN { -- read in forward bit GGParseIn.ReadRope[f, "fwd:"]; fwd ¬ GGParseIn.ReadBool[f]; }; IF version>=8905.19 THEN { -- read in strokeJoint GGParseIn.ReadRope[f, "strokeJoint:"]; strokeJoint ¬ GGParseIn.ReadStrokeJoint[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 seg: Segment ¬ boxData.segments[edge]; seg.strokeWidth ¬ strokeWidths[edge]; seg.strokeEnd ¬ ends[edge]; seg.color ¬ colors[edge]; seg.dashed ¬ dashes[edge]; IF dashes[edge] THEN { seg.pattern ¬ patterns[edge]; seg.offset ¬ offsets[edge]; seg.length ¬ lengths[edge]; }; FOR next: LIST OF Rope.ROPE ¬ props[edge], next.rest UNTIL next=NIL DO seg.props ¬ CONS[next.first, seg.props]; ENDLOOP; ENDLOOP; boxData.fillColor ¬ fill; boxData.fillPixelArray ¬ pa; boxData.forward ¬ fwd; boxData.strokeJoint ¬ strokeJoint; IF version=8803.08 THEN { -- read in text fill in old format truth ¬ GGParseIn.ReadBool[f]; IF truth THEN { fillText: Rope.ROPE; ref: TextNode.Ref; nodeSize, where: INT ¬ 0; truth ¬ GGParseIn.ReadBool[f]; -- read screenStyle IF version>=8803.24 THEN where ¬ GGParseIn.ReadCARD[f]; -- read location index nodeSize ¬ GGParseIn.ReadCARD[f]; -- read number of chars in node IF f.PeekChar[]= ' THEN []¬ f.GetChar[]; -- don't ask. Leave this in. fillText ¬ f.GetRope[nodeSize, TRUE]; ref ¬ TiogaIO.FromRope[fillText]; GGSlice.SetBoxText[slice, [ref, where], truth, NIL]; }; }; IF version>=8803.24 THEN { text: TextNode.Ref; screenStyle: BOOL ¬ FALSE; GGParseIn.ReadRope[f, "fillText:"]; [text, screenStyle] ¬ GGParseIn.ReadText[f, version]; GGSlice.SetBoxText[slice, [text, 0], screenStyle, NIL]; }; }; MakeComplete: PROC [boxParts: BoxParts] = { boxParts.corners ¬ ALL[TRUE]; boxParts.edges ¬ ALL[TRUE]; boxParts.center ¬ TRUE; }; IsComplete: PROC [boxParts: BoxParts] RETURNS [BOOL ¬ FALSE] = { RETURN[ boxParts#NIL AND boxParts.corners=ALL[TRUE] AND boxParts.edges=ALL[TRUE] AND boxParts.center ]; }; IsEmpty: PROC [boxParts: BoxParts] RETURNS [BOOL ¬ FALSE] = { RETURN[boxParts=NIL OR (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; }; BoxWalkPointsInDescriptor: PROC [sliceD: SliceDescriptor, walkProc: PointWalkProc] = { parts: BoxParts ¬ NARROW[sliceD.parts]; boxData: BoxData ¬ NARROW[sliceD.slice.data]; done: BOOL ¬ FALSE; FOR corner: INTEGER IN [0..4) DO IF parts.corners[corner] THEN { done ¬ walkProc[GetBoxPoint[boxData, corner]]; IF done THEN RETURN; }; ENDLOOP; IF parts.center THEN { done ¬ walkProc[GetBoxPoint[boxData, 4]]; }; }; 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] ¬ boxParts.corners[i] ¬ boxParts.corners[(i + 1) MOD 4] ¬ walkProc[boxData.segments[i], boxData.transform]; ENDLOOP; IF boxParts.edges=ALL[TRUE] AND boxParts.corners=ALL[TRUE] THEN boxParts.center ¬ TRUE; sliceD ¬ GGSlice.DescriptorFromParts[slice, boxParts]; }; BoxNextPoint: PROC [slice: Slice, pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = { IF pointGen=NIL OR pointGen.toGo = 0 THEN { pointAndDone.done ¬ TRUE; RETURN; } ELSE { sliceD: SliceDescriptor ¬ pointGen.sliceD; boxData: BoxData ¬ NARROW[sliceD.slice.data]; boxParts: BoxParts ¬ NARROW[sliceD.parts]; 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; pointAndDone.point ¬ GetBoxPoint[boxData, index]; 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 }; }; GetBoxPoint: PROC [boxData: BoxData, index: INTEGER] RETURNS [point: Point] = { OPEN ImagerTransformation; t: Transformation ¬ boxData.transform; SELECT index FROM -- index in [0..4) of next available point 0 => point ¬ Transform[t, [boxData.box.loX, boxData.box.loY]]; 1 => point ¬ Transform[t, [boxData.box.loX, boxData.box.hiY]]; 2 => point ¬ Transform[t, [boxData.box.hiX, boxData.box.hiY]]; 3 => point ¬ Transform[t, [boxData.box.hiX, boxData.box.loY]]; 4 => point ¬ Transform[t, [(boxData.box.loX + boxData.box.hiX)/2.0, (boxData.box.loY + boxData.box.hiY)/2.0]]; ENDCASE => SIGNAL Problem[msg: "Broken Invariant"]; }; BoxNextPointPair: PROC [slice: Slice, pointPairGen: PointPairGenerator] RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = { IF pointPairGen=NIL OR pointPairGen.toGo = 0 THEN { pointPairAndDone.done ¬ TRUE; RETURN; } ELSE { sliceD: SliceDescriptor ¬ pointPairGen.sliceD; boxData: BoxData ¬ NARROW[sliceD.slice.data]; boxParts: BoxParts ¬ NARROW[sliceD.parts]; t: 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 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 [slice: Slice, segGen: SegmentGenerator] RETURNS [seg: Segment, transform: Transformation] = { 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 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 ¬ FALSE] = { boxParts: BoxParts ¬ NARROW[sliceD.parts]; RETURN[IsEmpty[boxParts]]; }; BoxIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL ¬ FALSE] = { boxParts: BoxParts ¬ NARROW[sliceD.parts]; RETURN[IsComplete[boxParts]]; }; NearestBoxPoint: PROC [slice: Slice, point: Point] RETURNS [index: [-1..3], center: BOOL ¬ FALSE] = { 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 { lo, hi: NAT; boxParts.edges[boxHitData.edge] ¬ TRUE; [lo, hi] ¬ CornersOfEdge[boxHitData.edge]; boxParts.corners[lo] ¬ boxParts.corners[hi] ¬ 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, editConstraints: EditConstraints, bezierDrag: GGInterfaceTypes.BezierDragRecord] 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 ¬ GGSliceOps.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]; }; BoxAlterParts: PROC [sliceD: SliceDescriptor, action: ATOM] RETURNS [newD: SliceDescriptor] = { boxParts: BoxParts ¬ NARROW[sliceD.parts]; newParts: BoxParts; selectedCorner: INT ¬ -1; selectedEdge: INT ¬ -1; newParts ¬ NEW[BoxPartsObj ¬ [corners: ALL[FALSE], edges: ALL[FALSE], center: FALSE]]; FOR i: INTEGER IN [0..4) DO IF boxParts.corners[i] THEN { selectedCorner ¬ i; EXIT; }; ENDLOOP; IF selectedCorner = -1 THEN { FOR i: INTEGER IN [0..4) DO IF boxParts.edges[i] THEN { selectedEdge ¬ i; EXIT; }; ENDLOOP; }; SELECT action FROM $Forward => { IF selectedCorner > -1 THEN { IF selectedCorner = 3 THEN { newParts.center ¬ TRUE; } ELSE { selectedCorner ¬ selectedCorner+1; newParts.corners[selectedCorner] ¬ TRUE; }; } ELSE IF selectedEdge > -1 THEN { selectedEdge ¬ (selectedEdge+1) MOD 4; newParts.edges[selectedEdge] ¬ TRUE; } ELSE newParts.corners[0] ¬ TRUE; }; $Backward => { IF selectedCorner > -1 THEN { IF selectedCorner = 0 THEN { newParts.center ¬ TRUE; } ELSE { selectedCorner ¬ selectedCorner-1; newParts.corners[selectedCorner] ¬ TRUE; }; } ELSE IF selectedEdge > -1 THEN { selectedEdge ¬ (selectedEdge+3) MOD 4; newParts.edges[selectedEdge] ¬ TRUE; } ELSE newParts.corners[3] ¬ TRUE; }; $Grow => RETURN[GGSliceOps.NewParts[sliceD.slice, NIL, slice]]; $ShrinkForward, $ShrinkBackward => RETURN[NIL]; -- no children to shrink to ENDCASE => ERROR; newD ¬ GGSlice.DescriptorFromParts[sliceD.slice, newParts]; }; BoxClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point ¬ [0.0, 0.0], bestDist: REAL ¬ Real.LargestNumber, bestNormal: Vector ¬ [0, -1], hitData: REF ANY, success: BOOL ¬ FALSE] = { IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[sliceD.slice], tolerance] THEN RETURN; BEGIN 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 { IF boxHitData.corner # -1 THEN { lxly, lxhy, hxly,hxhy: Point; lxly ¬ GGTransform.Transform[boxData.transform, [box­.loX,box­.loY]]; lxhy ¬ GGTransform.Transform[boxData.transform, [box­.loX,box­.hiY]]; hxly ¬ GGTransform.Transform[boxData.transform, [box­.hiX,box­.loY]]; hxhy ¬ GGTransform.Transform[boxData.transform, [box­.hiX,box­.hiY]]; IF GGBoundBox.PointIsInBox[localTestPoint, box­] THEN { normal1, normal2: Vector; nearDir: Vector ¬ Vectors2d.VectorFromPoints[GGTransform.Transform[boxData.transform, bestPoint], testPoint]; IF boxHitData.corner = 0 THEN { normal1 ¬ Vectors2d.VectorFromPoints[lxly,lxhy]; normal2 ¬ Vectors2d.VectorFromPoints[lxly,hxly]; }; IF boxHitData.corner = 1 THEN { normal1 ¬ Vectors2d.VectorFromPoints[lxhy,lxly]; normal2 ¬ Vectors2d.VectorFromPoints[lxhy,hxhy]; }; IF boxHitData.corner = 2 THEN { normal1 ¬ Vectors2d.VectorFromPoints[hxhy,lxhy]; normal2 ¬ Vectors2d.VectorFromPoints[hxhy,hxly]; }; IF boxHitData.corner = 3 THEN { normal1 ¬ Vectors2d.VectorFromPoints[hxly,hxhy]; normal2 ¬ Vectors2d.VectorFromPoints[hxly,lxly]; }; IF ABS[Vectors2d.SmallestAngleBetweenVectors[normal1, nearDir]] > ABS[Vectors2d.SmallestAngleBetweenVectors[normal2, nearDir]] THEN{ bestNormal ¬ normal1; } ELSE {bestNormal ¬ normal2;}; } ELSE { normal1, normal2: Vector; nearDir: Vector ¬ Vectors2d.VectorFromPoints[GGTransform.Transform[boxData.transform, bestPoint], testPoint]; IF boxHitData.corner = 0 THEN { normal1 ¬ Vectors2d.VectorFromPoints[lxhy,lxly]; normal2 ¬ Vectors2d.VectorFromPoints[hxly,lxly]; }; IF boxHitData.corner = 1 THEN { normal1 ¬ Vectors2d.VectorFromPoints[lxly,lxhy]; normal2 ¬ Vectors2d.VectorFromPoints[hxhy,lxhy]; }; IF boxHitData.corner = 2 THEN { normal1 ¬ Vectors2d.VectorFromPoints[lxhy,hxhy]; normal2 ¬ Vectors2d.VectorFromPoints[hxly,hxhy]; }; IF boxHitData.corner = 3 THEN { normal1 ¬ Vectors2d.VectorFromPoints[hxhy,hxly]; normal2 ¬ Vectors2d.VectorFromPoints[lxly,hxly]; }; IF ABS[Vectors2d.SmallestAngleBetweenVectors[normal1, nearDir]] < ABS[Vectors2d.SmallestAngleBetweenVectors[normal2, nearDir]] THEN{ bestNormal ¬ normal1;} ELSE {bestNormal ¬ normal2;}; }; }; bestPoint ¬ GGTransform.Transform[boxData.transform, bestPoint]; boxHitData.hitPoint ¬ bestPoint; bestDist ¬ Vectors2d.Distance[testPoint, bestPoint]; }; END; }; BoxClosestJointToHitData: PROC [sliceD: SliceDescriptor, mapPoint, testPoint: Point, hitData: REF ANY] RETURNS [jointD: SliceDescriptor, point: Point, normal: Vector ¬ [0,-1]] = { hitD: REF ANY; boxData: BoxData ¬ NARROW[sliceD.slice.data]; box: BoundBox ¬ boxData.box; localTestPoint: Point ¬ GGTransform.Transform[boxData.inverse, testPoint]; [point, ----, normal, hitD, ----] ¬ BoxClosestPoint[sliceD, testPoint, GGUtility.plusInfinity]; jointD ¬ sliceD; -- this is a crock and WRONG!! but it saves work }; BoxClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point ¬ [0.0, 0.0], bestDist: REAL ¬ Real.LargestNumber, bestNormal: Vector ¬ [0,-1], hitData: REF ANY, success: BOOL ¬ FALSE] = { IF NOT GGBoundBox.PointIsInGrownBox[testPoint, GGSliceOps.GetTightBox[sliceD.slice], tolerance] THEN RETURN; BEGIN 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] ]; bestNormal ¬ Vectors2d.Sub[testPoint, bestPoint]; }; END; }; BoxFilledPathsUnderPoint: PUBLIC PROC [slice: Slice, point: Point, tolerance: REAL] RETURNS [hitData: REF ANY ¬ NIL, moreHitDatas: LIST OF REF ANY ¬ NIL] = { boxData: BoxData ¬ NARROW[slice.data]; pointBox: Point ¬ ImagerTransformation.Transform[boxData.inverse, point]; box: BoundBox ¬ boxData.box; success: BOOL ¬ FALSE; success ¬ boxData.fillColor # NIL AND (boxData.box.infinite OR (pointBox.x >= box.loX AND pointBox.x <= box.hiX AND pointBox.y >= box.loY AND pointBox.y <= box.hiY)); IF success THEN { hitData ¬ NEW[BoxHitDataObj ¬ [corner: -1, edge: -1, center: 0, hitPoint: point]]; }; }; BoxLineIntersection: PUBLIC PROC [sliceD: SliceDescriptor, line: Line] RETURNS [points: LIST OF Point, pointCount: NAT ¬ 0] = { 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; 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, setHow: ATOM, history: HistoryEvent] = { boxParts: BoxParts ¬ NARROW[parts]; boxData: BoxData ¬ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts=NIL OR boxParts.edges[edge] THEN { SELECT setHow FROM $Set => boxData.segments[edge].color ¬ color; $ChangeHue => { newColor: Color ¬ GGUtility.ChangeHue[boxData.segments[edge].color, color]; boxData.segments[edge].color ¬ newColor; }; ENDCASE => ERROR; }; ENDLOOP; }; BoxGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Color, isUnique: BOOL ¬ TRUE] = { boxParts: BoxParts ¬ NARROW[parts]; boxData: BoxData ¬ NARROW[slice.data]; colorFound: BOOL ¬ FALSE; thisColor: Color; FOR edge: INTEGER IN [0..4) DO IF boxParts=NIL OR boxParts.edges[edge] THEN { IF colorFound THEN { thisColor ¬ boxData.segments[edge].color; IF NOT GGCoreOps.EquivalentColors[thisColor, color] THEN RETURN [color, FALSE]; } ELSE { colorFound ¬ TRUE; color ¬ boxData.segments[edge].color; }; }; ENDLOOP; IF NOT colorFound THEN { FOR corner: INTEGER IN [0..4) DO IF boxParts=NIL OR boxParts.corners[corner] THEN { IF colorFound THEN { thisColor ¬ boxData.segments[corner].color; IF NOT GGCoreOps.EquivalentColors[thisColor, color] THEN RETURN [color, FALSE]; } ELSE { colorFound ¬ TRUE; color ¬ boxData.segments[corner].color; }; }; ENDLOOP; }; }; BoxSetFillColor: PROC [slice: Slice, parts: SliceParts, color: Color, setHow: ATOM, history: HistoryEvent] = { boxData: BoxData ¬ NARROW[slice.data]; SELECT setHow FROM $Set => boxData.fillColor ¬ color; $ChangeHue => { newColor: Color ¬ GGUtility.ChangeHue[boxData.fillColor, color]; boxData.fillColor ¬ newColor; }; ENDCASE => ERROR; }; BoxGetFillColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Color, isUnique: BOOL ¬ TRUE] = { boxData: BoxData ¬ NARROW[slice.data]; color ¬ boxData.fillColor; }; BoxSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL, history: HistoryEvent] RETURNS [box: BoundBox] = { boxParts: BoxParts ¬ NARROW[parts]; boxData: BoxData ¬ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts=NIL OR boxParts.edges[edge] THEN boxData.segments[edge].strokeWidth ¬ strokeWidth; ENDLOOP; box ¬ GGBoundBox.CopyBoundBox[GGSliceOps.GetBoundBox[slice, parts]]; -- return a copy in case the caller or descendants modifies the (slice) boundBox. }; BoxSetDefaults: PROC [slice: Slice, parts: SliceParts, defaults: DefaultData, history: HistoryEvent] = { boxParts: BoxParts ¬ NARROW[parts]; boxData: BoxData ¬ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts=NIL OR boxParts.edges[edge] THEN GGSegment.SetDefaults[boxData.segments[edge], defaults]; ENDLOOP; boxData.fillColor ¬ defaults.fillColor; boxData.strokeJoint ¬ defaults.strokeJoint; GGSlice.KillBoundBox[slice]; }; BoxGetStrokeWidth: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeWidth: REAL ¬ 17.0, isUnique: BOOL ¬ TRUE] = { boxParts: BoxParts ¬ NARROW[parts]; boxData: BoxData ¬ NARROW[slice.data]; found: BOOL ¬ FALSE; this: REAL; FOR edge: INTEGER IN [0..4) DO IF boxParts=NIL OR boxParts.edges[edge] THEN { IF found THEN { this ¬ boxData.segments[edge].strokeWidth; IF this#strokeWidth THEN RETURN [strokeWidth, FALSE]; } ELSE { found ¬ TRUE; strokeWidth ¬ boxData.segments[edge].strokeWidth; }; }; ENDLOOP; IF NOT found THEN RETURN[-1.0, FALSE]; -- nothing selected }; BoxSetStrokeEnd: PROC [slice: Slice, parts: SliceParts, strokeEnd: StrokeEnd, history: HistoryEvent] = { boxParts: BoxParts ¬ NARROW[parts]; boxData: BoxData ¬ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts=NIL OR boxParts.edges[edge] THEN boxData.segments[edge].strokeEnd ¬ strokeEnd; ENDLOOP; }; BoxSetStrokeJoint: PROC [slice: Slice, parts: SliceParts, strokeJoint: StrokeJoint, history: HistoryEvent] = { boxData: BoxData ¬ NARROW[slice.data]; boxData.strokeJoint ¬ strokeJoint; }; BoxGetStrokeJoint: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeJoint: StrokeJoint, isUnique: BOOL ¬ TRUE] = { boxData: BoxData ¬ NARROW[slice.data]; RETURN[boxData.strokeJoint]; }; BoxGetStrokeEnd: PROC [slice: Slice, parts: SliceParts] RETURNS [strokeEnd: StrokeEnd ¬ square, isUnique: BOOL ¬ TRUE] = { boxParts: BoxParts ¬ NARROW[parts]; boxData: BoxData ¬ NARROW[slice.data]; found: BOOL ¬ FALSE; this: StrokeEnd; FOR edge: INTEGER IN [0..4) DO IF boxParts=NIL OR boxParts.edges[edge] THEN { IF found THEN { this ¬ boxData.segments[edge].strokeEnd; IF this#strokeEnd THEN RETURN [strokeEnd, FALSE]; } ELSE { found ¬ TRUE; strokeEnd ¬ boxData.segments[edge].strokeEnd; }; }; ENDLOOP; IF NOT found THEN RETURN[round, FALSE]; -- nothing selected }; BoxSetDashed: PROC [slice: Slice, parts: SliceParts, dashed: BOOL ¬ FALSE, pattern: SequenceOfReal ¬ NIL, offset: REAL ¬ 0.0, length: REAL ¬ -1.0, history: HistoryEvent] = { boxParts: BoxParts ¬ NARROW[parts]; boxData: BoxData ¬ NARROW[slice.data]; FOR edge: INTEGER IN [0..4) DO IF boxParts=NIL OR 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 ¬ 0.0, isUnique: BOOL ¬ TRUE] = { boxParts: BoxParts ¬ NARROW[parts]; boxData: BoxData ¬ NARROW[slice.data]; found: BOOL ¬ FALSE; FOR edge: INTEGER IN [0..4) DO IF boxParts=NIL OR boxParts.edges[edge] THEN { seg: Segment ¬ boxData.segments[edge]; IF found THEN { IF dashed#seg.dashed OR (seg.dashed AND (seg.offset # offset OR seg.length # length OR NOT GGUtility.EquivalentPatterns[seg.pattern, pattern])) THEN RETURN[dashed, pattern, offset, length, FALSE]; } ELSE { found ¬ TRUE; dashed ¬ seg.dashed; pattern ¬ seg.pattern; offset ¬ seg.offset; length ¬ seg.length; }; }; ENDLOOP; IF NOT found THEN RETURN[FALSE, pattern, offset, length, FALSE]; -- nothing selected }; BoxSetOrientation: PROC [slice: Slice, parts: SliceParts, orientation: Orientation, history: HistoryEvent] RETURNS [success: BOOL ¬ TRUE] = { boxData: BoxData ¬ NARROW[slice.data]; SELECT orientation FROM cw => boxData.forward ¬ TRUE; ccw => boxData.forward ¬ FALSE; reverse => boxData.forward ¬ NOT boxData.forward; ENDCASE => ERROR; }; BoxGetOrientation: PROC [slice: Slice, parts: SliceParts] RETURNS [orientation: Orientation, isUnique: BOOL ¬ TRUE] = { boxData: BoxData ¬ NARROW[slice.data]; RETURN[IF boxData.forward THEN cw ELSE ccw]; }; globalEdge: Edge; Init: PROC [] = { globalEdge ¬ Lines2d.CreateEmptyEdge[]; }; Init[]; END. ’ GGSliceImplA.mesa (The Box Slice Class) Copyright Ó 1986, 1991, 1992 by Xerox Corporation. All rights reserved. Contents: Implements the Box slice class. Pier, on June 22, 1992 4:57 pm PDT Bier, February 23, 1993 7:14 pm PST Doug Wyatt, April 16, 1992 5:17 pm PDT If this record is changed UPDATE BoxCopy and BoxRestore! Fundamentals Drawing Transforming Textual Description Parts Part Generators Hit Testing Style requires a bound box input with loX<=hiX AND loY<=hiY fill color and other properties are filled in when the caller calls slice.setDefaults. BoxSetBoundBox not needed since boxValid=FALSE IF index NOT IN [0..4) THEN ERROR; Place segments at the proper coordinates, given the current boxData.box. Ignore parts for now. Fundamentals Try including parts. Ken Pier. July 23, 1991 Make tightBox from the actual box and transform GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceCopyProc Just ignore parts and copy the whole box. Bound box update not needed since boxValid=FALSE in copy GGModelTypes.SliceRestoreProc from and to must be nearly identically structured slices, probably because from was made as a copy of to. Restore the contents of "to", getting all non-REF values from "from". Good luck. to.nullDescriptor is always valid to.fullDescriptor is unused Drawing transformParts=NIL => complete parts. transform=NIL => don't transform any parts. Otherwise, transform those pieces of the slice indicated in transformParts by the tranformation in transform, then use the five callback procs to create the slice path. 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. drawParts=NIL => draw ALL parts. transformParts=NIL => transform ALL parts. transform=NIL => don't transform any parts. Otherwise, transform those pieces of the slice indicated in transformParts by the tranformation in transform, then draw those parts indicated in drawParts. [Artwork node; type 'Artwork on' to command tool] tWorld is the arrow shown in the figure. Draw fill (including, sampled colors, pixel arrays, and text) Draw outline Depending on which parts are selected, the points are transformed and the box is rubberbanded properly. The box data itself is not modified. Total transformation has already occurred on point, oppositePoint draw feedback GGModelTypes.SliceDrawSelectionFeedbackProc Drawing Utilities draw fill draw outline GGModelTypes.SliceSaveSelectionsProc GGModelTypes.SliceRemakeSelectionsProc Transforming GGModelTypes.SliceTransformProc Permanently transforms the box. Depending on which parts are selected, the points are transformed and the box is grown/shrunk properly. Box is responsible for entire document. Following code optimized from SetBoxText Textual Description GGModelTypes.SliceDescribeProc GGModelTypes.SliceFileoutProc Write a description of yourself onto stream f. only write node data if not an Outline child. Otherwise, parent Outline takes care of it. 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.SliceAlterPartsProc GGModelTypes.SliceClosestPointProc Note: It might be best if the angle check is reversed when inside the box. GGModelTypes.SliceClosestJointToHitDataProc GGModelTypes.SliceClosestSegmentProc Find the intersections of the given line with bBox. pointCount will be at most 2. Box Looks Êc5•NewlineDelimiter –(cedarcode) style™code™'Kšœ Ïeœ<™HKšÏnœ"™*K™"K™#K™&K˜—šÏk ˜ KšœÂŸœL˜K˜—šž œŸœŸ˜KšŸœµŸœ:˜øKšŸœ Ÿ˜—˜Kšœ ŸœŸœ ˜!Kšœ Ÿœ˜,KšœŸœ˜'KšœŸœ˜#KšœŸœ˜KšœŸœ˜Kšœ Ÿœ ˜1K˜5KšœŸœ˜KšœŸœ˜3Kšœ Ÿœ˜+KšœŸœ/˜KKšœ Ÿœ˜*KšœŸœ˜1KšœŸœ˜KšœŸœ˜Kšœ Ÿœ˜-KšœŸœ˜!KšœŸœ˜3KšœŸœ"˜9KšœŸœ#˜;KšœŸœ&˜AKšœŸœ˜1KšœŸœ˜!Kšœ Ÿœ˜'KšœŸœ!˜7KšœŸœ$˜=KšœŸœ#˜;KšœŸœ!˜5Kšœ Ÿœ˜+Kšœ Ÿœ˜'KšœŸœ˜2KšœŸœ˜!Kšœ Ÿœ˜+KšœŸœ˜1KšœŸœ ˜5KšœŸœ#˜;Kšœ Ÿœ˜'Kšœ Ÿœ˜+Kšœ Ÿœ˜#Kšœ Ÿœ˜'KšœŸœ'˜;KšŸœŸœ Ÿœ˜KšœŸœ˜#Kšœ Ÿœ˜'K˜Kšœ Ÿœ˜)Kšœ Ÿœ˜)Kšœ Ÿœ˜+Kšœ Ÿœ˜+Kšœ Ÿœ˜'K˜—Kšœ8™8Kšœ ŸœŸœ ˜šœ ŸœŸœ˜KšœÏcL˜[Kšœ˜Kšœ ˜0Kšœ 9˜OKšœ˜Kšœ$Ÿœ˜(K˜Kšœ+˜+Kšœ ŸœŸœ $˜?KšœŸœŸœ #˜@K˜(Kšœ ŸœŸœ B˜XKšœŸœŸœ #˜]Kšœ ŸœŸœ  $˜FK˜—K˜Kš œ ŸœŸœŸœŸœ .˜XKš œ ŸœŸœŸœŸœ 6˜^Kšœ Ÿœ˜K˜Kšœ ŸœŸœ ˜!šœ ŸœŸœ˜Kšœ %˜;Kšœ #˜5KšœŸœ  ˜-K˜K˜—Kšœ ŸœŸœ˜%šœŸœŸœ˜Kšœ˜Kšœ˜Kšœ˜K˜K˜—K˜KšžœŸœ Ÿœ˜4Kšœ ŸœŸœŸœ $˜EK˜šžœŸœŸœŸœ˜BKšÐbkÏb ˜ šœŸœ˜K˜ šœ !˜>Kš¢ ™ —Kšœ˜Kšœ žœ˜;K˜K˜˜Kš¢™—Kšœ˜Kšœ˜Kšœ˜K˜ K˜0K˜0K˜8K˜"šœ&˜&Kš¢ ™ —šœ˜Kš¢™—K˜K˜K˜˜Kš¢™—K˜K˜$K˜K˜K˜K˜K˜K˜šœ)˜)Kš¢™—K˜*K˜2K˜2K˜.K˜K˜K˜ ˜Kš¢ ™ —Kšœ˜Kšœ0˜0Kšœ3˜3Kšœ"˜"Kšœ0˜0K˜&K˜+˜.Kš¢™—K˜K˜"K˜"K˜K˜K˜"K˜"K˜"K˜"K˜K˜K˜K˜K˜K˜K˜"K˜!K˜—Kšœ˜K˜—šž œŸœŸœUŸœ˜“Kšœ)Ÿœ ™5Kšœ˜K˜AKšœŸœ‚Ÿœ˜²Kš¢V™VKš œŸœŸœŸœ ŸœŸœ Ÿœ˜aKš œŸœŸœŸœŸœ˜KKšœŸœ˜ KšŸœŸœŸœ,˜NKšŸœŸœŸœ,˜NšŸœŸœŸœŸ˜KšœuŸœ Ÿœ˜ŠK˜K˜KšœŸœ˜K˜Kšœ 6˜TKšŸœ˜—šœ Ÿœ ˜K˜%Kšœ˜KšœŸœ˜ KšœŸœŸœŸœ˜&Kšœ$˜$Kšœ$˜$Kšœ Ÿ˜Kšœ˜—Kšœ@Ÿœ˜EKšœ% 5˜ZKš¢.™.K˜9Kšœ˜K˜—š žœŸœŸœVŸœŸœ˜–Kšœ˜K˜9K˜K˜6Kšœ Ÿœ ˜K˜K˜šŸœŸœŸœŸ˜KšœŸœ˜ KšŸœ˜—K˜K˜—š žœŸœŸœŸœŸœ˜RKšœŸœ ˜&K˜K˜K˜—š ž œŸœŸœ5ŸœŸœ˜tKšœ1ŸœŸœ˜@šŸœŸ˜%˜ KšœŸœ ˜&K˜šŸœŸœ 7˜YKšœŸœ ŸœŸœDŸœ ŸœŸœ Ÿœ˜°K˜—šŸœ G˜NK˜=KšœŸœŸœŸœTŸœ ŸœŸœ Ÿœ˜ÆK˜—K˜"K˜$K˜K˜—KšŸœŸœ 1˜B—Kšœ˜K˜—š ž œŸœŸœŸœ'ŸœŸœ˜fšŸœŸ˜%˜ KšœŸœ ˜&K˜K˜"K˜—˜ Kšœ%Ÿœ ˜8K˜ K˜&K˜—KšŸœ˜—Kšœ˜K˜—šž œŸœŸœŸœ(˜WšŸœ ŸœŸ˜K˜3KšŸœ ŸœŸœ˜$—Kšœ˜K˜—•StartOfExpansion# -- [cluster: GGModelTypes.Cluster]šžœŸœŸœ Ÿœ˜GK˜+šŸœŸœŸœŸ˜Kšœ Ÿœ,˜:KšŸœ˜—K˜K˜—–# -- [cluster: GGModelTypes.Cluster]šžœŸœ Ÿœ˜QKš ŸœŸœŸœŸœŸœ™"šŸœŸœŸ˜K˜K˜K˜K˜KšŸœ˜K˜—K˜K˜—šžœŸœŸœ$Ÿœ˜HKšœH™HK™KšœŸœ ˜&šŸœŸœŸœŸ˜K˜.Kšœ1Ÿœ˜8KšŸœŸœ4Ÿœ˜_KšŸœŸœ4Ÿœ˜`KšŸœ˜—K˜K˜—šžœŸœŸœŸœ˜8KšŸœŸœŸœ9˜bK˜—K™šœ ™ K™—Kšœ Ÿœ˜–# -- [cluster: GGModelTypes.Cluster]šžœŸœ#Ÿœ#˜dšžœŸœ#Ÿœ#˜bK™,Kšœ ŸœŸœ˜:KšœŸœ! 1˜[KšœŸœ ˜&KšœŸœ˜#KšŸœ ŸœŸœŸœŸœŸœŸœ˜>Kš¢/™/š Ÿœ ŸœŸœŸœ(Ÿœ$Ÿœ ˜—K˜IK˜—šŸœ &˜-š ŸœŸœŸœŸœ /˜GšŸœŸœ˜K˜%Kšœ(Ÿœ˜/K˜;K˜;KšŸœ ŸœŸœ&ŸœŸœŸœŸœŸœŸœ"˜¬K˜—KšŸœ˜—š ŸœŸœŸœŸœ 3˜KšŸœŸœ˜K˜%K˜;KšŸœ ŸœŸœGŸœ)˜ˆK˜—KšŸœ˜—K˜—K˜-Kšœ*˜*Kšœ˜K˜K˜—Kšœ6 ˜PšŸœŸœŸœ .˜BKšœ$ ˜AK˜K˜KšœŸœ˜KšœŸœ˜K˜—K˜K˜—–# -- [cluster: GGModelTypes.Cluster]šžœŸœ#Ÿœ˜RKšœ™Kš ŸœŸœŸœŸœŸœ  ˜IKšŸœ, ˜MK˜K˜—–# -- [cluster: GGModelTypes.Cluster]šžœŸœ#Ÿœ˜RKšœ™Kš ŸœŸœŸœŸœŸœ  ˜IKšŸœ, ˜MK˜K˜—š žœŸœ$ŸœŸœŸœŸœ ˜WKšœ™Kšœ)™)Kšœ˜Kšœ˜KšœŸœ ˜&K˜5K˜IKšœ4 ˜KKšœ Ÿœ˜"šŸœŸœŸœŸœ˜K–[seg: GGSegmentTypes.Segment]˜GK–[seg: GGSegmentTypes.Segment]˜CKšŸœ˜—K˜CK˜#K˜Ÿœ R˜¹Kšœ8™8Kšœ6˜6KšŸœŸœ ˜K˜K˜—šž œŸœŸœ˜-Kšœ™K™¼Kš ŸœŸœŸœŸœŸœŸœ˜!KšŸœŸœŸœ˜"KšŸœŸœŸœ˜!šŸ˜KšœŸœ ˜&KšœŸœ ˜"K˜šŸœ ¢ œŸœ˜Kš œ¢œÏtœ£ ¢œ£˜Kšœ£œ£ œ £˜&Kšœ£œ£ œ£˜"Kšœ£œ£ œ £˜,Kšœ£œ£ œ £˜&Kšœ£œ£ œ£˜$Kšœ£œ£ œ£˜0Kšœ£œ£ œ £˜*Kšœ£œ£ œ £˜.Kšœ£œ£ œ £˜*Kšœ£œ£ œ£˜"Kšœ£œ£ œ£˜K˜—KšŸœ˜—K˜—K˜—šžœŸœ˜KšœŸœ ˜&KšœŸœ˜5K˜KšœŸœŸœ˜$K˜Kš ŸœŸœŸœŸœŸœ˜:Kšœ0¢œ-˜ošŸœ˜KšŸœ¢ œ#Ÿœ -˜lKš Ÿ¢ŸœŸœ¢ œŸœŸœ˜DKš Ÿœ¢ œŸœŸœ 7˜q—K˜—K˜)KšœŸœ ˜+Kš œ ŸœŸœŸœŸœŸœ˜CK˜#K˜K˜—š ¢œŸœ[Ÿœ>ŸœŸœ˜ÉI artworkFigure• InterpressšInterpress/Xerox/3.0  f j k j¡¥“ļ= ¤ ¨  Äné\ÄC¡£ r jìÄýG ¢ ¨¡¡¨ x j”””£¡ˆ¡¡ÅXeroxÅResearchÅ RGBLinear£¡¡¦ ç • ” ç­“ÄWÄ?Ä‚«W™Ä[[GÄ)/®—Ä ÓĘëO—ÄèëÄk=>—¡¡¡¡™¢¯“¢°“¡¡¨Ä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™3KšœÏuœ¥œ˜Kšœ¥œ˜)Kšœ,Ÿœ˜8K˜KšŸœ ŸœŸœŸœ˜'šŸ˜KšŸœŸœŸœ ŸœŸœŸœŸœŸœŸœ¡¢œ˜ƒKš ¡¢¡¢œ¢¡¢¡¢˜:K˜;KšŸœŸœ¡¢œ˜*K˜CKšŸœŸœ¡¢œ˜,K˜Kšœ¥œ˜Kšœ¥œ"™(Kšœ¥œ *˜OKšœ¥œ*¥œ¥œ˜;šŸœ Ÿœ ˜0KšœŸœ˜ K˜"šŸœŸœ Ÿœ˜HKšŸœ¡¢œ˜—K˜2K˜šŸœ Ÿ˜Kšœ  œ¥œ ˜BKšœ  œ¥œ˜BKšŸœŸœ˜—K˜—Kš ŸœŸœŸœ¡¢œ !˜QšŸœ ˜K˜!K˜9KšœM¥œ˜RK˜DK˜—šŸ˜Kšœ#Ÿœ˜(—KšŸœ˜—K˜K˜—š žœŸœ$ŸœŸœŸœ ˜eKšœŸœŸœ˜K˜—KšŸœ˜—K˜—K˜—KšœŸœ ˜&K˜"K˜K˜K˜—šžœŸœ$Ÿœf˜£K™Kšœ˜KšœŸœ ˜&KšœŸœ˜,K˜Kšœ'ŸœŸœ˜4K˜šŸœŸœ˜'KšœŸœ˜KšœŸœ˜K˜—šŸœŸœŸœ˜)Kšœ Ÿœ˜KšœŸœ˜K˜—KšŸœ1¢œ-˜tšŸœŸœ˜šžœŸœ˜šŸœ ŸœŸœ˜šŸœŸœ˜Kš¢˜Kšœ*˜*Kšœ˜—šŸœ˜Kšœ*˜*Kš¢˜Kšœ˜—Kšœ˜—KšŸœ+˜/Kš¢ œ>˜HK˜—Kšœ¢œ˜%K˜—šŸœ˜šž œŸœ˜KšœA™AKšœ*˜*Kš¢ œ,˜6K˜—Kšœ¢ œ˜!Kšœ˜—K˜K˜—šžœŸœWŸœK˜ÄšžœŸœ˜!KšœŸœ ˜&KšœŸœ ˜@Kšœ ŸœŸœ˜8K˜&KšœŸœ$Ÿœ%˜]KšœŸœ$Ÿœ%˜]Kš œŸœŸœŸœŸœŸœ˜(KšœŸœŸœ˜K˜0K˜˜QKšœO˜OK˜—KšœŸœ ˜&Kšœ*˜*Kšœ ˜K˜—šžœŸœF˜]š ž œŸœŸœŸœŸœ˜-KšŸœ ˜K˜—K˜(Kšœ Ÿœ˜)K˜+Kš ŸœŸœŸœŸœŸœ˜5K˜Kšœ'˜'Kšœ,˜,Kšœ/˜/Kšœ$˜$KšŸœŸœ¢œJ˜yKšŸœ¢œŸœ˜+K˜K˜—š žœŸœŸœŸœŸœ˜PKšœ ˜ K˜(KšœŸœ˜#K˜$K˜KšœŸœ˜K˜+KšœŸœ˜KšœŸœ˜šŸœŸœŸœŸ˜K˜KšŸœŸœŸœŸœ˜*KšŸœŸœŸœŸœ˜*KšŸœŸœŸœŸœ˜.Kš ŸœŸœ.ŸœŸœŸœ˜GKšŸœŸœŸœŸœ˜KšŸœŸœŸœŸœ˜*KšŸœŸœŸœŸœ˜*Kš ŸœŸœ4ŸœŸœŸœ˜M—šŸ˜KšŸœŸœŸœ˜—KšŸœ˜K˜K˜—šžœŸœ˜«šžœŸœ˜K˜K˜ KšœŸœ˜$KšœŸœŸœ˜K˜@K˜@K˜@K˜@šŸœ ŸœŸœŸ˜ KšœŸœŸœ˜>Kšœ"ŸœŸœ ˜IKšŸœ ŸœB˜UKšŸœŸœE˜]Kš ŸœŸœ ŸœŸœŸœ2˜bKšŸœ˜—KšœŸœŸœ˜5Kšœ"ŸœŸœ˜@K˜WKšŸœ Ÿœ9˜LKšŸœŸœ<˜TKš ŸœŸœ ŸœŸœŸœ)˜YK˜—Kšœ"˜"K˜K˜—šž œŸœS˜cKšžœT˜[K˜&KšœŸœŸœ˜KšœMŸœ Ÿœ˜iKš¢ ™ šŸœŸœŸœ˜šŸœŸ˜KšŸœ¢œ ˜!šŸœ˜šž œŸœ˜Kšœ˜Kšœ-˜-K˜—K˜——K˜—Kš¢ ™ KšŸœ'Ÿœ(˜UšŸœ˜šŸœŸœŸœŸ˜K˜&Kšœ1Ÿœ ˜>KšŸœ˜—K˜—K˜K˜—šžœŸœC˜ZKšœ$™$šž œŸœ Ÿœ Ÿœ#˜VšŸœ Ÿ˜K˜@K˜:K˜@K˜>KšŸœ˜—K˜—šžœŸœŸœ#˜VšŸœ Ÿ˜K˜0K˜*K˜0K˜.KšŸœ˜—K˜—Kšœ ŸœŸœ˜KšœŸœ˜#KšœŸœ ˜&KšŸœ ŸœŸœ Ÿœ˜'šŸœŸœŸœŸ˜KšœŸœ'˜IKšœ3Ÿœ%˜[KšŸœ˜—Kšœ%Ÿœ˜GK˜K˜—š¤œŸœ-Ÿœ˜eKšœ&™&š ž œŸœ ŸœŸœŸœ˜TšŸœŸœ Ÿ˜Kšœ5˜5Kšœ/˜/Kšœ5˜5Kšœ3˜3KšŸœ˜—K˜—šžœŸœ-ŸœŸœ˜UšŸœŸœ Ÿ˜Kšœ%˜%Kšœ˜Kšœ%˜%Kšœ#˜#KšŸœ˜—K˜—KšœŸœ ˜&KšœŸœ˜&šŸœŸœŸœŸ˜K˜K˜>K˜>K˜>K˜nKšŸœŸœ"˜3—K˜K˜—šžœŸœ2Ÿœ6˜…šŸœŸœŸœŸœ˜3KšœŸœ˜KšŸœ˜K˜—šŸœ˜K˜.KšœŸœ˜-KšœŸœ˜*K˜&KšœŸœ˜KšœŸœ˜šŸœ%Ÿœ Ÿ˜:KšŸœŸœŸœ *˜NKšŸœ˜—šŸœŸœ )˜;šœ˜K˜\K˜\K˜—šœ˜K˜\K˜\K˜—šœ˜K˜\K˜\K˜—šœ˜K˜\K˜\K˜—KšŸœŸœ"˜3—K˜*KšœŸœŸœŸœ  3˜uK˜—K˜K˜—šžœŸœŸœ*Ÿœ.˜zKšŸœŸœŸœŸœŸœŸœŸœ˜6šŸœ˜KšœŸœ˜4KšœŸœ˜1KšœŸœ˜šŸœŸœ Ÿ˜4KšŸœŸœŸœ *˜NKšŸœ˜—KšŸœ ŸœŸœ"˜9K˜K˜KšœŸœŸœŸœ  6˜lK˜—K˜K˜K˜—š žœŸœŸœŸœŸœ˜JKšœŸœ˜*KšŸœ˜K˜K˜—š žœŸœŸœŸœŸœ˜MKšœŸœ˜*KšŸœ˜K˜K˜—š žœŸœŸœŸœŸœ˜eKšœ˜Kšœ Ÿœ˜Kšœ ŸœŸœ˜Kšœ-Ÿœ ˜9K–\[sliceD: GGModelTypes.SliceDescriptor, testPoint: GGBasicTypes.Point, tolerance: REAL]šœ œ œq˜|KšŸœŸœ ŸœŸœ˜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šŸœŸœ˜3šŸœŸœŸœ˜!KšœŸœ˜ Kšœ"Ÿœ˜'K˜*Kšœ.Ÿœ˜3K˜—K˜—šœ˜KšœŸœŸœ˜KšœŸœ˜K˜—Kšœ0˜0šœ  ˜!K˜—KšŸœŸœ˜—K˜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˜4KšŸœ˜K˜—K˜:K˜2K˜Kš œ ŸœŸœŸœ ŸœŸœ Ÿœ˜PšŸ˜Kš ŸœŸœŸœŸœŸœ˜SšŸœŸœ˜KšœŸœ˜ KšŸœŸœŸœ˜-K˜"Kš ŸœŸœŸœŸœŸœŸœ˜RšŸ˜Kšœ5Ÿœ ˜AKšœŸœŸœ˜*KšœŸœŸœ˜,KšœŸœŸœ˜,K˜K˜6K˜'KšŸœŸœŸœ)˜;KšŸœ˜—K˜—šŸœŸœŸœ ˜=KšœŸœŸœ˜.K˜K˜6Kš œŸœŸœŸœ ŸœŸœ Ÿœ˜ZKšœ"Ÿœ˜'K˜;KšŸœŸœŸœ)˜;K˜—šŸœ ˜K˜:K˜ Kšœ˜—šŸ˜šœ˜K˜&K˜4K˜——KšŸœ˜—K˜K˜—šžœŸœ8Ÿœ˜pKšœ"™"KšœŸœ˜*Kšœ˜Kšœ Ÿœ]˜kK™-šŸœŸœŸœŸ˜šŸœŸœ˜KšœŸœ˜KšœŸœŸœ˜%K˜—KšŸœ˜—K˜;K˜K˜—šž œŸœ#ŸœŸœ˜_Kšœ ™ KšœŸœ˜*Kšœ˜KšœŸœ˜KšœŸœ˜Kš œ ŸœŸœŸœ ŸœŸœ Ÿœ˜VšŸœŸœŸœŸ˜šŸœŸœ˜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šœ Ÿœ˜&KšœŸœ˜$K˜—KšŸœŸœ˜ K˜—Kšœ Ÿœ#Ÿœ ˜?Kšœ#ŸœŸœ ˜KKšŸœŸœ˜—K˜;K˜K˜—šžœŸœ8ŸœŸœ+Ÿœ>ŸœŸœ ŸœŸœ˜êKšœ"™"šŸœŸœY˜_KšŸœŸœ˜ K˜—šŸ˜KšœŸœ˜Kšœ˜KšœŸœ˜-K˜KšœŸœ˜*Kšœ˜Kšœ Ÿœ˜K˜JKšœŸœŸœ Ÿœ3 3˜ŽK˜wšŸœ Ÿœ˜KšœŸœI˜cKšœ˜—šŸœŸœ /˜IKšœ< ˜KK˜9šŸœŸœŸœ˜;K˜K˜KšŸœ Ÿœ˜&KšŸœŸœF˜eK˜Kšœ Ÿœ˜K˜—K˜—šŸœ Ÿœ˜šŸœŸœ˜ Kšœ˜K˜EK˜EK˜EK˜EšŸœ/Ÿœ˜7Kšœ˜K˜mšŸœŸœ˜ K˜0K˜0Kšœ˜—šŸœŸœ˜K˜0K˜0Kšœ˜—šŸœŸœ˜K˜0K˜0Kšœ˜—šŸœŸœ˜K˜0K˜0Kšœ˜K˜KšœK™KK˜—šŸœŸœ<Ÿœ:Ÿœ˜„K˜—KšŸœ˜Kšœ˜—šŸœ˜Kšœ˜K˜mšŸœŸœ˜ K˜0K˜0Kšœ˜—šŸœŸœ˜K˜0K˜0Kšœ˜—šŸœŸœ˜K˜0K˜0Kšœ˜—šŸœŸœ˜K˜0K˜0Kšœ˜—šŸœŸœ<Ÿœ:Ÿœ˜„K˜—KšŸœ˜Kšœ˜—Kšœ˜—K˜@K˜ K˜4K˜—KšŸœ˜—K˜K˜—š žœŸœ@ŸœŸœŸœE˜³Kšœ+™+KšœŸœŸœ˜KšœŸœ˜-K˜K˜JKšœ œ œ?˜_Kšœ 0˜AKšœ˜K˜K˜K˜—šžœŸœ8ŸœŸœ+Ÿœ=ŸœŸœ ŸœŸœ˜ëKšœ$™$šŸœŸœY˜_KšŸœŸœ˜ —K˜šŸ˜KšœŸœ˜Kšœ˜KšœŸœ˜-KšœŸœ˜*K˜JKšœŸœŸœ Ÿœ3 2˜K˜}šŸœ Ÿœ˜K˜@K˜4KšœŸœL˜fK˜1K˜—KšŸœ˜—K˜K˜—šžœŸœŸœ)ŸœŸœ ŸœŸœŸœŸœŸœŸœŸœŸœ˜KšœŸœ ˜&Kšœ¥œA˜IK˜Kšœ ŸœŸœ˜K˜šœŸœ˜ KšœŸœŸ˜KšœŸ˜Kšœ¥œŸ˜Kšœ¥œŸ˜Kšœ¥œŸ˜Kšœ¥œ˜—šŸœ Ÿœ˜Kšœ ŸœE˜RK˜—K˜K˜—šžœŸœŸœ'Ÿœ ŸœŸœŸœ ˜KšœŸœ˜-KšœŸœ˜*K˜KšœŸœ˜ KšœŸœŸœŸœ˜Kšœ ŸœŸœ˜ Kšœ Ÿœ ˜K˜?Kšœ ŸœŸœ˜KšœR™RKš ŸœŸœŸœŸœ ŸœŸœ˜1šŸœŸœŸœŸ˜Kš ŸœŸœŸœŸœ ,˜PKšœ!˜!K˜EKšŸœŸœŸœ˜šŸœŸœ Ÿœ˜KšŸœŸœ.ŸœŸœ˜>KšŸœŸœ.ŸœŸœ˜>K˜—KšœŸœ˜.K˜Kšœ Ÿœ˜šŸ˜šŸœ˜ Kš ŸœŸœŸœŸœ ,˜UKšœ!˜!K˜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™ šžœŸœ9Ÿœ˜pKšœŸœ˜#KšœŸœ ˜&šŸœŸœŸœŸ˜šŸœ ŸœŸœŸœ˜.šŸœŸ˜K˜-˜K˜KK˜(K˜—KšŸœŸœ˜—K˜—KšŸœ˜—Kšœ˜K˜—š žœŸœ#ŸœŸœŸœ˜kKšœŸœ˜#KšœŸœ ˜&Kšœ ŸœŸœ˜Kšœ˜šŸœŸœŸœŸ˜šŸœ ŸœŸœŸœ˜.šŸœ Ÿ˜K˜)Kš ŸœŸœ.ŸœŸœ Ÿœ˜OK˜—šŸœ˜Kšœ Ÿœ˜K˜%K˜—K˜—KšŸœ˜—šŸœŸœ Ÿœ˜šŸœ ŸœŸœŸ˜ šŸœ ŸœŸœŸœ˜2šŸœ Ÿ˜K˜+Kš ŸœŸœ.ŸœŸœ Ÿœ˜OK˜—šŸœ˜Kšœ Ÿœ˜K˜'K˜—K˜—KšŸœ˜—K˜—Kšœ˜K˜—šžœŸœ9Ÿœ˜nKšœŸœ ˜&šŸœŸ˜K˜"˜K˜@K˜K˜—KšŸœŸœ˜—Kšœ˜K˜—š žœŸœ#ŸœŸœŸœ˜iKšœŸœ ˜&K˜Kšœ˜K˜—šžœŸœ0ŸœŸœ˜KšœŸœ˜#KšœŸœ ˜&šŸœŸœŸœŸ˜KšŸœ ŸœŸœŸœ2˜^KšŸœ˜—KšœE Q˜–K˜K˜—šžœŸœT˜hKšœŸœ˜#KšœŸœ ˜&šŸœŸœŸœŸ˜KšŸœ ŸœŸœŸœ9˜eKšŸœ˜—K˜'K˜+Kšœ˜K˜K˜—š žœŸœ#ŸœŸœŸœŸœ˜wKšœŸœ˜#KšœŸœ ˜&KšœŸœŸœ˜KšœŸœ˜ šŸœŸœŸœŸ˜šŸœ ŸœŸœŸœ˜.šŸœŸœ˜K˜*KšŸœŸœŸœŸœ˜5K˜—šŸœ˜KšœŸœ˜ K˜1K˜—K˜—KšŸœ˜—Kš ŸœŸœŸœŸœŸœ ˜:K˜K˜—šžœŸœS˜hKšœŸœ˜#KšœŸœ ˜&šŸœŸœŸœŸ˜KšŸœ ŸœŸœŸœ.˜ZKšŸœ˜—K˜K˜—š¢¤œŸœW˜nKšœŸœ ˜&K˜"K˜K˜—š ¢¤œŸœ#Ÿœ&ŸœŸœ˜wKšœŸœ ˜&KšŸœ˜K˜K˜—š žœŸœ#Ÿœ+ŸœŸœ˜zKšœŸœ˜#KšœŸœ ˜&KšœŸœŸœ˜Kšœ˜šŸœŸœŸœŸ˜šŸœ ŸœŸœŸœ˜.šŸœŸœ˜K˜(KšŸœŸœŸœ Ÿœ˜1K˜—šŸœ˜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šŸœ5˜8KšŸœŸœ"Ÿœ˜4K˜—šŸœ˜KšœŸœ˜ K˜K˜K˜K˜K˜—K˜—KšŸœ˜—Kš ŸœŸœŸœŸœŸœŸœ ˜TK˜K˜—š žœŸœTŸœ ŸœŸœ˜KšœŸœ ˜&šŸœ Ÿ˜KšœŸœ˜KšœŸœ˜KšœŸœ˜1KšŸœŸœ˜—K˜K˜—š žœŸœ#Ÿœ&ŸœŸœ˜wKšœŸœ ˜&KšŸœŸœŸœŸœ˜,K˜—K˜K˜šžœŸœ˜K˜'K˜—K˜K˜K˜KšŸœ˜—…—A¼µƒ