GGSliceImplF.mesa
Copyright Ó 1988 by Xerox Corporation. All rights reserved.
Pier, July 17, 1991 11:25 am PDT
Bier, January 31, 1991 5:41 pm PST
Contents: Implements IP slice classes in Gargoyle.
DIRECTORY
Feedback, FeedbackTypes, FileNames, FS, FunctionCache, GGBasicTypes, GGBoundBox, GGCoreTypes, GGFileOps, GGFromImager, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGMUserProfile, GGParseIn, GGParseOut, GGProps, GGScene, GGSegment, GGSegmentTypes, GGShapes, GGSlice, GGSliceOps, GGTransform, GGUtility, Imager, ImagerBox, ImagerInterpress, ImagerMaskCapture, ImagerMemory, ImagerTransformation, Interpress, IO, NodeStyle, Real, RefTab, Rope, SF, SymTab, Vectors2d;
GGSliceImplF: CEDAR PROGRAM
IMPORTS Feedback, FileNames, FS, FunctionCache, GGBoundBox, GGFileOps, GGFromImager, GGMUserProfile, GGParseIn, GGParseOut, GGProps, GGScene, GGSegment, GGShapes, GGSlice, GGSliceOps, GGTransform, Imager, ImagerBox, ImagerInterpress, ImagerMaskCapture, ImagerMemory, ImagerTransformation, Interpress, IO, RefTab, Rope, SymTab, Vectors2d
EXPORTS GGSlice = BEGIN
BoundBox: TYPE = REF BoundBoxObj;
BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj;
Camera: TYPE = GGModelTypes.Camera;
CameraObj: TYPE = GGModelTypes.CameraObj;
Color: TYPE = Imager.Color;
DefaultData: TYPE = GGModelTypes.DefaultData;
EditConstraints: TYPE = GGModelTypes.EditConstraints;
ExtendMode: TYPE = GGModelTypes.ExtendMode;
MsgRouter: TYPE = FeedbackTypes.MsgRouter;
HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent;
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;
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;
Transformation: TYPE = ImagerTransformation.Transformation;
Vector: TYPE = GGBasicTypes.Vector;
WalkProc: TYPE = GGModelTypes.WalkProc;
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem;
Interpress-Only Data and Procs
ipMaxPoints: INTEGER = 4;
ipMaxEdges: INTEGER = 4;
IPPointArray: TYPE = ARRAY [0..ipMaxPoints) OF BOOL; -- ll, ul, ur, lr
IPEdgeArray: TYPE = ARRAY [0..ipMaxEdges) OF BOOL; -- left, top, right, bottom
ipCornerRopes: ARRAY [0..ipMaxPoints) OF Rope.ROPE = [
"lower left corner", "upper left corner", "upper right corner", "lower right corner"];
ipEdgeRopes: ARRAY [0..ipMaxEdges) OF Rope.ROPE = [
"left edge", "top edge", "right edge", "bottom edge"];
IPData: TYPE = REF IPDataObj;
IPDataObj: TYPE = RECORD [
localTightBox: BoundBox, -- a tight box in local coordinates. Set when slice is created.
localBox: BoundBox, -- a loose box in local coordinates. Set when slice is created.
includeByValue: BOOL ← FALSE, -- store the actual interpress in the Gargoyle master?
file: Rope.ROPE, -- fileName of Interpress Master
mem: Imager.Context, -- imager memory context
transform: ImagerTransformation.Transformation, -- includes default position
inverse: ImagerTransformation.Transformation,
inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s
seg: Segment -- a null segment whose entire purpose is to contain properties in seg.props
];
IPParts: TYPE = REF IPPartsObj;
IPPartsObj: TYPE = RECORD [
points: IPPointArray, -- which corners of box are selected.
edges: IPEdgeArray, -- which edges of box are selected.
innards: BOOLFALSE -- FALSE => corners and edges only; no innards
];
IPHitData: TYPE = REF IPHitDataObj;
IPHitDataObj: TYPE = RECORD [
point: [-1..ipMaxPoints), -- which point of box is hit.
edge: INTEGER, -- which edge of box is hit.
edge: [-1..ipMaxEdges) THIS DEF CAUSES A FATAL COMPILER ERROR IN PASS FIVE !!
hitPoint: Point
];
MasterStuff: TYPE = RECORD [
master: Interpress.Master,
pixelsPerUnit: REAL
];
ContextStuff: TYPE = RECORD [
memContext: Imager.Context,
localTightBox: BoundBox,
localBox: BoundBox
];
BuildIPSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = {
OPEN GGSlice;
class ← NEW[SliceClassObj ← [
type: $IP,
unlink: GGSlice.UnlinkSlice, -- ipData doesn't need unlinking
Fundamentals
getBoundBox: IPGetBoundBox,
getTransformedBoundBox: GGSlice.GenericTransformedBoundBox,
getTightBox: IPGetTightBox,
copy: IPCopy,
restore: IPRestore,
Drawing
buildPath: NoOpBuildPath,
drawBorder: NoOpDrawBorder,
drawParts: IPDrawParts,
drawTransform: IPDrawTransform,
drawSelectionFeedback: IPDrawSelectionFeedback,
drawAttractorFeedback: NoOpDrawAttractorFeedback,
saveSelections: NoOpSaveSelections,
remakeSelections: NoOpRemakeSelections,
Transforming
transform: IPTransform,
Textual Description
describe: IPDescribe,
describeHit: IPDescribeHit,
fileout: IPFileout,
filein: IPFilein,
Parts
isEmptyParts: IPIsEmptyParts,
isCompleteParts: IPIsCompleteParts,
newParts: IPNewParts,
unionParts: IPUnionParts,
differenceParts: IPDiffParts,
movingParts: IPMovingParts,
augmentParts: IPAugmentParts,
alterParts: NoOpAlterParts,
setSelectedFields: NoOpSetSelectedFields,
Part Generators
pointsInDescriptor: IPPointsInDescriptor,
walkPointsInDescriptor: IPWalkPointsInDescriptor,
pointPairsInDescriptor: IPPointPairsInDescriptor,
segmentsInDescriptor: IPSegmentsInDescriptor,
walkSegments: IPWalkSegments,
nextPoint: IPNextPoint,
nextPointPair: IPNextPointPair,
nextSegment: IPNextSegment,
Hit Testing
closestPoint: IPClosestPoint,
closestJointToHitData: NoOpClosestJointToHitData,
closestPointAndTangent: NoOpClosestPointAndTangent,
closestSegment: IPClosestSegment,
filledPathsUnderPoint: IPFilledPathsUnderPoint,
lineIntersection: NoOpLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: NoOpHitDataAsSimpleCurve,
Style
setDefaults: NoOpSetDefaults,
setStrokeWidth: NoOpSetStrokeWidth,
getStrokeWidth: NoOpGetStrokeWidth,
setStrokeEnd: NoOpSetStrokeEnd,
getStrokeEnd: NoOpGetStrokeEnd,
setStrokeJoint: NoOpSetStrokeJoint,
getStrokeJoint: NoOpGetStrokeJoint,
setStrokeColor: NoOpSetStrokeColor,
getStrokeColor: NoOpGetStrokeColor,
setFillColor: NoOpSetFillColor,
getFillColor: NoOpGetFillColor,
setArrows: NoOpSetArrows,
getArrows: NoOpGetArrows,
setDashed: NoOpSetDashed,
getDashed: NoOpGetDashed,
setOrientation: NoOpSetOrientation,
getOrientation: NoOpGetOrientation
]];
};
MakeIPSliceFromFile: PROC [fullName: Rope.ROPE, router: MsgRouter, transform: ImagerTransformation.Transformation ← NIL, localTightBox: BoundBox, localBox: BoundBox, includeByValue: BOOL] RETURNS [slice: Slice] = {
ipMaster: Interpress.Master ← NIL;
success: BOOLFALSE;
[ipMaster, success] ← GetMaster[fullName, router];
IF NOT success THEN ipMaster ← NIL; -- and continue to make the IPSlice
slice ← MakeIPSliceFromMaster[ipMaster, 2834.646, fullName, router, transform, localTightBox, localBox, includeByValue];
};
MakeIPSliceFromMaster: PUBLIC PROC [ipMaster: Interpress.Master, pixelsPerUnit: REAL ← 2834.646, fullName: Rope.ROPENIL, router: MsgRouter, transform: ImagerTransformation.Transformation ← NIL, localTightBox: BoundBox ← NIL, localBox: BoundBox ← NIL, includeByValue: BOOL] RETURNS [slice: Slice] = {
The interpress master is assumed to be in units of pixelsPerUnit pixels.
The memory context has 1 unit = 1/72.0 inch.
CheckPair: PROC [key: RefTab.Key, val: RefTab.Val] RETURNS [quit: BOOLFALSE] = {
mRef: REF MasterStuff ← NARROW[key];
IF mRef.master=ipMaster AND mRef.pixelsPerUnit=pixelsPerUnit THEN {
contextStuff ← NARROW[val];
memContext ← contextStuff.memContext;
IF localTightBox=NIL THEN localTightBox ← contextStuff.localTightBox;
IF localBox=NIL THEN localBox ← contextStuff.localBox;
RETURN[TRUE];
};
};
found: BOOLFALSE;
memContext: Imager.Context;
masterStuff: REF MasterStuff;
contextStuff: REF ContextStuff;
IF ipMaster=NIL THEN RETURN; -- we need to make some sort of slice, even if the Interpress file is not found
found ← RefTab.Pairs[contextTable, CheckPair];
IF NOT found THEN { -- compute everything
ShowWarnings: Interpress.LogProc = {
Feedback.Append[router, oneLiner, $Warning, explanation];
};
PlayFakeScene: PROC [context: Imager.Context] = {
ImagerMemory.Replay[memContext, context];
};
memContext ← ImagerMemory.NewMemoryContext[]; -- in pixels
Imager.ScaleT[memContext, pixelsPerUnit]; -- pointsPerMeter=2834.646
Following code required to get the context state to the default assumed by Interpress.DoPage
Imager.SetColor[memContext, Imager.black];
Imager.SetAmplifySpace[memContext, 1.0];
Imager.SetStrokeWidth[memContext, 0.0];
Imager.SetStrokeEnd[memContext, square];
Imager.SetStrokeJoint[memContext, miter];
IF ipMaster # NIL THEN Interpress.DoPage[master: ipMaster, page: 1, context: memContext, log: ShowWarnings];
IF localTightBox = NIL THEN {
IF ipMaster = NIL THEN {
localTightBox ← GGBoundBox.CreateBoundBox[0,0,100,100];
localBox ← GGBoundBox.CopyBoundBox[localTightBox];
}
ELSE {
IF useMaskCapture THEN {
sfBox: SF.Box;
imagerBox: Imager.Rectangle;
sfBox ← ImagerMaskCapture.CaptureBounds[PlayFakeScene, ImagerTransformation.Scale[2.0] ! ImagerMaskCapture.Cant => RESUME];
imagerBox ← ImagerTransformation.InverseTransformRectangle[ImagerTransformation.Scale[2.0], ImagerBox.RectangleFromBox[[sfBox.min.s, sfBox.min.f, sfBox.max.s, sfBox.max.f]]]; -- Michael Plass magic
localTightBox ← GGBoundBox.BoundBoxFromRectangle[rect: imagerBox];
localBox ← GGBoundBox.CopyBoundBox[localTightBox];
}
ELSE {
fakeCamera: Camera ← NEW[CameraObj];
fakeScene: Scene ← GGFromImager.Capture[action: PlayFakeScene, camera: fakeCamera !
GGFromImager.WarningMessage => {
Feedback.Append[router, oneLiner, $Warning, message];
RESUME;
};
];
localTightBox ← GGScene.TightBoxOfScene[fakeScene];
localBox ← GGScene.BoundBoxOfScene[fakeScene];
GGScene.MakeSceneGarbage[fakeScene];
};
};
};
masterStuff ← NEW[MasterStuff ← [ipMaster, pixelsPerUnit]];
contextStuff ← NEW[ContextStuff ← [memContext, localTightBox, localBox]];
[] ← RefTab.Insert[contextTable, masterStuff, contextStuff];
};
slice ← MakeIPSliceAux[fullName, memContext, router, transform, includeByValue, localTightBox, localBox, NIL];
};
useMaskCapture: BOOLFALSE;
MakeIPSliceFromMaskPixel: PUBLIC PROC [pa: Imager.PixelArray, color: Color, router: MsgRouter, transform: ImagerTransformation.Transformation ← NIL] RETURNS [slice: Slice] = {
The transformations should be set up so that MaskPixel[pa] will look correct in a context where the units are pixels if transform is concatenated before pa is drawn. In the common case, transform will be a pure translation.
memContext: Imager.Context;
ipData: IPData;
inverse: ImagerTransformation.Transformation;
inverseScale: Imager.VEC;
localTightBox, localBox: BoundBox;
The memory context has 1 unit = 1/72.0 inch.
memContext ← ImagerMemory.NewMemoryContext[];
Imager.SetColor[memContext, color];
Imager.MaskPixel[memContext, pa];
transform ← IF transform=NIL THEN GGTransform.Identity[] ELSE transform;
inverse ← ImagerTransformation.Invert[transform];
inverseScale ← ImagerTransformation.Factor[inverse].s;
localTightBox ← GGBoundBox.BoundBoxOfPixelArray[pa];
localBox ← GGBoundBox.CopyBoundBox[localTightBox];
ipData ← NEW[IPDataObj ← [localTightBox: localTightBox, localBox: localBox, includeByValue: TRUE, file: NIL, mem: memContext, transform: transform, inverse: inverse, inverseScale: inverseScale, seg: GGSegment.MakeLine[p0: [-1.0, -1.0], p1: [1.0, 1.0], props: NIL ] ]];
ipData.seg.strokeWidth ← -1.0; -- so no legal stroke width matches
slice ← NEW[SliceObj ← [
class: GGSlice.FetchSliceClass[$IP],
data: ipData,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE],
tightBox: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox
boundBox: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox
boxValid: FALSE
]];
IPSetBoundBox[slice];
};
MakeIPSliceAux: PROC [fileName: Rope.ROPE, memContext: Imager.Context, router: MsgRouter, transform: ImagerTransformation.Transformation, includeByValue: BOOL, localTightBox: BoundBox, localBox: BoundBox, seg: Segment] RETURNS [slice: Slice] = {
ipData: IPData;
inverse: ImagerTransformation.Transformation;
inverseScale: Imager.VEC;
transform ← IF transform=NIL THEN GGTransform.Identity[] ELSE transform;
inverse ← ImagerTransformation.Invert[transform];
inverseScale ← ImagerTransformation.Factor[inverse].s;
ipData ← NEW[IPDataObj ← [localTightBox: localTightBox, localBox: localBox, includeByValue: includeByValue, file: fileName, mem: memContext, transform: transform, inverse: inverse, inverseScale: inverseScale, seg: IF seg#NIL THEN seg ELSE GGSegment.MakeLine[p0: [-1.0, -1.0], p1: [1.0, 1.0], props: NIL ] ]];
ipData.seg.strokeWidth ← -1.0; -- so no legal stroke width matches
slice ← NEW[SliceObj ← [
class: GGSlice.FetchSliceClass[$IP],
data: ipData,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE],
tightBox: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox
boundBox: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox
boxValid: FALSE
]];
IPSetBoundBox[slice];
};
SetIncludeByValue: PUBLIC PROC [slice: Slice, includeByValue: BOOL, history: HistoryEvent] = {
ipData: IPData ← NARROW[slice.data];
ipData.includeByValue ← includeByValue;
};
GetIncludeByValue: PUBLIC PROC [slice: Slice] RETURNS [includeByValue: BOOL ← FALSE] = {
ipData: IPData ← NARROW[slice.data];
includeByValue ← ipData.includeByValue;
};
gUseLatestIPVersion: BOOLFALSE;
SetDefaultUseLatestIPVersion: PUBLIC PROC [useLatestIPVersion: BOOL] = {
gUseLatestIPVersion ← useLatestIPVersion;
};
GetDefaultUseLatestIPVersion: PUBLIC PROC [] RETURNS [useLatestIPVersion: BOOL] = {
useLatestIPVersion ← gUseLatestIPVersion;
};
Interpress Class Procs
Fundamentals
IPSetBoundBox: PROC [slice: Slice] = {
ipData: IPData ← NARROW[slice.data];
slice.tightBox ← GGBoundBox.BoundBoxOfBoundBox[ipData.localTightBox, ipData.transform];
slice.boundBox ← GGBoundBox.BoundBoxOfBoundBox[ipData.localBox, ipData.transform];
};
IPGetBoundBox: PROC [slice: Slice, parts: SliceParts ← NIL] RETURNS [box: BoundBox]= {
GGModelTypes.SliceBoundBoxProc
IPs always keep a current bound box and tight box
RETURN[slice.boundBox];
};
IPGetTightBox: PROC [slice: Slice, parts: SliceParts ← NIL] RETURNS [box: BoundBox]= {
GGModelTypes.SliceBoundBoxProc
IPs always keep a current bound box and tight box
ipData: IPData ← NARROW[slice.data];
RETURN[slice.tightBox];
};
IPCopy: PROC [slice: Slice, parts: SliceParts ← NIL] RETURNS [copy: LIST OF Slice] = {
GGModelTypes.SliceCopyProc
Just ignore parts and copy the whole box.
copySlice: Slice;
ipData: IPData ← NARROW[slice.data];
transform: Imager.Transformation ← ImagerTransformation.Copy[ipData.transform];
copySlice ← MakeIPSliceAux[ipData.file, ipData.mem, NIL, transform, ipData.includeByValue, GGBoundBox.CopyBoundBox[ipData.localTightBox], GGBoundBox.CopyBoundBox[ipData.localBox], GGSegment.CopySegment[ipData.seg] ];
GGProps.CopyAll[fromSlice: slice, toSlice: copySlice];
RETURN[LIST[copySlice]];
};
IPRestore: 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#$IP THEN ERROR;
BEGIN
fromData: IPData ← NARROW[from.data];
toData: IPData ← NARROW[to.data];
IF GGSlice.copyRestore THEN {
toData.localTightBox^ ← fromData.localTightBox^;
toData.localBox^ ← fromData.localBox^;
toData.includeByValue ← fromData.includeByValue;
toData.file ← fromData.file;
toData.mem ← fromData.mem; -- ASSERT: ImagerMemory context immutable
toData.transform ← fromData.transform;
toData.inverse ← fromData.inverse;
toData.inverseScale ← fromData.inverseScale;
toData.seg ← fromData.seg;
}
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;
};
Drawing
IPDrawParts: PROC [slice: Slice, parts: SliceParts ← NIL, dc: Imager.Context, camera: Camera, quick: BOOL] = {
ipData: IPData ← NARROW[slice.data];
ipParts: IPParts ← NARROW[parts];
DoIPDrawParts: PROC = {
CachedIPDraw[dc, slice, NIL];
};
IF ipParts = NIL OR ipParts.innards THEN Imager.DoSave[dc, DoIPDrawParts];
};
IPDrawTransform: PROC [slice: Slice, parts: SliceParts ← NIL, dc: Imager.Context, camera: Camera, transform: Transformation, editConstraints: EditConstraints] = {
GGModelTypes.SliceDrawTransformProc
DoIPDrawTransform: PROC = {
CachedIPDraw[dc, slice, transform];
};
Imager.DoSave[dc, DoIPDrawTransform];
};
IPDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: Camera, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
The feedback rectangle can be rotated. It is the tight bounding box of the rectangular interpress master. It is derived from the local box.
slowNormal, slowHot, completeNormal, completeHot: BOOLFALSE;
firstJoint: Point;
normalParts, hotIPParts: IPParts;
ipData: IPData ← NARROW[slice.data];
transform: Transformation ← ipData.transform;
IF caretIsMoving OR dragInProgress OR camera.quality=quality THEN RETURN;
IF selectedParts = NIL AND hotParts = NIL THEN RETURN;
normalParts ← NARROW[selectedParts];
hotIPParts ← NARROW[hotParts];
completeNormal ← normalParts#NIL AND IsComplete[normalParts];
completeHot ← hotIPParts#NIL AND IsComplete[hotIPParts];
slowNormal ← normalParts#NIL AND (NOT quick OR (quick AND NOT completeNormal));
slowHot ← hotIPParts#NIL AND (NOT quick OR (quick AND NOT completeHot));
IF slowNormal AND slowHot THEN DrawSelectionFeedbackAux[slice, ipData, normalParts, hotIPParts, dc, transform, camera]
ELSE IF slowNormal THEN DrawSelectionFeedbackAux[slice, ipData, normalParts, NIL, dc, transform, camera]
ELSE IF slowHot THEN DrawSelectionFeedbackAux[slice, ipData, NIL, hotIPParts, dc, transform, camera];
IF (NOT slowNormal AND completeNormal) OR (NOT slowHot AND completeHot) THEN {
fullParts: IPParts ← IF completeNormal THEN normalParts ELSE hotIPParts;
[] ← GGSliceOps.GetTightBox[slice]; -- force update of internal data
firstJoint ← ImagerTransformation.Transform[transform, [ipData.localTightBox.loX, ipData.localTightBox.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
Props: TYPE = REF PropsRec;
PropsRec: TYPE = RECORD [
slice: Slice,
cacheable: BOOLTRUE
];
FindImagerObject: PROC [ipData: IPData] RETURNS [object: Imager.Object] = {
CompareProps: FunctionCache.CompareProc = {
cachedProps: Props ← NARROW[argument];
cachedSlice: Slice ← cachedProps.slice;
cachedData: IPData ← NARROW[cachedSlice.data];
RETURN[cachedData.mem=ipData.mem];
};
value: FunctionCache.Range;
ok: BOOL;
[value, ok] ←
FunctionCache.Lookup[x: cache, compare: CompareProps, clientID: $GGIPObject];
RETURN [IF ok THEN NARROW[value] ELSE NIL];
};
CreateProps: PROC [slice: Slice] RETURNS [props: Props] = {
props ← NEW[PropsRec];
props.slice ← IPCopy[slice].first;
};
CachedIPDraw: PROC [dc: Imager.Context, slice: Slice, viewView: Transformation] = {
OPEN ImagerTransformation; -- see Transform and InverseTransform
object: Imager.Object;
ipProps: Props;
ipData: IPData ← NARROW[slice.data];
success: BOOLTRUE;
IF GGMUserProfile.GetTurboOn[] AND (viewView=NIL OR CloseToTranslation[viewView,identity]) THEN {
object ← FindImagerObject[ipData];
IF object = NIL THEN { -- put circle in cache
clipR: Imager.Rectangle ← GGBoundBox.RectangleFromBoundBox[ipData.localBox];
props: Props ← CreateProps[slice];
object ← NEW[Imager.ObjectRep ← [draw: IPDrawObject, clip: clipR, data: props]];
FunctionCache.Insert[cache, props, object, 60, $GGIPObject];
};
BEGIN
ipProps ← NARROW[object.data];
IF viewView # NIL THEN Imager.ConcatT[dc, viewView];
Imager.ConcatT[dc, ipData.transform];
IF NOT ipProps.cacheable THEN GOTO DrawPlain;
Imager.DrawObject[context: dc, object: object, interactive: TRUE, position: [0.0, 0.0]
! ImagerMaskCapture.Cant => {
success ← FALSE;
CONTINUE;
};
];
IF NOT success THEN {
ipProps.cacheable ← FALSE;
GOTO DrawPlain;
};
EXITS
DrawPlain => IPDrawInnards[dc, ipData];
END
}
ELSE {
IF viewView # NIL THEN Imager.ConcatT[dc, viewView];
Imager.ConcatT[dc, ipData.transform];
IPDrawInnards[dc, ipData];
};
};
identity: Transformation ← ImagerTransformation.Scale[1.0];
IPDrawObject: PROC [self: Imager.Object, context: Imager.Context] = {
Imager.Object.draw PROC
NOT called with the dc transformation already set to object coordinates
OPEN ImagerTransformation;
ipProps: Props ← NARROW[self.data];
ipData: IPData ← NARROW[ipProps.slice.data];
IPDrawInnards[context, ipData];
};
IPDrawInnards: PROC [dc: Imager.Context, ipData: IPData] = {
ENABLE Imager.Warning => {
Feedback.PutFByName[$Gargoyle, oneLiner, $Warning, "Imager Warning[%g]: %g", [atom[error.code]], [rope[error.explanation]] ];
RESUME;
};
Imager.SetColor[dc, Imager.black]; -- otherwise the replay could end up in a random color.
ImagerMemory.Replay[ipData.mem, dc];
};
DrawSelectionFeedbackAux: PROC [slice: Slice, ipData: IPData, normalParts: IPParts, hotParts: IPParts, dc: Imager.Context, t: Transformation, camera: Camera] = {
DoDrawFeedback: PROC = {
box: BoundBox ← ipData.localTightBox;
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 point: INTEGER IN [0..ipMaxPoints) DO
thisCPisHot ← hotParts#NIL AND hotParts.points[point];
thisCPisSelected ← normalParts#NIL AND normalParts.points[point];
IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, pts[point], hot, camera.cpScale];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[point], normal, camera.cpScale];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[point], camera.cpScale];
ENDLOOP;
Imager.SetStrokeJoint[dc, round];
Imager.SetStrokeWidth[dc, 1.0];
Imager.SetColor[dc, Imager.black];
IF normalParts # NIL THEN {
FOR edge: INTEGER IN [0..ipMaxEdges) DO
IF normalParts.edges[edge] THEN SELECT edge FROM
0 => Imager.MaskVector[dc, pts[0], pts[1]];
1 => Imager.MaskVector[dc, pts[1], pts[2]];
2 => Imager.MaskVector[dc, pts[2], pts[3]];
3 => Imager.MaskVector[dc, pts[3], pts[0]];
ENDCASE => ERROR;
ENDLOOP;
};
};
Imager.DoSave[dc, DoDrawFeedback];
};
Transforming
IPTransform: PROC [slice: Slice, parts: SliceParts ← NIL, transform: Transformation, editConstraints: EditConstraints, history: HistoryEvent] = {
GGModelTypes.SliceTransformProc
ipData: IPData ← NARROW[slice.data];
ipData.transform ← ImagerTransformation.Concat[ipData.transform, transform];
ipData.inverse ← ImagerTransformation.Invert[ipData.transform];
ipData.inverseScale ← ImagerTransformation.Factor[ipData.inverse].s;
IPSetBoundBox[slice];
};
Textual Description
IPDescribe: PROC [sliceD: SliceDescriptor] RETURNS [rope: Rope.ROPE] = {
GGModelTypes.SliceDescribeProc
prefix: Rope.ROPE;
ipParts: IPParts;
eCount, pCount: NAT ← 0;
IF sliceD.parts = NIL THEN RETURN["an IP slice"];
ipParts ← NARROW[sliceD.parts];
FOR i: INTEGER IN [0..ipMaxPoints) DO
IF ipParts.points[i] THEN {
pCount ← pCount + 1;
prefix ← ipCornerRopes[i];
};
ENDLOOP;
FOR i: INTEGER IN [0..ipMaxEdges) DO
IF ipParts.edges[i] THEN {
eCount �ount + 1;
prefix ← ipEdgeRopes[i];
};
ENDLOOP;
IF pCount+eCount>1 THEN RETURN["multiple parts of an IP slice"];
IF eCount=0 AND pCount=0 THEN RETURN["NO parts of an IP slice"];
rope ← Rope.Concat[prefix, " of an IP slice"];
};
IPDescribeHit: PROC [slice: Slice, hitData: REF ANY] RETURNS [rope: Rope.ROPE] = {
ipHitData: IPHitData ← NARROW[hitData];
prefix: Rope.ROPE;
IF ipHitData.point#-1 THEN prefix ← ipCornerRopes[ipHitData.point]
ELSE IF ipHitData.edge#-1 THEN prefix ← ipEdgeRopes[ipHitData.edge]
ELSE ERROR;
rope ← Rope.Concat[prefix, " of an IP slice"];
};
IPFileout: PROC [slice: Slice, f: IO.STREAM] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
FromMemory: PROC [dc: Imager.Context] = {
Imager.SetColor[dc, Imager.black]; -- otherwise the replay could end up in a random color.
ImagerMemory.Replay[ipData.mem, dc];
};
ipData: IPData ← NARROW[slice.data];
ipRef: ImagerInterpress.Ref;
masterStream: IO.STREAM;
masterSize: CARD;
masterRope: Rope.ROPE;
f.PutF["\"%q\" ", [rope[ipData.file]]];
GGParseOut.WriteTransformation[f, ipData.transform];
f.PutChar[IO.SP];
GGParseOut.WriteBox[f, ipData.localTightBox];
f.PutChar[IO.SP];
GGParseOut.WriteBox[f, ipData.localBox];
f.PutRope[" includeByValue: "];
GGParseOut.WriteBool[f, ipData.includeByValue];
f.PutChar[IO.CR];
IF ipData.includeByValue THEN {
Write the interpress into a Rope.ROPE.
masterStream ← IO.ROS[];
ipRef ← ImagerInterpress.CreateFromStream[masterStream, "Interpress/Xerox/3.0 "];
ImagerInterpress.DoPage[ipRef, FromMemory];
ImagerInterpress.Finish[ipRef];
masterRope ← masterStream.RopeFromROS[];
masterSize ← Rope.Length[masterRope];
Send the rope to stream f.
f.PutF["%g\n", [integer[masterSize]]];
f.PutRope[masterRope];
};
f.PutRope[" props: ( "];
GGParseOut.WriteBool[f, ipData.seg.props#NIL];
IF ipData.seg.props#NIL THEN GGParseOut.WriteProps[f, ipData.seg.props] ELSE f.PutChar[IO.SP]; -- list of ROPE
f.PutRope[")"];
};
IPFilein: PROC [f: IO.STREAM, version: REAL, router: MsgRouter, camera: Camera] RETURNS [slice: Slice ← NIL] = {
GGModelTypes.SliceFileinProc
ipMaster: Interpress.Master ← NIL;
fullName: Rope.ROPE;
success: BOOLFALSE;
transform: ImagerTransformation.Transformation;
masterText: Rope.ROPE;
masterSize: CARD;
includeByValue: BOOL;
localTightBox, localBox: BoundBox;
props: LIST OF Rope.ROPENIL;
fullName ← f.GetRopeLiteral[];
transform ← GGParseIn.ReadTransformation[f];
IF version >= 8701.28 THEN {
localTightBox ← GGParseIn.ReadBox[f];
IF version >= 8710.19 THEN {
localBox ← GGParseIn.ReadBox[f];
}
ELSE {
localBox ← GGBoundBox.CopyBoundBox[localTightBox];
GGBoundBox.EnlargeByOffset[localTightBox, 5.0]; -- allow for stroke widths up to 10.0
};
}
ELSE {localTightBox ← NIL; localBox ← NIL};
IF Rope.Length[fullName] #0 THEN {
IF GetDefaultUseLatestIPVersion[] THEN {
fullName ← FileNames.StripVersionNumber[fullName];
localTightBox ← NIL; -- force recalculation of bound box
};
[fullName] ← FS.FileInfo[fullName ! FS.Error => CONTINUE;]; -- add the version number or die if no file name (includeByValue better be true!)
};
IF version >= 8612.04 THEN {
goodValue: BOOL;
GGParseIn.ReadWRope[f, "includeByValue:"];
[includeByValue, goodValue] ← GGParseIn.ReadBool[f, version];
IF NOT goodValue THEN ERROR;
IF includeByValue THEN {
Read a description of yourself from stream f.
masterSize ← GGParseIn.ReadWCARD[f];
GGParseIn.ReadBlank[f];
masterText ← IO.GetRope[f, masterSize, TRUE];
ipMaster ← Interpress.FromRope[masterText, NIL];
next make an IPSlice and return it
slice ← MakeIPSliceFromMaster[ipMaster, 1.0, fullName, router, transform, localTightBox, localBox, TRUE];
}
ELSE {
slice ← MakeIPSliceFromFile[fullName, router, transform, localTightBox, localBox, FALSE];
};
}
ELSE {
slice ← MakeIPSliceFromFile[fullName, router, transform, localTightBox, localBox, FALSE];
};
IF version>=8706.08 THEN { -- read in segment props
hasProps: BOOLFALSE;
GGParseIn.ReadWRope[f, "props: ( "];
hasProps ← GGParseIn.ReadBool[f, version].truth;
props ← IF hasProps THEN GGParseIn.ReadListOfRope[f] ELSE NIL;
GGParseIn.ReadWRope[f, ")"];
IF props#NIL THEN {
seg: Segment ← NARROW[slice.data, IPData].seg;
FOR next: LIST OF Rope.ROPE ← props, next.rest UNTIL next=NIL DO
seg.props ← CONS[next.first, seg.props];
ENDLOOP;
};
};
};
Hit Testing
IPIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL ← FALSE] = {
GGModelTypes.SliceEmptyPartsProc
RETURN[IsEmpty[sliceD.parts]];
};
IPIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL ← FALSE] = {
RETURN[IsComplete[sliceD.parts]];
};
IPNewParts: PROC [slice: Slice, hitData: REF ANY, mode: SelectMode] RETURNS [sliceD: SliceDescriptor] = {
GGModelTypes.SliceNewPartsProc
ipHitData: IPHitData ← NARROW[hitData];
ipParts: IPParts ← NEW[IPPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE] ] ];
SELECT mode FROM
literal => {
IF ipHitData.point#-1 THEN ipParts.points[ipHitData.point] ← TRUE
ELSE IF ipHitData.edge#-1 THEN ipParts.edges[ipHitData.edge] ← TRUE
ELSE ERROR;
};
joint => {
IF ipHitData.point#-1 THEN {
ipParts.points[ipHitData.point] ← TRUE;
}
ELSE {
hitData: REF ANY;
pointHitData: IPHitData;
success: BOOL;
wholeD: SliceDescriptor;
wholeD ← IPNewParts[slice, NIL, slice];
[----, ----, ----, hitData, success] ← GGSliceOps.ClosestPoint[wholeD, ipHitData.hitPoint, GGUtility.plusInfinity];
IF NOT success THEN ERROR;
pointHitData ← NARROW[hitData];
IF pointHitData.point = -1 THEN ERROR;
ipParts.points[pointHitData.point] ← TRUE;
};
ipParts.innards ← TRUE;
};
segment => IF ipHitData.edge#-1 THEN{
ipParts.edges[ipHitData.edge] ← TRUE;
ipParts.innards ← TRUE;
};
controlPoint => {
ipParts.points ← ALL[TRUE];
ipParts.edges ← ALL[TRUE];
};
traj, slice, topLevel => MakeComplete[ipParts];
none => { -- leave ipParts empty
};
ENDCASE => ERROR;
sliceD ← GGSlice.DescriptorFromParts[slice, ipParts];
};
IPUnionParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aPlusB: SliceDescriptor] = {
GGModelTypes.SliceUnionPartsProc
ipPartsA: IPParts ← NARROW[partsA.parts];
ipPartsB: IPParts ← NARROW[partsB.parts];
newParts: IPParts;
IF partsA = NIL OR partsB = NIL THEN ERROR;
IF partsA.parts = NIL THEN RETURN[partsB];
IF partsB.parts = NIL THEN RETURN[partsA];
newParts ← NEW[IPPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE] ] ];
FOR i: INTEGER IN [0..ipMaxPoints) DO
newParts.points[i] ← ipPartsA.points[i] OR ipPartsB.points[i];
ENDLOOP;
FOR i: INTEGER IN [0..ipMaxEdges) DO
newParts.edges[i] ← ipPartsA.edges[i] OR ipPartsB.edges[i];
ENDLOOP;
newParts.innards ← ipPartsA.innards OR ipPartsB.innards;
aPlusB ← GGSlice.DescriptorFromParts[partsA.slice, newParts];
};
IPDiffParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aMinusB: SliceDescriptor] = {
ipPartsA: IPParts ← NARROW[partsA.parts];
ipPartsB: IPParts ← NARROW[partsB.parts];
newParts: IPParts;
IF partsA = NIL OR partsB = NIL THEN ERROR;
IF partsA.parts = NIL OR partsB.parts = NIL THEN RETURN[partsA];
newParts ← NEW[IPPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE] ] ];
FOR i: INTEGER IN [0..ipMaxPoints) DO
newParts.points[i] ← IF ipPartsB.points[i] THEN FALSE ELSE ipPartsA.points[i];
ENDLOOP;
FOR i: INTEGER IN [0..ipMaxEdges) DO
newParts.edges[i] ← IF ipPartsB.edges[i] THEN FALSE ELSE ipPartsA.edges[i];
ENDLOOP;
newParts.innards ← IF ipPartsB.innards THEN FALSE ELSE ipPartsA.innards;
aMinusB ← GGSlice.DescriptorFromParts[partsA.slice, newParts];
};
IPMovingParts: PROC [slice: Slice, selectedParts: SliceParts, editConstraints: EditConstraints, bezierDrag: GGInterfaceTypes.BezierDragRecord] RETURNS [background, overlay, rubber, drag: SliceDescriptor] = {
GGModelTypes.SliceMovingPartsProc
If anything is moving, everything is dragging. Otherwise on background.
dragParts: IPParts ← NEW[IPPartsObj ← [points: ALL[TRUE], edges: ALL[TRUE], innards: TRUE ] ];
backgroundParts: IPParts ← NEW[IPPartsObj ← [points: ALL[TRUE], edges: ALL[TRUE], innards: TRUE ] ];
IF IsEmpty[selectedParts] THEN { -- all on background
dragParts.edges ← ALL[FALSE];
dragParts.points ← ALL[FALSE];
dragParts.innards ← FALSE;
}
ELSE { -- all dragging
backgroundParts.edges ← ALL[FALSE];
backgroundParts.points ← ALL[FALSE];
backgroundParts.innards ← FALSE;
};
background ← GGSlice.DescriptorFromParts[slice, backgroundParts];
overlay ← rubber ← GGSlice.DescriptorFromParts[slice, NIL];
drag ← GGSlice.DescriptorFromParts[slice, dragParts];
};
IPAugmentParts: PROC [sliceD: SliceDescriptor, selectClass: SelectionClass] RETURNS [more: SliceDescriptor] = {
GGModelTypes.SliceAugmentPartsProc
ipParts: IPParts ← NARROW[sliceD.parts];
newParts: IPParts ← NEW[IPPartsObj ← [points: ipParts.points, edges: ipParts.edges, innards: ipParts.innards] ];
For every edge, add its corresponding points
FOR i: INTEGER IN [0..ipMaxEdges) DO
IF ipParts.edges[i] THEN SELECT i FROM
0 => {newParts.points[0] ← TRUE; newParts.points[1] ← TRUE;};
1 => {newParts.points[1] ← TRUE; newParts.points[2] ← TRUE;};
2 => {newParts.points[2] ← TRUE; newParts.points[3] ← TRUE;};
3 => {newParts.points[3] ← TRUE; newParts.points[0] ← TRUE;};
ENDCASE => ERROR;
ENDLOOP;
more ← GGSlice.DescriptorFromParts[sliceD.slice, newParts];
};
IPPointsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = {
parts: IPParts ← NARROW[sliceD.parts];
pointGen ← NEW[PointGeneratorObj ← [sliceD, 0, 0, NIL] ];
FOR point: INTEGER IN [0..ipMaxPoints) DO
IF parts.points[point] THEN pointGen.toGo ← pointGen.toGo + 1;
ENDLOOP;
};
IPWalkPointsInDescriptor: PROC [sliceD: SliceDescriptor, walkProc: PointWalkProc] = {
parts: IPParts ← NARROW[sliceD.parts];
ipData: IPData ← NARROW[sliceD.slice.data];
done: BOOLFALSE;
FOR point: INTEGER IN [0..ipMaxPoints) DO
IF parts.points[point] THEN {
done ← walkProc[GetIPPoint[ipData, point]];
IF done THEN RETURN;
};
ENDLOOP;
};
IPPointPairsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointPairGen: PointPairGenerator] = {
parts: IPParts ← NARROW[sliceD.parts];
pointPairGen ← NEW[PointPairGeneratorObj ← [sliceD, 0, 0, NIL] ]; -- toGo and index not used
FOR edge: INTEGER IN [0..ipMaxEdges) DO
IF parts.edges[edge] THEN pointPairGen.toGo ← pointPairGen.toGo + 1;
ENDLOOP;
};
IPSegmentsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [segGen: SegmentGenerator] = {
IP slices have only one "segment" whose entire purpose is to contain properties in seg.props
parts: IPParts ← NARROW[sliceD.parts];
segGen ← NEW[SegmentGeneratorObj ← [NIL, 0, 0, 0, NIL, FALSE, sliceD, NIL] ]; -- toGo and index now used
FOR edge: INTEGER IN [0..ipMaxEdges) DO
IF parts.edges[edge] THEN segGen.toGo ← 1;-- any edge sets segGen.toGo = 1
ENDLOOP;
};
IPWalkSegments: PROC [slice: Slice, walkProc: WalkProc] RETURNS [sliceD: SliceDescriptor] = {
IP slices have only one "segment" whose entire purpose is to contain properties in seg.props
ipData: IPData ← NARROW[slice.data];
RETURN[IF walkProc[ipData.seg, NIL] THEN IPNewParts[slice, NIL, slice] ELSE IPNewParts[slice, NIL, none]];
};
IPNextSegment: PROC [slice: Slice, segGen: SegmentGenerator] RETURNS [seg: Segment, transform: Transformation] = {
IF segGen=NIL OR segGen.toGo = 0 THEN RETURN[NIL, NIL]
ELSE {
ipData: IPData ← NARROW[segGen.sliceD.slice.data];
seg ← ipData.seg;
segGen.toGo ← 0;
};
};
IPNextPoint: 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;
ipData: IPData ← NARROW[sliceD.slice.data];
ipParts: IPParts ← NARROW[sliceD.parts];
index: INTEGER ← -1;
pointAndDone.done ← FALSE;
FOR index ← pointGen.index, index+1 UNTIL index >=ipMaxPoints DO
IF ipParts.points[index] THEN EXIT; -- index will point to next available point
ENDLOOP;
pointAndDone.point ← GetIPPoint[ipData, 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
};
};
GetIPPoint: PROC [ipData: IPData, index: INTEGER] RETURNS [point: Point] = {
box: BoundBox ← ipData.localTightBox;
point ← ImagerTransformation.Transform[ipData.transform, SELECT index FROM
0 => [box.loX, box.loY],
1 => [box.loX, box.hiY],
2 => [box.hiX, box.hiY],
3 => [box.hiX, box.loY],
ENDCASE => ERROR
];
};
IPNextPointPair: 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;
ipData: IPData ← NARROW[sliceD.slice.data];
ipParts: IPParts ← NARROW[sliceD.parts];
box: BoundBox ← ipData.localTightBox;
t: ImagerTransformation.Transformation ← ipData.transform;
index: INTEGER ← -1;
pointPairAndDone.done ← FALSE;
FOR index ← pointPairGen.index, index+1 UNTIL index >=ipMaxEdges DO
IF ipParts.edges[index] THEN EXIT; -- index will point to next availabe edge
ENDLOOP;
SELECT index FROM -- index in [0..ipMaxEdges) of next available edge
0 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [box.loX, box.loY] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [box.loX, box.hiY] ];
};
1 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [box.loX, box.hiY] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [box.hiX, box.hiY] ];
};
2 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [box.hiX, box.hiY] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [box.hiX, box.loY] ];
};
3 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [box.hiX, box.loY] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [box.loX, 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
};
};
IPClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, bestNormal:Vector ← [0,-1], hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestPointProc
hitData ← NIL; -- automatically done by compiler
index: NAT ← 9999;
slice: Slice ← sliceD.slice;
ipData: IPData ← NARROW[slice.data];
tightBox: BoundBox ← GGSliceOps.GetTightBox[slice];
toleranceBox: BoundBoxObj ← [tightBox.loX-tolerance, tightBox.loY-tolerance, tightBox.hiX+tolerance, tightBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox] THEN {
ipHitData: IPHitData;
ipParts: IPParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[ipData.inverse, testPoint];
localTolerance: REALABS[tolerance*MAX[ipData.inverseScale.x, ipData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← GGBoundBox.NearestPoint[ipData.localTightBox, localTestpoint, localTolerance, ipParts.points];
IF success THEN {
bestPoint ← GGTransform.Transform[ipData.transform, bestPoint];
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
hitData ← ipHitData ← NEW[IPHitDataObj ← [point: index, edge: -1, hitPoint: bestPoint] ];
};
};
};
IPClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, bestNormal: Vector ← [0,-1], hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestSegmentProc
hitData ← NIL; -- automatically done by compiler
slice: Slice ← sliceD.slice;
ipData: IPData ← NARROW[slice.data];
tightBox: BoundBox ← GGSliceOps.GetTightBox[slice];
toleranceBox: BoundBoxObj ← [tightBox.loX-tolerance, tightBox.loY-tolerance, tightBox.hiX+tolerance, tightBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox] THEN {
ipHitData: IPHitData;
index: NAT ← 9999;
ipData: IPData ← NARROW[slice.data];
ipParts: IPParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[ipData.inverse, testPoint]; -- transfrom point to "IP space"
localTolerance: REALABS[tolerance*MAX[ipData.inverseScale.x, ipData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← GGBoundBox.NearestSegment[ipData.localTightBox, localTestpoint, localTolerance, ipParts.points];
IF success THEN {
bestPoint ← GGTransform.Transform[ipData.transform, bestPoint];
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
hitData ← ipHitData ← NEW[IPHitDataObj ← [point: -1, edge: index, hitPoint: bestPoint] ];
bestNormal ← Vectors2d.Sub[testPoint, bestPoint];
};
};
};
IPFilledPathsUnderPoint: PUBLIC PROC [slice: Slice, point: Point, tolerance: REAL] RETURNS [hitData: REF ANYNIL, moreHitDatas: LIST OF REF ANYNIL] = {
ipData: IPData ← NARROW[slice.data];
pointBox: Point ← ImagerTransformation.Transform[ipData.inverse, point];
box: BoundBox ← ipData.localTightBox;
success: BOOLFALSE;
success
ipData.localTightBox.infinite OR
(pointBox.x >= box.loX AND
pointBox.x <= box.hiX AND
pointBox.y >= box.loY AND
pointBox.y <= box.hiY);
IF success THEN {
bestDist: REAL;
segNum: NAT;
bestPoint: Point;
[bestDist, segNum, bestPoint, success] ← GGBoundBox.NearestSegment[box, pointBox];
bestPoint ← GGTransform.Transform[ipData.transform, bestPoint];
hitData ← NEW[IPHitDataObj ← [point: -1, edge: segNum, hitPoint: bestPoint] ];
};
};
PointIsInBox: PROC [test: Point, box: BoundBoxObj] RETURNS [BOOL ← FALSE] = {
RETURN[ NOT (test.x < box.loX OR test.x > box.hiX OR test.y < box.loY OR test.y > box.hiY) ];
};
MakeComplete: PROC [parts: SliceParts] = {
ipParts: IPParts ← NARROW[parts];
ipParts.points ← ALL[TRUE];
ipParts.edges ← ALL[TRUE];
ipParts.innards ← TRUE;
};
IsComplete: PROC [parts: SliceParts] RETURNS [BOOL ← FALSE] = {
ipParts: IPParts ← NARROW[parts];
RETURN[ipParts#NIL AND ipParts.points=ALL[TRUE] AND ipParts.edges=ALL[TRUE] AND ipParts.innards=TRUE];
};
IsEmpty: PROC [parts: SliceParts] RETURNS [BOOLFALSE] = {
ipParts: IPParts ← NARROW[parts];
RETURN[ipParts=NIL OR (ipParts.points=ALL[FALSE] AND ipParts.edges=ALL[FALSE] AND ipParts.innards = FALSE)];
};
NoOp Parts Routines
NoOpIsEmptyParts: PUBLIC GGModelTypes.SliceIsEmptyPartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
RETURN[TRUE];
};
NoOpIsCompleteParts: PUBLIC GGModelTypes.SliceIsCompletePartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
RETURN[FALSE];
};
NoOpNewParts: PUBLIC GGModelTypes.SliceNewPartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpUnionParts: PUBLIC GGModelTypes.SliceUnionPartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpDifferenceParts: PUBLIC GGModelTypes.SliceDifferencePartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpMovingParts: PUBLIC GGModelTypes.SliceMovingPartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpAugmentParts: PUBLIC GGModelTypes.SliceAugmentPartsProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpAlterParts: PUBLIC GGModelTypes.SliceAlterPartsProc = {
RETURN[NIL];
};
NoOpSetSelectedFields: PUBLIC GGModelTypes.SliceSetSelectedFieldsProc = {
};
NoOpPointsInDescriptor: PUBLIC GGModelTypes.SlicePointsInDescriptorProc = {
pointGen ← NIL;
};
NoOpPointPairsInDescriptor: PUBLIC GGModelTypes.SlicePointPairsInDescriptorProc = {
pointPairGen ← NIL;
};
NoOpSegmentsInDescriptor: PUBLIC GGModelTypes.SliceSegmentsInDescriptorProc = {
segGen ← NIL;
};
NoOpWalkSegments: PUBLIC GGModelTypes.SliceWalkSegmentsProc = {
sliceD ← GGSliceOps.NewParts[slice, NIL, none];
};
NoOpNextPoint: PUBLIC GGModelTypes.SliceNextPointProc = {
pointAndDone.done ← TRUE;
};
NoOpNextPointPair: PUBLIC GGModelTypes.SliceNextPointPairProc = {
pointPairAndDone.done ← TRUE;
};
NoOpNextSegment: PUBLIC GGModelTypes.SliceNextSegmentProc = {
seg ← NIL;
};
NoOpClosestPoint: PUBLIC GGModelTypes.SliceClosestPointProc = {
ERROR Problem [msg: "All slice classes must have this proc."];
};
NoOpClosestJointToHitData: PUBLIC GGModelTypes.SliceClosestJointToHitDataProc = {
[point, ----, normal, ----, ----] ← GGSliceOps.ClosestPoint[sliceD, mapPoint, GGUtility.plusInfinity];
jointD ← sliceD;
};
NoOpClosestPointAndTangent: PUBLIC GGModelTypes.SliceClosestPointAndTangentProc = {
success ← FALSE;
tangent ← [1.0, 0.0];
bestDist ← Real.LargestNumber;
bestPoint ← [0.0, 0.0];
};
NoOpClosestSegment: PUBLIC GGModelTypes.SliceClosestSegmentProc = {
ERROR Problem [msg: "All slice classes must have this proc."];
};
NoOpFilledPathsUnderPoint: PUBLIC GGModelTypes.SliceFilledPathsUnderPointProc = {
hitData ← NIL;
moreHitDatas ← NIL;
};
NoOpLineIntersection: PUBLIC GGModelTypes.SliceLineIntersectionProc = {
points ← NIL; pointCount ← 0;
};
NoOpCircleIntersection: PUBLIC GGModelTypes.SliceCircleIntersectionProc = {
points ← NIL; pointCount ← 0;
};
NoOpHitDataAsSimpleCurve: PUBLIC GGModelTypes.SliceHitDataAsSimpleCurveProc = {
simpleCurve ← NIL;
};
NoOp Style
NoOpSetDefaults: PUBLIC GGModelTypes.SliceSetDefaultsProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpSetStrokeWidth: PUBLIC GGModelTypes.SliceSetStrokeWidthProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetStrokeWidth: PUBLIC GGModelTypes.SliceGetStrokeWidthProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpSetStrokeEnd: PUBLIC GGModelTypes.SliceSetStrokeEndProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetStrokeEnd: PUBLIC GGModelTypes.SliceGetStrokeEndProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpSetStrokeJoint: PUBLIC GGModelTypes.SliceSetStrokeJointProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetStrokeJoint: PUBLIC GGModelTypes.SliceGetStrokeJointProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpSetStrokeColor: PUBLIC GGModelTypes.SliceSetStrokeColorProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpGetStrokeColor: PUBLIC GGModelTypes.SliceGetStrokeColorProc = {
SIGNAL Problem [msg: "All slice classes must have this proc."];
};
NoOpSetFillColor: PUBLIC GGModelTypes.SliceSetFillColorProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetFillColor: PUBLIC GGModelTypes.SliceGetFillColorProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
color ← NIL;
};
NoOpSetArrows: PUBLIC GGModelTypes.SliceSetArrowsProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetArrows: PUBLIC GGModelTypes.SliceGetArrowsProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpSetDashed: PUBLIC GGModelTypes.SliceSetDashedProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
NoOpGetDashed: PUBLIC GGModelTypes.SliceGetDashedProc = {
SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."];
};
GetMaster: PROC [fullName: Rope.ROPE, router: MsgRouter] RETURNS [ipMaster: Interpress.Master, success: BOOLFALSE] = {
val: SymTab.Val;
[success, val] ← SymTab.Fetch[masterTable, fullName];
IF success THEN ipMaster ← NARROW[val] ELSE {
[ipMaster, success] ← GGFileOps.OpenInterpressOrComplain["IP read", router, fullName];
IF success THEN [] ← SymTab.Insert[masterTable, fullName, ipMaster];
};
};
Init: PROC [] = {
contextTable ← RefTab.Create[];
masterTable ← SymTab.Create[];
cache ← FunctionCache.Create[maxEntries: 100];
};
cache: FunctionCache.Cache;
contextTable: RefTab.Ref; -- cache of filled ImagerMemory contexts
masterTable: SymTab.Ref; -- cache of Interpress.IPMasters
Init[];
END.