GGSliceImplA.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
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;
If this record is changed UPDATE BoxCopy and BoxRestore!
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
Fundamentals
getBoundBox: BoxGetBoundBox,
getTransformedBoundBox: GGSlice.GenericTransformedBoundBox,
getTightBox: BoxGetTightBox,
copy: BoxCopy,
restore: BoxRestore,
Drawing
buildPath: BoxBuildPath,
drawBorder: BoxDrawBorder,
drawParts: BoxDrawParts,
drawTransform: BoxDrawTransform,
drawSelectionFeedback: BoxDrawSelectionFeedback,
drawAttractorFeedback: BoxDrawAttractorFeedback,
attractorFeedbackBoundBox: BoxAttractorFeedbackBoundBox,
saveSelections: BoxSaveSelections,
remakeSelections: BoxRemakeSelections,
Transforming
transform: BoxTransform,
Textual Description
describe: BoxDescribe,
describeHit: BoxDescribeHit,
fileout: BoxFileout,
isEmptyParts: BoxIsEmptyParts,
isCompleteParts: BoxIsCompleteParts,
newParts: BoxNewParts,
unionParts: BoxUnionParts,
differenceParts: BoxDiffParts,
movingParts: BoxMovingParts,
augmentParts: BoxAugmentParts,
alterParts: BoxAlterParts,
setSelectedFields: NoOpSetSelectedFields,
Part Generators
pointsInDescriptor: BoxPointsInDescriptor,
walkPointsInDescriptor: BoxWalkPointsInDescriptor,
pointPairsInDescriptor: BoxPointPairsInDescriptor,
segmentsInDescriptor: BoxSegmentsInDescriptor,
walkSegments: BoxWalkSegments,
nextPoint: BoxNextPoint,
nextPointPair: BoxNextPointPair,
nextSegment: BoxNextSegment,
Hit Testing
closestPoint: BoxClosestPoint,
closestJointToHitData: BoxClosestJointToHitData,
closestPointAndTangent: NoOpClosestPointAndTangent,
closestSegment: BoxClosestSegment,
filledPathsUnderPoint: BoxFilledPathsUnderPoint,
lineIntersection: BoxLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: BoxHitDataAsSimpleCurve,
Style
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] = {
requires a bound box input with loX<=hiX AND loY<=hiY
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]];
fill color and other properties are filled in when the caller calls slice.setDefaults.
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
BoxSetBoundBox not needed since boxValid=FALSE
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] = {
IF index NOT IN [0..4) THEN ERROR;
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] = {
Place segments at the proper coordinates, given the current boxData.box.
Ignore parts for now.
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] = {
Ignore parts for now. Try including parts. Ken Pier. July 23, 1991
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];
Make tightBox from the actual box and transform
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] = {
GGModelTypes.SliceBoundBoxProc
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] = {
GGModelTypes.SliceBoundBoxProc
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] = {
GGModelTypes.SliceCopyProc
Just ignore parts and copy the whole box.
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!
Bound box update not needed since boxValid=FALSE in copy
GGProps.CopyAll[fromSlice: slice, toSlice: copySlice];
RETURN[LIST[copySlice]];
};
BoxRestore:
PROC [from: Slice, to: Slice]
= {
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.
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.nullDescriptor is always valid
to.fullDescriptor is unused
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] = {
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.
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
};
Box
DrawBorder:
PROC [slice: Slice, drawParts: SliceParts, transformParts: SliceParts, transform: Transformation, dc: Imager.Context, camera: GGModelTypes.Camera, quick:
BOOL, editConstraints: EditConstraints ← none] = {
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.
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] = {
[Artwork node; type 'Artwork on' to command tool]
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 is the arrow shown in the figure.
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;
Draw fill (including, sampled colors, pixel arrays, and text)
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];
Draw outline
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] = {
Depending on which parts are selected, the points are transformed and the box is rubberbanded properly. The box data itself is not modified.
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 = {
Total transformation has already occurred on point, oppositePoint
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] ];
draw feedback
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] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
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];
};
Drawing Utilities
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];
draw fill
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];
};
};
};
draw outline
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] = {
GGModelTypes.SliceSaveSelectionsProc
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] = {
GGModelTypes.SliceRemakeSelectionsProc
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
};
Transforming
BoxTransform:
PROC [slice: Slice, parts: SliceParts ←
NIL, transform: Transformation, editConstraints: EditConstraints, history: HistoryEvent] = {
GGModelTypes.SliceTransformProc
Permanently transforms the box. Depending on which parts are selected, the points are transformed and the box is grown/shrunk properly.
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 {
Box is responsible for entire document. Following code optimized from SetBoxText
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
};
};
Textual Description
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] = {
GGModelTypes.SliceDescribeProc
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] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
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];
only write node data if not an Outline child. Otherwise, parent Outline takes care of it.
END;
};
BoxFilein:
PROC [f:
IO.
STREAM, version:
REAL, router: MsgRouter, camera: Camera]
RETURNS [slice: Slice] = {
GGModelTypes.SliceFileinProc
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];
};
};
Parts
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] = {
cornerNum is the number of the last corner counted, if any. Otherwise cornerNum is undefined.
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] = {
edgeNum is the number of the last edge counted, if any. Otherwise edgeNum is undefined.
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] = {
GGModelTypes.SliceWalkSegmentsProc
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] = {
GGModelTypes.SliceNewPartsProc
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] = {
GGModelTypes.SliceUnionPartsProc
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] = {
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.
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] = {
GGModelTypes.SliceAugmentPartsProc
boxParts: BoxParts ← NARROW[sliceD.parts];
newParts: BoxParts;
newParts ← NEW[BoxPartsObj ← [corners: boxParts.corners, edges: boxParts.edges, center: boxParts.center] ];
For every edge, add its corresponding corners
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] = {
GGModelTypes.SliceAlterPartsProc
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] = {
GGModelTypes.SliceClosestPointProc
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];
};
Note: It might be best if the angle check is reversed when inside the box.
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]] = {
GGModelTypes.SliceClosestJointToHitDataProc
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] = {
GGModelTypes.SliceClosestSegmentProc
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;
Find the intersections of the given line with bBox. pointCount will be at most 2.
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;
};
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;
};
Box Looks
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;
};
Box
SetStrokeJoint:
PROC [slice: Slice, parts: SliceParts, strokeJoint: StrokeJoint, history: HistoryEvent] = {
boxData: BoxData ← NARROW[slice.data];
boxData.strokeJoint ← strokeJoint;
};
Box
GetStrokeJoint:
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.