DIRECTORY AtomButtonsTypes, Convert, Feedback, FileNames, FS, GGBasicTypes, GGBoundBox, GGFont, GGFromImager, GGInterfaceTypes, GGModelTypes, GGScene, GGParseIn, GGParseOut, GGShapes, GGSlice, GGTransform, GGUtility, Imager, ImagerFont, ImagerInterpress, ImagerMemory, ImagerTransformation, Interpress, IO, Lines2d, NodeStyle, RealFns, RefText, Rope, Vectors2d, ViewerClasses; GGSliceImplB: CEDAR PROGRAM IMPORTS Convert, Feedback, FileNames, FS, GGBoundBox, GGFont, GGFromImager, GGScene, GGParseIn, GGParseOut, GGShapes, GGSlice, GGTransform, GGUtility, Imager, ImagerFont, ImagerInterpress, ImagerMemory, ImagerTransformation, Interpress, IO, Lines2d, RealFns, RefText, Rope, Vectors2d EXPORTS GGSlice = BEGIN BoundBox: TYPE = GGModelTypes.BoundBox; CameraData: TYPE = GGInterfaceTypes.CameraData; Color: TYPE = Imager.Color; DefaultData: TYPE = GGModelTypes.DefaultData; ExtendMode: TYPE = GGModelTypes.ExtendMode; FeedbackData: TYPE = AtomButtonsTypes.FeedbackData; GGData: TYPE = GGInterfaceTypes.GGData; GargoyleDataObj: TYPE = GGInterfaceTypes.GargoyleDataObj; Point: TYPE = GGBasicTypes.Point; PointGenerator: TYPE = GGModelTypes.PointGenerator; PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj; PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator; PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj; Scene: TYPE = GGModelTypes.Scene; SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData; SelectionClass: TYPE = GGInterfaceTypes.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; Vector: TYPE = GGBasicTypes.Vector; Viewer: TYPE = ViewerClasses.Viewer; FontData: TYPE = GGFont.FontData; textMaxPoints: INTEGER = 12; textMaxEdges: INTEGER = 7; TextPoints: TYPE = REF TextPointsData; TextPointsData: TYPE = ARRAY [0..textMaxPoints) OF Point; -- see picture TextEdgeArray: TYPE = PACKED ARRAY [0..textMaxEdges) OF BOOL; -- left, top, right, bottom, baseline, centerline. TextPointArray: TYPE = PACKED ARRAY [0..textMaxPoints) OF BOOL; -- see picture. Used in text parts data. textPointRopes: ARRAY [0..textMaxPoints) OF Rope.ROPE = [ "lower left point", "base left point", "center left point", "upper left point", "lower mid point", "base mid point", "center mid point", "upper mid point", "lower right point", "base right point", "center right point", "upper right point" ]; textEdgeRopes: ARRAY [0..textMaxEdges) OF Rope.ROPE = [ "left edge", "top edge", "right edge", "bottom edge", "baseline", "horizontal center line", "vertical center line" ]; TextData: TYPE = REF TextDataObj; TextDataObj: TYPE = RECORD [ box: BoundBox, -- not a bounding box but part of the text representation feedbackBox: BoundBox, -- both a bounding box and part of the text representation points: TextPoints, -- cache for "joints" rope: Rope.ROPE, color: Imager.Color, fontData: FontData, trueFont: ImagerFont.Font, screenFont: ImagerFont.Font, inverse: ImagerTransformation.Transformation, -- inverse of transform inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s amplifySpace: REAL _ 1.0, -- because the Imager allows space correction (see Imager.mesa) dropShadowOn: BOOL _ FALSE, dropShadowOffset: Vector, shadowColor: Imager.Color ]; TextParts: TYPE = REF TextPartsObj; TextPartsObj: TYPE = RECORD [ points: TextPointArray, -- which corners of text box are selected. edges: TextEdgeArray, -- which edges of text box are selected. includeText: BOOL -- FALSE => corners and edges only; no text ]; TextHitData: TYPE = REF TextHitDataObj; TextHitDataObj: TYPE = RECORD [ point: [-1..textMaxPoints), -- which point of box is hit. edge: [-1..textMaxEdges), -- which edge of box is hit. hitPoint: Point ]; FontNameError: PUBLIC SIGNAL = CODE; BuildTextSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = { OPEN GGSlice; class _ NEW[SliceClassObj _ [ type: $Text, getBoundBox: TextBoundBox, getTightBox: TextTightBox, copy: TextCopy, drawParts: TextDrawParts, drawTransform: TextDrawTransform, drawSelectionFeedback: TextDrawSelectionFeedback, drawAttractorFeedback: NoOpDrawAttractorFeedback, transform: TextTransform, describe: TextDescribe, describeHit: TextDescribeHit, fileout: TextFileout, filein: TextFilein, isEmptyParts: TextIsEmptyParts, isCompleteParts: TextIsCompleteParts, newParts: TextNewParts, unionParts: TextUnionParts, differenceParts: TextDiffParts, movingParts: TextMovingParts, augmentParts: TextAugmentParts, setSelectedFields: NoOpSetSelectedFields, pointsInDescriptor: TextPointsInDescriptor, pointPairsInDescriptor: TextPointPairsInDescriptor, segmentsInDescriptor: NoOpSegmentsInDescriptor, walkSegments: NoOpWalkSegments, nextPoint: TextNextPoint, nextPointPair: TextNextPointPair, nextSegment: NoOpNextSegment, closestPoint: TextClosestPoint, closestJointToHitData: NoOpClosestJointToHitData, closestPointAndTangent: NoOpClosestPointAndTangent, closestSegment: TextClosestSegment, lineIntersection: NoOpLineIntersection, circleIntersection: NoOpCircleIntersection, hitDataAsSimpleCurve: NoOpHitDataAsSimpleCurve, setDefaults: TextSetDefaults, setStrokeWidth: NoOpSetStrokeWidth, getStrokeWidth: NoOpGetStrokeWidth, setStrokeEnd: NoOpSetStrokeEnd, getStrokeEnd: NoOpGetStrokeEnd, setStrokeJoint: NoOpSetStrokeJoint, getStrokeJoint: NoOpGetStrokeJoint, setStrokeColor: TextSetStrokeColor, getStrokeColor: TextGetStrokeColor, setFillColor: TextSetFillColor, getFillColor: TextGetFillColor, setArrows: NoOpSetArrows, getArrows: NoOpGetArrows, setDashed: NoOpSetDashed, getDashed: NoOpGetDashed ]]; }; MakeTextSlice: PUBLIC PROC [text: Rope.ROPE, amplifySpace: REAL, dropShadowsOn: BOOL _ FALSE, dropShadowOffset: Vector _ [0.0, 0.0], shadowColor: Imager.Color _ Imager.black] RETURNS [slice: Slice] = { textData: TextData _ NEW[TextDataObj _ [ box: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox feedbackBox: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox inverseScale: [-1.0, -1.0], -- dummy vector amplifySpace: amplifySpace, points: NEW[TextPointsData], fontData: GGFont.CreateFontData[], -- local storage color: Imager.black, rope: text, dropShadowOn: dropShadowsOn, dropShadowOffset: dropShadowOffset, shadowColor: shadowColor ]]; slice _ NEW[SliceObj _ [ class: GGSlice.FetchSliceClass[$Text], data: textData, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE], boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets filled in later onOverlay: FALSE ]]; }; IsWhitespace: PUBLIC PROC [slice: Slice] RETURNS [BOOL] = { IF slice.class.type#$Text THEN RETURN[FALSE] ELSE { rope: Rope.ROPE _ NARROW[slice.data, TextData].rope; RETURN[Rope.Length[rope]=IO.SkipWhitespace[IO.RIS[rope]] ]; }; }; GetText: PUBLIC PROC [slice: Slice] RETURNS [text: Rope.ROPE] = { RETURN[IF slice.class.type#$Text THEN NIL ELSE NARROW[slice.data, TextData].rope]; }; AppendText: PUBLIC PROC [slice: Slice, text: Rope.ROPE] = { textData: TextData; IF slice.class.type#$Text THEN RETURN; textData _ NARROW[slice.data]; textData.rope _ Rope.Concat[textData.rope, text]; -- update text TextSetBoundBox[slice]; }; BackspaceText: PUBLIC PROC [slice: Slice] = { textData: TextData; IF slice.class.type#$Text THEN RETURN; textData _ NARROW[slice.data]; textData.rope _ Rope.Substr[base: textData.rope, len: Rope.Length[textData.rope]-1]; -- update text TextSetBoundBox[slice]; }; DigitBreak: IO.BreakProc = { RETURN[IF char IN ['0..'9] THEN break ELSE other]; }; TFontParamsFromFontName: PROC [fontName: Rope.ROPE] RETURNS [fontFamily: Rope.ROPE, fontFace: Rope.ROPE, fontSize: REAL] = { GetNumber: PROC [s: IO.STREAM] RETURNS [x: REAL _ 0.0] = { frac, fracCount: REAL _ 0.0; c: CHAR; UNTIL (c _ IO.GetChar[s ! IO.EndOfStream => {c _ '\000; CONTINUE;}; ]) NOT IN ['0..'9] DO x _ x*10+(c-'0); ENDLOOP; IF c#'\000 AND c#'. THEN s.Backup[c]; -- restore the terminating character except for decimal point IF c='. THEN { -- fractional part UNTIL (c _ IO.GetChar[s ! IO.EndOfStream => {c _ '\000; CONTINUE;}; ]) NOT IN ['0..'9] DO fracCount _ fracCount+1; frac _ frac*10.0+(c-'0); ENDLOOP; x _ x+frac*RealFns.Power[10.0, -1*fracCount]; IF c#'\000 THEN s.Backup[c]; -- restore the terminating character }; }; face: Rope.ROPE; fontStream: IO.STREAM _ IO.RIS[fontName]; [fontFamily, ----] _ IO.GetTokenRope[stream: fontStream, breakProc: DigitBreak ! IO.EndOfStream => SIGNAL FontNameError]; fontSize _ GetNumber[fontStream ! IO.EndOfStream => SIGNAL FontNameError]; [face, ----] _ IO.GetTokenRope[stream: fontStream, breakProc: IO.TokenProc ! IO.EndOfStream => {face _ NIL; CONTINUE; };]; IF face#NIL THEN fontFace _ Rope.Concat["-", face]; }; SetTextFont: PUBLIC PROC [slice: Slice, fontData: FontData, feedback: FeedbackData] RETURNS [ success: BOOL _ TRUE] = { RETURN[SetTextFontAux[slice, fontData, feedback, FALSE]]; }; SetTextFontAndTransform: PUBLIC PROC [slice: Slice, fontData: FontData, feedback: FeedbackData] RETURNS [ success: BOOL _ TRUE] = { RETURN[SetTextFontAux[slice, fontData, feedback, TRUE]]; }; SetTextFontAux: PROC [slice: Slice, fontData: FontData, feedback: FeedbackData, andTransform: BOOL _ FALSE] RETURNS [ success: BOOL _ TRUE] = { transform, oldTransform: ImagerTransformation.Transformation; IF slice.class.type # $Text THEN RETURN[FALSE]; BEGIN textData: TextData _ NARROW[slice.data]; IF slice.class.type#$Text THEN RETURN; IF fontData.literal=NIL THEN GOTO Abort; oldTransform _ textData.fontData.transform; transform _ IF andTransform THEN ImagerTransformation.Copy[fontData.transform] ELSE IF fontData.transform=NIL THEN ImagerTransformation.PreScale[m: ImagerTransformation.Translate[ImagerTransformation.Factor[m: oldTransform].t], s: fontData.scale] ELSE ImagerTransformation.TranslateTo[fontData.transform, ImagerTransformation.Factor[oldTransform].t]; fontData.substituted _ FALSE; textData.trueFont _ ImagerFont.Find[fontData.literal, substituteWithWarning ! Imager.Error => GOTO Abort; Imager.Warning => { IF fontData.substituteOK THEN { fontData.substituted _ TRUE; RESUME; } ELSE GOTO NotFound; }; ]; IF fontData.substituted THEN Feedback.PutF[feedback, oneLiner, "%g substituted for %g", [rope[ImagerFont.Name[textData.trueFont]]], [rope[fontData.literal]] ]; textData.trueFont _ ImagerFont.Scale[textData.trueFont, IF fontData.substituted THEN 1.0 ELSE 1.0/fontData.storedSize]; textData.screenFont _ GGFont.AlternateFont[fontData, textData.trueFont, $visible]; textData.fontData^ _ fontData^; -- don't do this assignment until FindFont succeeds ! textData.fontData.transform _ transform; -- don't do this assignment until FindFont succeeds ! textData.inverse _ ImagerTransformation.Invert[transform]; textData.inverseScale _ ImagerTransformation.Factor[textData.inverse].s; TextSetBoundBox[slice]; EXITS Abort => { Feedback.PutF[feedback, oneLiner, "Fatal Imager Error during ImagerFont.Find of %g", [rope[fontData.literal]] ]; success _ FALSE; }; NotFound => { scratch: IO.STREAM _ IO.ROS[]; IF transform#NIL THEN GGParseOut.WriteFactoredTransformationVEC[scratch, transform]; Feedback.PutF[feedback, oneLiner, "No Such Font: %g %g %g %g", [rope[fontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]] ]; success _ FALSE; }; END; }; GetFontData: PUBLIC PROC [slice: Slice] RETURNS [fontData: FontData] = { textData: TextData; IF slice.class.type#$Text THEN RETURN[NIL]; textData _ NARROW[slice.data]; RETURN[textData.fontData]; }; GetFontDataRope: PUBLIC PROC [slice: Slice] RETURNS [Rope.ROPE] = { textData: TextData; fontData: FontData; scratch: IO.STREAM _ IO.ROS[]; IF slice.class.type#$Text THEN RETURN[NIL]; textData _ NARROW[slice.data]; fontData _ textData.fontData; GGParseOut.WriteFactoredTransformationVEC[scratch, fontData.transform]; IF fontData.comfortable THEN RETURN[IO.PutFLR["%g%g %g %g %g %g", LIST[ [rope[textData.fontData.prefix]], [rope[textData.fontData.userFSF]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]], [rope[IF fontData.substituted THEN "SUBSTITUTED" ELSE ""]] ]] ] ELSE RETURN[IO.PutFR["%g %g %g %g %g", [rope[fontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]], [rope[IF fontData.substituted THEN "SUBSTITUTED" ELSE ""]] ] ]; }; GetFontLiteralDataRope: PUBLIC PROC [slice: Slice] RETURNS [Rope.ROPE] = { textData: TextData; fontData: FontData; scratch: IO.STREAM _ IO.ROS[]; IF slice.class.type#$Text THEN RETURN[NIL]; textData _ NARROW[slice.data]; fontData _ textData.fontData; GGParseOut.WriteFactoredTransformationVEC[scratch, fontData.transform]; RETURN[IO.PutFR["%g %g %g %g %g", [rope[fontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]], [rope[IF fontData.substituted THEN "SUBSTITUTED" ELSE ""]] ] ]; }; SetTextAmplifySpace: PUBLIC PROC [slice: Slice, amplifySpace: REAL, feedback: FeedbackData] = { textData: TextData; textData _ NARROW[slice.data]; textData.amplifySpace _ amplifySpace; TextSetBoundBox[slice]; }; GetTextAmplifySpace: PUBLIC PROC [slice: Slice] RETURNS [amplifySpace: REAL] = { textData: TextData; textData _ NARROW[slice.data]; RETURN[textData.amplifySpace]; }; DropShadowOn: PUBLIC PROC [slice: Slice, offset: Vector] = { textData: TextData _ NARROW[slice.data]; textData.dropShadowOn _ TRUE; textData.dropShadowOffset _ offset; }; DropShadowOff: PUBLIC PROC [slice: Slice] = { textData: TextData _ NARROW[slice.data]; textData.dropShadowOn _ FALSE }; UnpackComplexFontName: PROC [fontName: Rope.ROPE, fontSize: REAL] RETURNS [new: Rope.ROPE] = { face: Rope.ROPE; new _ FileNames.GetShortName[fontName]; -- throw away "xerox/pressfonts/" face _ FileNames.Tail[new, '-]; -- get face component (MIR, BRR, ...) new _ Rope.Substr[base: new, start: 0, len: Rope.SkipTo[s: new, pos: 0, skip: "-"]]; -- throw away "-XXX" new _ Rope.Cat[new, Convert.RopeFromReal[fontSize], SELECT TRUE FROM Rope.Equal[face, "MRR", FALSE] => "", Rope.Equal[face, "BRR", FALSE] => "B", Rope.Equal[face, "MIR", FALSE] => "I", Rope.Equal[face, "BIR", FALSE] => "BI", ENDCASE => ERROR ]; }; TextBoundBox: PROC [slice: Slice, parts: SliceParts _ NIL] RETURNS [box: BoundBox]= { RETURN[slice.boundBox]; }; TextTightBox: PROC [slice: Slice, parts: SliceParts _ NIL] RETURNS [box: BoundBox] = { textData: TextData _ NARROW[slice.data]; RETURN[textData.feedbackBox]; }; StringEscapement: PROC [font: ImagerFont.Font, string: ImagerFont.XStringProc, amplifySpace: REAL] RETURNS [ImagerFont.VEC] = { sum: ImagerFont.VEC _ [0, 0]; charAction: ImagerFont.XCharProc ~ { width: ImagerFont.VEC; width _ ImagerFont.Escapement[font, char]; IF ImagerFont.Amplified[font, char] THEN { width.x _ width.x*amplifySpace; width.y _ width.y*amplifySpace; }; sum.x _ sum.x+width.x; sum.y _ sum.y+width.y; }; string[charAction]; RETURN[sum]; }; RopeEscapement: PROC [font: ImagerFont.Font, rope: Rope.ROPE, amplifySpace: REAL, start: INT _ 0, len: INT _ INT.LAST] RETURNS [ImagerFont.VEC] ~ { string: ImagerFont.XStringProc ~ { ImagerFont.MapRope[rope, start, len, charAction] }; RETURN[StringEscapement[font, string, amplifySpace]]; }; TextSetBoundBox: PROC [slice: Slice] = { textData: TextData _ NARROW[slice.data]; fontData: FontData _ textData.fontData; fontExtents: ImagerFont.Extents; halfCP: REAL = GGModelTypes.halfJointSize + 1; left, right, middle, top, base, bottom, center: REAL; left _ 0.0; right _ RopeEscapement[textData.trueFont, textData.rope, textData.amplifySpace].x; middle _ (left + right)/2.0; fontExtents _ ImagerFont.RopeBoundingBox[textData.trueFont, fontExtentRope]; top _ fontExtents.ascent; base _ 0.0; bottom _ -fontExtents.descent; center _ (top + bottom)/2.0; GGBoundBox.UpdateBoundBox[textData.box, left, bottom, right, top]; textData.box _ GGBoundBox.BoundBoxOfBoundBox[textData.box, fontData.transform]; GGBoundBox.EnlargeByOffset[textData.box, halfCP]; IF textData.dropShadowOn THEN GGBoundBox.EnlargeByVector[textData.box, [x: textData.dropShadowOffset.x*textData.inverseScale.x, y: textData.dropShadowOffset.y*textData.inverseScale.y] ]; slice.boundBox _ GGBoundBox.CopyBoundBox[textData.box]; GGBoundBox.UpdateBoundBox[textData.feedbackBox, left, bottom, right, top]; textData.feedbackBox _ GGBoundBox.BoundBoxOfBoundBox[textData.feedbackBox, fontData.transform]; -- take into account the font transformation textData.points[0] _ [left, bottom]; textData.points[1] _ [left, base]; textData.points[2] _ [left, center]; textData.points[3] _ [left, top]; textData.points[4] _ [middle, bottom]; textData.points[5] _ [middle, base]; textData.points[6] _ [middle, center]; textData.points[7] _ [middle, top]; textData.points[8] _ [right, bottom]; textData.points[9] _ [right, base]; textData.points[10] _ [right, center]; textData.points[11] _ [right, top]; }; TextCopy: PROC [slice: Slice] RETURNS [copy: Slice] = { copyData: TextData; textData: TextData _ NARROW[slice.data]; copy _MakeTextSlice[textData.rope, textData.amplifySpace, textData.dropShadowOn, textData.dropShadowOffset, textData.shadowColor]; copyData _ NARROW[copy.data]; IF NOT SetTextFontAndTransform[copy, GGFont.CopyFontData[textData.fontData, copyData.fontData], NIL] THEN ERROR; copy.class.setFillColor[copy, textData.color]; }; TextDrawParts: PROC [slice: Slice, parts: SliceParts _ NIL, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, quick: BOOL] = { DoDrawText: PROC = { Imager.ConcatT[dc, textData.fontData.transform]; IF textData.dropShadowOn THEN DrawDropShadow[dc, textData, camera]; DrawText[dc, textData, camera]; }; textData: TextData _ NARROW[slice.data]; textParts: TextParts _ NARROW[parts]; IF textParts = NIL OR textParts.includeText THEN Imager.DoSaveAll[dc, DoDrawText]; }; TextDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = { DoDrawFeedback: PROC = { feedbackBox: BoundBox _ textData.feedbackBox; thisCPisHot, thisCPisSelected: BOOL; pts: ARRAY [0..11] OF Point; t: ImagerTransformation.Transformation _ fontData.transform; FOR i: NAT IN [0..11] DO pts[i] _ ImagerTransformation.Transform[t, textData.points[i]]; ENDLOOP; Imager.SetColor[dc, Imager.black]; FOR point: INTEGER IN [0..textMaxPoints) DO thisCPisHot _ hotTextParts#NIL AND hotTextParts.points[point]; thisCPisSelected _ normalTextParts#NIL AND normalTextParts.points[point]; IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, pts[point], hot]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[point], normal]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[point]]; ENDLOOP; IF normalTextParts#NIL THEN { Imager.SetStrokeJoint[dc, round]; Imager.SetStrokeWidth[dc, 1.0]; FOR edge: INTEGER IN [0..textMaxEdges) DO IF normalTextParts.edges[edge] THEN SELECT edge FROM 0 => Imager.MaskVector[dc, pts[0], pts[3] ]; 1 => Imager.MaskVector[dc, pts[3], pts[11] ]; 2 => Imager.MaskVector[dc, pts[11], pts[8] ]; 3 => Imager.MaskVector[dc, pts[8], pts[0] ]; 4 => Imager.MaskVector[dc, pts[1], pts[9] ]; 5 => Imager.MaskVector[dc, pts[2], pts[10] ]; 6 => Imager.MaskVector[dc, pts[4], pts[7] ]; ENDCASE => ERROR; ENDLOOP; }; }; normalTextParts, hotTextParts: TextParts; textData: TextData _ NARROW[slice.data]; fontData: FontData _ textData.fontData; IF caretIsMoving OR dragInProgress THEN RETURN; IF selectedParts = NIL AND hotParts = NIL THEN RETURN; normalTextParts _ NARROW[selectedParts]; hotTextParts _ NARROW[hotParts]; IF camera.quality#quality THEN Imager.DoSaveAll[dc, DoDrawFeedback]; }; DrawText: PROC [dc: Imager.Context, textData: TextData, camera: CameraData] = { Imager.SetFont[dc, IF camera.displayStyle=screen THEN textData.screenFont ELSE textData.trueFont]; Imager.SetColor[dc, IF textData.color=NIL THEN Imager.black ELSE textData.color]; Imager.SetXY[dc, [0.0, 0.0]]; -- dc must be transformed completely here Imager.SetAmplifySpace[dc, textData.amplifySpace]; -- adjust spacing (for right justified text) Imager.ShowRope[dc, textData.rope]; }; DrawDropShadow: PROC [dc: Imager.Context, textData: TextData, camera: CameraData] = { DoDrawDropShadow: PROC = { tVec: Imager.VEC _ [x: textData.dropShadowOffset.x*dropScale, y: textData.dropShadowOffset.y*dropScale]; Imager.TranslateT[dc, tVec]; Imager.SetFont[dc, IF camera.displayStyle=screen THEN textData.screenFont ELSE textData.trueFont]; Imager.SetColor[dc, IF textData.shadowColor=NIL THEN Imager.black ELSE textData.shadowColor]; Imager.SetXY[dc, [0.0, 0.0]]; -- dc must be transformed completely here Imager.SetAmplifySpace[dc, textData.amplifySpace]; -- adjust spacing (for right justified text) Imager.ShowRope[dc, textData.rope]; }; dropScale: REAL _ 0.1; -- results in one point drop shadow/10 points of text Imager.DoSaveAll[dc, DoDrawDropShadow]; }; TextDrawTransform: PROC [sliceD: SliceDescriptor, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, transform: ImagerTransformation.Transformation] = { DoTextDrawTransform: PROC = { Imager.ConcatT[dc, ImagerTransformation.Concat[textData.fontData.transform, transform]]; IF textData.dropShadowOn THEN DrawDropShadow[dc, textData, camera]; DrawText[dc, textData, camera]; }; textData: TextData _ NARROW[sliceD.slice.data]; Imager.DoSaveAll[dc, DoTextDrawTransform]; }; TextTransform: PROC [sliceD: SliceDescriptor, transform: ImagerTransformation.Transformation] = { textData: TextData _ NARROW[sliceD.slice.data]; fontData: FontData _ textData.fontData; fontData.transform _ ImagerTransformation.Concat[fontData.transform, transform]; textData.inverse _ ImagerTransformation.Invert[fontData.transform]; textData.inverseScale _ ImagerTransformation.Factor[textData.inverse].s; TextSetBoundBox[sliceD.slice]; }; TextDescribe: PROC [sliceD: SliceDescriptor] RETURNS [rope: Rope.ROPE] = { prefix: Rope.ROPE; textParts: TextParts; eCount, pCount: NAT _ 0; IF sliceD.parts = NIL THEN RETURN["a Text slice"]; textParts _ NARROW[sliceD.parts]; FOR i: INTEGER IN [0..textMaxPoints) DO IF textParts.points[i] THEN { pCount _ pCount + 1; prefix _ textPointRopes[i]; }; ENDLOOP; FOR i: INTEGER IN [0..textMaxEdges) DO IF textParts.edges[i] THEN { eCount _ eCount + 1; prefix _ textEdgeRopes[i]; }; ENDLOOP; IF pCount+eCount>1 THEN RETURN["multiple parts of a Text slice"]; IF eCount=0 AND pCount=0 THEN RETURN["NO parts of a Text slice"]; rope _ Rope.Concat[prefix, " of a Text slice"]; }; TextDescribeHit: PROC [slice: Slice, hitData: REF ANY] RETURNS [rope: Rope.ROPE] = { textHitData: TextHitData _ NARROW[hitData]; prefix: Rope.ROPE; IF textHitData.point#-1 THEN prefix _ textPointRopes[textHitData.point] ELSE IF textHitData.edge#-1 THEN prefix _ textEdgeRopes[textHitData.edge] ELSE ERROR; rope _ Rope.Concat[prefix, " of a Text slice"]; }; TextFileout: PROC [slice: Slice, f: IO.STREAM] = { textData: TextData _ NARROW[slice.data]; fontData: FontData _ textData.fontData; GGParseOut.WriteBOOL[f, IF fontData.comfortable THEN TRUE ELSE FALSE]; IF fontData.comfortable THEN f.PutF[" \"%q\" %g%g ", [rope[textData.rope]], [rope[fontData.prefix]], [rope[fontData.userFSF]] ] ELSE f.PutF[" \"%q\" %g ", [rope[textData.rope]], [rope[fontData.literal]] ]; GGParseOut.WriteTransformation[f, fontData.transform]; IF NOT fontData.comfortable THEN f.PutF[" %g %g ", [real[fontData.storedSize]], [real[fontData.designSize]] ]; GGParseOut.WriteColor[f, textData.color]; f.PutChar[IO.SP]; IF textData.dropShadowOn THEN { GGParseOut.WriteBOOL[f, TRUE]; f.PutChar[IO.SP]; GGParseOut.WritePoint[f, textData.dropShadowOffset]; f.PutChar[IO.SP]; GGParseOut.WriteColor[f, textData.shadowColor]; } ELSE GGParseOut.WriteBOOL[f, FALSE]; f.PutF[" %g", [real[textData.amplifySpace]]]; }; TextFilein: PROC [f: IO.STREAM, version: REAL, feedback: FeedbackData] RETURNS [slice: Slice] = { text, fontName: Rope.ROPE; prefix: Rope.ROPE _ "xerox/pressfonts/"; family: Rope.ROPE _ "Helvetica"; face: Rope.ROPE _ ""; fontData: GGFont.FontData; fontDataComfortable: BOOL _ FALSE; size: REAL _ 1.0; preferredSize: REAL _ 1.0; transform: ImagerTransformation.Transformation; shadowColor: Imager.Color _ Imager.black; textColor: Imager.Color _ Imager.black; dropShadowOn, good: BOOL _ FALSE; dropShadowOffset: Vector _ [0.0, 0.0]; amplifySpace: REAL _ 1.0; IF version >= 8702.11 THEN { fontDataComfortable _ GGParseIn.ReadBOOL[f, version].truth; }; IF version > 8601.22 THEN { text _ f.GetRopeLiteral[]; } ELSE { GGParseIn.ReadBlankAndRope[f, "("]; text _ GGParseIn.ReadBlankAndWord[f]; GGParseIn.ReadBlankAndRope[f, ")"]; }; IF version < 8702.11 THEN { -- many old style formats IF version >= 8701.30 THEN { prefix _ GGParseIn.ReadBlankAndWord[f]; }; -- ELSE uses default prefix IF version <= 8601.06 THEN { }-- use default family and face ELSE IF version <= 8601.27 THEN { -- a simple name like "Helvetica" or "Gacha" family _ GGParseIn.ReadBlankAndWord[f]; } ELSE IF version <= 8605.12 THEN { -- a complex name like "xerox/pressfonts/Helvetica-BIR" fontName _ GGParseIn.ReadBlankAndWord[f]; size _ GGParseIn.ReadBlankAndReal[f]; [] _ GGParseIn.ReadBlankAndWord[f]; fontName _ UnpackComplexFontName[fontName, size]; -- like Helvetica12BI [family, face, ----] _ TFontParamsFromFontName[fontName]; } ELSE IF version <= 8605.28 THEN { -- a mixed mode name like Helvetica7BI or TimesRoman12 fontName _ GGParseIn.ReadBlankAndWord[f]; size _ GGParseIn.ReadBlankAndReal[f]; [] _ GGParseIn.ReadBlankAndWord[f]; -- throw away redundant size [family, face, ----] _ TFontParamsFromFontName[fontName]; } ELSE IF version < 8701.30 THEN { -- a mixed mode name like Helvetica7BI or TimesRoman12. No colorName, real text color fontName _ GGParseIn.ReadBlankAndWord[f]; size _ GGParseIn.ReadBlankAndReal[f]; [family, face, ----] _ TFontParamsFromFontName[fontName]; } ELSE IF version = 8701.30 THEN {-- an interim font format fail: BOOL _ FALSE; [fail, ----, family, face] _ GGFont.OldParseFontData[f, FALSE, TRUE, TRUE]; IF fail THEN ERROR; transform _ GGParseIn.ReadTransformation[f]; preferredSize _ GGParseIn.ReadBlankAndReal[f]; }; fontData _ GGFont.CreateFontData[]; fontData.transform _ transform; fontData.storedSize _ preferredSize; -- needed by ParseFontData fontData _ GGFont.ParseFontData[data: fontData, inStream: IO.RIS[rope: Rope.Cat[prefix,family,face], oldStream: NIL], prefixP: TRUE, familyP: TRUE, faceP: TRUE]; } ELSE { IF fontDataComfortable THEN { -- User data in file: userName, transformation userName: Rope.ROPE _ GGParseIn.ReadBlankAndWord[f]; fontData _ GGFont.ParseFontData[data: NIL, inStream: IO.RIS[rope: userName, oldStream: NIL], prefixP: TRUE, familyP: TRUE, faceP: TRUE]; fontData.transform _ GGParseIn.ReadTransformation[f]; } ELSE { -- Literal data in file literalName: Rope.ROPE _ GGParseIn.ReadBlankAndWord[f]; fontData _ GGFont.CreateFontData[]; fontData.transform _ GGParseIn.ReadTransformation[f]; fontData.storedSize _ GGParseIn.ReadBlankAndReal[f]; fontData.designSize _ GGParseIn.ReadBlankAndReal[f]; fontData _ GGFont.ParseFontData[data: fontData, inStream: IO.RIS[rope: literalName, oldStream: NIL], literalP: TRUE]; }; }; IF version > 8605.28 THEN { textColor _ GGParseIn.ReadColor[f, version]; }; IF version >= 8609.26 THEN { [dropShadowOn, good] _ GGParseIn.ReadBOOL[f, version]; IF NOT good THEN ERROR; IF dropShadowOn THEN { dropShadowOffset _ GGParseIn.ReadPoint[f]; shadowColor _ GGParseIn.ReadColor[f, version]; }; } ELSE dropShadowOn _ FALSE; IF version <= 8607.22 THEN { -- a point rather than a transform point: Point _ GGParseIn.ReadPoint[f]; fontData.transform _ ImagerTransformation.Translate[point]; fontData.transform _ ImagerTransformation.PreScale[fontData.transform, size]; size _ 1.0; } ELSE IF version < 8701.30 THEN { fontData.transform _ GGParseIn.ReadTransformation[f]; fontData.transform _ ImagerTransformation.PreScale[fontData.transform, size]; size _ 1.0; }; -- transform already read in for later versions IF version >= 8701.13 THEN { amplifySpace _ GGParseIn.ReadBlankAndReal[f]; } ELSE amplifySpace _ 1.0; slice _ MakeTextSlice[text: text, amplifySpace: amplifySpace, dropShadowsOn: dropShadowOn, dropShadowOffset: dropShadowOffset, shadowColor: shadowColor]; fontData.substituteOK _ TRUE; -- allow font substitution on file reads IF NOT SetTextFontAndTransform[slice, fontData, feedback] THEN SIGNAL FontNameError; slice.class.setFillColor[slice, textColor]; }; MakeComplete: PROC [parts: SliceParts] = { WITH parts SELECT FROM textParts: TextParts => { textParts.points _ ALL[TRUE]; textParts.edges _ ALL[TRUE]; textParts.includeText _ TRUE; }; ipParts: IPParts => { ipParts.points _ ALL[TRUE]; ipParts.edges _ ALL[TRUE]; ipParts.innards _ TRUE; }; ENDCASE => ERROR; }; IsComplete: PROC [parts: SliceParts] RETURNS [BOOL] = { WITH parts SELECT FROM textParts: TextParts => RETURN[ textParts.points=ALL[TRUE] AND textParts.edges=ALL[TRUE] AND textParts.includeText=TRUE]; ipParts: IPParts => RETURN[ ipParts.points=ALL[TRUE] AND ipParts.edges=ALL[TRUE] AND ipParts.innards=TRUE]; ENDCASE => ERROR; }; IsEmpty: PROC [parts: SliceParts] RETURNS [BOOL] = { WITH parts SELECT FROM textParts: TextParts => RETURN[ textParts.points=ALL[FALSE] AND textParts.edges=ALL[FALSE] AND textParts.includeText = FALSE]; ipParts: IPParts => RETURN[ ipParts.points=ALL[FALSE] AND ipParts.edges=ALL[FALSE] AND ipParts.innards = FALSE]; ENDCASE => ERROR; }; TextIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = { RETURN[IsEmpty[sliceD.parts]]; }; TextIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = { RETURN[IsComplete[sliceD.parts]]; }; TextNewParts: PROC [slice: Slice, hitData: REF ANY, mode: SelectMode] RETURNS [sliceD: SliceDescriptor] = { textHitData: TextHitData _ NARROW[hitData]; textParts: TextParts _ NEW[TextPartsObj _ [points: ALL[FALSE], edges: ALL[FALSE], includeText: FALSE ] ]; SELECT mode FROM literal => { IF textHitData.point#-1 THEN textParts.points[textHitData.point] _ TRUE ELSE IF textHitData.edge#-1 THEN textParts.edges[textHitData.edge] _ TRUE ELSE ERROR; }; joint => { IF textHitData.point#-1 THEN { textParts.points[textHitData.point] _ TRUE; } ELSE { hitData: REF ANY; pointHitData: TextHitData; success: BOOL; wholeD: SliceDescriptor; wholeD _ TextNewParts[slice, NIL, topLevel]; [----, ----, hitData, success] _ slice.class.closestPoint[wholeD, textHitData.hitPoint, GGUtility.plusInfinity]; IF NOT success THEN ERROR; pointHitData _ NARROW[hitData]; IF pointHitData.point = -1 THEN ERROR; textParts.points[pointHitData.point] _ TRUE; }; textParts.includeText _ TRUE; }; segment => IF textHitData.edge#-1 THEN { textParts.edges[textHitData.edge] _ TRUE; textParts.includeText _ TRUE; }; controlPoint => { textParts.points _ ALL[TRUE]; textParts.edges _ ALL[TRUE]; }; traj, slice, topLevel => MakeComplete[textParts]; none => { -- leave textParts empty }; ENDCASE => ERROR; sliceD _ GGSlice.DescriptorFromParts[slice, textParts]; }; TextUnionParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aPlusB: SliceDescriptor] = { textPartsA: TextParts _ NARROW[partsA.parts]; textPartsB: TextParts _ NARROW[partsB.parts]; newParts: TextParts; IF partsA = NIL OR partsB = NIL THEN ERROR; IF partsA.parts = NIL THEN RETURN[partsB]; IF partsB.parts = NIL THEN RETURN[partsA]; newParts _ NEW[TextPartsObj _ [points: ALL[FALSE], edges: ALL[FALSE], includeText: FALSE]]; FOR i: INTEGER IN [0..textMaxPoints) DO newParts.points[i] _ textPartsA.points[i] OR textPartsB.points[i]; ENDLOOP; FOR i: INTEGER IN [0..textMaxEdges) DO newParts.edges[i] _ textPartsA.edges[i] OR textPartsB.edges[i]; ENDLOOP; newParts.includeText _ textPartsA.includeText OR textPartsB.includeText; aPlusB _ GGSlice.DescriptorFromParts[partsA.slice, newParts]; }; TextDiffParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aMinusB: SliceDescriptor] = { textPartsA: TextParts _ NARROW[partsA.parts]; textPartsB: TextParts _ NARROW[partsB.parts]; newParts: TextParts; IF partsA = NIL OR partsB = NIL THEN ERROR; IF partsA.parts=NIL THEN RETURN[GGSlice.DescriptorFromParts[partsA.slice, NIL]]; IF partsB=NIL THEN RETURN[partsA]; newParts _ NEW[TextPartsObj _ [points: ALL[FALSE], edges: ALL[FALSE], includeText: FALSE ] ]; FOR i: INTEGER IN [0..textMaxPoints) DO newParts.points[i] _ IF textPartsB.points[i] THEN FALSE ELSE textPartsA.points[i]; ENDLOOP; FOR i: INTEGER IN [0..textMaxEdges) DO newParts.edges[i] _ IF textPartsB.edges[i] THEN FALSE ELSE textPartsA.edges[i]; ENDLOOP; newParts.includeText _ IF textPartsB.includeText THEN FALSE ELSE textPartsA.includeText; aMinusB _ GGSlice.DescriptorFromParts[partsA.slice, newParts]; }; TextMovingParts: PROC [slice: Slice, selectedParts: SliceParts] RETURNS [background, overlay, rubber, drag: SliceDescriptor] = { dragParts: TextParts _ NEW[TextPartsObj _ [points: ALL[TRUE], edges: ALL[TRUE], includeText: TRUE ] ]; backgroundParts: TextParts _ NEW[TextPartsObj _ [points: ALL[TRUE], edges: ALL[TRUE], includeText: TRUE ] ]; IF IsEmpty[selectedParts] THEN { -- everything on background dragParts.edges _ ALL[FALSE]; dragParts.points _ ALL[FALSE]; dragParts.includeText _ FALSE; } ELSE { -- everything is dragging backgroundParts.edges _ ALL[FALSE]; backgroundParts.points _ ALL[FALSE]; backgroundParts.includeText _ FALSE; }; background _ GGSlice.DescriptorFromParts[slice, backgroundParts]; overlay _ rubber _ GGSlice.DescriptorFromParts[slice, NIL]; drag _ GGSlice.DescriptorFromParts[slice, dragParts]; }; TextAugmentParts: PROC [sliceD: SliceDescriptor, selectClass: SelectionClass] RETURNS [more: SliceDescriptor] = { textParts: TextParts _ NARROW[sliceD.parts]; newParts: TextParts _ NEW[TextPartsObj _ [points: textParts.points, edges: textParts.edges, includeText: textParts.includeText] ]; FOR i: INTEGER IN [0..textMaxEdges) DO IF textParts.edges[i] THEN SELECT i FROM 0 => {newParts.points[0] _ TRUE; newParts.points[1] _ TRUE; newParts.points[2] _ TRUE; newParts.points[3] _ TRUE;}; 1 => {newParts.points[3] _ TRUE; newParts.points[7] _ TRUE; newParts.points[11] _ TRUE;}; 2 => {newParts.points[8] _ TRUE; newParts.points[9] _ TRUE; newParts.points[10] _ TRUE; newParts.points[11] _ TRUE;}; 3 => {newParts.points[0] _ TRUE; newParts.points[4] _ TRUE; newParts.points[8] _ TRUE;}; 4 => {newParts.points[1] _ TRUE; newParts.points[5] _ TRUE; newParts.points[9] _ TRUE;}; 5 => {newParts.points[2] _ TRUE; newParts.points[6] _ TRUE; newParts.points[10] _ TRUE;}; 6 => {newParts.points[4] _ TRUE; newParts.points[5] _ TRUE; newParts.points[6] _ TRUE; newParts.points[7] _ TRUE;}; ENDCASE => ERROR; ENDLOOP; more _ GGSlice.DescriptorFromParts[sliceD.slice, newParts]; }; TextPointsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = { parts: TextParts _ NARROW[sliceD.parts]; pointGen _ NEW[PointGeneratorObj _ [sliceD, 0, 0, NIL] ]; FOR point: INTEGER IN [0..textMaxPoints) DO IF parts.points[point] THEN pointGen.toGo _ pointGen.toGo + 1; ENDLOOP; }; TextPointPairsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointPairGen: PointPairGenerator] = { parts: TextParts _ NARROW[sliceD.parts]; pointPairGen _ NEW[PointPairGeneratorObj _ [sliceD, 0, 0, NIL] ]; -- toGo and index now used FOR edge: INTEGER IN [0..textMaxEdges) DO IF parts.edges[edge] THEN pointPairGen.toGo _ pointPairGen.toGo + 1; ENDLOOP; }; TextNextPoint: PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = { IF pointGen=NIL OR pointGen.toGo = 0 THEN { pointAndDone.done _ TRUE; RETURN; } ELSE { sliceD: SliceDescriptor _ pointGen.sliceD; textData: TextData _ NARROW[sliceD.slice.data]; fontData: FontData _ textData.fontData; textParts: TextParts _ NARROW[sliceD.parts]; index: INTEGER _ -1; pointAndDone.done _ FALSE; FOR index _ pointGen.index, index+1 UNTIL index >=textMaxPoints DO IF textParts.points[index] THEN EXIT; -- index will point to next available point ENDLOOP; pointAndDone.point _ ImagerTransformation.Transform[fontData.transform, textData.points[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 }; }; TextNextPointPair: PROC [pointPairGen: PointPairGenerator] RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = { IF pointPairGen=NIL OR pointPairGen.toGo = 0 THEN { pointPairAndDone.done _ TRUE; RETURN; } ELSE { sliceD: SliceDescriptor _ pointPairGen.sliceD; textData: TextData _ NARROW[sliceD.slice.data]; textParts: TextParts _ NARROW[sliceD.parts]; t: ImagerTransformation.Transformation _ textData.fontData.transform; index: INTEGER _ -1; pointPairAndDone.done _ FALSE; FOR index _ pointPairGen.index, index+1 UNTIL index >=textMaxEdges DO IF textParts.edges[index] THEN EXIT; -- index will point to next availabe edge ENDLOOP; SELECT index FROM -- index in [0..textMaxEdges) of next available edge 0 => { pointPairAndDone.lo _ ImagerTransformation.Transform[t, textData.points[0] ]; pointPairAndDone.hi _ ImagerTransformation.Transform[t, textData.points[3] ]; }; 1 => { pointPairAndDone.lo _ ImagerTransformation.Transform[t, textData.points[3] ]; pointPairAndDone.hi _ ImagerTransformation.Transform[t, textData.points[11] ]; }; 2 => { pointPairAndDone.lo _ ImagerTransformation.Transform[t, textData.points[11] ]; pointPairAndDone.hi _ ImagerTransformation.Transform[t, textData.points[8] ]; }; 3 => { pointPairAndDone.lo _ ImagerTransformation.Transform[t, textData.points[8] ]; pointPairAndDone.hi _ ImagerTransformation.Transform[t, textData.points[0] ]; }; 4 => { pointPairAndDone.lo _ ImagerTransformation.Transform[t, textData.points[1] ]; pointPairAndDone.hi _ ImagerTransformation.Transform[t, textData.points[9] ]; }; 5 => { pointPairAndDone.lo _ ImagerTransformation.Transform[t, textData.points[2] ]; pointPairAndDone.hi _ ImagerTransformation.Transform[t, textData.points[10] ]; }; 6 => { pointPairAndDone.lo _ ImagerTransformation.Transform[t, textData.points[4] ]; pointPairAndDone.hi _ ImagerTransformation.Transform[t, textData.points[7] ]; }; ENDCASE => SIGNAL Feedback.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 }; }; PointIsInBox: PROC [test: Point, box: GGBasicTypes.BoundBoxObj] RETURNS [BOOL] = { RETURN[ NOT (test.x < box.loX OR test.x > box.hiX OR test.y < box.loY OR test.y > box.hiY) ]; }; TextClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL _ FALSE] = { index: NAT _ 9999; textData: TextData _ NARROW[sliceD.slice.data]; bigBox: GGBasicTypes.BoundBoxObj _ [textData.feedbackBox.loX-tolerance, textData.feedbackBox.loY-tolerance, textData.feedbackBox.hiX+tolerance, textData.feedbackBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates IF PointIsInBox[testPoint, bigBox] THEN { textParts: TextParts _ NARROW[sliceD.parts]; localTestpoint: Point _ GGTransform.Transform[textData.inverse, testPoint]; localTolerance: REAL _ ABS[tolerance*MAX[textData.inverseScale.x, textData.inverseScale.y]]; -- MAX function is not correct when text is skewed. [bestDist, index, bestPoint, success] _ TextNearestPoint[textData.points, localTestpoint, localTolerance, textParts.points]; IF success THEN { bestPoint _ GGTransform.Transform[textData.fontData.transform, bestPoint]; bestDist _ Vectors2d.Distance[testPoint, bestPoint]; hitData _ NEW[TextHitDataObj _ [point: index, edge: -1, hitPoint: bestPoint] ]; }; }; }; TextClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOL _ FALSE] = { textData: TextData _ NARROW[sliceD.slice.data]; bigBox: GGBasicTypes.BoundBoxObj _ [textData.feedbackBox.loX-tolerance, textData.feedbackBox.loY-tolerance, textData.feedbackBox.hiX+tolerance, textData.feedbackBox.hiY+tolerance, FALSE, FALSE]; IF PointIsInBox[testPoint, bigBox] THEN { seg: NAT _ 9999; textHitData: TextHitData; textParts: TextParts _ NARROW[sliceD.parts]; localTestpoint: Point _ GGTransform.Transform[textData.inverse, testPoint]; localTolerance: REAL _ ABS[tolerance*MAX[textData.inverseScale.x, textData.inverseScale.y]]; -- MAX function is not correct when text is skewed. [bestDist, seg, bestPoint, success] _ TextNearestEdge[textData.points, localTestpoint, localTolerance, textParts.edges]; IF success THEN { bestPoint _ GGTransform.Transform[textData.fontData.transform, bestPoint]; bestDist _ Vectors2d.Distance[testPoint, bestPoint]; hitData _ textHitData _ NEW[TextHitDataObj _ [point: -1, edge: seg, hitPoint: bestPoint] ]; }; }; }; TextNearestPoint: PROC [points: TextPoints, testPoint: Point, tolerance: REAL _ 1E6, mask: TextPointArray ] RETURNS [bestDist: REAL, bestPoint: NAT, bestXY: Point, success: BOOL _ FALSE] = { thisPoint: Point; thisDist2, bestDist2: REAL; tolerance2: REAL _ tolerance*tolerance; index: NAT _ 0; bestXY _ [-1.0, -1.0]; bestDist2 _ 1E12; -- better be big enough bestPoint _ 9999; FOR index IN [0..textMaxPoints) DO IF mask[index] THEN { thisPoint _ points[index]; -- use feedbackBox relative coordinates thisDist2 _ Vectors2d.DistanceSquared[thisPoint, testPoint]; IF thisDist2 < bestDist2 THEN { bestDist2 _ thisDist2; bestPoint _ index; bestXY _ thisPoint; }; }; ENDLOOP; bestDist _ RealFns.SqRt[bestDist2]; IF bestDist < tolerance THEN success _ TRUE; }; scratchEdge: GGBasicTypes.Edge _ Lines2d.CreateEmptyEdge[]; TextNearestEdge: PUBLIC PROC [points: TextPoints, testPoint: Point, tolerance: REAL _ 1E6, mask: TextEdgeArray _ ALL[TRUE] ] RETURNS [bestDist: REAL, bestSeg: NAT, bestPoint: Point, success: BOOL _ FALSE] = { thisPoint: Point; thisDist2, bestDist2: REAL; tolerance2: REAL _ tolerance*tolerance; index: NAT _ 0; bestPoint _ [-1.0, -1.0]; bestDist2 _ 1E12; -- better be big enough bestSeg _ 9999; FOR index IN [0..textMaxEdges) DO IF mask[index] THEN { SELECT index FROM 0 => Lines2d.FillEdge[points[0], points[3], scratchEdge ]; 1 => Lines2d.FillEdge[points[3], points[11], scratchEdge ]; 2 => Lines2d.FillEdge[points[11], points[8], scratchEdge ]; 3 => Lines2d.FillEdge[points[8], points[0], scratchEdge ]; 4 => Lines2d.FillEdge[points[1], points[9], scratchEdge ]; 5 => Lines2d.FillEdge[points[2], points[10], scratchEdge ]; 6 => Lines2d.FillEdge[points[4], points[7], scratchEdge ]; ENDCASE => ERROR; thisPoint _ Lines2d.NearestPointOnEdge[testPoint, scratchEdge]; thisDist2 _ Vectors2d.DistanceSquared[thisPoint, testPoint]; IF thisDist2 < bestDist2 THEN { bestDist2 _ thisDist2; bestSeg _ index; bestPoint _ thisPoint; }; }; ENDLOOP; bestDist _ RealFns.SqRt[bestDist2]; IF bestDist < tolerance THEN success _ TRUE; }; TextSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = { textData: TextData _ NARROW[slice.data]; textData.shadowColor _ color; }; TextGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Imager.Color] = { textData: TextData _ NARROW[slice.data]; color _ textData.shadowColor; }; TextSetDefaults: PROC [slice: Slice, parts: SliceParts, defaults: DefaultData] = { textData: TextData _ NARROW[slice.data]; textParts: TextParts _ NARROW[parts]; IF textParts.includeText THEN textData.color _ defaults.fillColor; }; TextSetFillColor: PROC [slice: Slice, color: Imager.Color] = { textData: TextData _ NARROW[slice.data]; textData.color _ color; }; TextGetFillColor: PROC [slice: Slice] RETURNS [color: Imager.Color] = { textData: TextData _ NARROW[slice.data]; RETURN[textData.color]; }; ipMaxPoints: INTEGER = 4; ipMaxEdges: INTEGER = 4; IPPointArray: TYPE = --PACKED-- ARRAY [0..ipMaxPoints) OF BOOL; -- ll, ul, ur, lr IPEdgeArray: TYPE = --PACKED-- 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 [ localBox: BoundBox, -- a bounding box in local coordinates. Set once when the slice is created. tightBox: BoundBox, -- a tight-fitting box in scene coordinates. includeByValue: BOOL, -- 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 ]; 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 ]; BuildIPSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = { OPEN GGSlice; class _ NEW[SliceClassObj _ [ type: $IP, getBoundBox: IPBoundBox, getTightBox: IPTightBox, copy: IPCopy, drawParts: IPDrawParts, drawTransform: IPDrawTransform, drawSelectionFeedback: IPDrawSelectionFeedback, drawAttractorFeedback: NoOpDrawAttractorFeedback, transform: IPTransform, describe: IPDescribe, describeHit: IPDescribeHit, fileout: IPFileout, filein: IPFilein, isEmptyParts: IPIsEmptyParts, isCompleteParts: IPIsCompleteParts, newParts: IPNewParts, unionParts: IPUnionParts, differenceParts: IPDiffParts, movingParts: IPMovingParts, augmentParts: IPAugmentParts, setSelectedFields: NoOpSetSelectedFields, pointsInDescriptor: IPPointsInDescriptor, pointPairsInDescriptor: IPPointPairsInDescriptor, segmentsInDescriptor: NoOpSegmentsInDescriptor, walkSegments: NoOpWalkSegments, nextPoint: IPNextPoint, nextPointPair: IPNextPointPair, nextSegment: NoOpNextSegment, closestPoint: IPClosestPoint, closestJointToHitData: NoOpClosestJointToHitData, closestPointAndTangent: NoOpClosestPointAndTangent, closestSegment: IPClosestSegment, 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 ]]; }; MakeIPSliceFromFile: PROC [fullName: Rope.ROPE, feedback: FeedbackData, transform: ImagerTransformation.Transformation _ NIL, localBox: BoundBox, includeByValue: BOOL] RETURNS [slice: Slice] = { ipMaster: Interpress.Master; success: BOOL _ FALSE; [ipMaster, success] _ GGUtility.OpenInterpressOrComplain[feedback, fullName]; IF NOT success THEN RETURN; slice _ MakeIPSliceFromMaster[ipMaster, 2834.646, fullName, feedback, transform, localBox, includeByValue]; }; MakeIPSliceFromMaster: PUBLIC PROC [ipMaster: Interpress.Master, pixelsPerUnit: REAL _ 2834.646, fullName: Rope.ROPE _ NIL, feedback: FeedbackData, transform: ImagerTransformation.Transformation _ NIL, localBox: BoundBox _ NIL, includeByValue: BOOL] RETURNS [slice: Slice] = { memContext: Imager.Context; memContext _ ImagerMemory.NewMemoryContext[]; -- in pixels Imager.ScaleT[memContext, pixelsPerUnit]; Interpress.DoPage[master: ipMaster, page: 1, context: memContext, log: NIL]; BEGIN -- compute the bounding box MakeScene: PROC [context: Imager.Context] = { ImagerMemory.Replay[memContext, context]; }; IF localBox = NIL THEN { scene: Scene _ GGFromImager.Capture[action: MakeScene ! GGFromImager.WarningMessage => { Feedback.Append[feedback, message, oneLiner]; RESUME; }]; localBox _ GGScene.TightBoxOfScene[scene]; }; END; slice _ MakeIPSliceAux[fullName, memContext, feedback, transform, includeByValue, localBox]; }; MakeIPSliceFromMaskPixel: PUBLIC PROC [pa: Imager.PixelArray, color: Color, feedback: FeedbackData, transform: ImagerTransformation.Transformation _ NIL] RETURNS [slice: Slice] = { memContext: Imager.Context; ipData: IPData; inverse: ImagerTransformation.Transformation; inverseScale: Imager.VEC; 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; localBox _ GGBoundBox.BoundBoxOfPixelArray[pa]; ipData _ NEW[IPDataObj _ [localBox: localBox, includeByValue: TRUE, file: NIL, mem: memContext, transform: transform, inverse: inverse, inverseScale: inverseScale ]]; slice _ NEW[SliceObj _ [ class: GGSlice.FetchSliceClass[$IP], data: ipData, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE], boundBox: GGBoundBox.CreateBoundBox[0,0,0,0] -- gets set next line ]]; IPUpdateBoundBox[slice]; }; MakeIPSliceAux: PROC [fileName: Rope.ROPE, memContext: Imager.Context, feedback: FeedbackData, transform: ImagerTransformation.Transformation, includeByValue: BOOL, localBox: BoundBox] 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 _ [localBox: localBox, includeByValue: includeByValue, file: fileName, mem: memContext, transform: transform, inverse: inverse, inverseScale: inverseScale]]; slice _ NEW[SliceObj _ [ class: GGSlice.FetchSliceClass[$IP], data: ipData, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE], boundBox: GGBoundBox.CreateBoundBox[0,0,0,0] -- gets set next line ]]; IPUpdateBoundBox[slice]; }; SetIncludeByValue: PUBLIC PROC [slice: Slice, includeByValue: BOOL] = { ipData: IPData _ NARROW[slice.data]; ipData.includeByValue _ includeByValue; }; GetIncludeByValue: PUBLIC PROC [slice: Slice] RETURNS [includeByValue: BOOL] = { ipData: IPData _ NARROW[slice.data]; includeByValue _ ipData.includeByValue; }; IPUpdateBoundBox: PROC [slice: Slice] = { ipData: IPData _ NARROW[slice.data]; ipData.tightBox _ GGBoundBox.BoundBoxOfBoundBox[ipData.localBox, ipData.transform]; slice.boundBox _ GGBoundBox.CopyBoundBox[ipData.tightBox]; GGBoundBox.AllowForJoints[slice.boundBox]; }; IPBoundBox: PROC [slice: Slice, parts: SliceParts _ NIL] RETURNS [box: BoundBox]= { RETURN[slice.boundBox]; }; IPTightBox: PROC [slice: Slice, parts: SliceParts _ NIL] RETURNS [box: BoundBox]= { ipData: IPData _ NARROW[slice.data]; RETURN[ipData.tightBox]; }; IPCopy: PROC [slice: Slice] RETURNS [copy: Slice _ NIL] = { ipData: IPData _ NARROW[slice.data]; transform: Imager.Transformation _ ImagerTransformation.Copy[ipData.transform]; copy _ MakeIPSliceAux[ipData.file, ipData.mem, NIL, transform, ipData.includeByValue, ipData.localBox]; }; IPDrawParts: PROC [slice: Slice, parts: SliceParts _ NIL, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, quick: BOOL] = { DoDrawInnards: PROC = { Imager.ConcatT[dc, ipData.transform]; 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]; ipParts: IPParts _ NARROW[parts]; IF ipParts = NIL OR ipParts.innards THEN Imager.DoSaveAll[dc, DoDrawInnards]; }; IPDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = { DoDrawFeedback: PROC = { t: ImagerTransformation.Transformation _ ipData.transform; box: BoundBox _ ipData.localBox; 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 _ hotIPParts#NIL AND hotIPParts.points[point]; thisCPisSelected _ normalIPParts#NIL AND normalIPParts.points[point]; IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, pts[point], hot]; IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[point], normal]; IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[point]]; ENDLOOP; Imager.SetStrokeJoint[dc, round]; Imager.SetStrokeWidth[dc, 1.0]; Imager.SetColor[dc, Imager.black]; IF normalIPParts # NIL THEN { FOR edge: INTEGER IN [0..ipMaxEdges) DO IF normalIPParts.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; }; }; normalIPParts, hotIPParts: IPParts; ipData: IPData _ NARROW[slice.data]; IF caretIsMoving OR dragInProgress THEN RETURN; IF selectedParts = NIL AND hotParts = NIL THEN RETURN; normalIPParts _ NARROW[selectedParts]; hotIPParts _ NARROW[hotParts]; IF camera.quality#quality THEN Imager.DoSaveAll[dc, DoDrawFeedback]; }; IPDrawTransform: PROC [sliceD: SliceDescriptor, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, transform: ImagerTransformation.Transformation] = { DoIPDrawTransform: PROC = { Imager.ConcatT[dc, ImagerTransformation.Concat[ipData.transform, transform]]; Imager.SetColor[dc, Imager.black]; -- otherwise the replay could end up in a random color. ImagerMemory.Replay[ipData.mem, dc]; }; ipData: IPData _ NARROW[sliceD.slice.data]; Imager.DoSaveAll[dc, DoIPDrawTransform]; }; IPTransform: PROC [sliceD: SliceDescriptor, transform: ImagerTransformation.Transformation] = { ipData: IPData _ NARROW[sliceD.slice.data]; ipData.transform _ ImagerTransformation.Concat[ipData.transform, transform]; ipData.inverse _ ImagerTransformation.Invert[ipData.transform]; ipData.inverseScale _ ImagerTransformation.Factor[ipData.inverse].s; IPUpdateBoundBox[sliceD.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: LONG CARDINAL; masterRope: Rope.ROPE; f.PutF["\"%q\" ", [rope[ipData.file]]]; GGParseOut.WriteTransformation[f, ipData.transform]; 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 { 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.PutF["%g\n", [integer[masterSize]]]; f.PutRope[masterRope]; }; }; IPFilein: PROC [f: IO.STREAM, version: REAL, feedback: FeedbackData] RETURNS [slice: Slice _ NIL] = { ipMaster: Interpress.Master _ NIL; fullName: Rope.ROPE; success: BOOL _ FALSE; transform: ImagerTransformation.Transformation; masterText: REF TEXT; masterSize, nBytesRead: NAT; masterStream: IO.STREAM; includeByValue: BOOL; localBox: BoundBox; fullName _ f.GetRopeLiteral[]; [fullName] _ FS.FileInfo[fullName]; -- add the version number transform _ GGParseIn.ReadTransformation[f]; IF version >= 8701.28 THEN { localBox _ GGParseIn.ReadBox[f]; } ELSE localBox _ NIL; IF version >= 8612.04 THEN { goodValue: BOOL; GGParseIn.ReadBlankAndRope[f, "includeByValue:"]; [includeByValue, goodValue] _ GGParseIn.ReadBOOL[f, version]; IF NOT goodValue THEN ERROR; IF includeByValue THEN { masterSize _ GGParseIn.ReadBlankAndNAT[f]; GGParseIn.ReadBlank[f]; masterText _ RefText.ObtainScratch[masterSize+2]; nBytesRead _ IO.GetBlock[f, masterText, 0, masterSize]; IF nBytesRead # masterSize THEN ERROR; masterStream _ IO.TIS[masterText]; ipMaster _ Interpress.FromStream[masterStream, NIL]; slice _ MakeIPSliceFromMaster[ipMaster, 1.0, fullName, feedback, transform, localBox, TRUE]; } ELSE { slice _ MakeIPSliceFromFile[fullName, feedback, transform, localBox, FALSE]; }; } ELSE { slice _ MakeIPSliceFromFile[fullName, feedback, transform, localBox, FALSE]; }; }; IPIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = { RETURN[IsEmpty[sliceD.parts]]; }; IPIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = { 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] _ slice.class.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] 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] ]; 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; }; 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; }; IPNextPoint: PROC [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; box: BoundBox _ ipData.localBox; 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 _ 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 ]; 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 }; }; IPNextPointPair: PROC [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.localBox; 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 Feedback.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, hitData: REF ANY, success: BOOL _ FALSE] = { index: NAT _ 9999; slice: Slice _ sliceD.slice; ipData: IPData _ NARROW[slice.data]; toleranceBox: GGBasicTypes.BoundBoxObj _ [ipData.tightBox.loX-tolerance, ipData.tightBox.loY-tolerance, ipData.tightBox.hiX+tolerance, ipData.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.localBox, 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, hitData: REF ANY, success: BOOL _ FALSE] = { slice: Slice _ sliceD.slice; ipData: IPData _ NARROW[slice.data]; toleranceBox: GGBasicTypes.BoundBoxObj _ [ipData.tightBox.loX-tolerance, ipData.tightBox.loY-tolerance, ipData.tightBox.hiX+tolerance, ipData.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.localBox, 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] ]; }; }; }; fontExtentRope: Rope.ROPE; -- filled in by Init Init: PROC = { fontExtentRope _ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; }; Init[]; END. ŠGGSliceImplB.mesa Copyright c 1986 by Xerox Corporation. All rights reserved. Last edited by Pier on May 11, 1987 7:00:52 pm PDT Last edited by Bier on April 7, 1987 0:11:59 am PDT Contents: Implements various slice classes in Gargoyle. [Artwork node; type 'ArtworkInterpress on' to command tool] Text Points as shown Text Edges: 0 => left, 1=> top, 2=> right, 3=> bottom, 4 => baseline, 5 => horizontal centerline, 6 => vertical centerline Filled in by BoxClosestPoint, BoxClosestSegment, in same order as TextPointArray and TextEdgeArray. -1 means not hit Procs peculiar to text Fundamentals Drawing Transforming Textual Description Parts Part Generators Hit Testing Style other font related values must be filled in by client call to SetTextFont A fontName is a font designation with family, size, and face, like Helvetica23BI or TimesRoman6.95 (real font sizes are OK) can't user IO.GetInt because a string like Helvetica9BI causes it to fail! The text slice's font will be set using fontData. IF andTransform, fontData.transform will be used (unmodified) ELSE the translation component of the existing slice transform will be used. If fontData.transform=NIL, the scale component of the new transform is set to fontData.scale and the rotation component to 0. transform _ IF fontData.transform=NIL THEN ImagerTransformation.PreScale[m: ImagerTransformation.Translate[ImagerTransformation.Factor[m: textData.fontData.transform].t], s: fontData.scale] ELSE ImagerTransformation.Copy[fontData.transform]; Takes a complex name like "xerox/pressfonts/Helvetica-MIR" and a font size and returns a simple name like Helvetica10I Fundamental GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceTightBoxProc The following 7 values are of interest: left: = 0.0. right: = Width[string]. middle: (left + right) / 2. top: base: 0.0 bottom: center: (top + bottom) / 2 ASSERT: extents all positivesubtract left and descent, add right and ascent Bounding Boxes are calculated from screen fonts (which are larger). IF textData.dropShadowOn THEN GGBoundBox.EnlargeByVector[textData.box, textData.dropShadowOffset]; feedbackBoxes are used for hit testing and are calculated from the print font. left _ 0.0; right _ RopeEscapement[textData.trueFont, textData.rope, textData.amplifySpace].x; middle _ (left + right)/2.0; fontExtents _ ImagerFont.RopeBoundingBox[textData.trueFont, fontExtentRope]; top _ fontExtents.ascent; base _ 0.0; bottom _ -fontExtents.descent; center _ (top + bottom)/2.0; GGModelTypes.SliceCopyProc Drawing GGModelTypes.SliceDrawPartsProc GGModelTypes.SliceDrawSelectionFeedbackProc IF NOT quick AND normalTextParts#NIL THEN { called with the dc transformation already set to object coordinates called with the dc transformation already set to object coordinates tVec: Imager.VEC _ [x: textData.dropShadowOffset.x*textData.inverseScale.x, y: textData.dropShadowOffset.y*textData.inverseScale.y]; Imager.TranslateT[dc, textData.dropShadowOffset]; GGModelTypes.SliceDrawTransformProc Transforming GGModelTypes.SliceTransformProc Textual Description GGModelTypes.SliceDescribeProc GGModelTypes.SliceFileoutProc Write a description of yourself onto stream f. GGModelTypes.SliceFileinProc Read a description of yourself from stream f. Get the comfortable bit if it is there Get the text Get the prefix Get the family and face and size now manufacture the stuff needed by SetTextFont. Use the parser! have: prefix, family, face, transform, size, preferredSize get color Handle Drop Shadows Hit Testing GGModelTypes.SliceNewPartsProc GGModelTypes.SliceUnionPartsProc GGModelTypes.SliceMovingPartsProc If anything is moving, everything is dragging. Otherwise, leave it in the background. GGModelTypes.SliceAugmentPartsProc For every edge, add its corresponding points GGModelTypes.SliceClosestPointProc GGModelTypes.SliceClosestSegmentProc hitData _ NIL; -- automatically done by compiler Finds the point which is in mask and nearest to testPoint (and its distance from testPoint). Number the points as shown in the picture above Finds the edge which is in mask and nearest to testPoint (and its distance from testPoint). Style DON'T MESS WITH FONTS FOR DEFAULTS Interpress slice data and class procs slice.boundBox allows for joints and stroke-width IPHitDataObj: TYPE = RECORD [ point: [-1..ipMaxPoints), -- which point of box is hit. edge: [-1..ipMaxEdges) -- which edge of box is hit. THIS DEF CAUSES A FATAL COMPILER ERROR IN PASS FIVE !! ]; Routines peculiar to IPSlices 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. 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. IP Class Routines Fundamentals GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceCopyProc Drawing GGModelTypes.SliceDrawPartsProc 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. IF NOT quick AND normalIPParts # NIL THEN { GGModelTypes.SliceDrawTransformProc 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 ENDCASE => [0.0, 0.0] -- should not happen GGModelTypes.SliceClosestPointProc hitData _ NIL; -- automatically done by compiler GGModelTypes.SliceClosestSegmentProc hitData _ NIL; -- automatically done by compiler ÊR(˜codešœ™Kšœ<™Kšœ žœŸ,˜>K˜K˜—Kšœ žœžœ˜'šœžœžœ˜Kšœt™tKšœŸ˜9KšœŸ˜6K˜K˜—K˜Kš œžœžœžœ˜$K™K™šœžœžœžœ˜CKšžœ ˜ šœžœ˜˜ KšÏb ™ —Kšœ˜K˜šœ˜Kš ™—Kšœ˜K˜!K˜1˜1Kš  ™ —šœ˜Kš ™—K˜K˜K˜˜Kš ™—K˜K˜%K˜K˜K˜K˜K˜šœ)˜)Kš ™—K˜+K˜3K˜/K˜K˜K˜!˜Kš  ™ —Kšœ˜K˜1Kšœ3˜3Kšœ#˜#K˜'K˜+˜/Kš ™—K˜K˜#K˜#K˜K˜K˜#K˜#K˜#K˜#K˜K˜K˜K˜K˜K˜K˜—K˜K˜—š œžœžœ žœžœžœžœSžœ˜Éšœžœ˜(Kšœ Ÿ'˜GKšœ(Ÿ'˜OKšœŸ˜,Kšœ˜Kšœžœ˜K•StartOfExpansion[]šœ#Ÿ˜3Kšœ˜Kšœ ˜ Kšœ˜Kšœ#˜#Kšœ˜Kšœ˜Kš I™I—šœžœ ˜K˜&Kšœ˜Kšœžœ˜ Kšœžœžœžœ˜&Kšœ.Ÿ˜EKšœ ž˜Kšœ˜—K˜K˜—š œžœžœžœžœ˜;š žœžœžœžœžœ˜3Kšœ žœžœ˜4Kšžœžœžœžœ ˜;K˜—K˜K˜—š œžœžœžœ žœ˜AKš žœžœžœžœžœžœ˜RK˜K˜—š œžœžœžœ˜;Kšœ˜Kšžœžœžœ˜&Kšœ žœ ˜Kšœ2Ÿ˜@Kšœ˜K˜K˜—š œžœžœ˜-Kšœ˜Kšžœžœžœ˜&Kšœ žœ ˜KšœUŸ˜cKšœ˜K˜K˜—š œžœ˜Kš žœžœžœ žœžœ˜3K˜K˜—šœžœžœžœžœžœ žœ˜}Kšœxžœ™{š œžœžœžœžœžœ ˜:Kšœžœ˜Kšœžœ˜š žœžœ žœžœžœžœ ž˜YK˜Kšžœ˜—Kšžœ žœžœŸ=˜cšžœžœŸ˜!š žœžœ žœžœžœžœ ž˜YKšœ˜K˜Kšžœ˜—K˜-Kšžœ žœŸ$˜AK˜—K˜—K˜Kšœ žœ˜K–[pName: ROPE]š œ žœžœžœžœ ˜)Kš œ Ÿœžœ:žœžœ˜yšœ"žœžœ˜JKš  Ðbk =™J—Kš œŸœžœ-žœ žœžœžœ˜zKšžœžœžœ#˜3K˜K˜—š œžœžœ<žœ žœžœ˜wKšžœ+žœ˜9K˜K˜—š œžœžœ<žœ žœžœ˜ƒKšžœ+žœ˜8K˜K˜—šœžœJžœžœžœ žœžœ˜Kšœ¼™¼K™Kšœ=˜=Kšžœžœžœžœ˜/šž˜Kšœžœ ˜(Kšžœžœžœ˜&Kšžœžœžœžœ˜(Kšœ+˜+Kš œ žœžœžœ”žœ/™ñKšœ žœžœ/žœžœžœžœ…žœc˜ÞKšœžœ˜š œ  œ*˜MKšœžœ˜šœ˜šžœžœ˜Kšœžœ˜Kšžœ˜K˜—Kšžœžœ ˜Kšœ˜—Kšœ˜—K–p[prefix: ATOM, family: ATOM, face: NodeStyle.FontFace, size: REAL, alphabets: NodeStyle.FontAlphabets]šžœžœR œ ˜ŸKš  œ  œ œžœžœžœ˜wKšœR˜RKšœ Ÿ5˜UKšœ)Ÿ5˜^Kšœ:˜:KšœH˜HKšœ˜šž˜Kšœ ˜ Kšœp˜pšœ žœ˜K˜—šœ ˜ K–“[fileName: ROPE, accessOptions: FS.AccessOptions _ read, streamOptions: FS.StreamOptions _ (5)[TRUE, TRUE, TRUE, TRUE, TRUE], keep: CARDINAL _ 1B (1), createByteCount: FS.ByteCount _ 2560, streamBufferParms: FS.StreamBufferParms _ [vmPagesPerBuffer: 8, nBuffers: 2], extendFileProc: FS.ExtendFileProc, wantedCreatedTime: GMT _ nullGMT, remoteCheck: BOOL _ TRUE, wDir: ROPE _ NIL]š œ žœžœžœ¡œ˜Kšžœ žœžœ?˜TKšœ_žœT˜µKšœ žœ˜K˜——Kšžœ˜—K˜K˜—š œžœžœžœ˜HKšœ˜Kšžœžœžœžœ˜+Kšœ žœ ˜Kšžœ˜K˜K˜—š œžœžœžœžœ˜CKšœ˜Kšœ˜K–ldStream: STREAM _ NIL]š œ žœžœžœžœ˜Kšžœžœžœžœ˜+Kšœ žœ ˜K˜KšœG˜GKšžœžœžœžœmžœYžœžœžœ ˜§Kš žœžœ—žœžœžœ ˜ÛK˜K˜—š œžœžœžœžœ˜JKšœ˜Kšœ˜K–ldStream: STREAM _ NIL]š œ žœžœžœžœ˜Kšžœžœžœžœ˜+Kšœ žœ ˜K˜KšœG˜GKšžœ—žœžœžœ ˜ÖK˜K˜—šœžœžœžœ˜_Kšœ˜Kšœ žœ ˜Kšœ%˜%Kšœ˜K˜K˜—š œžœžœžœžœ˜PKšœ˜Kšœ žœ ˜Kšžœ˜K˜K˜—š œžœžœ#˜Kšœ#žœžœ˜IKšžœ žœ1˜DKšžœžœ4˜LKš žœžœ žœžœžœ!˜QKšžœ˜—Kš žœžœžœžœžœ™+šžœžœžœ˜K–>[context: Imager.Context, strokeJoint: Imager.StrokeJoint]šœ!˜!K˜šžœžœžœž˜)Kšžœž˜#šžœž˜Kšœ-˜-Kšœ.˜.Kšœ.˜.Kšœ-˜-Kšœ-˜-Kšœ.˜.Kšœ-˜-Kšžœžœ˜—Kšžœ˜—K˜—K˜—K˜)Kšœžœ ˜(K˜'Kšžœžœžœžœ˜/Kš žœžœžœ žœžœžœ˜6Kšœžœ˜(Kšœžœ ˜ Kšžœžœ&˜DK˜K˜—šœžœA˜OK™CKšœžœžœžœ˜bKš œžœžœžœžœ˜QKšœŸ)˜GKšœ3Ÿ,˜_Kšœ#˜#K˜K˜—šœžœA˜UK™Cšœžœ˜Kšœ žœt™„Kšœ1™1Kšœ žœX˜hKšœ˜Kšœžœžœžœ˜bKš œžœžœžœžœ˜]KšœŸ)˜GKšœ3Ÿ,˜_Kšœ#˜#K˜—Kšœ žœŸ5˜LKšœ'˜'K˜K˜—šÐbn œžœ‡˜žKšœ#™#š¢ œžœ˜KšœX˜XKšžœžœ&˜CKšœ˜K˜—Kšœžœ˜/Kšœ  œ˜*K˜K˜—Kšœ ™ š œžœN˜aK™Kšœžœ˜/K˜'KšœP˜PKšœC˜CKšœH˜HKšœ˜K˜K˜—Kšœ™š œžœžœ žœ˜JK™Kšœ žœ˜Kšœ˜Kšœžœ˜Kšžœžœžœžœ˜2Kšœ žœ˜!šžœžœžœž˜'šžœžœ˜Kšœ˜K˜K˜—Kšžœ˜—šžœžœžœž˜&šžœžœ˜Kšœ˜K˜K˜—Kšžœ˜—Kšžœžœžœ#˜AKšžœ žœ žœžœ˜AKšœ/˜/K˜K˜—š œžœžœžœžœ žœ˜TKšœžœ ˜+Kšœ žœ˜Kšžœžœ+˜GKšžœžœžœ)˜IKšžœžœ˜ Kšœ/˜/K˜K˜—š œžœžœžœ˜2K™K™.Kšœžœ ˜(K˜'K–: STREAM, bool: BOOL]š œžœžœžœžœžœ˜FKšžœžœdžœI˜ÍKšœ6˜6KšžœžœžœN˜nK˜)Kšœ žœžœ˜šžœžœ˜Kšœžœ˜Kšœ žœžœ˜Kšœ4˜4Kšœ žœžœ˜K˜/K˜—Kšžœžœ˜$Kšœ-˜-K˜K˜—š œžœžœžœ žœžœ˜aKš ™Kš -™-Kšœžœ˜Kšœ žœ˜(Kšœ žœ˜ Kšœ žœ˜K˜Kšœžœžœ˜"Kšœžœ˜Kšœžœ˜K˜/Kšœ)˜)Kšœ'˜'Kšœžœžœ˜!Kšœ&˜&šœžœ˜Kš &™&—šžœžœ˜Kšœ;˜;K˜Kš  ™ —šžœžœ˜Kšœ˜K˜—šžœ˜Kšœ#˜#Kšœ%˜%K˜#K˜—šžœžœŸ˜5Kš ™šžœžœ˜Kšœ'˜'KšœŸ˜Kš  ™ —KšžœžœŸ˜<šžœžœžœŸ,˜NKšœ'˜'K˜—šžœžœžœŸ7˜YK˜)K˜%K˜#Kšœ2Ÿ˜GKšœŸœ&˜9K˜—šžœžœžœŸ6˜XK˜)K˜%Kšœ$Ÿ˜@KšœŸœ&˜9Kšœ˜—šžœžœžœŸU˜vK˜)K˜%KšœŸœ&˜9Kšœ˜—šžœžœžœŸ˜9Kšœžœžœ˜Kš œŸœ-žœžœžœ˜KKšžœžœžœ˜Kšœ,˜,K˜.K˜Kš @™@Kšœ:™:—K–)[rope: ROPE, oldStream: STREAM _ NIL]˜#KšœÏtœ £˜Kšœ£œ £Ðct˜?Kšœ:žœžœ£ œ žœ žœ žœ žœ˜¡K˜—šžœ˜šžœžœŸ.˜LKšœžœ!˜4K–)[rope: ROPE, oldStream: STREAM _ NIL]šœ&žœ žœžœžœ žœ žœ žœ˜ˆKšœ£œ ˜5K˜—šžœŸ˜Kšœžœ!˜7K–)[rope: ROPE, oldStream: STREAM _ NIL]˜#Kšœ£œ ˜5K˜4K˜4Kš œ:žœžœžœ žœ˜uK˜—K˜—˜Kš  ™ —šžœžœ˜Kšœ,˜,K˜K– [f: STREAM]š ™—šžœžœ˜Kšœ6˜6Kšžœžœžœžœ˜šžœžœ˜Kšœ*˜*Kšœ.˜.K˜—K˜—šžœžœ˜K˜—šžœžœŸ"˜?Kšœ&˜&Kšœ;˜;KšœM˜MK˜ K˜—šžœžœžœ˜ Kšœ5˜5KšœM˜MK˜ KšœŸ/˜2—K˜šžœž˜Kšœ-˜-K˜—Kšžœ˜K˜Kšœ™˜™K–ß[slice: GGModelTypes.Slice, prefix: ROPE, family: ROPE, face: ROPE, faceRope: ROPE, scale: REAL _ 1.0, fontScale: REAL _ 1.0, transform: ImagerTransformation.Transformation, feedback: ViewerClasses.Viewer]šœžœŸ(˜FKšžœžœ4žœžœ˜TKšœ+˜+K˜K˜—Kšœ ™ š œžœ˜+šžœžœž˜šœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœ˜K˜—šœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœ˜K˜—Kšžœžœ˜—K˜K˜—š œžœžœžœ˜7šžœžœž˜Kšœžœžœžœžœžœžœžœžœ˜yKšœžœžœžœžœžœžœžœžœ˜kKšžœžœ˜—K˜K˜—šœžœžœžœ˜4šžœžœž˜Kšœžœžœžœžœžœžœžœžœ˜~Kšœžœžœžœžœžœžœžœžœ˜pKšžœžœ˜—K˜K˜—šœžœžœžœ˜CKšžœ˜K˜K˜—šœžœžœžœ˜FKšžœ˜!K˜—K˜š œžœžœžœžœ˜kKšœ™Kšœžœ ˜+Kš œžœžœžœ žœžœžœ˜išžœž˜˜ Kšžœžœ'ž˜GKšžœžœžœ%ž˜IKšžœžœ˜ K˜—šœ ˜ šžœžœ˜Kšœ&žœ˜+K˜—šžœ˜Kšœ žœžœ˜Kšœ˜Kšœ žœ˜K˜Kšœžœ ˜,KšœŸœe˜pKšžœžœ žœžœ˜Kšœžœ ˜Kšžœžœžœ˜&Kšœ'žœ˜,K˜—Kšœžœ˜K˜—šœ žœžœ˜(Kšœ$žœ˜)Kšœžœ˜K˜—šœ˜Kšœžœžœ˜Kšœžœžœ˜K˜—Kšœ1˜1šœ Ÿ˜"K˜—Kšžœžœ˜—Kšœ7˜7K˜K˜—šœžœ4žœ˜mKšœ ™ Kšœžœ˜-Kšœžœ˜-Kšœ˜Kš žœ žœžœ žœžœžœ˜+Kšžœžœžœžœ ˜*Kšžœžœžœžœ ˜*Kš œ žœžœžœ žœžœžœ˜[šžœžœžœž˜'Kšœ*žœ˜BKšžœ˜—šžœžœžœž˜&Kšœ(žœ˜?Kšžœ˜—Kšœ.žœ˜HKšœ=˜=K˜K˜—š œžœ4žœ˜mKšœžœ˜-Kšœžœ˜-Kšœ˜Kš žœ žœžœ žœžœžœ˜+Kš žœžœžœžœ+žœ˜PKšžœžœžœžœ ˜"Kš œ žœžœžœ žœžœžœ˜]šžœžœžœž˜'Kš œžœžœžœžœ˜RKšžœ˜—šžœžœžœž˜&Kš œžœžœžœžœ˜OKšžœ˜—Kš œžœžœžœžœ˜XKšœ>˜>K˜K˜—šœžœ+žœ9˜€Kšœ!™!KšœV™VKš œžœžœžœ žœžœžœ˜fKš œžœžœžœ žœžœžœ˜lšžœžœŸœ Ÿœ ˜Kšžœ˜—K˜K˜—šœžœžœ'˜iKšœžœ˜(Kšœžœ(žœŸ˜\šžœžœžœž˜)Kšžœžœ+˜DKšžœ˜—K˜K˜—š œžœžœ.˜dšžœ žœžœžœ˜+Kšœžœ˜Kšžœ˜K˜—šžœ˜Kšœ*˜*Kšœžœ˜/K˜'Kšœžœ˜,Kšœžœ˜Kšœžœ˜šžœ!žœž˜BKšžœžœžœŸ+˜QKšžœ˜—Kšœa˜aKšœ ˜ Kšœžœžœžœ Ÿ4˜nK˜—K˜K˜—šœžœ$žœ6˜xšžœžœžœžœ˜3Kšœžœ˜Kšžœ˜K˜—šžœ˜Kšœ.˜.Kšœžœ˜/Kšœžœ˜,KšœF˜FKšœžœ˜Kšœžœ˜šžœ%žœž˜EKšžœžœžœŸ)˜NKšžœ˜—šžœžœŸ4˜Fšœ˜KšœM˜MKšœM˜MK˜—šœ˜KšœM˜MKšœN˜NK˜—šœ˜KšœN˜NKšœM˜MK˜—šœ˜KšœM˜MKšœM˜MK˜—šœ˜KšœM˜MKšœM˜MK˜—šœ˜KšœM˜MKšœN˜NK˜—šœ˜KšœM˜MKšœM˜MK˜—Kšžœžœ+˜<—Kšœ*˜*Kšœžœžœžœ Ÿ3˜uK˜—K˜K˜—š œžœ.žœžœ˜RKš žœžœžœžœžœ˜]K˜K˜—šœžœ8žœžœžœ žœžœ žœžœ˜«K™"Kšœžœ˜Kšœžœ˜/Kšœ´žœžœŸ˜Ûšžœ!žœ˜)Kšœžœ˜,KšœK˜KKšœžœžœ žœ5Ÿ3˜Kšœ|˜|šžœ žœ˜KšœJ˜JKšœ4˜4Kšœ žœB˜OK˜—K˜—K˜K˜—šœžœ8žœžœžœ žœžœ žœžœ˜­Kšœ$™$Kšœ žœŸ!™1Kšœžœ˜/Kšœ´žœžœ˜Âšžœ!žœ˜)Kšœžœ˜Kšœ˜Kšœžœ˜,KšœK˜KKšœžœžœ žœ5Ÿ3˜Kšœx˜xšžœ žœ˜KšœJ˜JKšœ4˜4Kšœžœ@˜[K˜—K˜—K˜K˜—šœžœ3žœžœ žœ žœžœžœ˜¾Kšœ™Kšœ˜Kšœžœ˜Kšœ žœ˜'Kšœžœ˜Kšœ˜KšœŸ˜)Kšœ˜šžœžœž˜"šžœ žœ˜KšœŸ'˜BKšœ<˜<šžœžœ˜Kšœ˜Kšœ˜Kšœ˜K˜—K˜—Kšžœ˜—Kšœ#˜#Kšžœžœ žœ˜,K˜K˜—Kšœ;˜;šœžœžœ3žœžœžœžœ žœ žœžœžœ˜ÐKšœ]™]Kšœ˜Kšœžœ˜Kšœ žœ˜'Kšœžœ˜Kšœ˜KšœŸ˜)Kšœ˜šžœžœž˜!šžœ žœ˜šžœž˜Kšœ:˜:Kšœ;˜;Kšœ;˜;Kšœ:˜:Kšœ:˜:Kšœ;˜;Kšœ:˜:Kšžœžœ˜—Kšœ?˜?Kšœ<˜<šžœžœ˜Kšœ˜Kšœ˜Kšœ˜K˜—K˜—Kšžœ˜—Kšœ#˜#Kšžœžœ žœ˜,K˜—K˜Kšœ™šœžœ;˜SKšœžœ ˜(Kšœ˜Kšœ˜—K˜šœžœ#žœ˜\Kšœžœ ˜(Kšœ˜Kšœ˜K˜—šœžœ=˜RKšœžœ ˜(Kšœžœ˜%šžœžœ%˜BKš "™"—Kšœ˜K˜—šœžœ(˜>Kšœžœ ˜(Kšœ˜Kšœ˜K˜—šœžœžœ˜GKšœžœ ˜(Kšžœ˜Kšœ˜—K˜K™%Kšœ žœ˜šœ žœ˜K˜—Kš œžœŸ œžœžœžœŸ˜Rš œ žœŸ œžœžœžœŸ˜YK˜—šœžœžœžœ˜6K˜V—šœ žœžœžœ˜3K˜6K˜—Kšœžœžœ ˜šœ žœžœ˜KšœŸM˜`šœ4Ÿ œ˜@Kš 1™1—KšœžœŸ6˜LKšœ žœŸ ˜1KšœŸ˜-Kšœ0Ÿ˜LKšœ-˜-KšœŸ9˜NK˜K˜—Kšœ žœžœ ˜šœ žœžœ˜KšœŸ%˜;KšœŸ#˜7Kšœ žœžœŸ/˜EK˜K˜—Kšœ žœžœ˜#šœžœžœ˜KšœŸ˜7KšœžœŸ˜+K˜K˜K˜—šœžœžœ™KšœŸ™7KšœŸS™jK™K™—K™šœžœžœžœ˜AKšžœ ˜ šœžœ˜˜ Kš  ™ —Kšœ˜K˜šœ ˜ Kš ™—Kšœ˜K˜K˜/˜1Kš  ™ —šœ˜Kš ™—K˜K˜K˜˜Kš ™—Kšœ˜Kšœ#˜#K˜K˜K˜K˜K˜šœ)˜)Kš ™—K˜)K˜1K˜/K˜K˜K˜˜Kš  ™ —Kšœ˜K˜1Kšœ3˜3Kšœ!˜!K˜'K˜+˜/Kš ™—K˜K˜#K˜#K˜K˜K˜#K˜#K˜#K˜#K˜K˜K˜K˜K˜K˜K˜—K˜K˜—š œžœžœKžœ&žœžœ˜ÂKšœ˜Kšœ žœžœ˜KšœM˜MKšžœžœ žœžœ˜Kšœk˜k˜K˜——K™šœžœžœ.žœžœžœKžœžœžœžœ˜”Kš H™HKšœ˜˜Kš ,™,—Kšœ:˜:Jšœ)˜)JšœGžœ˜LK˜šžœŸ˜!š œžœ˜-Kšœ)˜)Kšœ˜—šžœ žœžœ˜šœX˜XKšœ-˜-Kšžœ˜Kšœ˜—Kšœ*˜*K˜——Kšžœ˜Kšœ\˜\K˜K˜—š œžœžœpžœžœ˜´Kš à™àKšœ˜Kšœ˜Kšœ-˜-Kšœžœ˜Kšœ˜˜Kš ,™,—Kšœ-˜-Kšœ#˜#Kšœ!˜!K˜Kš œ žœ žœžœžœ ˜HKšœ1˜1K–*[m: ImagerTransformation.Transformation]šœ6˜6Kšœ/˜/K˜Kšœ žœ2žœžœY˜¦K˜šœžœ ˜K˜$Kšœ ˜ Kšœžœ˜ Kšœžœžœžœ˜&Kšœ-Ÿ˜BKšœ˜—Kšœ˜K˜—K˜š œžœžœvžœžœ˜ÓKšœ˜Kšœ-˜-Kšœžœ˜K˜Kš œ žœ žœžœžœ ˜HKšœ1˜1K–*[m: ImagerTransformation.Transformation]šœ6˜6K˜Kšœ žœ¨˜´šœžœ ˜K˜$Kšœ ˜ Kšœžœ˜ Kšœžœžœžœ˜&Kšœ-Ÿ˜BKšœ˜—Kšœ˜K˜K˜—K™šœž œ žœ˜GKšœžœ ˜$Kšœ'˜'K˜K˜—šœž œžœžœ˜PKšœžœ ˜$Kšœ'˜'K˜—K™K™K™Kšœ ™ šœžœ˜)Kšœžœ ˜$KšœS˜SKšœ:˜:Kšœ*˜*K˜K˜—š œžœ$žœžœ˜SK™Kšžœ˜K˜K˜—š œžœ$žœžœ˜SK™Kšœžœ ˜$Kšžœ˜K˜K™—šœžœžœžœ˜;Kšœ™Kšœžœ ˜$KšœO˜OKšœ/žœ5˜gK˜K˜—K™Kšœ™š œžœ$žœBžœ˜ƒKšœ™š œžœ˜Kšœ%˜%Kšœ#Ÿ7˜ZKšœ$˜$Kšœ˜—Kšœžœ ˜$Kšœžœ˜!Kšžœ žœžœžœ%˜MK˜K˜—šœžœ˜žœ˜¾K™+K™šœžœ˜Kšœ:˜:Kšœ ˜ Kšœžœ˜$Kšœžœžœ˜Kšœ?˜?Kšœ?˜?Kšœ?˜?Kšœ?˜?šžœžœžœž˜)Kšœžœžœ˜:Kšœ!žœžœ˜EKšžœ žœ1˜DKšžœžœ4˜LKš žœžœ žœžœžœ!˜QKšžœ˜—K–>[context: Imager.Context, strokeJoint: Imager.StrokeJoint]šœ!˜!K˜Kšœ"˜"Kš žœžœžœžœžœ™+šžœžœžœ˜šžœžœžœž˜'šžœžœžœž˜2Kšœ,˜,Kšœ,˜,Kšœ,˜,Kšœ,˜,Kšžœžœ˜——Kšžœ˜K˜—K˜—K˜#Kšœžœ ˜$Kšžœžœžœžœ˜/Kš žœžœžœ žœžœžœ˜6Kšœžœ˜&Kšœ žœ ˜Kšžœžœ&˜DK˜K˜—š¢ œžœ‡˜œKšœ#™#š¢ œžœ˜KšœM˜MKšœ#Ÿ7˜ZKšœ$˜$K˜—Kšœžœ˜+Kšœ  œ˜(K˜K˜—K™Kšœ ™ K™š œžœN˜_K™Kšœžœ˜+KšœL˜LKšœ?˜?KšœD˜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šœQ˜QKšœ+˜+Kšœ˜Kšœ(˜(K–[base: ROPE]šœ%˜%˜Kš ™—Kšœ&˜&Kšœ˜K˜—K˜K˜—šœžœžœžœ žœžœžœ˜eK™Kšœžœ˜"Kšœžœ˜Kšœ žœžœ˜K˜/Kšœ žœžœ˜Kšœžœ˜Kšœžœžœ˜Kšœžœ˜K˜K˜Kšœ˜Kšœ žœŸ˜=Kšœ,˜,šžœžœ˜Kšœ ˜ K˜—Kšžœ žœ˜šžœžœ˜Kšœ žœ˜Kšœ1˜1Kšœ=˜=Kšžœžœ žœžœ˜K˜šžœžœ˜Kš -™-K˜*K˜K˜1Kšœ žœ(˜7Kšžœžœžœ˜&Kšœžœžœ ˜"K˜Kšœ/žœ˜4˜Kš "™"—KšœVžœ˜\K˜—šžœ˜KšœEžœ˜LK˜—K˜—šžœ˜KšœEžœ˜LK˜—K˜—K™Kšœ ™ šœžœžœžœ˜AKšœ ™ Kšžœ˜K˜K˜—šœžœžœžœ˜DKšžœ˜!K˜K˜—š œžœžœžœžœ˜iKšœ™Kšœžœ ˜'Kš œžœžœžœ žœžœ˜Ošžœž˜˜ Kšžœžœ#ž˜AKšžœžœžœ!ž˜CKšžœžœ˜ K˜—šœ ž˜ šžœžœ˜Kšœ"žœ˜'K˜—šžœ˜Kšœ žœžœ˜Kšœ˜Kšœ žœ˜K˜Kšœžœ ˜'KšœŸœc˜nKšžœžœ žœžœ˜Kšœžœ ˜Kšžœžœžœ˜&Kšœ%žœ˜*K˜—Kšœžœ˜K˜—šœ žœžœ˜%Kšœ žœ˜%Kšœžœ˜K˜—šœ˜Kšœžœžœ˜Kšœžœžœ˜K˜—Kšœ/˜/šœ Ÿ˜ K˜—Kšžœžœ˜—Kšœ5˜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šœA˜AKšœ6žœ˜;Kšœ5˜5Kšœ˜K˜—šœžœ8žœ˜oKšœ"™"Kšœžœ˜(Kšœžœ?˜VK™,šžœžœžœž˜$šžœžœžœž˜&Kšœžœžœ˜=Kšœžœžœ˜=Kšœžœžœ˜=Kšœžœžœ˜=—Kšžœžœ˜Kšžœ˜—Kšœ;˜;K˜—K˜šœžœžœ˜[Kšœžœ˜&Kšœ žœ$žœ˜9šžœžœžœž˜)Kšžœžœ#˜>Kšžœ˜—K˜K˜—šœžœžœ'˜gKšœžœ˜&Kšœžœ(žœŸ˜\šžœžœžœž˜'Kšžœžœ+˜DKšžœ˜—K˜K˜—š œžœžœ.˜bšžœ žœžœžœ˜+Kšœžœ˜Kšžœ˜K˜—šžœ˜Kšœ*˜*Kšœžœ˜+Kšœžœ˜(Kšœžœ˜K˜ Kšœžœ˜šžœ!žœž˜@KšžœžœžœŸ+˜OKšžœ˜—šœFžœž˜WK˜K˜K˜K˜KšžœŸ™*Kšžœž˜Kšœ˜—Kšœ ˜ Kšœžœžœžœ Ÿ4˜nK˜—K˜K˜—šœžœ$žœ6˜všžœžœžœžœ˜3Kšœžœ˜Kšžœ˜K˜—šžœ˜Kšœ.˜.Kšœžœ˜+Kšœžœ˜(K˜ Kšœ;˜;Kšœžœ˜Kšœžœ˜šžœ%žœž˜CKšžœžœžœŸ)˜LKšžœ˜—šžœžœŸ2˜Dšœ˜KšœM˜MKšœM˜MK˜—šœ˜KšœM˜MKšœM˜MK˜—šœ˜KšœM˜MKšœM˜MK˜—šœ˜KšœM˜MKšœM˜MK˜—Kšžœžœ+˜<—Kšœ*˜*Kšœžœžœžœ Ÿ3˜uK˜—K˜K˜—šœžœ8žœžœžœ žœžœ žœžœ˜©K™"Kšœ žœŸ!™1Kšœžœ˜Kšœ˜Kšœžœ ˜$Kšœ¦žœžœŸ˜Íšžœ'žœ˜/Kšœ˜Kšœžœ˜(KšœI˜IKšœžœžœ žœ1Ÿ3˜ŒKšœ˜šžœ žœ˜Kšœ?˜?Kšœ4˜4Kšœžœ@˜YK˜—K˜—K˜K˜—šœžœ8žœžœžœ žœžœ žœžœ˜«Kšœ$™$Kšœ žœŸ!™1Kšœ˜Kšœžœ ˜$Kšœ¦žœžœŸ˜Íšžœ'žœ˜/Kšœ˜Kšœžœ˜Kšœžœ ˜$Kšœžœ˜(KšœJŸ ˜jKšœžœžœ žœ1Ÿ3˜ŒKšœƒ˜ƒšžœ žœ˜Kšœ?˜?Kšœ4˜4Kšœžœ@˜YK˜—K˜—K˜—K˜KšœžœŸ˜/K˜šœžœ˜KšœH˜HK˜—K˜K˜K˜Kšžœ˜—…— žuP