DIRECTORY Convert, Feedback, FeedbackTypes, FileNames, GGBasicTypes, GGBoundBox, GGCoreOps, GGCoreTypes, GGFont, GGFromImager, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGParseIn, GGParseOut, GGProps, GGScene, GGSegment, GGSegmentTypes, GGShapes, GGSlice, GGSliceOps, GGTransform, GGUtility, Imager, ImagerFont, ImagerSys, ImagerTransformation, ImagerTypeface, IO, Lines2d, NodeStyle, PFS, Real, RealFns, Rope, SF, Vector2, Vectors2d; GGSliceImplB: CEDAR PROGRAM IMPORTS Convert, Feedback, FileNames, GGBoundBox, GGCoreOps, GGFont, GGFromImager, GGParseIn, GGParseOut, GGProps, GGScene, GGSegment, GGShapes, GGSlice, GGSliceOps, GGTransform, GGUtility, Imager, ImagerFont, ImagerSys, ImagerTransformation, ImagerTypeface, IO, Lines2d, PFS, Real, RealFns, Rope, Vector2, Vectors2d EXPORTS GGSlice = BEGIN BoundBox: TYPE = GGCoreTypes.BoundBox; BoundBoxObj: TYPE = GGCoreTypes.BoundBoxObj; Camera: TYPE = GGModelTypes.Camera; CameraObj: TYPE = GGModelTypes.CameraObj; Color: TYPE = Imager.Color; DefaultData: TYPE = GGModelTypes.DefaultData; DisplayStyle: TYPE = GGModelTypes.DisplayStyle; EditConstraints: TYPE = GGModelTypes.EditConstraints; ExtendMode: TYPE = GGModelTypes.ExtendMode; MsgRouter: TYPE = FeedbackTypes.MsgRouter; Font: TYPE = ImagerFont.Font; FontData: TYPE = GGFont.FontData; 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; VEC: TYPE = Imager.VEC; Vector: TYPE = GGBasicTypes.Vector; WalkProc: TYPE = GGModelTypes.WalkProc; Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem; 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" ]; LeftRightAnchor: TYPE = {left, middle, right}; defaultLineSpacing: REAL = 1.2; TextData: TYPE = REF TextDataObj; TextDataObj: TYPE = RECORD [ box: BoundBox, -- not a bounding box but part of the text representation points: TextPoints, -- cache for "joints" leftRightAnchor: LeftRightAnchor, -- which point of the text remains fixed when the text is edited rope: Rope.ROPE, color: Imager.Color, fontData: FontData, trueFont: ImagerFont.Font, screenFont: ImagerFont.Font, displayStyle: DisplayStyle _ print, -- which of the fonts to use 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, lineSpacing: REAL _ defaultLineSpacing, -- multiplicative factor on the font scale for line spacing after CR seg: Segment -- a null segment whose entire purpose is to contain properties in seg.props ]; 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, unlink: GGSlice.UnlinkSlice, -- textData doesn't need unlinking getBoundBox: TextGetBoundBox, getTransformedBoundBox: GGSlice.GenericTransformedBoundBox, getTightBox: TextGetTightBox, copy: TextCopy, restore: TextRestore, buildPath: NoOpBuildPath, drawBorder: NoOpDrawBorder, drawParts: TextDrawParts, drawTransform: TextDrawTransform, drawSelectionFeedback: TextDrawSelectionFeedback, drawAttractorFeedback: TextDrawAttractorFeedback, saveSelections: NoOpSaveSelections, remakeSelections: NoOpRemakeSelections, transform: TextTransform, describe: TextDescribe, describeHit: TextDescribeHit, fileout: TextFileout, filein: TextFilein, isEmptyParts: TextIsEmptyParts, isCompleteParts: TextIsCompleteParts, newParts: TextNewParts, unionParts: TextUnionParts, differenceParts: TextDiffParts, movingParts: TextMovingParts, augmentParts: TextAugmentParts, alterParts: NoOpAlterParts, setSelectedFields: NoOpSetSelectedFields, pointsInDescriptor: TextPointsInDescriptor, walkPointsInDescriptor: TextWalkPointsInDescriptor, pointPairsInDescriptor: TextPointPairsInDescriptor, segmentsInDescriptor: TextSegmentsInDescriptor, walkSegments: TextWalkSegments, nextPoint: TextNextPoint, nextPointPair: TextNextPointPair, nextSegment: TextNextSegment, closestPoint: TextClosestPoint, closestJointToHitData: TextClosestJointToHitData, closestPointAndTangent: NoOpClosestPointAndTangent, closestSegment: TextClosestSegment, filledPathsUnderPoint: NoOpFilledPathsUnderPoint, 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, setOrientation: NoOpSetOrientation, getOrientation: NoOpGetOrientation ]]; }; MakeTextSlice: PUBLIC PROC [text: Rope.ROPE, color: Imager.Color, displayStyle: DisplayStyle, amplifySpace: REAL _ 1.0, 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 leftRightAnchor: left, inverseScale: [-1.0, -1.0], -- dummy vector amplifySpace: amplifySpace, points: NEW[TextPointsData], fontData: GGFont.CreateFontData[], -- local storage displayStyle: displayStyle, color: color, rope: text, dropShadowOn: dropShadowsOn, dropShadowOffset: dropShadowOffset, shadowColor: shadowColor, seg: GGSegment.MakeLine[p0: [-1.0, -1.0], p1: [1.0, 1.0], props: NIL] ]]; textData.seg.strokeWidth _ -1.0; -- so no legal stroke width matches slice _ NEW[SliceObj _ [ class: GGSlice.FetchSliceClass[$Text], data: textData, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE], tightBox: GGBoundBox.NullBoundBox[], -- filled in by call to TextGetBoundBox boundBox: GGBoundBox.NullBoundBox[], -- filled in by call to TextGetBoundBox boxValid: FALSE ]]; }; IsWhitespace: PUBLIC PROC [slice: Slice] RETURNS [BOOL _ FALSE] = { IF GGSliceOps.GetType[slice]#$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 GGSliceOps.GetType[slice]#$Text THEN NIL ELSE NARROW[slice.data, TextData].rope]; }; SetText: PUBLIC PROC [slice: Slice, text: Rope.ROPE] = { textData: TextData; IF GGSliceOps.GetType[slice]#$Text THEN RETURN; textData _ NARROW[slice.data]; textData.rope _ text; -- update text GGSlice.KillBoundBox[slice]; }; AppendText: PUBLIC PROC [slice: Slice, text: Rope.ROPE] = { textData: TextData; IF GGSliceOps.GetType[slice]#$Text THEN RETURN; textData _ NARROW[slice.data]; textData.rope _ Rope.Concat[textData.rope, text]; -- update text GGSlice.KillBoundBox[slice]; }; BackspaceText: PUBLIC PROC [slice: Slice, word: BOOL _ FALSE] = { textData: TextData; IF GGSliceOps.GetType[slice]#$Text THEN RETURN; textData _ NARROW[slice.data]; IF word THEN { WHILE TRUE DO length: INT _ Rope.Length[textData.rope]; lastSpaceAt: INT _ Rope.FindBackward[textData.rope, " ",]; -- index of last space in string IF lastSpaceAt=length-1 AND length#0 THEN { -- space at very end of non-empty string textData.rope _ Rope.Substr[base: textData.rope, len: lastSpaceAt]; -- slough the final space } ELSE { textData.rope _ IF lastSpaceAt=-1 THEN "" ELSE Rope.Substr[base: textData.rope, len: lastSpaceAt+1]; -- slough the non-spaces back to the next space EXIT; -- finished }; ENDLOOP; } ELSE textData.rope _ Rope.Substr[base: textData.rope, len: Rope.Length[textData.rope]-1]; -- BS GGSlice.KillBoundBox[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 _ 0.0] = { 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, router: MsgRouter, history: HistoryEvent] RETURNS [ success: BOOL _ TRUE] = { RETURN[SetTextFontAux[slice, fontData, router, FALSE, history]]; }; SetTextFontAndTransform: PUBLIC PROC [slice: Slice, fontData: FontData, router: MsgRouter, history: HistoryEvent] RETURNS [ success: BOOL _ TRUE] = { RETURN[SetTextFontAux[slice, fontData, router, TRUE, history]]; }; SetTextFontAux: PROC [slice: Slice, fontData: FontData, router: MsgRouter, andTransform: BOOL _ FALSE, history: HistoryEvent, substituteFont, trueFont, screenFont: Imager.Font _ NIL] RETURNS [success: BOOL _ TRUE] = { transform, oldTransform: ImagerTransformation.Transformation; IF GGSliceOps.GetType[slice]#$Text THEN RETURN[FALSE]; BEGIN textData: TextData _ NARROW[slice.data]; IF GGSliceOps.GetType[slice]#$Text THEN RETURN; IF fontData.namelessFont # NIL THEN { textData.fontData^ _ fontData^; textData.trueFont _ textData.fontData.namelessFont; textData.screenFont _ textData.trueFont; transform _ ImagerTransformation.Copy[fontData.transform]; textData.fontData.transform _ transform; textData.inverse _ ImagerTransformation.Invert[transform]; textData.inverseScale _ ImagerTransformation.Factor[textData.inverse].s; GGSlice.KillBoundBox[slice]; -- this is all we really care about 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]; IF trueFont # NIL THEN textData.trueFont _ trueFont -- if we know it don't look for it ELSE IF fontData.substituted AND substituteFont#NIL THEN { textData.trueFont _ substituteFont; } ELSE { textData.trueFont _ ImagerFont.Find[fontData.literal, substituteWithWarning ! PFS.Error => GOTO PFSError; ImagerSys.FileError => { Feedback.PutF[router, oneLiner, $Complaint, "ImagerSys.FileError [%g, %g] during ImagerFont.Find %g", [atom[code]], [rope[explanation]], [rope[fontData.literal]] ]; GOTO ImagerSysFileError; }; Imager.Error => GOTO Abort; Imager.Warning => { IF fontData.substituteOK THEN { fontData.substituted _ TRUE; RESUME; } ELSE GOTO NotFound; }; ]; IF fontData.substituted THEN Feedback.PutF[router, oneLiner, $Warning, "%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]; }; fontData.transform _ transform; -- must be done before computing alternate font textData.screenFont _ IF screenFont # NIL THEN screenFont ELSE NIL; textData.fontData^ _ fontData^; -- don't do this assignment until FindFont succeeds ! textData.inverse _ ImagerTransformation.Invert[transform]; textData.inverseScale _ ImagerTransformation.Factor[textData.inverse].s; GGSlice.KillBoundBox[slice]; EXITS PFSError => { Feedback.PutF[router, oneLiner, $Complaint, "Fatal PFS Error during ImagerFont.Find of %g", [rope[fontData.literal]] ]; success _ FALSE; }; Abort => { Feedback.PutF[router, oneLiner, $Complaint, "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[router, oneLiner, $Complaint, "No Such Font: %g %g %g %g", [rope[fontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]] ]; success _ FALSE; }; ImagerSysFileError => success _ FALSE; END; }; GetLooksDataRope: PUBLIC PROC [slice: Slice] RETURNS [Rope.ROPE] = { scratch: IO.STREAM _ IO.ROS[]; IF GGSliceOps.GetType[slice]#$Text THEN RETURN[NIL] ELSE { scale: REAL _ 10.0; -- convert fractions to offsets needed by SetDropShadow textData: TextData _ NARROW[slice.data]; IF textData.color#NIL THEN scratch.PutF["Text color: %g ", [rope[GGUtility.DescribeColor[textData.color]]] ] ELSE scratch.PutF["%g", [rope["No Text Fill Color "]]]; IF textData.dropShadowOn THEN { IF textData.shadowColor#NIL THEN scratch.PutF["ShadowColor: %g ", [rope[GGUtility.DescribeColor[textData.shadowColor]]] ] ELSE scratch.PutF["%g", [rope["No Drop Shadow color "]]]; scratch.PutF[" offset: [%g, %g]", [real[textData.dropShadowOffset.x/scale]], [real[textData.dropShadowOffset.y/scale]] ]; }; }; RETURN[IO.RopeFromROS[scratch]]; }; GetFontData: PUBLIC PROC [slice: Slice] RETURNS [fontData: FontData] = { textData: TextData; IF GGSliceOps.GetType[slice]#$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 GGSliceOps.GetType[slice]#$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; IF GGSliceOps.GetType[slice]#$Text THEN RETURN[NIL]; textData _ NARROW[slice.data]; RETURN[GGFont.FontAsLiteralRope[textData.fontData]]; }; 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 ]; }; SetTextAmplifySpace: PUBLIC PROC [slice: Slice, amplifySpace: REAL, router: MsgRouter, history: HistoryEvent] = { IF GGSliceOps.GetType[slice]=$Text THEN { textData: TextData _ NARROW[slice.data]; textData.amplifySpace _ amplifySpace; GGSlice.KillBoundBox[slice]; }; }; GetTextAmplifySpace: PUBLIC PROC [slice: Slice] RETURNS [amplifySpace: REAL] = { IF GGSliceOps.GetType[slice]=$Text THEN { textData: TextData _ NARROW[slice.data]; RETURN[textData.amplifySpace]; }; RETURN[1.0]; }; DropShadowOn: PUBLIC PROC [slice: Slice, offset: Vector, history: HistoryEvent] = { IF GGSliceOps.GetType[slice]=$Text THEN { textData: TextData _ NARROW[slice.data]; textData.dropShadowOn _ TRUE; textData.dropShadowOffset _ offset; GGSlice.KillBoundBox[slice]; }; }; DropShadowOff: PUBLIC PROC [slice: Slice, history: HistoryEvent] = { IF GGSliceOps.GetType[slice]=$Text THEN { textData: TextData _ NARROW[slice.data]; textData.dropShadowOn _ FALSE; GGSlice.KillBoundBox[slice]; }; }; GetTextDropShadow: PUBLIC PROC [slice: Slice] RETURNS [dropShadowOn: BOOL _ FALSE, dropShadow: Vector _ [0.0, 0.0], color: Imager.Color] = { IF GGSliceOps.GetType[slice]=$Text THEN { textData: TextData _ NARROW[slice.data]; RETURN[textData.dropShadowOn, textData.dropShadowOffset, textData.shadowColor]; }; }; GetTextColors: PUBLIC PROC [slice: Slice] RETURNS [textColor, shadowColor: Imager.Color] = { IF GGSliceOps.GetType[slice]=$Text THEN { textData: TextData _ NARROW[slice.data]; RETURN[textData.color, textData.shadowColor]; }; }; SetTextColors: PUBLIC PROC [slice: Slice, textColor: Imager.Color, useTextColor: BOOL _ TRUE, shadowColor: Imager.Color, useShadowColor: BOOL _ FALSE, history: HistoryEvent] = { IF GGSliceOps.GetType[slice]=$Text THEN { textData: TextData _ NARROW[slice.data]; IF useTextColor THEN textData.color _ textColor; IF useShadowColor THEN textData.shadowColor _ shadowColor; }; }; GetTextLineSpacing: PUBLIC PROC [slice: Slice] RETURNS [REAL] = { IF GGSliceOps.GetType[slice]=$Text THEN { textData: TextData _ NARROW[slice.data]; RETURN[textData.lineSpacing]; } ELSE RETURN[0.0]; }; SetTextLineSpacing: PUBLIC PROC [slice: Slice, lineSpacing: REAL, history: HistoryEvent] = { IF GGSliceOps.GetType[slice]=$Text THEN { textData: TextData _ NARROW[slice.data]; textData.lineSpacing _ lineSpacing; }; }; SetTextDisplayStyle: PUBLIC PROC [slice: Slice, displayStyle: DisplayStyle, history: HistoryEvent] = { textData: TextData _ NARROW[slice.data]; IF displayStyle # textData.displayStyle THEN GGSlice.KillBoundBox[slice]; textData.displayStyle _ displayStyle; }; OutlinesFromTextString: PUBLIC PROC [slice: Slice] RETURNS [shadowOutlines, outlines: LIST OF Slice] = { textData: TextData _ NARROW[slice.data]; fontData: FontData _ textData.fontData; font: Font _ textData.trueFont; amplifySpace: REAL _ textData.amplifySpace; camera: Camera _ NEW[CameraObj]; -- this data is not really used in Capture in this case SplinesFromTextString: PROC [dc: Imager.Context] = { SplinesFromChar: PROC [char: ImagerFont.XChar] = { proc: PROC = { escapement: VEC _ ImagerFont.Escapement[font, char]; IF amplifySpace#1.0 AND ImagerFont.Amplified[font, char] THEN { escapement _ Vector2.Mul[escapement, amplifySpace]; }; Imager.Move[dc]; ImagerTypeface.MaskChar[font, char, dc]; Imager.SetXYRel[dc, escapement]; SELECT ImagerFont.Correction[font, char] FROM none => NULL; space => Imager.CorrectSpace[dc, escapement]; mask => Imager.CorrectMask[dc]; ENDCASE => ERROR; }; Imager.DoSave[dc, proc]; }; IF shadows THEN GGCoreOps.SetColor[dc, IF textData.shadowColor=NIL THEN Imager.black ELSE textData.shadowColor] ELSE GGCoreOps.SetColor[dc, IF textData.color=NIL THEN Imager.black ELSE textData.color]; Imager.ConcatT[dc, fontData.transform]; IF shadows THEN Imager.TranslateT[dc, tVec]; Imager.SetXY[dc, [0.0, 0.0]]; ImagerFont.MapRope[textData.rope, 0, INT.LAST, SplinesFromChar]; }; fakeScene: Scene; shadows: BOOL _ TRUE; tVec: Imager.VEC; IF textData.dropShadowOn THEN { tVec _ [ x: textData.dropShadowOffset.x*dropScale, y: textData.dropShadowOffset.y*dropScale]; fakeScene _ GGFromImager.Capture[SplinesFromTextString, camera]; shadowOutlines _ GGScene.ListSlices[fakeScene, first]; }; shadows _ FALSE; fakeScene _ GGFromImager.Capture[SplinesFromTextString, camera]; outlines _ GGScene.ListSlices[fakeScene, first]; }; 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]]; }; UpdateTextPoints: PROC [textData: TextData, left, middle, right, bottom, base, center, top: REAL] = { 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]; }; fontExtentRope: Rope.ROPE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; ComputeTextBounds: PROC [font, namelessFont: Font, rope: Rope.ROPE, amplifySpace: REAL] RETURNS [middle, right, bottom, top, cpBottom, cpCenter, cpTop: REAL] = { fontExtents: ImagerFont.Extents; left, base: REAL = 0.0; right _ RopeEscapement[font, rope, amplifySpace].x; middle _ (left + right)/2.0; fontExtents _ ImagerFont.RopeBoundingBox[font, rope]; top _ fontExtents.ascent; bottom _ -fontExtents.descent; IF namelessFont # NIL THEN { cpTop _ top; cpBottom _ bottom; } ELSE { fontExtents _ ImagerFont.RopeBoundingBox[font, fontExtentRope]; cpTop _ fontExtents.ascent; cpBottom _ -fontExtents.descent; }; cpCenter _ (cpTop + cpBottom)/2.0; }; TextGetBoundBoxAux: PROC [slice: Slice, parts: SliceParts _ NIL] RETURNS [tightBox, boundBox: BoundBox]= { TextFindBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [tightBox, boundBox: BoundBox] = { textData: TextData _ NARROW[slice.data]; fontData: FontData _ textData.fontData; halfCP: REAL = GGModelTypes.halfJointSize + 1; left, base: REAL = 0.0; middle, right, bottom, top, cpBottom, cpCenter, cpTop: REAL; altMiddle, altRight, altBottom, altTop: REAL; [middle, right, bottom, top, cpBottom, cpCenter, cpTop] _ ComputeTextBounds[textData.trueFont, textData.fontData.namelessFont, textData.rope, textData.amplifySpace]; IF textData.displayStyle = print OR GetScreenFont[textData] = NIL THEN { altMiddle _ middle; altRight _ right; altBottom _ bottom; altTop _ top; } ELSE [altMiddle, altRight, altBottom, altTop, ----, ----, ----] _ ComputeTextBounds[GetScreenFont[textData], textData.fontData.namelessFont, textData.rope, textData.amplifySpace]; GGBoundBox.UpdateBoundBox[textData.box, left, altBottom, altRight, altTop]; IF textData.dropShadowOn THEN GGBoundBox.EnlargeByVector[textData.box, [x: textData.dropShadowOffset.x*dropScale, y: textData.dropShadowOffset.y*dropScale]]; textData.box _ GGBoundBox.BoundBoxOfBoundBox[textData.box, fontData.transform]; GGBoundBox.EnlargeByOffset[textData.box, 1.0]; boundBox _ GGBoundBox.CopyBoundBox[textData.box]; tightBox _ GGBoundBox.CreateBoundBox[left, MIN[bottom, cpBottom], right, MAX[top, cpTop]]; tightBox _ GGBoundBox.BoundBoxOfBoundBox[tightBox, fontData.transform]; UpdateTextPoints[textData, left, middle, right, cpBottom, base, cpCenter, cpTop]; }; [tightBox, boundBox] _ TextFindBoundBox[slice, parts]; -- do the bound box update IF parts=NIL THEN { -- set up cache for fast case next time around GGSlice.KillBoundBox[slice.parent]; -- invalidate ancestor caches slice.boundBox _ boundBox; slice.tightBox _ tightBox; slice.boxValid _ TRUE; slice.tightBoxValid _ TRUE; -- not really necessary since only boxValid is used }; }; TextGetBoundBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { IF slice.boxValid AND parts=NIL THEN RETURN[slice.boundBox]; -- fast case RETURN[TextGetBoundBoxAux[slice, parts].boundBox]; -- cache update if possible }; TextGetTightBox: PROC [slice: Slice, parts: SliceParts] RETURNS [box: BoundBox] = { IF slice.boxValid AND parts=NIL THEN RETURN[slice.tightBox]; -- fast case RETURN[TextGetBoundBoxAux[slice, parts].tightBox]; -- cache update if possible }; TextCopy: PROC [slice: Slice, parts: SliceParts _ NIL] RETURNS [copy: LIST OF Slice] = { copySlice: Slice; copyData: TextData; textData: TextData _ NARROW[slice.data]; success: BOOL _ TRUE; router: MsgRouter _ Feedback.EnsureRouter[$Gargoyle]; copySlice _ MakeTextSlice[textData.rope, GGCoreOps.CopyColor[textData.color], textData.displayStyle, textData.amplifySpace, textData.dropShadowOn, textData.dropShadowOffset, GGCoreOps.CopyColor[textData.shadowColor]]; copyData _ NARROW[copySlice.data]; success _ SetTextFontAux[slice: copySlice, fontData: GGFont.CopyFontData[textData.fontData, copyData.fontData], router: router, andTransform: TRUE, history: NIL, substituteFont: textData.trueFont, trueFont: textData.trueFont, screenFont: textData.screenFont]; IF NOT success THEN ERROR; copyData.seg _ GGSegment.CopySegment[seg: textData.seg]; copyData.lineSpacing _ GGSlice.GetTextLineSpacing[slice]; GGProps.CopyAll[fromSlice: slice, toSlice: copySlice]; RETURN[LIST[copySlice]]; }; TextRestore: PROC [from: Slice, to: Slice] = { IF to=NIL OR from=NIL THEN ERROR; IF to.class#from.class THEN ERROR; IF to.class.type#$Text THEN ERROR; BEGIN fromData: TextData _ NARROW[from.data]; toData: TextData _ NARROW[to.data]; IF GGSlice.copyRestore THEN { toData.box^ _ fromData.box^; toData.points^ _ fromData.points^; toData.rope _ fromData.rope; toData.color _ fromData.color; toData.fontData _ fromData.fontData; toData.trueFont _ fromData.trueFont; toData.screenFont _ fromData.screenFont; toData.displayStyle _ fromData.displayStyle; toData.inverse _ fromData.inverse; toData.inverseScale _ fromData.inverseScale; toData.amplifySpace _ fromData.amplifySpace; toData.dropShadowOn _ fromData.dropShadowOn; toData.dropShadowOffset _ fromData.dropShadowOffset; toData.dropShadowOffset _ fromData.dropShadowOffset; toData.shadowColor _ fromData.shadowColor; toData.lineSpacing _ fromData.lineSpacing; 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; }; TextDrawParts: PROC [slice: Slice, parts: SliceParts _ NIL, dc: Imager.Context, camera: GGModelTypes.Camera, quick: BOOL] = { DoDrawText: PROC = { IF textData.dropShadowOn THEN DrawDropShadow[dc, textData, camera, quick]; DrawText[dc, textData, camera, quick]; }; textData: TextData _ NARROW[slice.data]; textParts: TextParts _ NARROW[parts]; IF textParts = NIL OR textParts.includeText THEN Imager.DoSave[dc, DoDrawText]; }; TextDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: Camera, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = { jointSize: REAL = GGModelTypes.jointSize; halfJointSize: REAL = GGModelTypes.halfJointSize; hotJointSize: REAL = GGModelTypes.hotJointSize; halfHotJointSize: REAL = GGModelTypes.halfHotJointSize; slowNormal, slowHot, completeNormal, completeHot: BOOL _ FALSE; firstJoint: Point; normalTextParts, hotTextParts: TextParts; textData: TextData _ NARROW[slice.data]; transform: Transformation _ textData.fontData.transform; IF caretIsMoving OR dragInProgress OR camera.quality=quality THEN RETURN; IF selectedParts=NIL AND hotParts=NIL THEN RETURN; normalTextParts _ NARROW[selectedParts]; hotTextParts _ NARROW[hotParts]; completeNormal _ normalTextParts#NIL AND IsComplete[normalTextParts]; completeHot _ hotTextParts#NIL AND IsComplete[hotTextParts]; slowNormal _ normalTextParts#NIL AND (NOT quick OR (quick AND NOT completeNormal)); slowHot _ hotTextParts#NIL AND (NOT quick OR (quick AND NOT completeHot)); IF slowNormal AND slowHot THEN DrawSelectionFeedbackText[slice, textData, normalTextParts, hotTextParts, dc, transform, camera] ELSE IF slowNormal THEN DrawSelectionFeedbackText[slice, textData, normalTextParts, NIL, dc, transform, camera] ELSE IF slowHot THEN DrawSelectionFeedbackText[slice, textData, NIL, hotTextParts, dc, transform, camera]; IF (NOT slowNormal AND completeNormal) OR (NOT slowHot AND completeHot) THEN { fullParts: TextParts _ IF completeNormal THEN normalTextParts ELSE hotTextParts; [] _ GGSliceOps.GetTightBox[slice]; -- force update of internal data firstJoint _ ImagerTransformation.Transform[transform, textData.points[0]]; }; 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]; }; DrawSelectionFeedbackText: PROC [slice: Slice, textData: TextData, normalTextParts: TextParts, hotTextParts: TextParts, dc: Imager.Context, t: ImagerTransformation.Transformation, camera: Camera] = { DoDrawFeedback: PROC = { thisCPisHot, thisCPisSelected: BOOL _ FALSE; pts: ARRAY [0..11] OF Point; [] _ GGSliceOps.GetTightBox[slice]; -- force update of internal data 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, 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.DoSave[dc, DoDrawFeedback]; }; TextDrawAttractorFeedback: PROC [slice: Slice, attractorParts: SliceParts, selectedParts: SliceParts, dragInProgress: BOOL, dc: Imager.Context, camera: Camera, editConstraints: EditConstraints] = { bigReal: REAL _ 1.0e37; IF NOT (dragInProgress AND selectedParts#NIL) THEN { DoDrawFeedback: PROC = { DrawEdgeAndPoints: PROC [start, end, cp1, cp2: Point _ [bigReal, bigReal] ] = { Imager.MaskVector[dc, start, end]; GGShapes.DrawCP[dc, start, camera.cpScale]; GGShapes.DrawCP[dc, end, camera.cpScale]; IF cp1.x#bigReal THEN GGShapes.DrawCP[dc, cp1, camera.cpScale]; IF cp2.x#bigReal THEN GGShapes.DrawCP[dc, cp2, camera.cpScale]; }; pts: ARRAY [0..11] OF Point; t: ImagerTransformation.Transformation _ fontData.transform; [] _ GGSliceOps.GetTightBox[slice]; -- force update of internal data FOR i: NAT IN [0..11] DO pts[i] _ ImagerTransformation.Transform[t, textData.points[i]]; ENDLOOP; Imager.SetColor[dc, Imager.black]; Imager.SetStrokeJoint[dc, round]; Imager.SetStrokeWidth[dc, 1.0]; IF hitParts.points#ALL[FALSE] THEN FOR point: INTEGER IN [0..textMaxPoints) DO GGShapes.DrawCP[dc, pts[point], camera.cpScale]; ENDLOOP; IF hitParts.edges#ALL[FALSE] THEN FOR edge: INTEGER IN [0..textMaxEdges) DO IF hitParts.edges[edge] THEN { SELECT edge FROM 0 => DrawEdgeAndPoints[pts[0], pts[3], pts[1], pts[2] ]; 1 => DrawEdgeAndPoints[pts[3], pts[11], pts[7] ]; 2 => DrawEdgeAndPoints[pts[11], pts[8], pts[9], pts[10] ]; 3 => DrawEdgeAndPoints[pts[8], pts[0], pts[4] ]; 4 => DrawEdgeAndPoints[pts[1], pts[9], pts[5] ]; 5 => DrawEdgeAndPoints[pts[2], pts[10], pts[6] ]; 6 => DrawEdgeAndPoints[pts[4], pts[7], pts[5], pts[6] ]; ENDCASE => ERROR; EXIT; }; ENDLOOP; }; textData: TextData _ NARROW[slice.data]; hitParts: TextParts _ NARROW[attractorParts]; fontData: FontData _ textData.fontData; IF camera.quality#quality AND hitParts#NIL THEN Imager.DoSave[dc, DoDrawFeedback]; }; }; DrawText: PROC [dc: Imager.Context, textData: TextData, camera: GGModelTypes.Camera, quick: BOOL] = { IF textData.color=NIL THEN RETURN; Imager.SetFont[dc, IF (camera.quality=quality OR camera.displayStyle=print OR textData.displayStyle=print) THEN textData.trueFont ELSE GetScreenFont[textData]]; GGCoreOps.SetColor[dc, textData.color]; -- must happen before the text transform is applied Imager.ConcatT[dc, textData.fontData.transform]; 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: GGModelTypes.Camera, quick: BOOL] = { DoDrawDropShadow: PROC = { tVec: Imager.VEC _ [x: textData.dropShadowOffset.x*dropScale, y: textData.dropShadowOffset.y*dropScale]; GGCoreOps.SetColor[dc, textData.shadowColor]; -- must happen before text transform is applied Imager.ConcatT[dc, textData.fontData.transform]; Imager.TranslateT[dc, tVec]; Imager.SetFont[dc, IF (camera.quality=quality OR camera.displayStyle=print OR textData.displayStyle=print) THEN textData.trueFont ELSE GetScreenFont[textData]]; 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]; }; IF textData.shadowColor=NIL THEN RETURN; Imager.DoSave[dc, DoDrawDropShadow]; }; GetScreenFont: PROC [textData: TextData] RETURNS [font: ImagerFont.Font] ~ { RETURN[IF textData.screenFont#NIL THEN textData.screenFont ELSE GGFont.AlternateFont[textData.fontData, textData.trueFont, $visible]]; }; TextDrawTransform: PROC [slice: Slice, parts: SliceParts _ NIL, dc: Imager.Context, camera: Camera, transform: ImagerTransformation.Transformation, editConstraints: EditConstraints] = { DoTextDrawTransform: PROC = { Imager.ConcatT[dc, transform]; IF textData.dropShadowOn THEN DrawDropShadow[dc, textData, camera, FALSE]; DrawText[dc, textData, camera, FALSE]; }; textData: TextData _ NARROW[slice.data]; Imager.DoSave[dc, DoTextDrawTransform]; }; TextTransform: PROC [slice: Slice, parts: SliceParts _ NIL, transform: ImagerTransformation.Transformation, editConstraints: EditConstraints, history: HistoryEvent] = { textData: TextData _ NARROW[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; textData.color _ GGCoreOps.TransformColor[textData.color, transform]; textData.shadowColor _ GGCoreOps.TransformColor[textData.shadowColor, transform]; IF InvolvesScaling[transform] THEN textData.screenFont _ NIL; GGSlice.KillBoundBox[slice]; }; InvolvesScaling: PROC [transform: ImagerTransformation.Transformation] RETURNS [BOOL] = { epsilon: REAL = 0.01; sigma: ImagerTransformation.VEC _ ImagerTransformation.SingularValues[transform]; IF ABS[sigma.x-1.0] > epsilon THEN RETURN[TRUE]; IF ABS[sigma.y-1.0] > epsilon THEN RETURN[TRUE]; RETURN[FALSE]; }; 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]]]; f.PutRope[" props: ( "]; GGParseOut.WriteBool[f, textData.seg.props#NIL]; IF textData.seg.props#NIL THEN GGParseOut.WriteProps[f, textData.seg.props] ELSE f.PutChar[IO.SP]; -- list of ROPE f.PutF[") ls: %g", [real[GGSlice.GetTextLineSpacing[slice]]]]; }; TextFilein: PROC [f: IO.STREAM, version: REAL, router: MsgRouter, camera: Camera] RETURNS [slice: Slice] = { text, fontName: Rope.ROPE; prefix: Rope.ROPE _ "xerox/pressfonts/"; tiogaPrefix: Rope.ROPE _ "xerox/tiogafonts/"; family: Rope.ROPE _ "Helvetica"; face: Rope.ROPE _ ""; fontData: GGFont.FontData; fontDataComfortable: BOOL _ FALSE; size: REAL _ 1.0; preferredSize: REAL _ 1.0; preferredSizeRope: Rope.ROPE _ ""; 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; lineSpacing: REAL _ 1.0; props: LIST OF Rope.ROPE _ NIL; IF version >= 8702.11 THEN { fontDataComfortable _ GGParseIn.ReadBool[f, version].truth; }; IF version > 8601.22 THEN { text _ f.GetRopeLiteral[]; } ELSE { GGParseIn.ReadWRope[f, "("]; text _ GGParseIn.ReadWWord[f]; GGParseIn.ReadWRope[f, ")"]; }; IF version < 8702.11 THEN { -- many old style formats IF version >= 8701.30 THEN { prefix _ GGParseIn.ReadWWord[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.ReadWWord[f]; } ELSE IF version <= 8605.12 THEN { -- a complex name like "xerox/pressfonts/Helvetica-BIR" fontName _ GGParseIn.ReadWWord[f]; size _ GGParseIn.ReadWReal[f]; [] _ GGParseIn.ReadWWord[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.ReadWWord[f]; size _ GGParseIn.ReadWReal[f]; [] _ GGParseIn.ReadWWord[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.ReadWWord[f]; size _ GGParseIn.ReadWReal[f]; [family, face, ----] _ TFontParamsFromFontName[fontName]; } ELSE IF version = 8701.30 THEN {-- an interim font format ps: INT _ 0; fail: BOOL _ FALSE; [fail, ----, family, face] _ GGFont.OldParseFontData[f, FALSE, TRUE, TRUE]; IF fail THEN ERROR; transform _ GGParseIn.ReadTransformation[f]; preferredSize _ GGParseIn.ReadWReal[f]; ps _ Real.Round[preferredSize]; IF Rope.Equal[prefix, tiogaPrefix, FALSE] THEN preferredSizeRope _ IO.PutFR["%g", [integer[ps]] ]; }; 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, preferredSizeRope], oldStream: NIL], prefixP: TRUE, familyP: TRUE, faceP: TRUE ! GGFont.ParseError => CONTINUE;]; -- failures handled downstream } ELSE { name: Rope.ROPE _ GGParseIn.ReadWWord[f]; fontData _ GGFont.CreateFontData[]; -- assure non-NIL fontData IF fontDataComfortable THEN { -- User data in file: name, transformation fontData _ GGFont.ParseFontData[data: fontData, inStream: IO.RIS[rope: name, oldStream: NIL], prefixP: TRUE, familyP: TRUE, faceP: TRUE ! GGFont.ParseError => CONTINUE;]; -- failures handled downstream fontData.transform _ GGParseIn.ReadTransformation[f]; } ELSE { -- Literal data in file fontData.transform _ GGParseIn.ReadTransformation[f]; fontData.storedSize _ GGParseIn.ReadWReal[f]; fontData.designSize _ GGParseIn.ReadWReal[f]; fontData _ GGFont.ParseFontData[data: fontData, inStream: IO.RIS[name], literalP: TRUE ! GGFont.ParseError => CONTINUE;]; -- failures handled downstream }; }; IF version > 8605.28 THEN { textColor _ GGParseIn.ReadColor[f, version]; IF textColor = NIL AND version < 9106.27 THEN textColor _ Imager.black; }; 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]; IF shadowColor = NIL AND version < 9106.27 THEN shadowColor _ Imager.black; }; } 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.ReadWReal[f]; } ELSE amplifySpace _ 1.0; IF version>=8706.08 THEN { -- read in segment props hasProps: BOOL _ FALSE; GGParseIn.ReadWRope[f, "props: ( "]; hasProps _ GGParseIn.ReadBool[f, version].truth; props _ IF hasProps THEN GGParseIn.ReadListOfRope[f] ELSE NIL; GGParseIn.ReadWRope[f, ")"]; }; IF version>=8803.08 THEN { -- read in line spacing GGParseIn.ReadWRope[f, "ls: "]; lineSpacing _ GGParseIn.ReadWReal[f]; }; slice _ MakeTextSlice[text, textColor, camera.displayStyle, amplifySpace, dropShadowOn, dropShadowOffset, shadowColor]; fontData.substituteOK _ TRUE; -- allow font substitution on file reads IF NOT SetTextFontAndTransform[slice, fontData, router, NIL] THEN SIGNAL FontNameError; IF props#NIL THEN { seg: Segment _ NARROW[slice.data, TextData].seg; FOR next: LIST OF Rope.ROPE _ props, next.rest UNTIL next=NIL DO seg.props _ CONS[next.first, seg.props]; ENDLOOP; }; GGSlice.SetTextLineSpacing[slice, lineSpacing, NIL]; }; MakeComplete: PROC [parts: SliceParts] = { textParts: TextParts _ NARROW[parts]; textParts.points _ ALL[TRUE]; textParts.edges _ ALL[TRUE]; textParts.includeText _ TRUE; }; IsComplete: PROC [parts: SliceParts] RETURNS [BOOL _ FALSE] = { textParts: TextParts _ NARROW[parts]; RETURN[textParts#NIL AND textParts.points=ALL[TRUE] AND textParts.edges=ALL[TRUE] AND textParts.includeText=TRUE]; }; IsEmpty: PROC [parts: SliceParts] RETURNS [BOOL _ FALSE] = { textParts: TextParts _ NARROW[parts]; RETURN[textParts=NIL OR (textParts.points=ALL[FALSE] AND textParts.edges=ALL[FALSE] AND textParts.includeText = FALSE)]; }; TextIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL _ FALSE] = { RETURN[IsEmpty[sliceD.parts]]; }; TextIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL _ FALSE] = { 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] _ GGSliceOps.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, editConstraints: EditConstraints, bezierDrag: GGInterfaceTypes.BezierDragRecord] 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] ]; IF NOT sliceD.slice.boxValid THEN [] _ TextGetBoundBox[sliceD.slice, NIL]; -- need side effect of updating textData.points, if needed FOR point: INTEGER IN [0..textMaxPoints) DO IF parts.points[point] THEN pointGen.toGo _ pointGen.toGo + 1; ENDLOOP; }; TextWalkPointsInDescriptor: PROC [sliceD: SliceDescriptor, walkProc: PointWalkProc] = { parts: TextParts _ NARROW[sliceD.parts]; textData: TextData _ NARROW[sliceD.slice.data]; fontData: FontData _ textData.fontData; done: BOOL _ FALSE; IF NOT sliceD.slice.boxValid THEN [] _ TextGetBoundBox[sliceD.slice, NIL]; -- need side effect of updating textData.points, if needed FOR index: INTEGER IN [0..textMaxPoints) DO IF parts.points[index] THEN { point: Point _ ImagerTransformation.Transform[fontData.transform, textData.points[index]]; done _ walkProc[point]; }; ENDLOOP; }; TextPointPairsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointPairGen: PointPairGenerator] = { parts: TextParts _ NARROW[sliceD.parts]; IF NOT sliceD.slice.boxValid THEN [] _ TextGetBoundBox[sliceD.slice, NIL]; -- need side effect of updating textData.points, if needed 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; }; TextSegmentsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [segGen: SegmentGenerator] = { parts: TextParts _ NARROW[sliceD.parts]; segGen _ NEW[SegmentGeneratorObj _ [NIL, 0, 0, 0, NIL, FALSE, sliceD, NIL] ]; -- toGo and index now used FOR edge: INTEGER IN [0..textMaxEdges) DO IF parts.edges[edge] THEN segGen.toGo _ 1;-- any edge sets segGen.toGo = 1 ENDLOOP; }; TextWalkSegments: PROC [slice: Slice, walkProc: WalkProc] RETURNS [sliceD: SliceDescriptor] = { textData: TextData _ NARROW[slice.data]; RETURN[IF walkProc[textData.seg, NIL] THEN TextNewParts[slice, NIL, slice] ELSE TextNewParts[slice, NIL, none]]; }; TextNextSegment: PUBLIC PROC [slice: Slice, segGen: SegmentGenerator] RETURNS [seg: Segment, transform: Transformation] = { IF segGen=NIL OR segGen.toGo = 0 THEN RETURN[NIL, NIL] ELSE { textData: TextData _ NARROW[segGen.sliceD.slice.data]; seg _ textData.seg; segGen.toGo _ 0; }; }; TextNextPoint: 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; 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 [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; 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; [] _ GGSliceOps.GetBoundBox[slice, NIL]; -- update of textData.points required 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 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: 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) ]; }; TextClosestPoint: 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; textData: TextData _ NARROW[sliceD.slice.data]; tightBox: BoundBox _ GGSliceOps.GetTightBox[sliceD.slice]; -- update may be required bigBox: BoundBoxObj _ [tightBox.loX-tolerance, tightBox.loY-tolerance, tightBox.hiX+tolerance, tightBox.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] ]; }; }; }; TextClosestJointToHitData: PROC [sliceD: SliceDescriptor, mapPoint, testPoint: Point, hitData: REF ANY] RETURNS [jointD: SliceDescriptor, point: Point, normal: Vector] = { bestDist: REAL; newHitData: REF ANY; textHitData: TextHitData; textParts: TextParts _ NEW[TextPartsObj _ [points: ALL[FALSE], edges: ALL[FALSE], includeText: FALSE ] ]; success: BOOL _ FALSE; [point, bestDist, normal, newHitData, success] _ GGSliceOps.ClosestPoint[sliceD, mapPoint, GGUtility.plusInfinity]; textHitData _ NARROW[newHitData]; textParts.points[textHitData.point] _ TRUE; jointD _ GGSlice.DescriptorFromParts[slice: sliceD.slice, parts: textParts]; }; TextClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, bestNormal: Vector _ [0, -1], hitData: REF ANY, success: BOOL _ FALSE] = { textData: TextData _ NARROW[sliceD.slice.data]; tightBox: BoundBox _ GGSliceOps.GetTightBox[sliceD.slice]; -- update may be required bigBox: BoundBoxObj _ [tightBox.loX-tolerance, tightBox.loY-tolerance, tightBox.hiX+tolerance, tightBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates 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] ]; bestNormal _ Vectors2d.Sub[testPoint, 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 tightBox 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; }; 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, setHow: ATOM, history: HistoryEvent] = { textData: TextData _ NARROW[slice.data]; SELECT setHow FROM $Set => textData.shadowColor _ color; $ChangeHue => { newColor: Color _ GGUtility.ChangeHue[textData.shadowColor, color]; textData.shadowColor _ newColor; }; ENDCASE => ERROR; }; TextGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Imager.Color, isUnique: BOOL _ TRUE] = { textData: TextData _ NARROW[slice.data]; color _ textData.shadowColor; }; TextSetDefaults: PROC [slice: Slice, parts: SliceParts, defaults: DefaultData, history: HistoryEvent] = { textData: TextData _ NARROW[slice.data]; textParts: TextParts _ NARROW[parts]; router: MsgRouter _ Feedback.EnsureRouter[$Gargoyle]; IF textParts=NIL OR textParts.includeText THEN { [] _ SetTextFont[slice, defaults.font, router, NIL]; textData.color _ defaults.textColor; IF defaults.dropShadowOn THEN DropShadowOn[slice, defaults.dropShadowOffset, NIL] ELSE DropShadowOff[slice, NIL]; textData.shadowColor _ defaults.dropShadowColor; }; }; TextSetFillColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color, setHow: ATOM, history: HistoryEvent] = { textData: TextData _ NARROW[slice.data]; SELECT setHow FROM $Set => textData.color _ color; $ChangeHue => { newColor: Color _ GGUtility.ChangeHue[textData.color, color]; textData.color _ newColor; }; ENDCASE => ERROR; }; TextGetFillColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Imager.Color, isUnique: BOOL _ TRUE] = { textData: TextData _ NARROW[slice.data]; RETURN[textData.color]; }; dropScale: REAL _ 0.1; -- ratio of drop shadow displacement to font size scratchEdge: GGBasicTypes.Edge _ Lines2d.CreateEmptyEdge[]; -- global storage used in TextNearestEdge END. :GGSliceImplB.mesa Contents: Implements Text slice class in Gargoyle. Copyright Ó 1987, 1988, 1989 by Xerox Corporation. All rights reserved. Pier, June 10, 1992 12:28 pm PDT Bier, on May 21, 1992 3:22 pm PDT Kurlander, July 17, 1987 11:30:09 am PDT Eisenman, July 22, 1987 6:32:21 pm PDT Doug Wyatt, December 18, 1989 4:04:31 pm PST Text Slice Class [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 Viewer: TYPE = ViewerClasses.Viewer; IMPORTANT CONVENTION FOR TEXT SLICES: The procedure TextSetBoundBox maintains not only the bound and tight boxes but the data array textData.points as well. Any code that uses textData.points must be sure and call TextGetBoundBox or TextGetTightBox before using textData.points to force update of textData.points if needed before use. Text-Only Data and Procs 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 TopBottomAnchor: TYPE = {lower, base, center, upper}; Filled in by TextClosestPoint, TextClosestSegment, in same order as TextPointArray and TextEdgeArray. -1 means not hit 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. Avoid finding an already substituted font. Just use the substitution. IF screenFont # NIL THEN textData.screenFont _ screenFont ELSE textData.screenFont _ GGFont.AlternateFont[fontData, textData.trueFont, $visible]; defer setting screenFont until it is really needed. Takes a complex name like "xerox/pressfonts/Helvetica-MIR" and a font size and returns a simple name like Helvetica10I The tight box should still be OK. feedback: Viewer _ NIL; -- use the MessageWindow Modified starting from ImagerRasterShowImpl.BasicShowChar. Text Class Procs Fundamental ASSERT: extents all positivesubtract left and descent, add right and ascent cpBottom, cpCenter, and cpTop are the y coordinates to use for the Text control points IF textData.displayStyle = print OR textData.screenFont = NIL Update textData.box amd find boundBox. GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceBoundBoxProc GGModelTypes.SliceCopyProc Just ignore parts and copy the whole slice. MakeTextSlice allocates but does not assign fields: box, points, fontData, and seg. MakeTextSlice puts dummy values into trueFont, screenFont, inverse, inverseScale, and lineSpacing 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.SliceDrawPartsProc GGModelTypes.SliceDrawSelectionFeedbackProc If any joint was hit, feedback all the joints If any edge was hit, feedback that edge and joints that touch that edge Assume a simple descriptor with only one edge selected called with the dc transformation already set to object coordinates N.B.: camera quality and displayStyle take precedence over textData displayStyle GGCoreOps.SetColor[dc, IF textData.color=NIL THEN Imager.black ELSE textData.color]; called with the dc transformation already set to object coordinates GGCoreOps.SetColor[dc, IF textData.shadowColor=NIL THEN Imager.black ELSE textData.shadowColor]; N.B.: camera quality and displayStyle take precedence over textData displayStyle This proc should only be called by callers who really need the screenFont. Callers that are copying or otherwise using the screenFont REF should do so directly and not call this proc. GGModelTypes.SliceDrawTransformProc Transforming GGModelTypes.SliceTransformProc IF InvolvesScaling[transform] THEN textData.screenFont _ GGFont.AlternateFont[fontData, textData.trueFont, $visible]; relys on deferred evaluation if the transformed screenFont is every really needed 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 Text slices have only one "segment" whose entire purpose is to contain properties in seg.props Text slices have only one "segment" whose entire purpose is to contain properties in seg.props GGModelTypes.SliceClosestPointProc GGModelTypes.SliceClosestJointToHitDataProc 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 ÊO+˜Icodešœ™šÏnœ+™3KšœH™HKšœ ™ Kšœ!™!K™(Kšœ&™&K™,—Idefault™L™K™šÏk ˜ Jšœ®˜®—K˜š œžœž˜Jšžœúžœ7˜¼Kšžœ ž˜K˜I artworkFigure–G39.86389 mm topLeading 39.86389 mm topIndent 1.411111 mm bottomLeading •Bounds:0.0 mm xmin 0.0 mm ymin 117.1222 mm xmax 37.04167 mm ymax •Artwork Interpress• Interpressˆ Interpress/Xerox/3.0  f j k j¡¥“ÄWB ¤ ¨  ì ¡£={ ¢ ¨ r jÄç­ ¢ ¨¡¡¨ÄWB ¤ ¨ r j¡¥“ÄWB ¤ ¨¡¡¨ r j¡¡¨¢¯“1Ë1¡¹¡¡¨¢¯“18¡¹¡¡¨¢¯“88Ë¡¹¡¡¨¢¯“8Ë1Ë¡¹ k é¢¯“¡¡¨¢·“1Ú™8Ž˜¢¯“¡¡¨¢·“8Ä™1Ž˜¢¯“¡¡¨¢·“Ä)™˘ r jÄù Ä­ ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ0–¡¡¨ k é r jij0Ä«  ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ1–¡¡¨ k é r jÄ×0Ämn ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ2–¡¡¨ k é r jÄ1…eÄáòŸ ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ3–¡¡¨ k é r jÄ"§Ì ¤Ä\¤£Ä‰Ãp ¢ ¥ ¨ÅXeroxÅ PressFontsÅ Helvetica-mrr£¡ “ª ¤ ” •  —¡¡¨  ŠÁ qwil jrut–¡¡¨ k é r j·Ä9 ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ4–¡¡¨ k é r jÄ ùÄÏ  ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ5–¡¡¨ k é r jÄ^ØWÄA¯ÿ ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ6–¡¡¨ k é r jÄMÄå  ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ7–¡¡¨ k é r jÄEÄE ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ8–¡¡¨ k é r jĆwRÄJÅ= ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ9–¡¡¨ k é r jÄM×0Ä mký ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ10–¡¡¨ k é r jÄM­0Ä  ¢ ¨ÅXeroxÅ PressFontsÅ Helvetica-brr£¡ “® ¤ ” •  —¡¡¨  ŠÁ11–¡¡¨ k é k é k é k gšœ=™=IartworkCaption™N™zK˜Kšœ žœ˜&Kšœ žœ˜,Kšœžœ˜#Kšœ žœ˜)Kšœžœ˜Kšœ žœ˜-Kšœžœ˜/Kšœžœ ˜5Kšœ žœ˜+Jšœ žœ˜*Jšœžœ˜Kšœ žœ˜!Kšœžœ˜1Kšœžœ˜!Kšœžœ˜3Kšœžœ"˜9Kšœžœ#˜;Kšœžœ&˜AKšœžœ˜1Kšœžœ˜!Kšœ žœ˜'Kšœžœ!˜7Kšœžœ$˜=Kšœžœ#˜;Kšœžœ!˜5Kšœ žœ˜+Kšœžœ˜!Kšœ žœ˜+Kšœžœ˜1Kšœžœ ˜5Kšœžœ#˜;Kšœ žœ˜'Kšœ žœ˜+Kšœžœ'˜Kšœ žœ ,˜>K˜K˜—Kšœ žœžœ˜'šœžœžœ˜Kšœv™vKšœ ˜9Kšœ ˜6K˜K˜—K˜Kš œžœžœžœ˜$K˜šœžœžœžœ˜CKšžœ ˜ šœžœ˜K˜ šœ "˜?KšŸ ™ —Kšœ˜Kšœ œ˜;K˜Kšœ˜šœ˜KšŸ™—Kšœ˜Kšœ˜Kšœ˜K˜!K˜1K˜1K˜#šœ'˜'KšŸ ™ —šœ˜KšŸ™—K˜K˜K˜˜KšŸ™—K˜K˜%K˜K˜K˜K˜K˜K˜šœ)˜)KšŸ™—K˜+K˜3K˜3K˜/K˜K˜K˜!˜KšŸ ™ —Kšœ˜K˜1Kšœ3˜3Kšœ#˜#Kšœ1˜1K˜'K˜+˜/KšŸ™—K˜K˜#K˜#K˜K˜K˜#K˜#K˜#K˜#K˜K˜K˜K˜K˜K˜K˜#K˜"K˜—K˜K˜—š œžœžœ žœAžœžœžœSžœ˜€šœžœ˜(Kšœ  '˜GK˜Kšœ ˜,Kšœ˜Kšœžœ˜K•StartOfExpansion[]šœ# ˜3Kšœ˜Kšœ ˜ Kšœ ˜ Kšœ˜Kšœ#˜#Kšœ˜K–N[p0: Lines2dTypes.Point, p1: Lines2dTypes.Point, props: LIST OF REF ANY]šœAžœ˜EKšœ˜—KšŸI™IKšœ! #˜Dšœžœ ˜K˜&Kšœ˜Kšœžœ˜ Kšœžœžœžœ˜&Kšœ% '˜LKšœ% '˜LKšœ ž˜Kšœ˜—K˜K˜—š œžœžœžœž œ˜Cš žœ!žœžœžœžœ˜Kšœ#žœžœ˜IKšžœ žœA˜TKšžœžœD˜\Kš žœžœ žœžœžœ1˜aKšžœ˜—K˜—Kšœ"˜"K˜K˜—šœžœWžœK˜ÆJšœ žœ ˜š žœžœžœžœžœ˜4šœžœ˜šœžœ8˜OKšœ"˜"Kšœ+˜+Kšœ)˜)Kšžœžœ*˜?Kšžœžœ*˜?K˜—Kšœžœ žœ˜Kšœ<˜[context: Imager.Context, strokeJoint: Imager.StrokeJoint]šœ!˜!K˜K™-šžœžœžœžœžœžœžœž˜NKšœ0˜0Kšžœ˜—K™GK™6šžœžœžœžœžœžœžœž˜Kšžœžœ˜šžœž˜Kšœœ"˜9Kšœœ˜2Kšœœ$˜;Kšœœ˜1Kšœœ˜1Kšœœ˜2Kšœœ"˜9Kšžœžœ˜—Kšžœ˜K˜—Kšžœ˜—K˜—Kšœžœ ˜(Kšœžœ˜-K˜'Kšžœžœ žœžœ#˜RK˜—˜K˜——šœžœNžœ˜eK™CKšžœžœžœžœ˜"K™PKš œžœžœžœžœžœŸ œ ˜ Kš œžœžœžœžœ™TKšœ( 3˜[Kšœ0˜0Kšœ )˜GKšœ3 ,˜_Kšœ#˜#K˜K˜—šœžœNžœ˜kK™Cšœžœ˜Kšœ žœX˜hKš œžœžœžœžœ™`Kšœ. /˜]Kšœ0˜0Kšœ˜K™PKš œžœžœžœžœžœŸ œ ˜ Kšœ )˜GKšœ3 ,˜_Kšœ#˜#K˜—Kšžœžœžœžœ˜(Kšœ$˜$K˜K˜—šÐbn œžœžœ˜LK™¸šžœžœžœžœ˜:KšžœG˜K—K˜K˜—š£ œžœ$žœ{˜¹Kšœ#™#š£ œžœ˜Kšœ˜Kšžœžœ&žœ˜JKšœžœ˜&K˜—Kšœžœ ˜(Kšœ'˜'K˜K˜—K™Kšœ ™ š œžœ$žœn˜¨K™Kšœžœ ˜(K˜'KšœP˜PKšœC˜CKšœH˜HKšœE˜EKšœQ˜QKšžœžœŸœ(™ušžœžœžœ˜=K™Q—Kšœ˜K˜K˜—K™šœžœ2žœžœ˜YKšœ žœ˜Kšœžœ2˜QKš žœžœžœžœžœ˜0Kš žœžœžœžœžœ˜0Kšžœžœ˜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šœ˜Iprocšœ+žœ˜0Pš žœžœžœ.žœ žœžœ ˜rKšœ>˜>K˜K˜—š œžœžœžœ žœ%žœ˜lKšŸ™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˜—šžœžœ ˜5KšŸ™šžœžœ˜Kšœ ˜ Kšœ ˜—KšŸ ™ Kšžœžœ ˜<šžœžœžœ ,˜NKšœ ˜ K˜—šžœžœžœ 7˜YK˜"K˜K˜Kšœ2 ˜GKšœ œ&˜9K˜—šžœžœžœ 6˜XK˜"K˜Kšœ ˜9Kšœ œ&˜9Kšœ˜—šžœžœžœ U˜vK˜"K˜Kšœ œ&˜9Kšœ˜—šžœžœžœ ˜9Kšœžœ˜ Kšœžœžœ˜Kš œ œ-žœžœžœ˜KKšžœžœžœ˜Kšœ,˜,K˜'Kšœ˜Kš žœ!žœžœ¢œžœ˜bK˜KšŸ@™@Kšœ:™:—K–)[rope: ROPE, oldStream: STREAM _ NIL]˜#Kšœ¢œ ¢˜Kšœ¢œ ¢Ðct˜?Kšœ:žœžœ¢œ ¢œ žœ žœ žœ žœžœ ˜öK˜—šžœ˜Kšœ žœ˜)K–)[rope: ROPE, oldStream: STREAM _ NIL]šœ$ Ðbc˜>šžœžœ *˜HK–)[rope: ROPE, oldStream: STREAM _ NIL]šœ:žœžœžœ žœ žœ žœžœ ˜ÊKšœ¢œ ˜5K˜—šžœ ˜Kšœ¢œ ˜5K˜-K˜-Kš œ:žœžœžœžœ ˜˜K˜—K˜—˜KšŸ ™ —šžœžœ˜Kšœ,˜,Kšžœ žœžœžœ˜GK˜K– [f: STREAM]šŸ™—šžœžœ˜Kšœ6˜6Kšžœžœžœžœ˜šžœžœ˜Kšœ*˜*Kšœ.˜.Kšžœžœžœžœ˜KK˜—K˜—šžœžœ˜K˜—šžœžœ "˜?Kšœ&˜&Kšœ;˜;KšœM˜MK˜ K˜—šžœžœžœ˜ Kšœ5˜5KšœM˜MK˜ Kšœ /˜2—K˜šžœžœ˜Kšœ&˜&K˜—Kšžœ˜šžœžœ ˜3Kšœ žœžœ˜Kšœ$˜$Kšœ0˜0Kš œžœ žœžœžœ˜>Kšœ˜K˜—šžœžœ ˜2Kšœ˜K– [f: STREAM]šœ%˜%K˜K˜—Kšœw˜wK–ß[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š žœžœ2žœžœžœ˜Wšžœžœžœ˜Kšœžœ˜0š žœžœžœžœžœžœž˜@Pšœ žœ˜(Pšžœ˜—K˜—K–2[slice: GGModelTypes.Slice, lineSpacing: REAL]šœ/žœ˜4K˜K˜—K™Kšœ ™ š œžœ˜+Kšœžœ˜%Kšœžœžœ˜Kšœžœžœ˜Kšœžœ˜K˜K˜—š œžœžœž œ˜?Kšœžœ˜%Kšžœ žœžœžœžœžœžœžœžœžœ˜rK˜K˜—š œžœžœžœžœ˜˜>K˜K˜—šœžœ|žœ9˜ÑKšœ!™!KšœV™VKš œžœžœžœ žœžœžœ˜fKš œžœžœžœ žœžœžœ˜lšžœžœ ˜Kšžœ˜—K˜K˜—šœžœ7˜WKšœžœ˜(Kšœžœ˜/K˜'Kšœžœžœ˜Kš žœžœžœ$žœ :˜…šžœžœžœž˜+šžœžœ˜KšœZ˜ZKšœ˜K˜—Kšžœ˜—K˜K˜—šœžœžœ'˜iKšœžœ˜(Kš žœžœžœ$žœ :˜…Kšœžœ(žœ ˜\šžœžœžœž˜)Kšžœžœ+˜DKšžœ˜—K˜K˜—šœžœžœ˜_K™^Kšœžœ˜(Kš œ žœžœ žœžœ žœ ˜hšžœžœžœž˜)Kšžœžœ  ˜JKšžœ˜—K˜K˜—šœžœ$žœ˜_K™^Kšœžœ ˜(Kšžœžœžœžœžœ žœžœ ˜pK˜K˜—šœžœžœ*žœ.˜{Kšžœžœžœžœžœžœžœ˜6šžœ˜Kšœžœ˜6Kšœ˜Kšœ˜Kšœ˜—K˜K˜—š œžœ*žœ.˜ršžœ žœžœžœ˜+Kšœžœ˜Kšžœ˜K˜—šžœ˜Kšœ*˜*Kšœžœ˜/K˜'Kšœžœ˜,Kšœžœ˜Kšœžœ˜šžœ!žœž˜BKšžœžœžœ +˜QKšžœ˜—Kšœa˜aKšœ ˜ Kšœžœžœžœ  4˜nK˜—K˜K˜—šœžœ2žœ6˜†šžœžœžœžœ˜3Kšœžœ˜Kšžœ˜K˜—šžœ˜Kšœ.˜.Kšœžœ˜/Kšœžœ˜,KšœF˜FKšœžœ˜Kšœžœ˜šžœ%žœž˜EKšžœžœžœ )˜NKšžœ˜—Kšœ#žœ %˜Nšžœžœ 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šžœžœ"˜3—Kšœ*˜*Kšœžœžœžœ  3˜uK˜—K˜K˜—š œžœ!žœž œ˜MKš žœžœžœžœžœ˜]K˜K˜—šœžœ8žœžœžœ(žœžœ žœžœ˜ÈK™"Kšœžœ˜Kšœžœ˜/Kšœ; ˜TKšœwžœžœ ˜žšžœ!žœ˜)Kšœžœ˜,KšœK˜KKšœžœžœ žœ5 3˜Kšœ|˜|šžœ žœ˜KšœJ˜JKšœ4˜4Kšœ žœB˜OK˜—K˜—K˜K˜—š œžœ@žœžœžœ<˜«Kšœ+™+Kšœ žœ˜Kšœ žœžœ˜Kšœ˜Kš œžœžœžœ žœžœžœ˜iKšœ žœžœ˜Kšœ œY˜sKšœžœ ˜!Kšœ&žœ˜+K–?[slice: GGModelTypes.Slice, parts: GGModelTypes.SliceParts]šœL˜LK˜K˜—šœžœ8žœžœžœ)žœžœ žœžœ˜ËKšœ$™$Kšœ žœ !™1Kšœžœ˜/Kšœ; ˜TKšœwžœžœ ˜žšžœ!žœ˜)Kšœžœ˜Kšœ˜Kšœžœ˜,KšœK˜KKšœžœžœ žœ5 3˜Kšœx˜xšžœ žœ˜KšœJ˜JKšœ4˜4Kšœžœ@˜[Kšœ1˜1K˜—K˜—K˜K˜—šœžœ3žœžœ žœ žœžœžœ˜¾Kšœ™Kšœ˜Kšœžœ˜Kšœ žœ˜'Kšœžœ˜Kšœ˜Kšœ ˜)Kšœ˜šžœžœž˜"šžœ žœ˜Kšœ $˜?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šœ™šœžœ@žœ˜xKšœžœ ˜(šžœž˜Kšœ%˜%˜KšœC˜CKšœ ˜ K˜—Kšžœžœ˜—Kšœ˜K˜—š œžœ#žœ!žœžœ˜sKšœžœ ˜(Kšœ˜Kšœ˜K˜—šœžœT˜iKšœžœ ˜(Kšœžœ˜%Kšœ5˜5šžœ žœžœžœ˜0KšœŸ œžœ˜4Kšœ$˜$Kš žœžœ0žœžœžœ˜qKšœ0˜0K˜—Kšœ˜K˜—šœžœ@žœ˜vKšœžœ ˜(šžœž˜Kšœ˜˜Kšœ=˜=Kšœ˜K˜—Kšžœžœ˜—Kšœ˜K˜—š œžœ#žœ!žœžœ˜qKšœžœ ˜(Kšžœ˜Kšœ˜—K˜Kšœ žœ 1˜HKšœ< )˜eK˜Kšžœ˜—…—úFa«