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;
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: BOOL ← FALSE -- 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,
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: BOOL ← FALSE;
[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.
ROPE ←
NIL, 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:
BOOL ←
FALSE] = {
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: BOOL ← FALSE;
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: BOOL ← FALSE;
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: BOOL ← FALSE;
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];
};
IP
DrawTransform:
PROC [slice: Slice, parts: SliceParts ←
NIL, dc: Imager.Context, camera: Camera, transform: Transformation, editConstraints: EditConstraints] = {
GGModelTypes.SliceDrawTransformProc
DoIP
DrawTransform:
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: BOOL ← FALSE;
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: BOOL ← TRUE
];
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, view
View: Transformation] = {
OPEN ImagerTransformation; -- see Transform and InverseTransform
object: Imager.Object;
ipProps: Props;
ipData: IPData ← NARROW[slice.data];
success: BOOL ← TRUE;
IF GGMUserProfile.GetTurboOn[]
AND (view
View=
NIL
OR CloseToTranslation[view
View,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: BOOL ← FALSE;
transform: ImagerTransformation.Transformation;
masterText: Rope.ROPE;
masterSize: CARD;
includeByValue: BOOL;
localTightBox, localBox: BoundBox;
props: LIST OF Rope.ROPE ← NIL;
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: BOOL ← FALSE;
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: BOOL ← FALSE;
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:
BOOL ←
FALSE] = {
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: REAL ← ABS[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:
BOOL ←
FALSE] = {
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: REAL ← ABS[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
ANY ←
NIL, moreHitDatas:
LIST
OF
REF
ANY ←
NIL] = {
ipData: IPData ← NARROW[slice.data];
pointBox: Point ← ImagerTransformation.Transform[ipData.inverse, point];
box: BoundBox ← ipData.localTightBox;
success: BOOL ← FALSE;
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 [
BOOL ←
FALSE] = {
ipParts: IPParts ← NARROW[parts];
RETURN[ipParts=NIL OR (ipParts.points=ALL[FALSE] AND ipParts.edges=ALL[FALSE] AND ipParts.innards = FALSE)];
};
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:
BOOL ←
FALSE] = {
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.