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, ImagerError, ImagerInterpress, ImagerMaskCapture, ImagerMemory, ImagerTransformation, InterpressInterpreter, 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, ImagerError, ImagerInterpress, ImagerMaskCapture, ImagerMemory, ImagerTransformation, InterpressInterpreter, 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; 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: 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. hitPoint: Point ]; MasterStuff: TYPE = RECORD [ master: InterpressInterpreter.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 getBoundBox: IPGetBoundBox, getTransformedBoundBox: GGSlice.GenericTransformedBoundBox, getTightBox: IPGetTightBox, copy: IPCopy, restore: IPRestore, buildPath: NoOpBuildPath, drawBorder: NoOpDrawBorder, drawParts: IPDrawParts, drawTransform: IPDrawTransform, drawSelectionFeedback: IPDrawSelectionFeedback, drawAttractorFeedback: NoOpDrawAttractorFeedback, saveSelections: NoOpSaveSelections, remakeSelections: NoOpRemakeSelections, transform: IPTransform, describe: IPDescribe, describeHit: IPDescribeHit, fileout: IPFileout, filein: IPFilein, isEmptyParts: IPIsEmptyParts, isCompleteParts: IPIsCompleteParts, newParts: IPNewParts, unionParts: IPUnionParts, differenceParts: IPDiffParts, movingParts: IPMovingParts, augmentParts: IPAugmentParts, alterParts: NoOpAlterParts, setSelectedFields: NoOpSetSelectedFields, pointsInDescriptor: IPPointsInDescriptor, walkPointsInDescriptor: IPWalkPointsInDescriptor, pointPairsInDescriptor: IPPointPairsInDescriptor, segmentsInDescriptor: IPSegmentsInDescriptor, walkSegments: IPWalkSegments, nextPoint: IPNextPoint, nextPointPair: IPNextPointPair, nextSegment: IPNextSegment, closestPoint: IPClosestPoint, closestJointToHitData: NoOpClosestJointToHitData, closestPointAndTangent: NoOpClosestPointAndTangent, closestSegment: IPClosestSegment, filledPathsUnderPoint: IPFilledPathsUnderPoint, lineIntersection: NoOpLineIntersection, circleIntersection: NoOpCircleIntersection, hitDataAsSimpleCurve: NoOpHitDataAsSimpleCurve, 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: InterpressInterpreter.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: InterpressInterpreter.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] = { 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; found ฌ RefTab.Pairs[contextTable, CheckPair]; IF NOT found THEN { -- compute everything ShowWarnings: InterpressInterpreter.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 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 InterpressInterpreter.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] = { memContext: Imager.Context; ipData: IPData; inverse: ImagerTransformation.Transformation; inverseScale: Imager.VEC; localTightBox, localBox: BoundBox; 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; }; 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]= { RETURN[slice.boundBox]; }; IPGetTightBox: PROC [slice: Slice, parts: SliceParts ฌ NIL] RETURNS [box: BoundBox]= { ipData: IPData ฌ NARROW[slice.data]; RETURN[slice.tightBox]; }; IPCopy: PROC [slice: Slice, parts: SliceParts ฌ NIL] RETURNS [copy: LIST OF Slice] = { 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] = { 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.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; }; 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] = { 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] = { 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]; }; 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, viewView: Transformation] = { OPEN ImagerTransformation; -- see Transform and InverseTransform object: Imager.Object; ipProps: Props; ipData: IPData ฌ NARROW[slice.data]; success: BOOL ฌ TRUE; 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] = { OPEN ImagerTransformation; ipProps: Props ฌ NARROW[self.data]; ipData: IPData ฌ NARROW[ipProps.slice.data]; IPDrawInnards[context, ipData]; }; IPDrawInnards: PROC [dc: Imager.Context, ipData: IPData] = { ENABLE ImagerError.Warning => { Feedback.PutFL[Feedback.EnsureRouter[$Gargoyle], oneLiner, $Warning, "Imager Warning[%g]: %g", LIST[[atom[ImagerError.AtomFromErrorCode[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]; }; IPTransform: PROC [slice: Slice, parts: SliceParts ฌ NIL, transform: Transformation, editConstraints: EditConstraints, history: HistoryEvent] = { 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]; }; IPDescribe: PROC [sliceD: SliceDescriptor] RETURNS [rope: Rope.ROPE] = { 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 ฌeCount + 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] = { 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.PutF1["\"%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.LF]; IF ipData.includeByValue THEN { 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]; f.PutF1["%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] = { ipMaster: InterpressInterpreter.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 { GGParseIn.ReadRope[f, "includeByValue:"]; includeByValue ฌ GGParseIn.ReadBool[f]; IF includeByValue THEN { masterSize ฌ GGParseIn.ReadCARD[f]; GGParseIn.ReadBlank[f]; masterText ฌ IO.GetRope[f, masterSize, TRUE]; ipMaster ฌ InterpressInterpreter.FromRope[masterText, NIL]; 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.ReadRope[f, "props: ( "]; hasProps ฌ GGParseIn.ReadBool[f]; props ฌ IF hasProps THEN GGParseIn.ReadListOfRope[f] ELSE NIL; GGParseIn.ReadChar[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; }; }; }; IPIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL ฌ FALSE] = { 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] = { 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] = { 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] = { 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] = { ipParts: IPParts ฌ NARROW[sliceD.parts]; newParts: IPParts ฌ NEW[IPPartsObj ฌ [points: ipParts.points, edges: ipParts.edges, innards: ipParts.innards] ]; 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] = { 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] = { 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] = { 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] = { 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; }; NoOpSetDefaults: PUBLIC GGModelTypes.SliceSetDefaultsProc = { }; NoOpSetStrokeWidth: PUBLIC GGModelTypes.SliceSetStrokeWidthProc = { }; NoOpGetStrokeWidth: PUBLIC GGModelTypes.SliceGetStrokeWidthProc = { }; NoOpSetStrokeEnd: PUBLIC GGModelTypes.SliceSetStrokeEndProc = { }; NoOpGetStrokeEnd: PUBLIC GGModelTypes.SliceGetStrokeEndProc = { }; NoOpSetStrokeJoint: PUBLIC GGModelTypes.SliceSetStrokeJointProc = { }; NoOpGetStrokeJoint: PUBLIC GGModelTypes.SliceGetStrokeJointProc = { }; NoOpSetStrokeColor: PUBLIC GGModelTypes.SliceSetStrokeColorProc = { }; NoOpGetStrokeColor: PUBLIC GGModelTypes.SliceGetStrokeColorProc = { }; NoOpSetFillColor: PUBLIC GGModelTypes.SliceSetFillColorProc = { }; NoOpGetFillColor: PUBLIC GGModelTypes.SliceGetFillColorProc = { color ฌ NIL; }; NoOpSetArrows: PUBLIC GGModelTypes.SliceSetArrowsProc = { }; NoOpGetArrows: PUBLIC GGModelTypes.SliceGetArrowsProc = { }; NoOpSetDashed: PUBLIC GGModelTypes.SliceSetDashedProc = { }; NoOpGetDashed: PUBLIC GGModelTypes.SliceGetDashedProc = { }; GetMaster: PROC [fullName: Rope.ROPE, router: MsgRouter] RETURNS [ipMaster: InterpressInterpreter.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 InterpressInterpreter.IPMasters Init[]; END. J GGSliceImplF.mesa Copyright ำ 1988, 1991, 1992 by Xerox Corporation. All rights reserved. Contents: Implements IP slice classes in Gargoyle. Bier, December 3, 1992 5:38 pm PST Pier, July 17, 1991 11:25 am PDT Doug Wyatt, April 17, 1992 1:52 pm PDT Interpress-Only Data and Procs edge: [-1..ipMaxEdges) THIS DEF CAUSES A FATAL COMPILER ERROR IN PASS FIVE !! Fundamentals Drawing Transforming Textual Description Parts Part Generators Hit Testing Style The interpress master is assumed to be in units of pixelsPerUnit pixels. The memory context has 1 unit = 1/72.0 inch. IF ipMaster=NIL THEN RETURN; -- we need to make some sort of slice, even if the Interpress file is not found Following code required to get the context state to the default assumed by InterpressInterpreter.DoPage 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. The memory context has 1 unit = 1/72.0 inch. Interpress Class Procs Fundamentals GGModelTypes.SliceBoundBoxProc IPs always keep a current bound box and tight box GGModelTypes.SliceBoundBoxProc IPs always keep a current bound box and tight box GGModelTypes.SliceCopyProc Just ignore parts and copy the whole box. GGModelTypes.SliceRestoreProc from and to must be nearly identically structured slices, probably because from was made as a copy of to. Restore the contents of "to", getting all non-REF values from "from". Good luck. to.nullDescriptor is always valid to.fullDescriptor is unused Drawing GGModelTypes.SliceDrawTransformProc 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. Drawing Utilities Imager.Object.draw PROC NOT called with the dc transformation already set to object coordinates Transforming GGModelTypes.SliceTransformProc Textual Description GGModelTypes.SliceDescribeProc GGModelTypes.SliceFileoutProc Write a description of yourself onto stream f. Write the interpress into a Rope.ROPE. Send the rope to stream f. GGModelTypes.SliceFileinProc Read a description of yourself from stream f. next make an IPSlice and return it Hit Testing GGModelTypes.SliceEmptyPartsProc GGModelTypes.SliceNewPartsProc GGModelTypes.SliceUnionPartsProc GGModelTypes.SliceMovingPartsProc If anything is moving, everything is dragging. Otherwise on background. GGModelTypes.SliceAugmentPartsProc For every edge, add its corresponding points IP slices have only one "segment" whose entire purpose is to contain properties in seg.props IP slices have only one "segment" whose entire purpose is to contain properties in seg.props GGModelTypes.SliceClosestPointProc hitData _ NIL; -- automatically done by compiler GGModelTypes.SliceClosestSegmentProc hitData _ NIL; -- automatically done by compiler NoOp Parts Routines NoOp Style SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "All slice classes must have this proc."]; SIGNAL Problem [msg: "All slice classes must have this proc."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; SIGNAL Problem [msg: "Unimplemented operation for this slice class. Proceed."]; ส/ฎ–(cedarcode) style•NewlineDelimiter ™codešœ™Kšœ ฯeœ=™HKšฯnœ+™3K™"Kšœ ™ K™&K™—šฯk ˜ Kšœ$ŸœŒŸœ!Ÿœ˜๋K˜—šž œŸœŸ˜KšŸœŸœฆŸœ!˜่KšŸœ Ÿ˜K˜Kšœ ŸœŸœ ˜!Kšœ Ÿœ˜,KšœŸœ˜#Kšœ Ÿœ˜)KšœŸœ˜Kšœ Ÿœ˜-KšœŸœ ˜5Kšœ Ÿœ˜+Kšœ Ÿœ˜*KšœŸœ˜1KšœŸœ˜!KšœŸœ˜3KšœŸœ"˜9KšœŸœ#˜;KšœŸœ&˜AKšœŸœ˜1KšœŸœ˜!Kšœ Ÿœ˜'KšœŸœ!˜7KšœŸœ$˜=KšœŸœ#˜;KšœŸœ!˜5Kšœ Ÿœ˜+KšœŸœ˜!Kšœ Ÿœ˜+KšœŸœ˜1KšœŸœ ˜5KšœŸœ#˜;Kšœ Ÿœ˜'Kšœ Ÿœ˜+KšœŸœ'˜[context: Imager.Context, strokeJoint: Imager.StrokeJoint]šœ!˜!K˜Kšœ"˜"šŸœŸœŸœ˜šŸœŸœŸœŸ˜'šŸœŸœŸœŸ˜0Kšœ,˜,Kšœ,˜,Kšœ,˜,Kšœ,˜,KšŸœŸœ˜——KšŸœ˜K˜—K˜—Kšœ"˜"K˜K˜—K™Kšœ ™ šž œŸœ$ŸœY˜‘K™KšœŸœ ˜$K˜LK˜?K˜DKšœ˜K˜K˜—K™Kšœ™šž œŸœŸœ Ÿœ˜HK™Kšœ Ÿœ˜Kšœ˜KšœŸœ˜KšŸœŸœŸœŸœ˜1Kšœ Ÿœ˜šŸœŸœŸœŸ˜%šŸœŸœ˜K˜K˜K˜—KšŸœ˜—šŸœŸœŸœŸ˜$šŸœŸœ˜K˜K˜K˜—KšŸœ˜—KšŸœŸœŸœ"˜@KšŸœ Ÿœ ŸœŸœ˜@K˜.K˜K˜—š ž œŸœŸœŸœŸœ Ÿœ˜RKšœŸœ ˜'Kšœ Ÿœ˜KšŸœŸœ(˜BKšŸœŸœŸœ%˜CKšŸœŸœ˜ K˜.K˜K˜—šž œŸœŸœŸœ˜0K™K™.šž œŸœ˜)Kšœ# 7˜ZKšœ$˜$K˜—KšœŸœ ˜$Kšœ˜KšœŸœŸœ˜Kšœ Ÿœ˜KšœŸœ˜K˜K˜(Kšœ4˜4Kšœ ŸœŸœ˜Kšœ-˜-Kšœ ŸœŸœ˜Kšœ(˜(Kšœ˜Kšœ/˜/Kšœ ŸœŸœ˜K˜šŸœŸœ˜Kšก!ะbkก™&KšœŸœŸœ˜K˜QKšœ+˜+Kšœ˜K˜(K–[base: ROPE]˜%˜Kšก™—K˜'Kšœ˜K˜K˜—Kšœ˜Mšœ)Ÿœ˜.Mš ŸœŸœŸœ,Ÿœ ŸœŸœ ˜nKšœ˜K˜K˜—šžœŸœŸœŸœ Ÿœ%ŸœŸœ˜pK™Kšœ)Ÿœ˜-KšœŸœ˜Kšœ ŸœŸœ˜K˜/KšœŸœ˜Kšœ Ÿœ˜KšœŸœ˜K˜"Kš œŸœŸœŸœŸœ˜K˜K˜K˜,šŸœŸœ˜K˜%šŸœŸœ˜K˜ K˜—šŸœ˜K˜2Kšœ0 %˜UK˜—K˜—KšŸœŸœ Ÿœ˜+šŸœŸœ˜"šŸœ Ÿœ˜(K˜2KšœŸœ #˜8K˜—Kšœ ŸœŸœ Ÿœ Q˜K˜—šŸœŸœ˜K˜)K˜'K˜šŸœŸœ˜Kšก-™-K˜#K˜Kšœ ŸœŸœ˜-K˜Kšœ6Ÿœ˜;K˜Kšก"™"KšœcŸœ˜iK˜—šŸœ˜KšœPŸœ˜YK˜—K˜—šŸœ˜KšœRŸœ˜YK˜—šŸœŸœ ˜3Kšœ ŸœŸœ˜K˜#K˜!Kš œŸœ ŸœŸœŸœ˜>K˜šŸœŸœŸœ˜KšœŸœ˜.š ŸœŸœŸœŸœŸœŸœŸ˜@Mšœ Ÿœ˜(MšŸœ˜—K˜—K˜—K˜—K™Kšœ ™ š žœŸœŸœŸœŸœ˜IKšœ ™ KšŸœ˜K˜K˜—š žœŸœŸœŸœŸœ˜LKšŸœ˜!K˜K˜—š ž œŸœŸœŸœŸœ˜iKšœ™KšœŸœ ˜'Kš œŸœŸœŸœ ŸœŸœ˜OšŸœŸ˜˜ KšŸœŸœ#Ÿ˜AKšŸœŸœŸœ!Ÿ˜CKšŸœŸœ˜ K˜—šœ ˜ šŸœŸœ˜Kšœ"Ÿœ˜'K˜—šŸœ˜Kšœ ŸœŸœ˜Kšœ˜Kšœ Ÿœ˜K˜KšœŸœ ˜'Kšœ œ œh˜sKšŸœŸœ ŸœŸœ˜KšœŸœ ˜KšŸœŸœŸœ˜&Kšœ%Ÿœ˜*K˜—KšœŸœ˜K˜—šœ ŸœŸœ˜%Kšœ Ÿœ˜%KšœŸœ˜K˜—šœ˜KšœŸœŸœ˜KšœŸœŸœ˜K˜—Kšœ/˜/šœ  ˜ K˜—KšŸœŸœ˜—K˜5K˜K˜—šž œŸœ4Ÿœ˜kKšœ ™ KšœŸœ˜)KšœŸœ˜)Kšœ˜Kš Ÿœ ŸœŸœ ŸœŸœŸœ˜+KšŸœŸœŸœŸœ ˜*KšŸœŸœŸœŸœ ˜*Kš œ ŸœŸœŸœ ŸœŸœ˜GšŸœŸœŸœŸ˜%Kšœ(Ÿœ˜>KšŸœ˜—šŸœŸœŸœŸ˜$Kšœ&Ÿœ˜;KšŸœ˜—Kšœ$Ÿœ˜8K˜=K˜K˜—šž œŸœ4Ÿœ˜kKšœŸœ˜)KšœŸœ˜)Kšœ˜Kš Ÿœ ŸœŸœ ŸœŸœŸœ˜+Kš ŸœŸœŸœŸœŸœŸœ ˜@Kš œ ŸœŸœŸœ ŸœŸœ˜GšŸœŸœŸœŸ˜%Kš œŸœŸœŸœŸœ˜NKšŸœ˜—šŸœŸœŸœŸ˜$Kš œŸœŸœŸœŸœ˜KKšŸœ˜—Kš œŸœŸœŸœŸœ˜HK˜>K˜K˜—šž œŸœ|Ÿœ9˜ฯKšœ!™!K™HKš œŸœŸœŸœ ŸœŸœ Ÿœ˜^Kš œŸœŸœŸœ ŸœŸœ Ÿœ˜dšŸœŸœ ˜6KšœŸœŸœ˜KšœŸœŸœ˜KšœŸœ˜Kšœ˜—šŸœ ˜KšœŸœŸœ˜#KšœŸœŸœ˜$KšœŸœ˜ K˜—K˜AKšœ6Ÿœ˜;K˜5Kšœ˜K˜—šžœŸœ8Ÿœ˜oKšœ"™"KšœŸœ˜(KšœŸœY˜pK™,šŸœŸœŸœŸ˜$šŸœŸœŸœŸ˜&KšœŸœŸœ˜=KšœŸœŸœ˜=KšœŸœŸœ˜=KšœŸœŸœ˜=—KšŸœŸœ˜KšŸœ˜—K˜;K˜—K˜šžœŸœŸœ˜[KšœŸœ˜&Kšœ Ÿœ$Ÿœ˜9šŸœŸœŸœŸ˜)KšŸœŸœ#˜>KšŸœ˜—K˜K˜—šžœŸœ7˜UKšœŸœ˜&KšœŸœ˜+KšœŸœŸœ˜šŸœŸœŸœŸ˜)šŸœŸœ˜K˜+KšŸœŸœŸœ˜K˜—KšŸœ˜—K˜—K˜šžœŸœŸœ'˜gKšœŸœ˜&KšœŸœ(Ÿœ ˜\šŸœŸœŸœŸ˜'KšŸœŸœ+˜DKšŸœ˜—K˜K˜—šžœŸœŸœ˜]K™\KšœŸœ˜&Kš œ ŸœŸœ ŸœŸœ Ÿœ ˜hšŸœŸœŸœŸ˜'KšŸœŸœ  ˜JKšŸœ˜—K˜K˜—šžœŸœ$Ÿœ˜]K™\KšœŸœ ˜$KšŸœŸœŸœŸœŸœ ŸœŸœ ˜jK˜K˜—šž œŸœ*Ÿœ.˜rKšŸœŸœŸœŸœŸœŸœŸœ˜6šŸœ˜KšœŸœ˜2K˜K˜Kšœ˜—K˜K˜—šž œŸœ*Ÿœ.˜pšŸœ ŸœŸœŸœ˜+KšœŸœ˜KšŸœ˜K˜—šŸœ˜K˜*KšœŸœ˜+KšœŸœ˜(KšœŸœ˜KšœŸœ˜šŸœ!ŸœŸ˜@KšŸœŸœŸœ +˜OKšŸœ˜—K˜/K˜ KšœŸœŸœŸœ  4˜nK˜—K˜K˜—šž œŸœŸœŸœ˜LK˜%šœ9ŸœŸ˜JK˜K˜K˜K˜KšŸœŸ˜Kšœ˜—K˜K˜—šžœŸœ2Ÿœ6˜„šŸœŸœŸœŸœ˜3KšœŸœ˜KšŸœ˜K˜—šŸœ˜K˜.KšœŸœ˜+KšœŸœ˜(K˜%K˜;KšœŸœ˜KšœŸœ˜šŸœ%ŸœŸ˜CKšŸœŸœŸœ )˜LKšŸœ˜—šŸœŸœ 2˜Dšœ˜K˜MK˜MK˜—šœ˜K˜MK˜MK˜—šœ˜K˜MK˜MK˜—šœ˜K˜MK˜MK˜—KšŸœŸœ"˜3—K˜*KšœŸœŸœŸœ  3˜uK˜—K˜K˜—šžœŸœ8ŸœŸœŸœ'ŸœŸœ ŸœŸœ˜ลK™"Kšœ Ÿœ !™1KšœŸœ˜K˜KšœŸœ ˜$K˜3Kšœ}ŸœŸœ ˜คšŸœ'Ÿœ˜/Kšœ˜KšœŸœ˜(K˜IKšœŸœŸœ Ÿœ1 3˜ŒK˜†šŸœ Ÿœ˜K˜?K˜4KšœŸœ@˜YK˜—K˜—K˜K˜—šžœŸœ8ŸœŸœŸœ(ŸœŸœ ŸœŸœ˜ศKšœ$™$Kšœ Ÿœ !™1K˜KšœŸœ ˜$K˜3Kšœ}ŸœŸœ ˜คšŸœ'Ÿœ˜/Kšœ˜KšœŸœ˜KšœŸœ ˜$KšœŸœ˜(KšœJ  ˜jKšœŸœŸœ Ÿœ1 3˜ŒK˜ˆšŸœ Ÿœ˜K˜?K˜4KšœŸœ@˜YK˜1K˜—K˜—K˜—K˜šžœŸœŸœ)ŸœŸœ ŸœŸœŸœŸœŸœŸœŸœŸœ˜œKšœŸœ ˜$Kšœฆœ@˜HK˜%Kšœ ŸœŸœ˜K˜šœŸœ˜ KšœŸ˜ KšœฆœŸ˜KšœฆœŸ˜KšœฆœŸ˜Kšœฆœ˜K˜—šŸœ Ÿœ˜Kšœ˜KšœŸœ˜ Kšœ˜K˜KšœMฆœ˜RK˜?Kšœ ŸœA˜NK˜—K˜K˜—š ž œŸœ!ŸœŸœŸœ˜MKš ŸœŸœŸœŸœŸœ˜]K˜K˜—šž œŸœ˜+KšœŸœ˜!KšœŸœŸœ˜KšœŸœŸœ˜KšœŸœ˜K˜K˜—š ž œŸœŸœŸœŸœ˜?KšœŸœ˜!KšŸœ ŸœŸœŸœŸœŸœŸœŸœŸœŸœ˜fK˜K˜—š žœŸœŸœŸœŸœ˜K˜—šžœŸœ0˜QKšœ œ œ œF˜fK˜K˜—šžœŸœ1˜SKšœ Ÿœ˜K˜K˜K˜K˜—šžœŸœ)˜CKšŸœ9˜>K˜—šžœŸœ0˜QKšœ Ÿœ˜KšœŸœ˜K˜—šžœŸœ+˜GKšœ Ÿœ˜K˜—šžœŸœ-˜KKšœ Ÿœ˜K˜—šžœŸœ/˜OKšœŸœ˜K˜—K™Kšœ ™ šžœŸœ&˜=KšŸœI™OK˜—šžœŸœ)˜CKšŸœI™OK˜—šžœŸœ)˜CKšŸœI™OK˜—šžœŸœ'˜?KšŸœI™OK˜—šžœŸœ'˜?KšŸœI™OK˜—šžœŸœ)˜CKšŸœI™OK˜—šžœŸœ)˜CKšŸœI™OK˜—šžœŸœ)˜CKšŸœ9™?K˜—šžœŸœ)˜CKšŸœ9™?K˜—šžœŸœ'˜?KšŸœI™OK˜—šžœŸœ'˜?KšŸœI™OKšœŸœ˜ K˜—šž œŸœ$˜9KšŸœI™OK˜—šž œŸœ$˜9KšŸœI™OK˜—šž œŸœ$˜9KšŸœI™OK˜—šž œŸœ$˜9KšŸœI™OK˜—K˜š ž œŸœŸœŸœ3ŸœŸœ˜„Mšœ˜K˜6šŸœ Ÿœ ŸœŸœ˜-K˜VKšŸœ Ÿœ5˜DK˜—K˜—K˜šžœŸœ˜K˜K˜K–:[maxEntries: INT _ 10, maxTotalSize: INT _ 2147483647]˜.K˜—K˜K˜Kšœ (˜BKšœ +˜DM˜K˜M˜KšŸœ˜—…—ญาํส