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, PutGet, Real, Rope, TextNode, TiogaImager, Vectors2d; GGSliceImplA: CEDAR PROGRAM IMPORTS Feedback, GGBoundBox, GGCoreOps, GGParent, GGParseIn, GGParseOut, GGProps, GGScene, GGSegment, GGShapes, GGSlice, GGSliceOps, GGTransform, GGUtility, Imager, ImagerTransformation, IO, Lines2d, PutGet, Rope, TextNode, TiogaImager, 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; 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], screenStyle] 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], screenStyle] 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 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], NOT boxData.isScreenStyle] 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], NOT boxData.isScreenStyle] 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: ImagerTransformation.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: ImagerTransformation.VEC _ f.t; mInverse: Transformation _ ImagerTransformation.Invert[boxData.transform]; localTranslate: ImagerTransformation.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], boxData.screenStyle] -- 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.PutF["%g ", [real[boxData.segments[edge].strokeWidth]] ]; ENDLOOP; f.PutRope[") strokeEnds: ( "]; FOR edge: INTEGER IN [0..4) DO GGParseOut.WriteStrokeEnd[f, boxData.segments[edge].strokeEnd]; f.PutChar[IO.SP]; ENDLOOP; f.PutRope[") strokeColors: ( "]; FOR edge: INTEGER IN [0..4) DO GGParseOut.WriteColor[f, boxData.segments[edge].color]; f.PutChar[IO.SP]; ENDLOOP; f.PutRope[") fillColor: "]; GGParseOut.WriteColor[f, boxData.fillColor]; f.PutRope[" 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, good, 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.ReadWRope[f, "strokeWidths: ("]; FOR edge: INTEGER IN [0..4) DO strokeWidths[edge] _ GGParseIn.ReadWReal[f]; ENDLOOP; } ELSE strokeWidths _ ALL[2.0]; IF version>=8702.26 THEN { -- read in strokeEnds GGParseIn.ReadWRope[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.ReadWRope[f, ") strokeColors: ("]; FOR edge: INTEGER IN [0..4) DO colors[edge] _ GGParseIn.ReadColor[f, version]; ENDLOOP; GGParseIn.ReadWRope[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.ReadWRope[f, "pa:"]; pa _ GGParseIn.ReadPixelArray[f]; }; IF version>=8704.03 THEN { -- read in dash patterns GGParseIn.ReadWRope[f, "dashes: ("]; FOR edge: INTEGER IN [0..4) DO dashes[edge] _ GGParseIn.ReadBool[f, version].truth; IF dashes[edge] THEN { patterns[edge] _ GGParseIn.ReadArrayOfReal[f]; offsets[edge] _ GGParseIn.ReadWReal[f]; lengths[edge] _ GGParseIn.ReadWReal[f]; }; ENDLOOP; GGParseIn.ReadWRope[f, ")"]; }; IF version>=8706.08 THEN { -- read in segment props hasProps: BOOL _ FALSE; GGParseIn.ReadWRope[f, "props: ("]; FOR edge: INTEGER IN [0..4) DO GGParseIn.ReadWRope[f, "( "]; hasProps _ GGParseIn.ReadBool[f, version].truth; props[edge] _ IF hasProps THEN GGParseIn.ReadListOfRope[f] ELSE NIL; GGParseIn.ReadWRope[f, ")"]; ENDLOOP; GGParseIn.ReadWRope[f, ")"]; }; IF version>=8802.04 THEN { -- read in forward bit GGParseIn.ReadWRope[f, "fwd:"]; [fwd, good] _ GGParseIn.ReadBool[f, version]; }; IF version>=8905.19 THEN { -- read in strokeJoint GGParseIn.ReadWRope[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, good] _ GGParseIn.ReadBool[f, version]; IF NOT good THEN truth _ FALSE; IF truth THEN { fillText: Rope.ROPE; ref: TextNode.Ref; nodeSize, where: INT _ 0; [truth, good] _ GGParseIn.ReadBool[f, version]; -- read screenStyle IF version>=8803.24 THEN where _ GGParseIn.ReadWCARD[f]; -- read location index nodeSize _ GGParseIn.ReadWCARD[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 _ PutGet.FromRope[fillText]; GGSlice.SetBoxText[slice, [ref, where], IF good THEN truth ELSE FALSE, NIL]; }; }; IF version>=8803.24 THEN { text: TextNode.Ref; screenStyle: BOOL _ FALSE; GGParseIn.ReadWRope[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 _ GGSliceOps.GetBoundBox[slice, parts]; -- return a good bound box }; 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. lGGSliceImplA.mesa Contents: Implements the Box slice class. Bier, June 1, 1992 6:11 pm PDT Pier, on May 22, 1992 12:09 pm PDT Copyright c 1986 by Xerox Corporation. All rights reserved. Box Slice Class 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 Ignore parts for now. 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 Êg²˜Icodešœ™šÏnœ"™*Kšœ™Kšœ"™"Kšœ<™<—K™Kšœ™K™šÏk ˜ Jšœ˜—K˜š œžœž˜Jšžœµžœ9˜÷Kšžœ ž˜—˜Kšœ žœžœ ˜!Kšœ žœ˜,Kšœžœ˜'Kšœžœ˜#Kšœžœ˜Kšœžœ˜Kšœ žœ ˜1K˜5Kšœžœ˜Kšœžœ˜3Kšœ žœ˜+Jšœžœ/˜KJšœ žœ˜*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šœ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šœA˜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šœ9˜9Kšœ˜K˜—š œžœžœVžœžœ˜–Kšœ˜Kšœ9˜9K˜Kšœ6˜6Kšœ žœ ˜Kšœ˜Kšœ˜šžœžœžœž˜Kšœžœ˜ Kšžœ˜—K˜K˜—š œžœžœžœžœ˜RKšœžœ ˜&Kšœ˜K˜K˜—š œžœžœ5žœžœ˜tKšœ1žœžœ˜@šžœž˜%˜ Kšœžœ ˜&Kšœ˜šžœžœŸ7˜YKš œžœ žœžœPžœ˜”K˜—šžœŸG˜NK˜=Kš œžœžœžœ`žœ˜ªK˜—Kšœ"˜"Kšœ$˜$Kšœ˜K˜—KšžœžœŸ1˜B—Kšœ˜K˜—š œžœžœžœ'žœžœ˜fšžœž˜%˜ Kšœžœ ˜&Kšœ˜Kšœ"˜"K˜—˜ Kšœ%žœ ˜8Kšœ ˜ Kšœ&˜&K˜—Kšžœ˜—Kšœ˜K˜—š œžœžœžœ(˜Wšžœ žœž˜Kšœ3˜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šÏyœ.™CKšœ žœžœ˜:Kšœžœ!Ÿ1˜[Kšœžœ ˜&Kšœžœ˜#Kš¡/™/š žœ žœžœžœ(žœ$žœŸ˜—KšœI˜IK˜—šžœŸ&˜-š žœžœžœžœŸ/˜Gšžœžœ˜Kšœ%˜%Kšœ(žœ˜/Kšœ;˜;Kšœ;˜;Kšžœ žœžœ&žœžœžœžœžœžœ"˜¬K˜—Kšžœ˜—š žœžœžœžœŸ3˜Kšžœžœ˜Kšœ%˜%Kšœ;˜;Kšžœ žœžœGžœ)˜ˆK˜—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šœ5˜5KšœI˜IKšœ4Ÿ˜KKšœ žœ˜"šžœžœžœžœ˜K–[seg: GGSegmentTypes.Segment]šœG˜GK–[seg: GGSegmentTypes.Segment]šœC˜CKšžœ˜—KšœC˜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–Ó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•Bounds:0.0 mm xmin 0.0 mm ymin 108.8741 mm xmax 56.97361 mm ymax •Artwork Interpress• 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šœ3™3KšœÏuœ¥œ˜Kšœ¥œ˜)Kšœ,žœ˜8K˜Kšžœ žœžœžœ˜'šž˜Kšžœžœžœ žœžœžœžœžœžœ ¡œ˜ƒKš ¡ ¡  ¡ ¡˜:Kšœ;˜;Kšžœžœ ¡œ˜*KšœC˜CKšžœžœ ¡œ˜,K˜Kšœ¥œ˜Kšœ¥œ"™(Kšœ¥œŸ*˜OKšœ¥œ*¥œ¥œ˜;šžœ žœŸ˜0Kšœžœ˜ Kšœ"˜"šžœžœ žœ˜HKšžœ ¡œ˜—Kšœ2˜2Kšœ˜šžœ ž˜Kšœ Ÿœ¥œ ˜BKšœ Ÿœ¥œ˜BKšžœžœ˜—K˜—Kš žœžœžœ ¡œŸ!˜QšžœŸ˜Kšœ!˜!Kšœ9˜9KšœM¥œ˜RKšœD˜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šœ0˜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šœW˜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šœ0˜0Kšœ*˜*Kšœ0˜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šœn˜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šœ6˜6K˜K˜—š œžœ4žœ˜lKšœ ™ Kšœžœ˜+Kšœžœ˜+Kšœ˜Kšžœžœžœžœ ˜*Kšžœžœžœžœ ˜*Kšžœžœžœ ˜*Kšžœžœžœ ˜*Kšžœžœžœ ˜-Kšžœžœžœ ˜-K˜Kš œ žœžœžœ žœžœ žœ˜Xšžœžœžœž˜Kšœ+žœ˜CKšœ'žœ˜=Kšžœ˜—Kšœ#žœ˜7Kšœ=˜=Kšœ˜K˜—š œžœ4žœ˜lKšœžœ˜+Kšœžœ˜+Kšœ˜Kš žœ žœžœ žœžœžœ˜+Kš žœžœžœžœžœžœ ˜@Kš œ žœžœžœ žœžœ žœ˜Xšžœžœžœž˜Kš œžœžœžœžœ˜SKš œžœžœžœžœ˜MKšžœ˜—Kš œžœžœžœžœ˜GKšœ>˜>K˜K˜—š œžœžœžœ žœ˜9šžœž˜Kšžœ'žœ˜0Kšžœžœ˜—K˜—š œžœ žœžœ žœ˜>šžœž˜Kšžœ#žœ˜+Kšžœžœ˜—K˜—šœžœžœžœ˜Fšœžœž˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšžœžœ˜—K˜K˜—šœžœ|žœ9˜ÐKšœ!™!K™WK™MK™jK™ŠKšœžœ˜+Kšœžœ ˜&Kšœ˜Kšœžœžœ˜'Kšœ.˜.Kšœ˜Kšœ,žœ˜4Kš œ žœžœžœžœ˜8K˜šžœžœ˜Kšœ-˜-Kšžœ˜K˜—šžœžœ˜Kšœ&˜&Kšœ4˜4Kšžœ˜K˜—Kšœ:˜:Kšœ2˜2K˜Kš œ žœžœžœ žœžœ žœ˜Pšž˜Kš žœžœžœžœžœ˜Sšžœžœ˜Kšœžœ˜ Kšžœžœžœ˜-Kšœ"˜"Kš žœžœžœžœžœžœ˜Ršž˜Kšœ5žœ ˜AKšœžœžœ˜*Kšœžœžœ˜,Kšœžœžœ˜,Kšœ˜Kšœ6˜6Kšœ'˜'Kšžœžœžœ)˜;Kšžœ˜—K˜—šžœžœžœŸ˜=Kšœžœžœ˜.Kšœ˜Kšœ6˜6Kš œžœžœžœ žœžœ žœ˜ZKšœ"žœ˜'Kšœ;˜;Kšžœžœžœ)˜;K˜—šžœŸ˜Kšœ:˜:Kšœ ˜ Kšœ˜—šž˜šœ˜Kšœ&˜&Kšœ4˜4K˜——Kšžœ˜—K˜K˜—šœžœ8žœ˜pKšœ"™"Kšœžœ˜*Kšœ˜Kšœ žœ]˜kK™-šžœžœžœž˜šžœžœ˜Kšœžœ˜Kšœžœžœ˜%K˜—Kšžœ˜—Kšœ;˜;K˜K˜—š œžœ#žœžœ˜_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šœJ˜JKšœžœžœ žœ3Ÿ3˜ŽKšœw˜wšžœ žœ˜KšœžœI˜cKšœ˜—šžœžœŸ/˜IKšœ<Ÿ˜KKšœ9˜9šžœžœžœ˜;Kšœ˜Kšœ˜Kšžœ žœ˜&KšžœžœF˜eKšœ˜Kšœ žœ˜K˜—K˜—šžœ žœ˜šžœžœ˜ Kšœ˜KšœE˜EKšœE˜EKšœE˜EKšœE˜Ešžœ/žœ˜7Kšœ˜Kšœm˜mšžœžœ˜ Kšœ0˜0Kšœ0˜0Kšœ˜—šžœžœ˜Kšœ0˜0Kšœ0˜0Kšœ˜—šžœžœ˜Kšœ0˜0Kšœ0˜0Kšœ˜—šžœžœ˜Kšœ0˜0Kšœ0˜0Kšœ˜K˜KšœK™KK˜—šžœžœ<žœ:žœ˜„Kšœ˜—Kšžœ˜Kšœ˜—šžœ˜Kšœ˜Kšœm˜mšžœžœ˜ Kšœ0˜0Kšœ0˜0Kšœ˜—šžœžœ˜Kšœ0˜0Kšœ0˜0Kšœ˜—šžœžœ˜Kšœ0˜0Kšœ0˜0Kšœ˜—šžœžœ˜Kšœ0˜0Kšœ0˜0Kšœ˜—šžœžœ<žœ:žœ˜„Kšœ˜—Kšžœ˜Kšœ˜—Kšœ˜—Kšœ@˜@Kšœ ˜ Kšœ4˜4K˜—Kšžœ˜—K˜K˜—š œžœ@žœžœžœE˜³Kšœ+™+Kšœžœžœ˜Kšœžœ˜-Kšœ˜KšœJ˜JKšœŸœŸœ?˜_KšœŸ0˜AKšœ˜K˜K˜K˜—šœžœ8žœžœ+žœ=žœžœ žœžœ˜ëKšœ$™$šžœžœY˜_Kšžœžœ˜ —K˜šž˜Kšœžœ˜Kšœ˜Kšœžœ˜-Kšœžœ˜*KšœJ˜JKšœžœžœ žœ3Ÿ2˜Kšœ}˜}šžœ žœ˜Kšœ@˜@Kšœ4˜4KšœžœL˜fKšœ1˜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šœE˜EKšžœžœžœ˜šžœžœ žœ˜Kšžœžœ.žœžœ˜>Kšžœžœ.žœžœ˜>K˜—Kšœžœ˜.Kšœ˜Kšœ žœ˜šž˜šžœ˜ Kš žœžœžœžœŸ,˜UKšœ!˜!KšœE˜EKšžœžœžœ˜šžœ žœ˜Kšžœžœ,žœžœ˜@Kšžœžœ,žœžœ˜@K˜—šžœ žœ˜Kšžœžœ,žœžœ˜@Kšžœžœ,žœžœ˜@K˜—Kšœžœ˜.Kšœ˜Kšœ žœ˜šž˜Kšœžœ˜ —K˜——Kšžœ˜—Kšžœžœžœ˜Kšœ žœ˜ š žœžœžœ žœžœž˜DK–4[m: ImagerTransformation.Transformation, v: VEC]šœ žœN˜[Kšžœ˜—K˜K˜—šœžœžœžœžœžœžœ˜aKšœžœ ˜)Kšœžœ ˜&Kšœ˜K˜ Kšžœžœžœžœ˜)Kšœ!˜!Kšœ)˜)Kšœ9˜9Kšœ˜K˜K˜—š œžœžœ%žœ˜FKšžœ žœžœžœ˜)Kšžœž˜KšœH˜HKšœH˜HKšœH˜HKšœH˜HKšžœžœ˜K˜—K˜K™ šœžœ9žœ˜pKšœžœ˜#Kšœžœ ˜&šžœžœžœž˜šžœ žœžœžœ˜.šžœž˜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šœ,Ÿ˜FK˜K˜—šœžœT˜hKšœžœ˜#Kšœžœ ˜&šžœžœžœž˜Kšžœ žœžœžœ9˜eKšžœ˜—K˜'K˜+Kšœ˜K˜K˜—š œžœ#žœžœžœžœ˜wKšœžœ˜#Kšœžœ ˜&Kšœžœžœ˜Kšœžœ˜ šžœžœžœž˜šžœ žœžœžœ˜.šžœžœ˜Kšœ*˜*Kšžœžœžœžœ˜5K˜—šžœ˜Kšœžœ˜ Kšœ1˜1K˜—K˜—Kšžœ˜—Kš žœžœžœžœžœŸ˜:K˜K˜—šœžœS˜hKšœžœ˜#Kšœžœ ˜&šžœžœžœž˜Kšžœ žœžœžœ.˜ZKšžœ˜—K˜K˜—š¡¤œžœW˜nJšœžœ ˜&Kšœ"˜"K˜K˜—š ¡¤œžœ#žœ&žœžœ˜wJšœžœ ˜&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šžœžœ ž˜'Jšœž˜Jšœž˜Jšžœ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šžœ˜—…—Ar¹