<> <> <> <> <> <<>> DIRECTORY Atom, Convert, FileNames, GGBasicTypes, GGBoundBox, GGBoxCluster, GGCluster, GGError, GGInterfaceTypes, GGModelTypes, GGParseOut, GGParseIn, GGTransform, Imager, ImagerFont, ImagerTransformation, IO, NodeStyle, NodeStyleFont, Real, Rope; GGClusterImpl: CEDAR PROGRAM IMPORTS Atom, Convert, FileNames, GGBoundBox, GGBoxCluster, GGError, GGParseIn, GGParseOut, GGTransform, Imager, ImagerFont, IO, NodeStyleFont, Real, Rope EXPORTS GGCluster = BEGIN Point: TYPE = GGBasicTypes.Point; ClusterObj: TYPE = GGModelTypes.ClusterObj; ClusterClass: TYPE = GGModelTypes.ClusterClass; ClusterClassObj: TYPE = GGModelTypes.ClusterClassObj; ClusterParts: TYPE = GGModelTypes.ClusterParts; Cluster: TYPE = GGModelTypes.Cluster; SelectMode: TYPE = GGModelTypes.SelectMode; ExtendMode: TYPE = GGModelTypes.ExtendMode; BoundBox: TYPE = GGModelTypes.BoundBox; SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData; CameraData: TYPE = GGInterfaceTypes.CameraData; DisplayStyle: TYPE = GGCluster.DisplayStyle; ClusterClassDef: TYPE = REF ClusterClassDefObj; ClusterClassDefObj: TYPE = RECORD[type: ATOM, class: ClusterClass]; TextData: TYPE = REF TextDataObj; TextDataObj: TYPE = RECORD [ rope: Rope.ROPE, worldPt: Point, <> fontStyle: DisplayStyle, -- the desired font style trueFontStyle: DisplayStyle, -- the actual font style fontString: Rope.ROPE _ NIL, -- the desired font string trueFontString: Rope.ROPE _ NIL, -- the actual font string fontFamily: ATOM, fontFace: NodeStyle.FontFace, fontSize: REAL, font: ImagerFont.Font, validFontInfo: BOOL _ FALSE, -- TRUE if desires match actual color: Imager.Color, colorName: Rope.ROPE, feedbackBox: BoundBox ]; TextHitData: TYPE = REF TextHitDataObj; TextHitDataObj: TYPE = RECORD [ bestSeg: NAT _ 0 -- segment number from ClosestSegementProc ]; IPData: TYPE = REF IPDataObj; IPDataObj: TYPE = RECORD [ file: Rope.ROPE, worldPt: Point, feedbackBox: BoundBox, transform: ImagerTransformation.Transformation ]; IPHitData: TYPE = REF IPHitDataObj; IPHitDataObj: TYPE = RECORD [ bestSeg: NAT _ 0 -- segment number from ClosestSegementProc ]; NotFound: PUBLIC SIGNAL = CODE; FontStringError: PUBLIC SIGNAL = CODE; FetchClusterClass: PUBLIC PROC [type: ATOM] RETURNS [class: ClusterClass] = { FOR l: LIST OF ClusterClassDef _ clusterClasses, l.rest UNTIL l=NIL DO IF l.first.type=type THEN RETURN[l.first.class]; ENDLOOP; SIGNAL NotFound; RETURN[NIL]; }; <<>> <> BuildTextClusterClass: PROC [] RETURNS [class: ClusterClass] = { class _ NEW[ClusterClassObj _ [ type: $Text, boundBox: TextBoundBox, copy: TextCopy, draw: TextDraw, drawTransform: TextDrawTransform, drawSelectionFeedback: TextDrawSelectionFeedback, transform: TextTransform, emptyParts: TextEmptyParts, newParts: TextNewParts, addParts: TextAddParts, removeParts: TextRemoveParts, closestPoint: TextClosestPoint, closestPointAndTangent: NIL, closestSegment: TextClosestSegment, fileout: TextFileout, filein: TextFilein ]]; }; MakeTextCluster: PUBLIC PROC [text: Rope.ROPE, fontStyle: DisplayStyle, fontString: Rope.ROPE, colorName: Rope.ROPE, worldPt: Point] RETURNS [clus: Cluster] = { feedbackBox: BoundBox _ GGBoundBox.CreateBoundBox[0,0,0,0]; textData: TextData _ NEW[TextDataObj _ [text, worldPt, , , , , , , , , FALSE, Imager.black, colorName, feedbackBox]]; clus _ NEW[ClusterObj _ [ class: FetchClusterClass[$Text], data: textData, children: NIL, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE, FALSE], boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets filled in later onOverlay: FALSE, hitData: NEW[TextHitDataObj _ [] ] ]]; SetTextFont[clus, fontStyle, fontString]; <> <> <> }; GetText: PUBLIC PROC [clus: Cluster] RETURNS [text: Rope.ROPE] = { RETURN[IF clus.class.type#$Text THEN NIL ELSE NARROW[clus.data, TextData].rope]; }; AppendText: PUBLIC PROC [clus: Cluster, text: Rope.ROPE] = { textData: TextData; IF clus.class.type#$Text THEN RETURN; textData _ NARROW[clus.data]; textData.rope _ Rope.Concat[textData.rope, text]; -- update text clus.class.boundBox[clus]; -- update bound box }; BackspaceText: PUBLIC PROC [clus: Cluster] = { textData: TextData; IF clus.class.type#$Text THEN RETURN; textData _ NARROW[clus.data]; textData.rope _ Rope.Substr[base: textData.rope, len: Rope.Length[textData.rope]-1]; -- update text clus.class.boundBox[clus]; -- update bound box }; DigitBreak: IO.BreakProc = { RETURN[IF char IN ['0..'9] THEN break ELSE other]; }; FontParamsFromFontString: PUBLIC PROC [fontStyle: DisplayStyle, fontString: Rope.ROPE] RETURNS [fontPrefix: ATOM, fontFamily: ATOM, fontFace: NodeStyle.FontFace, fontSize: REAL] = { <> GetNumber: PROC [s: IO.STREAM] RETURNS [x: REAL _ 0.0] = { c: CHAR; UNTIL (c _ IO.GetChar[s ! IO.EndOfStream => {c _ 'A; CONTINUE;}; ]) NOT IN ['0..'9] DO x _ x*10+(c-'0); ENDLOOP; }; familyName, face: Rope.ROPE; bold, italic: BOOL _ FALSE; fontStream: IO.STREAM _ IO.RIS[fontString]; [familyName, ----] _ IO.GetTokenRope[stream: fontStream, breakProc: DigitBreak ! IO.EndOfStream => SIGNAL FontStringError]; fontSize _ GetNumber[fontStream ! IO.EndOfStream => SIGNAL FontStringError]; <> [face, ----] _ IO.GetTokenRope[stream: fontStream, breakProc: IO.TokenProc ! IO.EndOfStream => {face _ ""; CONTINUE; };]; fontPrefix _ Atom.MakeAtom[IF fontStyle IN [print..wysiwyg] THEN "xerox/pressfonts/" ELSE "xerox/tiogafonts/"]; fontFamily _ Atom.MakeAtom[familyName]; SELECT Rope.Length[face] FROM 0 => NULL; 1 => bold _ Rope.Fetch[base: face, index: 0]='B; 2 => { bold _ Rope.Fetch[base: face, index: 0]='B; italic _ Rope.Fetch[base: face, index: 1]='I; }; ENDCASE => NULL; fontFace _ SELECT TRUE FROM bold AND italic => BoldItalic, bold => Bold, italic => Italic, ENDCASE => Regular; }; UpdateFontInfo: PRIVATE PROC [clus: Cluster, displayStyle: DisplayStyle] = { textData: TextData; font: Imager.Font; fontPrefix, fontFamily: ATOM; fontFace: NodeStyle.FontFace; fontSize: REAL _ 0.0; IF clus.class.type#$Text THEN ERROR; -- shouldn't happen textData _ NARROW[clus.data]; IF textData.fontString=NIL THEN ERROR; -- shouldn't happen IF displayStyle= textData.trueFontStyle AND Rope.Equal[textData.fontString, textData.trueFontString, FALSE] THEN { textData.fontStyle _ displayStyle; clus.class.boundBox[clus]; -- update bound box textData.validFontInfo _ TRUE; -- last thing to set RETURN; }; [fontPrefix, fontFamily, fontFace, fontSize] _ FontParamsFromFontString[displayStyle, textData.fontString ! FontStringError => GOTO Abort; ]; font _ NodeStyleFont.FontFromStyleParams[prefix: fontPrefix, family: fontFamily, face: fontFace, size: fontSize, alphabets: CapsAndLower]; IF Rope.Find[s1: font.name, s2: Atom.GetPName[fontFamily], case: FALSE]=-1 THEN { <> font _ NodeStyleFont.FontFromStyleParams[prefix: Atom.MakeAtom["xerox/pressfonts/"], family: fontFamily, face: fontFace, size: fontSize, alphabets: CapsAndLower]; IF Rope.Find[s1: font.name, s2: Atom.GetPName[fontFamily], case: FALSE]=-1 THEN GOTO Abort; }; textData.fontStyle _ textData.trueFontStyle _ displayStyle; textData.trueFontString _ textData.fontString; textData.fontFamily _ fontFamily; textData.fontFace _ fontFace; textData.fontSize _ fontSize; textData.font _ font; clus.class.boundBox[clus]; -- update bound box textData.validFontInfo _ TRUE; -- last thing to set EXITS Abort => GGError.AppendHerald["Gargoyle Font not Found", TRUE]; }; <> <> <> <> <> <> <> <> <> <> <<};>> <<>> SetTextFont: PUBLIC PROC [clus: Cluster, fontStyle: DisplayStyle, fontString: Rope.ROPE] = { <> textData: TextData; IF clus.class.type#$Text OR fontString=NIL THEN RETURN; textData _ NARROW[clus.data]; textData.validFontInfo _ FALSE; textData.fontStyle _ fontStyle; textData.fontString _ fontString; <> UpdateFontInfo[clus, fontStyle]; }; GetTextFont: PUBLIC PROC [clus: Cluster] RETURNS [font: ImagerFont.Font, fontString: Rope.ROPE] = { textData: TextData; IF clus.class.type#$Text THEN RETURN[NIL, NIL]; textData _ NARROW[clus.data]; RETURN[textData.font, textData.trueFontString]; }; SetTextColor: PUBLIC PROC [clus: Cluster, color: Imager.Color, colorName: Rope.ROPE] = { textData: TextData; IF clus.class.type#$Text THEN RETURN; textData _ NARROW[clus.data]; textData.color _ color; textData.colorName _ colorName; }; GetTextColor: PUBLIC PROC [clus: Cluster] RETURNS [color: Imager.Color, colorName: Rope.ROPE] = { textData: TextData; IF clus.class.type#$Text THEN RETURN[NIL, NIL]; textData _ NARROW[clus.data]; RETURN[textData.color, textData.colorName]; }; TextBoundBox: PROC [cluster: Cluster] = { <> textData: TextData _ NARROW[cluster.data]; extents: ImagerFont.Extents; halfCP: REAL = GGModelTypes.halfJointSize + 1; extents _ ImagerFont.RopeBoundingBox[textData.font, textData.rope]; <> GGBoundBox.UpdateBoundBox[cluster.boundBox, textData.worldPt[1] - extents.leftExtent - halfCP, textData.worldPt[2] - extents.descent - halfCP, textData.worldPt[1] + extents.rightExtent + halfCP, textData.worldPt[2] + extents.ascent + halfCP]; GGBoundBox.UpdateBoundBox[textData.feedbackBox, textData.worldPt[1] - extents.leftExtent, textData.worldPt[2] - extents.descent, textData.worldPt[1] + extents.rightExtent, textData.worldPt[2] + extents.ascent]; }; TextCopy: PROC [cluster: Cluster] RETURNS [copy: Cluster] = { <> textData: TextData _ NARROW[cluster.data]; copy _ MakeTextCluster[textData.rope, textData.fontStyle, textData.fontString, textData.colorName, textData.worldPt]; <> copy.parent _ cluster.parent; copy.children _ cluster.children; }; TextDraw: PROC [cluster: Cluster, dc: Imager.Context, camera: CameraData] = { <> textData: TextData _ NARROW[cluster.data]; IF NOT textData.validFontInfo THEN UpdateFontInfo[cluster, camera.displayStyle]; Imager.SetFont[dc, textData.font]; Imager.SetXY[dc, [textData.worldPt[1], textData.worldPt[2]]]; Imager.ShowRope[dc, textData.rope]; <> <> }; <> <> <> <> <> <> <> <<};>> <<>> TextDrawTransform: PROC [cluster: Cluster, parts: ClusterParts, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = { <> <> textData: TextData _ NARROW[cluster.data]; tempPoint: Point _ GGTransform.Transform[transform, textData.worldPt]; IF NOT textData.validFontInfo THEN UpdateFontInfo[cluster, camera.displayStyle]; Imager.SetFont[dc, textData.font]; Imager.SetXY[dc, [ tempPoint[1], tempPoint[2] ] ]; Imager.ShowRope[dc, textData.rope]; }; TextDrawSelectionFeedback: PROC [cluster: Cluster, parts: ClusterParts, dc: Imager.Context, camera: CameraData, quick: BOOL _ FALSE] = { <> textData: TextData _ NARROW[cluster.data]; IF NOT textData.validFontInfo THEN UpdateFontInfo[cluster, camera.displayStyle]; GGBoundBox.DrawBoundBox[dc, textData.feedbackBox]; <> }; TextTransform: PROC [cluster: Cluster, parts: ClusterParts, transform: ImagerTransformation.Transformation] = { <> <> textData: TextData _ NARROW[cluster.data]; textData.worldPt _ GGTransform.Transform[transform, textData.worldPt]; TextBoundBox[cluster]; }; TextEmptyParts: PROC [cluster: Cluster, parts: ClusterParts] RETURNS [BOOL] = { RETURN[TRUE] }; TextNewParts: PROC [cluster: Cluster, mode: SelectMode] RETURNS [parts: ClusterParts] = { RETURN[NIL] }; TextAddParts: PROC [cluster: Cluster, parts: ClusterParts, mode: ExtendMode] RETURNS [newParts: ClusterParts] = { RETURN[NIL] }; TextRemoveParts: PROC [cluster: Cluster, parts: ClusterParts, mode: ExtendMode] RETURNS [newParts: ClusterParts] = { RETURN[NIL] }; TextClosestPoint: PROC [cluster: Cluster, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = { <> <> textData: TextData _ NARROW[cluster.data]; <<[bestDist, ----, bestPoint] _ GGBoundBox.NearestPoint[textData.feedbackBox, testPoint];>> <> [bestDist, ----, bestPoint, success] _ GGBoundBox.NearestPoint[textData.feedbackBox, testPoint]; }; TextClosestSegment: PROC [cluster: Cluster, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = { <> <> textData: TextData _ NARROW[cluster.data]; textHitData: TextHitData _ NARROW[cluster.hitData]; [bestDist, textHitData.bestSeg, bestPoint, success] _ GGBoundBox.NearestSegment[textData.feedbackBox, testPoint, tolerance]; }; TextFileout: PROC [cluster: Cluster, f: IO.STREAM] = { <> <> textData: TextData _ NARROW[cluster.data]; f.PutF["\"%g\" %g %g", [rope[textData.rope]], [rope[textData.fontString]], [real[textData.fontSize]] ]; f.PutF[" %g ", [rope[textData.colorName]] ]; GGParseOut.WritePoint[f, textData.worldPt]; }; TextFilein: PROC [f: IO.STREAM, version: REAL] RETURNS [clus: Cluster] = { <> <> 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.RopeFromInt[Real.FixI[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 ]; }; rope, fontName, colorName: Rope.ROPE; fontSize: REAL; point: Point; <> IF version > 8601.22 THEN { rope _ f.GetRopeLiteral[]; } ELSE { GGParseIn.ReadBlankAndRope[f, "("]; rope _ GGParseIn.ReadBlankAndWord[f]; GGParseIn.ReadBlankAndRope[f, ")"]; }; <> IF version <= 8601.06 THEN { -- no font name at all fontName _ "Helvetica10"; fontSize _ 10.0; colorName _ "black"; } ELSE IF version <= 8601.27 THEN { -- a simple name like "Helvetica" or "Gacha" fontName _ GGParseIn.ReadBlankAndWord[f]; fontName _ Rope.Concat[fontName, "10"]; fontSize _ 10.0; colorName _ "black"; } ELSE IF version <= 8605.12 THEN { -- a complex name like "xerox/pressfonts/Helvetica-BIR" fontName _ GGParseIn.ReadBlankAndWord[f]; fontSize _ GGParseIn.ReadBlankAndReal[f]; colorName _ GGParseIn.ReadBlankAndWord[f]; fontName _ UnpackComplexFontName[fontName, fontSize]; } ELSE { -- a mixed mode name like Helvetica7BI or TimesRoman12 fontName _ GGParseIn.ReadBlankAndWord[f]; fontSize _ GGParseIn.ReadBlankAndReal[f]; colorName _ GGParseIn.ReadBlankAndWord[f]; }; GGParseIn.ReadBlank[f]; point _ GGParseIn.ReadPoint[f]; clus _ MakeTextCluster[rope, print, fontName, colorName, point]; }; <> BuildIPClusterClass: PROC [] RETURNS [class: ClusterClass] = { class _ NEW[ClusterClassObj _ [ type: $IP, boundBox: IPBoundBox, <> draw: IPDraw, drawTransform: IPDrawTransform, <> <> drawSelectionFeedback: IPDrawSelectionFeedback, transform: IPTransform, newParts: IPEndSelectProc, addParts: IPEndExtendProc, closestPoint: IPClosestPoint, closestPointAndTangent: NIL, closestSegment: IPClosestSegment, fileout: IPFileout, filein: IPFilein ]]; }; MakeIPCluster: PUBLIC PROC [fileName: Rope.ROPE, worldPt: Point] RETURNS [clus: Cluster] = { ipData: IPData; feedbackBox: BoundBox; feedbackBox _ GGBoundBox.CreateBoundBox[0,0,0,0]; -- gets real values later in this proc. ipData _ NEW[IPDataObj _ [fileName, worldPt, feedbackBox]]; clus _ NEW[ClusterObj _ [ class: FetchClusterClass[$IP], data: ipData, children: NIL, parent: NIL, selectedInFull: [FALSE, FALSE, FALSE, FALSE], boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets set next line hitData: NEW[IPHitDataObj _ [] ] ]]; clus.class.boundBox[clus]; }; IPBoundBox: PROC [cluster: Cluster] = { <> ipData: IPData _ NARROW[cluster.data]; rect: ImagerTransformation.Rectangle; halfCP: REAL = GGModelTypes.halfJointSize + 1; rect.x _ 0.0; rect.y _ 0.0; rect.w _ 300.0; rect.h _ 300.0; GGBoundBox.UpdateBoundBox[cluster.boundBox, ipData.worldPt[1] + rect.x - halfCP, ipData.worldPt[2] + rect.y - halfCP, ipData.worldPt[1] + rect.w + halfCP, ipData.worldPt[2] + rect.h + halfCP]; GGBoundBox.UpdateBoundBox[ipData.feedbackBox, ipData.worldPt[1] + rect.x, ipData.worldPt[2] + rect.y, ipData.worldPt[1] + rect.w, ipData.worldPt[2] + rect.h]; }; IPDraw: PROC [cluster: Cluster, dc: Imager.Context, camera: CameraData] = { <> ipData: IPData _ NARROW[cluster.data]; <> }; IPDrawTransform: PROC [cluster: Cluster, parts: ClusterParts, dc: Imager.Context, camera: CameraData, transform: ImagerTransformation.Transformation] = { <> ipData: IPData _ NARROW[cluster.data]; tempPoint: Point _ GGTransform.Transform[transform, ipData.worldPt]; }; <> <> <<};>> <<>> <> <> <<};>> IPDrawSelectionFeedback: PROC [cluster: Cluster, parts: ClusterParts, dc: Imager.Context, camera: CameraData, quick: BOOL _ FALSE] = { <> ipData: IPData _ NARROW[cluster.data]; GGBoundBox.DrawBoundBox[dc, ipData.feedbackBox]; }; IPTransform: PROC [cluster: Cluster, parts: ClusterParts, transform: ImagerTransformation.Transformation] = { <> <> ipData: IPData _ NARROW[cluster.data]; ipData.worldPt _ GGTransform.Transform[transform, ipData.worldPt]; IPBoundBox[cluster]; }; IPEndSelectProc: GGModelTypes.ClusterNewPartsProc = { <> }; IPEndExtendProc: GGModelTypes.ClusterAddPartsProc = { <> }; IPClosestPoint: PROC [cluster: Cluster, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = { <> <> ipData: IPData _ NARROW[cluster.data]; <<[bestDist, ----, bestPoint] _ GGBoundBox.NearestPoint[ipData.feedbackBox, testPoint];>> <> [bestDist, ----, bestPoint, success] _ GGBoundBox.NearestPoint[ipData.feedbackBox, testPoint]; }; IPClosestSegment: PROC [cluster: Cluster, testPoint: Point, tolerance: REAL] RETURNS [bestDist: REAL, bestPoint: Point, success: BOOL] = { <> <> ipData: IPData _ NARROW[cluster.data]; ipHitData: IPHitData _ NARROW[cluster.hitData]; [bestDist, ipHitData.bestSeg, bestPoint, success] _ GGBoundBox.NearestSegment[ipData.feedbackBox, testPoint, tolerance]; }; IPFileout: PROC [cluster: Cluster, f: IO.STREAM] = { <> <> ipData: IPData _ NARROW[cluster.data]; f.PutF["\"%g\"", [rope[ipData.file]]]; GGParseOut.WritePoint[f, ipData.worldPt]; }; IPFilein: PROC [f: IO.STREAM, version: REAL] RETURNS [clus: Cluster] = { <> <> fileName: Rope.ROPE; point: Point; fileName _ f.GetRopeLiteral[]; GGParseIn.ReadBlank[f]; point _ GGParseIn.ReadPoint[f]; clus _ MakeIPCluster[fileName, point]; }; <> <> clusterClasses: LIST OF ClusterClassDef; Init: PRIVATE PROC [] = { textDef: ClusterClassDef _ NEW[ClusterClassDefObj _ [type: $Text, class: BuildTextClusterClass[]]]; ipDef: ClusterClassDef _ NEW[ClusterClassDefObj _ [type: $IP, class: BuildIPClusterClass[]]]; boxDef: ClusterClassDef _ NEW[ClusterClassDefObj _ [type: $Box, class: GGBoxCluster.BuildBoxClusterClass[]]]; clusterClasses _ LIST[boxDef, ipDef, textDef]; <> <> <> <> }; Init[]; END. <> <> <<>>