GGSliceImplB.mesa
Copyright c 1986 by Xerox Corporation. All rights reserved.
Last edited by Pier on May 11, 1987 7:00:52 pm PDT
Last edited by Bier on April 7, 1987 0:11:59 am PDT
Contents: Implements various slice classes in Gargoyle.
DIRECTORY
AtomButtonsTypes, Convert, Feedback, FileNames, FS, GGBasicTypes, GGBoundBox, GGFont, GGFromImager, GGInterfaceTypes, GGModelTypes, GGScene, GGParseIn, GGParseOut, GGShapes, GGSlice, GGTransform, GGUtility, Imager, ImagerFont, ImagerInterpress, ImagerMemory, ImagerTransformation, Interpress, IO, Lines2d, NodeStyle, RealFns, RefText, Rope, Vectors2d, ViewerClasses;
GGSliceImplB: CEDAR PROGRAM
IMPORTS Convert, Feedback, FileNames, FS, GGBoundBox, GGFont, GGFromImager, GGScene, GGParseIn, GGParseOut, GGShapes, GGSlice, GGTransform, GGUtility, Imager, ImagerFont, ImagerInterpress, ImagerMemory, ImagerTransformation, Interpress, IO, Lines2d, RealFns, RefText, Rope, Vectors2d
EXPORTS GGSlice = BEGIN
[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
BoundBox: TYPE = GGModelTypes.BoundBox;
CameraData: TYPE = GGInterfaceTypes.CameraData;
Color: TYPE = Imager.Color;
DefaultData: TYPE = GGModelTypes.DefaultData;
ExtendMode: TYPE = GGModelTypes.ExtendMode;
FeedbackData: TYPE = AtomButtonsTypes.FeedbackData;
GGData: TYPE = GGInterfaceTypes.GGData;
GargoyleDataObj: TYPE = GGInterfaceTypes.GargoyleDataObj;
Point: TYPE = GGBasicTypes.Point;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj;
Scene: TYPE = GGModelTypes.Scene;
SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData;
SelectionClass: TYPE = GGInterfaceTypes.SelectionClass;
SelectMode: TYPE = GGModelTypes.SelectMode;
Slice: TYPE = GGModelTypes.Slice;
SliceClass: TYPE = GGModelTypes.SliceClass;
SliceClassObj: TYPE = GGModelTypes.SliceClassObj;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj;
SliceObj: TYPE = GGModelTypes.SliceObj;
SliceParts: TYPE = GGModelTypes.SliceParts;
Vector: TYPE = GGBasicTypes.Vector;
Viewer: TYPE = ViewerClasses.Viewer;
FontData: TYPE = GGFont.FontData;
textMaxPoints: INTEGER = 12;
textMaxEdges: INTEGER = 7;
TextPoints: TYPE = REF TextPointsData;
TextPointsData: TYPE = ARRAY [0..textMaxPoints) OF Point; -- see picture
TextEdgeArray: TYPE = PACKED ARRAY [0..textMaxEdges) OF BOOL; -- left, top, right, bottom, baseline, centerline.
TextPointArray: TYPE = PACKED ARRAY [0..textMaxPoints) OF BOOL; -- see picture. Used in text parts data.
textPointRopes: ARRAY [0..textMaxPoints) OF Rope.ROPE = [
"lower left point", "base left point", "center left point", "upper left point",
"lower mid point", "base mid point", "center mid point", "upper mid point",
"lower right point", "base right point", "center right point", "upper right point"
];
textEdgeRopes: ARRAY [0..textMaxEdges) OF Rope.ROPE = [
"left edge", "top edge", "right edge", "bottom edge",
"baseline", "horizontal center line", "vertical center line"
];
TextData: TYPE = REF TextDataObj;
TextDataObj: TYPE = RECORD [
box: BoundBox, -- not a bounding box but part of the text representation
feedbackBox: BoundBox, -- both a bounding box and part of the text representation
points: TextPoints, -- cache for "joints"
rope: Rope.ROPE,
color: Imager.Color,
fontData: FontData,
trueFont: ImagerFont.Font,
screenFont: ImagerFont.Font,
inverse: ImagerTransformation.Transformation, -- inverse of transform
inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s
amplifySpace: REAL ← 1.0, -- because the Imager allows space correction (see Imager.mesa)
dropShadowOn: BOOLFALSE,
dropShadowOffset: Vector,
shadowColor: Imager.Color
];
TextParts: TYPE = REF TextPartsObj;
TextPartsObj: TYPE = RECORD [
points: TextPointArray, -- which corners of text box are selected.
edges: TextEdgeArray, -- which edges of text box are selected.
includeText: BOOL -- FALSE => corners and edges only; no text
];
TextHitData: TYPE = REF TextHitDataObj;
TextHitDataObj: TYPE = RECORD [
Filled in by BoxClosestPoint, BoxClosestSegment, in same order as TextPointArray and TextEdgeArray. -1 means not hit
point: [-1..textMaxPoints), -- which point of box is hit.
edge: [-1..textMaxEdges), -- which edge of box is hit.
hitPoint: Point
];
FontNameError: PUBLIC SIGNAL = CODE;
Procs peculiar to text
BuildTextSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = {
OPEN GGSlice;
class ← NEW[SliceClassObj ← [
type: $Text,
Fundamentals
getBoundBox: TextBoundBox,
getTightBox: TextTightBox,
copy: TextCopy,
Drawing
drawParts: TextDrawParts,
drawTransform: TextDrawTransform,
drawSelectionFeedback: TextDrawSelectionFeedback,
drawAttractorFeedback: NoOpDrawAttractorFeedback,
Transforming
transform: TextTransform,
Textual Description
describe: TextDescribe,
describeHit: TextDescribeHit,
fileout: TextFileout,
filein: TextFilein,
Parts
isEmptyParts: TextIsEmptyParts,
isCompleteParts: TextIsCompleteParts,
newParts: TextNewParts,
unionParts: TextUnionParts,
differenceParts: TextDiffParts,
movingParts: TextMovingParts,
augmentParts: TextAugmentParts,
setSelectedFields: NoOpSetSelectedFields,
Part Generators
pointsInDescriptor: TextPointsInDescriptor,
pointPairsInDescriptor: TextPointPairsInDescriptor,
segmentsInDescriptor: NoOpSegmentsInDescriptor,
walkSegments: NoOpWalkSegments,
nextPoint: TextNextPoint,
nextPointPair: TextNextPointPair,
nextSegment: NoOpNextSegment,
Hit Testing
closestPoint: TextClosestPoint,
closestJointToHitData: NoOpClosestJointToHitData,
closestPointAndTangent: NoOpClosestPointAndTangent,
closestSegment: TextClosestSegment,
lineIntersection: NoOpLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: NoOpHitDataAsSimpleCurve,
Style
setDefaults: TextSetDefaults,
setStrokeWidth: NoOpSetStrokeWidth,
getStrokeWidth: NoOpGetStrokeWidth,
setStrokeEnd: NoOpSetStrokeEnd,
getStrokeEnd: NoOpGetStrokeEnd,
setStrokeJoint: NoOpSetStrokeJoint,
getStrokeJoint: NoOpGetStrokeJoint,
setStrokeColor: TextSetStrokeColor,
getStrokeColor: TextGetStrokeColor,
setFillColor: TextSetFillColor,
getFillColor: TextGetFillColor,
setArrows: NoOpSetArrows,
getArrows: NoOpGetArrows,
setDashed: NoOpSetDashed,
getDashed: NoOpGetDashed
]];
};
MakeTextSlice: PUBLIC PROC [text: Rope.ROPE, amplifySpace: REAL, dropShadowsOn: BOOLFALSE, dropShadowOffset: Vector ← [0.0, 0.0], shadowColor: Imager.Color ← Imager.black] RETURNS [slice: Slice] = {
textData: TextData ← NEW[TextDataObj ← [
box: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox
feedbackBox: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox
inverseScale: [-1.0, -1.0], -- dummy vector
amplifySpace: amplifySpace,
points: NEW[TextPointsData],
fontData: GGFont.CreateFontData[], -- local storage
color: Imager.black,
rope: text,
dropShadowOn: dropShadowsOn,
dropShadowOffset: dropShadowOffset,
shadowColor: shadowColor
]];
other font related values must be filled in by client call to SetTextFont
slice ← NEW[SliceObj ← [
class: GGSlice.FetchSliceClass[$Text],
data: textData,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE],
boundBox: GGBoundBox.CreateBoundBox[0,0,0,0], -- gets filled in later
onOverlay: FALSE
]];
};
IsWhitespace: PUBLIC PROC [slice: Slice] RETURNS [BOOL] = {
IF slice.class.type#$Text THEN RETURN[FALSE] ELSE {
rope: Rope.ROPENARROW[slice.data, TextData].rope;
RETURN[Rope.Length[rope]=IO.SkipWhitespace[IO.RIS[rope]] ];
};
};
GetText: PUBLIC PROC [slice: Slice] RETURNS [text: Rope.ROPE] = {
RETURN[IF slice.class.type#$Text THEN NIL ELSE NARROW[slice.data, TextData].rope];
};
AppendText: PUBLIC PROC [slice: Slice, text: Rope.ROPE] = {
textData: TextData;
IF slice.class.type#$Text THEN RETURN;
textData ← NARROW[slice.data];
textData.rope ← Rope.Concat[textData.rope, text]; -- update text
TextSetBoundBox[slice];
};
BackspaceText: PUBLIC PROC [slice: Slice] = {
textData: TextData;
IF slice.class.type#$Text THEN RETURN;
textData ← NARROW[slice.data];
textData.rope ← Rope.Substr[base: textData.rope, len: Rope.Length[textData.rope]-1]; -- update text
TextSetBoundBox[slice];
};
DigitBreak: IO.BreakProc = {
RETURN[IF char IN ['0..'9] THEN break ELSE other];
};
TFontParamsFromFontName: PROC [fontName: Rope.ROPE] RETURNS [fontFamily: Rope.ROPE, fontFace: Rope.ROPE, fontSize: REAL] = {
A fontName is a font designation with family, size, and face, like Helvetica23BI or TimesRoman6.95 (real font sizes are OK)
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.STREAMIO.RIS[fontName];
[fontFamily, ----] ← IO.GetTokenRope[stream: fontStream, breakProc: DigitBreak ! IO.EndOfStream => SIGNAL FontNameError];
fontSize ← GetNumber[fontStream ! IO.EndOfStream => SIGNAL FontNameError];
can't user IO.GetInt because a string like Helvetica9BI causes it to fail!
[face, ----] ← IO.GetTokenRope[stream: fontStream, breakProc: IO.TokenProc ! IO.EndOfStream => {face ← NIL; CONTINUE; };];
IF face#NIL THEN fontFace ← Rope.Concat["-", face];
};
SetTextFont: PUBLIC PROC [slice: Slice, fontData: FontData, feedback: FeedbackData] RETURNS [ success: BOOLTRUE] = {
RETURN[SetTextFontAux[slice, fontData, feedback, FALSE]];
};
SetTextFontAndTransform: PUBLIC PROC [slice: Slice, fontData: FontData, feedback: FeedbackData] RETURNS [ success: BOOLTRUE] = {
RETURN[SetTextFontAux[slice, fontData, feedback, TRUE]];
};
SetTextFontAux: PROC [slice: Slice, fontData: FontData, feedback: FeedbackData, andTransform: BOOLFALSE] RETURNS [ success: BOOLTRUE] = {
The text slice's font will be set using fontData. IF andTransform, fontData.transform will be used (unmodified) ELSE the translation component of the existing slice transform will be used. If fontData.transform=NIL, the scale component of the new transform is set to fontData.scale and the rotation component to 0.
transform, oldTransform: ImagerTransformation.Transformation;
IF slice.class.type # $Text THEN RETURN[FALSE];
BEGIN
textData: TextData ← NARROW[slice.data];
IF slice.class.type#$Text THEN RETURN;
IF fontData.literal=NIL THEN GOTO Abort;
oldTransform ← textData.fontData.transform;
transform ← IF fontData.transform=NIL THEN ImagerTransformation.PreScale[m: ImagerTransformation.Translate[ImagerTransformation.Factor[m: textData.fontData.transform].t], s: fontData.scale] ELSE ImagerTransformation.Copy[fontData.transform];
transform ← IF andTransform THEN ImagerTransformation.Copy[fontData.transform] ELSE IF fontData.transform=NIL THEN ImagerTransformation.PreScale[m: ImagerTransformation.Translate[ImagerTransformation.Factor[m: oldTransform].t], s: fontData.scale] ELSE ImagerTransformation.TranslateTo[fontData.transform, ImagerTransformation.Factor[oldTransform].t];
fontData.substituted ← FALSE;
textData.trueFont ← ImagerFont.Find[fontData.literal, substituteWithWarning !
Imager.Error => GOTO Abort;
Imager.Warning => {
IF fontData.substituteOK THEN {
fontData.substituted ← TRUE;
RESUME;
}
ELSE GOTO NotFound;
};
];
IF fontData.substituted THEN Feedback.PutF[feedback, oneLiner, "%g substituted for %g", [rope[ImagerFont.Name[textData.trueFont]]], [rope[fontData.literal]] ];
textData.trueFont ← ImagerFont.Scale[textData.trueFont, IF fontData.substituted THEN 1.0 ELSE 1.0/fontData.storedSize];
textData.screenFont ← GGFont.AlternateFont[fontData, textData.trueFont, $visible];
textData.fontData^ ← fontData^; -- don't do this assignment until FindFont succeeds !
textData.fontData.transform ← transform; -- don't do this assignment until FindFont succeeds !
textData.inverse ← ImagerTransformation.Invert[transform];
textData.inverseScale ← ImagerTransformation.Factor[textData.inverse].s;
TextSetBoundBox[slice];
EXITS
Abort => {
Feedback.PutF[feedback, oneLiner, "Fatal Imager Error during ImagerFont.Find of %g", [rope[fontData.literal]] ];
success ← FALSE;
};
NotFound => {
scratch: IO.STREAMIO.ROS[];
IF transform#NIL THEN GGParseOut.WriteFactoredTransformationVEC[scratch, transform];
Feedback.PutF[feedback, oneLiner, "No Such Font: %g %g %g %g", [rope[fontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]] ];
success ← FALSE;
};
END;
};
GetFontData: PUBLIC PROC [slice: Slice] RETURNS [fontData: FontData] = {
textData: TextData;
IF slice.class.type#$Text THEN RETURN[NIL];
textData ← NARROW[slice.data];
RETURN[textData.fontData];
};
GetFontDataRope: PUBLIC PROC [slice: Slice] RETURNS [Rope.ROPE] = {
textData: TextData;
fontData: FontData;
scratch: IO.STREAMIO.ROS[];
IF slice.class.type#$Text THEN RETURN[NIL];
textData ← NARROW[slice.data];
fontData ← textData.fontData;
GGParseOut.WriteFactoredTransformationVEC[scratch, fontData.transform];
IF fontData.comfortable THEN RETURN[IO.PutFLR["%g%g %g %g %g %g", LIST[ [rope[textData.fontData.prefix]], [rope[textData.fontData.userFSF]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]], [rope[IF fontData.substituted THEN "SUBSTITUTED" ELSE ""]] ]] ]
ELSE RETURN[IO.PutFR["%g %g %g %g %g", [rope[fontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]], [rope[IF fontData.substituted THEN "SUBSTITUTED" ELSE ""]] ] ];
};
GetFontLiteralDataRope: PUBLIC PROC [slice: Slice] RETURNS [Rope.ROPE] = {
textData: TextData;
fontData: FontData;
scratch: IO.STREAMIO.ROS[];
IF slice.class.type#$Text THEN RETURN[NIL];
textData ← NARROW[slice.data];
fontData ← textData.fontData;
GGParseOut.WriteFactoredTransformationVEC[scratch, fontData.transform];
RETURN[IO.PutFR["%g %g %g %g %g", [rope[fontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]], [rope[IF fontData.substituted THEN "SUBSTITUTED" ELSE ""]] ] ];
};
SetTextAmplifySpace: PUBLIC PROC [slice: Slice, amplifySpace: REAL, feedback: FeedbackData] = {
textData: TextData;
textData ← NARROW[slice.data];
textData.amplifySpace ← amplifySpace;
TextSetBoundBox[slice];
};
GetTextAmplifySpace: PUBLIC PROC [slice: Slice] RETURNS [amplifySpace: REAL] = {
textData: TextData;
textData ← NARROW[slice.data];
RETURN[textData.amplifySpace];
};
DropShadowOn: PUBLIC PROC [slice: Slice, offset: Vector] = {
textData: TextData ← NARROW[slice.data];
textData.dropShadowOn ← TRUE;
textData.dropShadowOffset ← offset;
};
DropShadowOff: PUBLIC PROC [slice: Slice] = {
textData: TextData ← NARROW[slice.data];
textData.dropShadowOn ← FALSE
};
UnpackComplexFontName: PROC [fontName: Rope.ROPE, fontSize: REAL] RETURNS [new: Rope.ROPE] = {
Takes a complex name like "xerox/pressfonts/Helvetica-MIR" and a font size and returns a simple name like Helvetica10I
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
];
};
Fundamental
TextBoundBox: PROC [slice: Slice, parts: SliceParts ← NIL] RETURNS [box: BoundBox]= {
GGModelTypes.SliceBoundBoxProc
RETURN[slice.boundBox];
};
TextTightBox: PROC [slice: Slice, parts: SliceParts ← NIL] RETURNS [box: BoundBox] = {
GGModelTypes.SliceTightBoxProc
textData: TextData ← NARROW[slice.data];
RETURN[textData.feedbackBox];
};
The following 7 values are of interest:
left: <the origin of the first character> = 0.0.
right: <the right escapement of the last character> = Width[string].
middle: (left + right) / 2.
top: <the ascender extent of the string "acbdefghijklmnopqrstuvwxyzACBDEFGHIJKLMNOPQRSTUVWXYZ">
base: 0.0
bottom: <the descender extent of the string "acbdefghijklmnopqrstuvwxyzACBDEFGHIJKLMNOPQRSTUVWXYZ">
center: (top + bottom) / 2
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: INTINT.LAST] RETURNS [ImagerFont.VEC] ~ {
string: ImagerFont.XStringProc ~ { ImagerFont.MapRope[rope, start, len, charAction] };
RETURN[StringEscapement[font, string, amplifySpace]];
};
TextSetBoundBox: PROC [slice: Slice] = {
textData: TextData ← NARROW[slice.data];
fontData: FontData ← textData.fontData;
fontExtents: ImagerFont.Extents;
halfCP: REAL = GGModelTypes.halfJointSize + 1;
left, right, middle, top, base, bottom, center: REAL;
ASSERT: extents all positive—subtract left and descent, add right and ascent
Bounding Boxes are calculated from screen fonts (which are larger).
left ← 0.0;
right ← RopeEscapement[textData.trueFont, textData.rope, textData.amplifySpace].x;
middle ← (left + right)/2.0;
fontExtents ← ImagerFont.RopeBoundingBox[textData.trueFont, fontExtentRope];
top ← fontExtents.ascent;
base ← 0.0;
bottom ← -fontExtents.descent;
center ← (top + bottom)/2.0;
GGBoundBox.UpdateBoundBox[textData.box, left, bottom, right, top];
textData.box ← GGBoundBox.BoundBoxOfBoundBox[textData.box, fontData.transform];
GGBoundBox.EnlargeByOffset[textData.box, halfCP];
IF textData.dropShadowOn THEN GGBoundBox.EnlargeByVector[textData.box, textData.dropShadowOffset];
IF textData.dropShadowOn THEN GGBoundBox.EnlargeByVector[textData.box, [x: textData.dropShadowOffset.x*textData.inverseScale.x, y: textData.dropShadowOffset.y*textData.inverseScale.y] ];
slice.boundBox ← GGBoundBox.CopyBoundBox[textData.box];
feedbackBoxes are used for hit testing and are calculated from the print font.
left ← 0.0;
right ← RopeEscapement[textData.trueFont, textData.rope, textData.amplifySpace].x;
middle ← (left + right)/2.0;
fontExtents ← ImagerFont.RopeBoundingBox[textData.trueFont, fontExtentRope];
top ← fontExtents.ascent;
base ← 0.0;
bottom ← -fontExtents.descent;
center ← (top + bottom)/2.0;
GGBoundBox.UpdateBoundBox[textData.feedbackBox, left, bottom, right, top];
textData.feedbackBox ← GGBoundBox.BoundBoxOfBoundBox[textData.feedbackBox, fontData.transform]; -- take into account the font transformation
textData.points[0] ← [left, bottom];
textData.points[1] ← [left, base];
textData.points[2] ← [left, center];
textData.points[3] ← [left, top];
textData.points[4] ← [middle, bottom];
textData.points[5] ← [middle, base];
textData.points[6] ← [middle, center];
textData.points[7] ← [middle, top];
textData.points[8] ← [right, bottom];
textData.points[9] ← [right, base];
textData.points[10] ← [right, center];
textData.points[11] ← [right, top];
};
TextCopy: PROC [slice: Slice] RETURNS [copy: Slice] = {
GGModelTypes.SliceCopyProc
copyData: TextData;
textData: TextData ← NARROW[slice.data];
copy ←MakeTextSlice[textData.rope, textData.amplifySpace, textData.dropShadowOn, textData.dropShadowOffset, textData.shadowColor];
copyData ← NARROW[copy.data];
IF NOT SetTextFontAndTransform[copy, GGFont.CopyFontData[textData.fontData, copyData.fontData], NIL] THEN ERROR;
copy.class.setFillColor[copy, textData.color];
};
Drawing
TextDrawParts: PROC [slice: Slice, parts: SliceParts ← NIL, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, quick: BOOL] = {
GGModelTypes.SliceDrawPartsProc
DoDrawText: PROC = {
Imager.ConcatT[dc, textData.fontData.transform];
IF textData.dropShadowOn THEN DrawDropShadow[dc, textData, camera];
DrawText[dc, textData, camera];
};
textData: TextData ← NARROW[slice.data];
textParts: TextParts ← NARROW[parts];
IF textParts = NIL OR textParts.includeText THEN Imager.DoSaveAll[dc, DoDrawText];
};
TextDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
DoDrawFeedback: PROC = {
feedbackBox: BoundBox ← textData.feedbackBox;
thisCPisHot, thisCPisSelected: BOOL;
pts: ARRAY [0..11] OF Point;
t: ImagerTransformation.Transformation ← fontData.transform;
FOR i: NAT IN [0..11] DO
pts[i] ← ImagerTransformation.Transform[t, textData.points[i]];
ENDLOOP;
Imager.SetColor[dc, Imager.black];
FOR point: INTEGER IN [0..textMaxPoints) DO
thisCPisHot ← hotTextParts#NIL AND hotTextParts.points[point];
thisCPisSelected ← normalTextParts#NIL AND normalTextParts.points[point];
IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, pts[point], hot];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[point], normal];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[point]];
ENDLOOP;
IF NOT quick AND normalTextParts#NIL THEN {
IF normalTextParts#NIL THEN {
Imager.SetStrokeJoint[dc, round];
Imager.SetStrokeWidth[dc, 1.0];
FOR edge: INTEGER IN [0..textMaxEdges) DO
IF normalTextParts.edges[edge] THEN
SELECT edge FROM
0 => Imager.MaskVector[dc, pts[0], pts[3] ];
1 => Imager.MaskVector[dc, pts[3], pts[11] ];
2 => Imager.MaskVector[dc, pts[11], pts[8] ];
3 => Imager.MaskVector[dc, pts[8], pts[0] ];
4 => Imager.MaskVector[dc, pts[1], pts[9] ];
5 => Imager.MaskVector[dc, pts[2], pts[10] ];
6 => Imager.MaskVector[dc, pts[4], pts[7] ];
ENDCASE => ERROR;
ENDLOOP;
};
};
normalTextParts, hotTextParts: TextParts;
textData: TextData ← NARROW[slice.data];
fontData: FontData ← textData.fontData;
IF caretIsMoving OR dragInProgress THEN RETURN;
IF selectedParts = NIL AND hotParts = NIL THEN RETURN;
normalTextParts ← NARROW[selectedParts];
hotTextParts ← NARROW[hotParts];
IF camera.quality#quality THEN Imager.DoSaveAll[dc, DoDrawFeedback];
};
DrawText: PROC [dc: Imager.Context, textData: TextData, camera: CameraData] = {
called with the dc transformation already set to object coordinates
Imager.SetFont[dc, IF camera.displayStyle=screen THEN textData.screenFont ELSE textData.trueFont];
Imager.SetColor[dc, IF textData.color=NIL THEN Imager.black ELSE textData.color];
Imager.SetXY[dc, [0.0, 0.0]]; -- dc must be transformed completely here
Imager.SetAmplifySpace[dc, textData.amplifySpace]; -- adjust spacing (for right justified text)
Imager.ShowRope[dc, textData.rope];
};
DrawDropShadow: PROC [dc: Imager.Context, textData: TextData, camera: CameraData] = {
called with the dc transformation already set to object coordinates
DoDrawDropShadow: PROC = {
tVec: Imager.VEC ← [x: textData.dropShadowOffset.x*textData.inverseScale.x, y: textData.dropShadowOffset.y*textData.inverseScale.y];
Imager.TranslateT[dc, textData.dropShadowOffset];
tVec: Imager.VEC ← [x: textData.dropShadowOffset.x*dropScale, y: textData.dropShadowOffset.y*dropScale];
Imager.TranslateT[dc, tVec];
Imager.SetFont[dc, IF camera.displayStyle=screen THEN textData.screenFont ELSE textData.trueFont];
Imager.SetColor[dc, IF textData.shadowColor=NIL THEN Imager.black ELSE textData.shadowColor];
Imager.SetXY[dc, [0.0, 0.0]]; -- dc must be transformed completely here
Imager.SetAmplifySpace[dc, textData.amplifySpace]; -- adjust spacing (for right justified text)
Imager.ShowRope[dc, textData.rope];
};
dropScale: REAL ← 0.1; -- results in one point drop shadow/10 points of text
Imager.DoSaveAll[dc, DoDrawDropShadow];
};
TextDrawTransform: PROC [sliceD: SliceDescriptor, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceDrawTransformProc
DoTextDrawTransform: PROC = {
Imager.ConcatT[dc, ImagerTransformation.Concat[textData.fontData.transform, transform]];
IF textData.dropShadowOn THEN DrawDropShadow[dc, textData, camera];
DrawText[dc, textData, camera];
};
textData: TextData ← NARROW[sliceD.slice.data];
Imager.DoSaveAll[dc, DoTextDrawTransform];
};
Transforming
TextTransform: PROC [sliceD: SliceDescriptor, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceTransformProc
textData: TextData ← NARROW[sliceD.slice.data];
fontData: FontData ← textData.fontData;
fontData.transform ← ImagerTransformation.Concat[fontData.transform, transform];
textData.inverse ← ImagerTransformation.Invert[fontData.transform];
textData.inverseScale ← ImagerTransformation.Factor[textData.inverse].s;
TextSetBoundBox[sliceD.slice];
};
Textual Description
TextDescribe: PROC [sliceD: SliceDescriptor] RETURNS [rope: Rope.ROPE] = {
GGModelTypes.SliceDescribeProc
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] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
textData: TextData ← NARROW[slice.data];
fontData: FontData ← textData.fontData;
GGParseOut.WriteBOOL[f, IF fontData.comfortable THEN TRUE ELSE FALSE];
IF fontData.comfortable THEN f.PutF[" \"%q\" %g%g ", [rope[textData.rope]], [rope[fontData.prefix]], [rope[fontData.userFSF]] ] ELSE f.PutF[" \"%q\" %g ", [rope[textData.rope]], [rope[fontData.literal]] ];
GGParseOut.WriteTransformation[f, fontData.transform];
IF NOT fontData.comfortable THEN f.PutF[" %g %g ", [real[fontData.storedSize]], [real[fontData.designSize]] ];
GGParseOut.WriteColor[f, textData.color];
f.PutChar[IO.SP];
IF textData.dropShadowOn THEN {
GGParseOut.WriteBOOL[f, TRUE];
f.PutChar[IO.SP];
GGParseOut.WritePoint[f, textData.dropShadowOffset];
f.PutChar[IO.SP];
GGParseOut.WriteColor[f, textData.shadowColor];
}
ELSE GGParseOut.WriteBOOL[f, FALSE];
f.PutF[" %g", [real[textData.amplifySpace]]];
};
TextFilein: PROC [f: IO.STREAM, version: REAL, feedback: FeedbackData] RETURNS [slice: Slice] = {
GGModelTypes.SliceFileinProc
Read a description of yourself from stream f.
text, fontName: Rope.ROPE;
prefix: Rope.ROPE ← "xerox/pressfonts/";
family: Rope.ROPE ← "Helvetica";
face: Rope.ROPE ← "";
fontData: GGFont.FontData;
fontDataComfortable: BOOLFALSE;
size: REAL ← 1.0;
preferredSize: REAL ← 1.0;
transform: ImagerTransformation.Transformation;
shadowColor: Imager.Color ← Imager.black;
textColor: Imager.Color ← Imager.black;
dropShadowOn, good: BOOLFALSE;
dropShadowOffset: Vector ← [0.0, 0.0];
amplifySpace: REAL ← 1.0;
Get the comfortable bit if it is there
IF version >= 8702.11 THEN {
fontDataComfortable ← GGParseIn.ReadBOOL[f, version].truth;
};
Get the text
IF version > 8601.22 THEN {
text ← f.GetRopeLiteral[];
}
ELSE {
GGParseIn.ReadBlankAndRope[f, "("];
text ← GGParseIn.ReadBlankAndWord[f];
GGParseIn.ReadBlankAndRope[f, ")"];
};
IF version < 8702.11 THEN { -- many old style formats
Get the prefix
IF version >= 8701.30 THEN {
prefix ← GGParseIn.ReadBlankAndWord[f];
}; -- ELSE uses default prefix
Get the family and face and size
IF version <= 8601.06 THEN { }-- use default family and face
ELSE IF version <= 8601.27 THEN { -- a simple name like "Helvetica" or "Gacha"
family ← GGParseIn.ReadBlankAndWord[f];
}
ELSE IF version <= 8605.12 THEN { -- a complex name like "xerox/pressfonts/Helvetica-BIR"
fontName ← GGParseIn.ReadBlankAndWord[f];
size ← GGParseIn.ReadBlankAndReal[f];
[] ← GGParseIn.ReadBlankAndWord[f];
fontName ← UnpackComplexFontName[fontName, size]; -- like Helvetica12BI
[family, face, ----] ← TFontParamsFromFontName[fontName];
}
ELSE IF version <= 8605.28 THEN { -- a mixed mode name like Helvetica7BI or TimesRoman12
fontName ← GGParseIn.ReadBlankAndWord[f];
size ← GGParseIn.ReadBlankAndReal[f];
[] ← GGParseIn.ReadBlankAndWord[f]; -- throw away redundant size
[family, face, ----] ← TFontParamsFromFontName[fontName];
}
ELSE IF version < 8701.30 THEN { -- a mixed mode name like Helvetica7BI or TimesRoman12. No colorName, real text color
fontName ← GGParseIn.ReadBlankAndWord[f];
size ← GGParseIn.ReadBlankAndReal[f];
[family, face, ----] ← TFontParamsFromFontName[fontName];
}
ELSE IF version = 8701.30 THEN {-- an interim font format
fail: BOOLFALSE;
[fail, ----, family, face] ← GGFont.OldParseFontData[f, FALSE, TRUE, TRUE];
IF fail THEN ERROR;
transform ← GGParseIn.ReadTransformation[f];
preferredSize ← GGParseIn.ReadBlankAndReal[f];
};
now manufacture the stuff needed by SetTextFont. Use the parser!
have: prefix, family, face, transform, size, preferredSize
fontData ← GGFont.CreateFontData[];
fontData.transform ← transform;
fontData.storedSize ← preferredSize; -- needed by ParseFontData
fontData ← GGFont.ParseFontData[data: fontData, inStream: IO.RIS[rope: Rope.Cat[prefix,family,face], oldStream: NIL], prefixP: TRUE, familyP: TRUE, faceP: TRUE];
}
ELSE {
IF fontDataComfortable THEN { -- User data in file: userName, transformation
userName: Rope.ROPE ← GGParseIn.ReadBlankAndWord[f];
fontData ← GGFont.ParseFontData[data: NIL, inStream: IO.RIS[rope: userName, oldStream: NIL], prefixP: TRUE, familyP: TRUE, faceP: TRUE];
fontData.transform ← GGParseIn.ReadTransformation[f];
}
ELSE { -- Literal data in file
literalName: Rope.ROPE ← GGParseIn.ReadBlankAndWord[f];
fontData ← GGFont.CreateFontData[];
fontData.transform ← GGParseIn.ReadTransformation[f];
fontData.storedSize ← GGParseIn.ReadBlankAndReal[f];
fontData.designSize ← GGParseIn.ReadBlankAndReal[f];
fontData ← GGFont.ParseFontData[data: fontData, inStream: IO.RIS[rope: literalName, oldStream: NIL], literalP: TRUE];
};
};
get color
IF version > 8605.28 THEN {
textColor ← GGParseIn.ReadColor[f, version];
};
Handle Drop Shadows
IF version >= 8609.26 THEN {
[dropShadowOn, good] ← GGParseIn.ReadBOOL[f, version];
IF NOT good THEN ERROR;
IF dropShadowOn THEN {
dropShadowOffset ← GGParseIn.ReadPoint[f];
shadowColor ← GGParseIn.ReadColor[f, version];
};
}
ELSE dropShadowOn ← FALSE;
IF version <= 8607.22 THEN { -- a point rather than a transform
point: Point ← GGParseIn.ReadPoint[f];
fontData.transform ← ImagerTransformation.Translate[point];
fontData.transform ← ImagerTransformation.PreScale[fontData.transform, size];
size ← 1.0;
}
ELSE IF version < 8701.30 THEN {
fontData.transform ← GGParseIn.ReadTransformation[f];
fontData.transform ← ImagerTransformation.PreScale[fontData.transform, size];
size ← 1.0;
}; -- transform already read in for later versions
IF version >= 8701.13 THEN {
amplifySpace ← GGParseIn.ReadBlankAndReal[f];
}
ELSE amplifySpace ← 1.0;
slice ← MakeTextSlice[text: text, amplifySpace: amplifySpace, dropShadowsOn: dropShadowOn, dropShadowOffset: dropShadowOffset, shadowColor: shadowColor];
fontData.substituteOK ← TRUE; -- allow font substitution on file reads
IF NOT SetTextFontAndTransform[slice, fontData, feedback] THEN SIGNAL FontNameError;
slice.class.setFillColor[slice, textColor];
};
Hit Testing
MakeComplete: PROC [parts: SliceParts] = {
WITH parts SELECT FROM
textParts: TextParts => {
textParts.points ← ALL[TRUE];
textParts.edges ← ALL[TRUE];
textParts.includeText ← TRUE;
};
ipParts: IPParts => {
ipParts.points ← ALL[TRUE];
ipParts.edges ← ALL[TRUE];
ipParts.innards ← TRUE;
};
ENDCASE => ERROR;
};
IsComplete: PROC [parts: SliceParts] RETURNS [BOOL] = {
WITH parts SELECT FROM
textParts: TextParts => RETURN[ textParts.points=ALL[TRUE] AND textParts.edges=ALL[TRUE] AND textParts.includeText=TRUE];
ipParts: IPParts => RETURN[ ipParts.points=ALL[TRUE] AND ipParts.edges=ALL[TRUE] AND ipParts.innards=TRUE];
ENDCASE => ERROR;
};
IsEmpty: PROC [parts: SliceParts] RETURNS [BOOL] = {
WITH parts SELECT FROM
textParts: TextParts => RETURN[ textParts.points=ALL[FALSE] AND textParts.edges=ALL[FALSE] AND textParts.includeText = FALSE];
ipParts: IPParts => RETURN[ ipParts.points=ALL[FALSE] AND ipParts.edges=ALL[FALSE] AND ipParts.innards = FALSE];
ENDCASE => ERROR;
};
TextIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = {
RETURN[IsEmpty[sliceD.parts]];
};
TextIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = {
RETURN[IsComplete[sliceD.parts]];
};
TextNewParts: PROC [slice: Slice, hitData: REF ANY, mode: SelectMode] RETURNS [sliceD: SliceDescriptor] = {
GGModelTypes.SliceNewPartsProc
textHitData: TextHitData ← NARROW[hitData];
textParts: TextParts ← NEW[TextPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE], includeText: FALSE ] ];
SELECT mode FROM
literal => {
IF textHitData.point#-1 THEN textParts.points[textHitData.point] ← TRUE
ELSE IF textHitData.edge#-1 THEN textParts.edges[textHitData.edge] ← TRUE
ELSE ERROR;
};
joint => {
IF textHitData.point#-1 THEN {
textParts.points[textHitData.point] ← TRUE;
}
ELSE {
hitData: REF ANY;
pointHitData: TextHitData;
success: BOOL;
wholeD: SliceDescriptor;
wholeD ← TextNewParts[slice, NIL, topLevel];
[----, ----, hitData, success] ← slice.class.closestPoint[wholeD, textHitData.hitPoint, GGUtility.plusInfinity];
IF NOT success THEN ERROR;
pointHitData ← NARROW[hitData];
IF pointHitData.point = -1 THEN ERROR;
textParts.points[pointHitData.point] ← TRUE;
};
textParts.includeText ← TRUE;
};
segment => IF textHitData.edge#-1 THEN {
textParts.edges[textHitData.edge] ← TRUE;
textParts.includeText ← TRUE;
};
controlPoint => {
textParts.points ← ALL[TRUE];
textParts.edges ← ALL[TRUE];
};
traj, slice, topLevel => MakeComplete[textParts];
none => { -- leave textParts empty
};
ENDCASE => ERROR;
sliceD ← GGSlice.DescriptorFromParts[slice, textParts];
};
TextUnionParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aPlusB: SliceDescriptor] = {
GGModelTypes.SliceUnionPartsProc
textPartsA: TextParts ← NARROW[partsA.parts];
textPartsB: TextParts ← NARROW[partsB.parts];
newParts: TextParts;
IF partsA = NIL OR partsB = NIL THEN ERROR;
IF partsA.parts = NIL THEN RETURN[partsB];
IF partsB.parts = NIL THEN RETURN[partsA];
newParts ← NEW[TextPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE], includeText: FALSE]];
FOR i: INTEGER IN [0..textMaxPoints) DO
newParts.points[i] ← textPartsA.points[i] OR textPartsB.points[i];
ENDLOOP;
FOR i: INTEGER IN [0..textMaxEdges) DO
newParts.edges[i] ← textPartsA.edges[i] OR textPartsB.edges[i];
ENDLOOP;
newParts.includeText ← textPartsA.includeText OR textPartsB.includeText;
aPlusB ← GGSlice.DescriptorFromParts[partsA.slice, newParts];
};
TextDiffParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aMinusB: SliceDescriptor] = {
textPartsA: TextParts ← NARROW[partsA.parts];
textPartsB: TextParts ← NARROW[partsB.parts];
newParts: TextParts;
IF partsA = NIL OR partsB = NIL THEN ERROR;
IF partsA.parts=NIL THEN RETURN[GGSlice.DescriptorFromParts[partsA.slice, NIL]];
IF partsB=NIL THEN RETURN[partsA];
newParts ← NEW[TextPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE], includeText: FALSE ] ];
FOR i: INTEGER IN [0..textMaxPoints) DO
newParts.points[i] ← IF textPartsB.points[i] THEN FALSE ELSE textPartsA.points[i];
ENDLOOP;
FOR i: INTEGER IN [0..textMaxEdges) DO
newParts.edges[i] ← IF textPartsB.edges[i] THEN FALSE ELSE textPartsA.edges[i];
ENDLOOP;
newParts.includeText ← IF textPartsB.includeText THEN FALSE ELSE textPartsA.includeText;
aMinusB ← GGSlice.DescriptorFromParts[partsA.slice, newParts];
};
TextMovingParts: PROC [slice: Slice, selectedParts: SliceParts] RETURNS [background, overlay, rubber, drag: SliceDescriptor] = {
GGModelTypes.SliceMovingPartsProc
If anything is moving, everything is dragging. Otherwise, leave it in the background.
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] = {
GGModelTypes.SliceAugmentPartsProc
textParts: TextParts ← NARROW[sliceD.parts];
newParts: TextParts ← NEW[TextPartsObj ← [points: textParts.points, edges: textParts.edges, includeText: textParts.includeText] ];
For every edge, add its corresponding points
FOR i: INTEGER IN [0..textMaxEdges) DO
IF textParts.edges[i] THEN SELECT i FROM
0 => {newParts.points[0] ← TRUE; newParts.points[1] ← TRUE; newParts.points[2] ← TRUE; newParts.points[3] ← TRUE;};
1 => {newParts.points[3] ← TRUE; newParts.points[7] ← TRUE; newParts.points[11] ← TRUE;};
2 => {newParts.points[8] ← TRUE; newParts.points[9] ← TRUE; newParts.points[10] ← TRUE; newParts.points[11] ← TRUE;};
3 => {newParts.points[0] ← TRUE; newParts.points[4] ← TRUE; newParts.points[8] ← TRUE;};
4 => {newParts.points[1] ← TRUE; newParts.points[5] ← TRUE; newParts.points[9] ← TRUE;};
5 => {newParts.points[2] ← TRUE; newParts.points[6] ← TRUE; newParts.points[10] ← TRUE;};
6 => {newParts.points[4] ← TRUE; newParts.points[5] ← TRUE; newParts.points[6] ← TRUE; newParts.points[7] ← TRUE;};
ENDCASE => ERROR;
ENDLOOP;
more ← GGSlice.DescriptorFromParts[sliceD.slice, newParts];
};
TextPointsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = {
parts: TextParts ← NARROW[sliceD.parts];
pointGen ← NEW[PointGeneratorObj ← [sliceD, 0, 0, NIL] ];
FOR point: INTEGER IN [0..textMaxPoints) DO
IF parts.points[point] THEN pointGen.toGo ← pointGen.toGo + 1;
ENDLOOP;
};
TextPointPairsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointPairGen: PointPairGenerator] = {
parts: TextParts ← NARROW[sliceD.parts];
pointPairGen ← NEW[PointPairGeneratorObj ← [sliceD, 0, 0, NIL] ]; -- toGo and index now used
FOR edge: INTEGER IN [0..textMaxEdges) DO
IF parts.edges[edge] THEN pointPairGen.toGo ← pointPairGen.toGo + 1;
ENDLOOP;
};
TextNextPoint: PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = {
IF pointGen=NIL OR pointGen.toGo = 0 THEN {
pointAndDone.done ← TRUE;
RETURN;
}
ELSE {
sliceD: SliceDescriptor ← pointGen.sliceD;
textData: TextData ← NARROW[sliceD.slice.data];
fontData: FontData ← textData.fontData;
textParts: TextParts ← NARROW[sliceD.parts];
index: INTEGER ← -1;
pointAndDone.done ← FALSE;
FOR index ← pointGen.index, index+1 UNTIL index >=textMaxPoints DO
IF textParts.points[index] THEN EXIT; -- index will point to next available point
ENDLOOP;
pointAndDone.point ← ImagerTransformation.Transform[fontData.transform, textData.points[index] ];
pointGen.toGo ← pointGen.toGo-1;
pointGen.index ← IF pointGen.toGo=0 THEN 99 ELSE index+1; -- bump to the next available point in the generator
};
};
TextNextPointPair: PROC [pointPairGen: PointPairGenerator] RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = {
IF pointPairGen=NIL OR pointPairGen.toGo = 0 THEN {
pointPairAndDone.done ← TRUE;
RETURN;
}
ELSE {
sliceD: SliceDescriptor ← pointPairGen.sliceD;
textData: TextData ← NARROW[sliceD.slice.data];
textParts: TextParts ← NARROW[sliceD.parts];
t: ImagerTransformation.Transformation ← textData.fontData.transform;
index: INTEGER ← -1;
pointPairAndDone.done ← FALSE;
FOR index ← pointPairGen.index, index+1 UNTIL index >=textMaxEdges DO
IF textParts.edges[index] THEN EXIT; -- index will point to next availabe edge
ENDLOOP;
SELECT index FROM -- index in [0..textMaxEdges) of next available edge
0 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, textData.points[0] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, textData.points[3] ];
};
1 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, textData.points[3] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, textData.points[11] ];
};
2 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, textData.points[11] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, textData.points[8] ];
};
3 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, textData.points[8] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, textData.points[0] ];
};
4 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, textData.points[1] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, textData.points[9] ];
};
5 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, textData.points[2] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, textData.points[10] ];
};
6 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, textData.points[4] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, textData.points[7] ];
};
ENDCASE => SIGNAL Feedback.Problem[msg: "Broken Invariant"];
pointPairGen.toGo ← pointPairGen.toGo - 1;
pointPairGen.index ← IF pointPairGen.toGo=0 THEN 99 ELSE index+1; -- bump to the next available pair in the generator
};
};
PointIsInBox: PROC [test: Point, box: GGBasicTypes.BoundBoxObj] RETURNS [BOOL] = {
RETURN[ NOT (test.x < box.loX OR test.x > box.hiX OR test.y < box.loY OR test.y > box.hiY) ];
};
TextClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestPointProc
index: NAT ← 9999;
textData: TextData ← NARROW[sliceD.slice.data];
bigBox: GGBasicTypes.BoundBoxObj ← [textData.feedbackBox.loX-tolerance, textData.feedbackBox.loY-tolerance, textData.feedbackBox.hiX+tolerance, textData.feedbackBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, bigBox] THEN {
textParts: TextParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[textData.inverse, testPoint];
localTolerance: REALABS[tolerance*MAX[textData.inverseScale.x, textData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← TextNearestPoint[textData.points, localTestpoint, localTolerance, textParts.points];
IF success THEN {
bestPoint ← GGTransform.Transform[textData.fontData.transform, bestPoint];
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
hitData ← NEW[TextHitDataObj ← [point: index, edge: -1, hitPoint: bestPoint] ];
};
};
};
TextClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestSegmentProc
hitData ← NIL; -- automatically done by compiler
textData: TextData ← NARROW[sliceD.slice.data];
bigBox: GGBasicTypes.BoundBoxObj ← [textData.feedbackBox.loX-tolerance, textData.feedbackBox.loY-tolerance, textData.feedbackBox.hiX+tolerance, textData.feedbackBox.hiY+tolerance, FALSE, FALSE];
IF PointIsInBox[testPoint, bigBox] THEN {
seg: NAT ← 9999;
textHitData: TextHitData;
textParts: TextParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[textData.inverse, testPoint];
localTolerance: REALABS[tolerance*MAX[textData.inverseScale.x, textData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, seg, bestPoint, success] ← TextNearestEdge[textData.points, localTestpoint, localTolerance, textParts.edges];
IF success THEN {
bestPoint ← GGTransform.Transform[textData.fontData.transform, bestPoint];
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
hitData ← textHitData ← NEW[TextHitDataObj ← [point: -1, edge: seg, hitPoint: bestPoint] ];
};
};
};
TextNearestPoint: PROC [points: TextPoints, testPoint: Point, tolerance: REAL ← 1E6, mask: TextPointArray ] RETURNS [bestDist: REAL, bestPoint: NAT, bestXY: Point, success: BOOLFALSE] = {
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
thisPoint: Point;
thisDist2, bestDist2: REAL;
tolerance2: REAL ← tolerance*tolerance;
index: NAT ← 0;
bestXY ← [-1.0, -1.0];
bestDist2 ← 1E12; -- better be big enough
bestPoint ← 9999;
FOR index IN [0..textMaxPoints) DO
IF mask[index] THEN {
thisPoint ← points[index]; -- use feedbackBox relative coordinates
thisDist2 ← Vectors2d.DistanceSquared[thisPoint, testPoint];
IF thisDist2 < bestDist2 THEN {
bestDist2 ← thisDist2;
bestPoint ← index;
bestXY ← thisPoint;
};
};
ENDLOOP;
bestDist ← RealFns.SqRt[bestDist2];
IF bestDist < tolerance THEN success ← TRUE;
};
scratchEdge: GGBasicTypes.Edge ← Lines2d.CreateEmptyEdge[];
TextNearestEdge: PUBLIC PROC [points: TextPoints, testPoint: Point, tolerance: REAL ← 1E6, mask: TextEdgeArray ← ALL[TRUE] ] RETURNS [bestDist: REAL, bestSeg: NAT, bestPoint: Point, success: BOOLFALSE] = {
Finds the edge which is in mask and nearest to testPoint (and its distance from testPoint).
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;
};
Style
TextSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = {
textData: TextData ← NARROW[slice.data];
textData.shadowColor ← color;
};
TextGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Imager.Color] = {
textData: TextData ← NARROW[slice.data];
color ← textData.shadowColor;
};
TextSetDefaults: PROC [slice: Slice, parts: SliceParts, defaults: DefaultData] = {
textData: TextData ← NARROW[slice.data];
textParts: TextParts ← NARROW[parts];
IF textParts.includeText THEN textData.color ← defaults.fillColor;
DON'T MESS WITH FONTS FOR DEFAULTS
};
TextSetFillColor: PROC [slice: Slice, color: Imager.Color] = {
textData: TextData ← NARROW[slice.data];
textData.color ← color;
};
TextGetFillColor: PROC [slice: Slice] RETURNS [color: Imager.Color] = {
textData: TextData ← NARROW[slice.data];
RETURN[textData.color];
};
Interpress slice data and class procs
ipMaxPoints: INTEGER = 4;
ipMaxEdges: INTEGER = 4;
IPPointArray: TYPE = --PACKED-- ARRAY [0..ipMaxPoints) OF BOOL; -- ll, ul, ur, lr
IPEdgeArray: TYPE = --PACKED-- ARRAY [0..ipMaxEdges) OF BOOL; -- left, top, right, bottom
ipCornerRopes: ARRAY [0..ipMaxPoints) OF Rope.ROPE = [
"lower left corner", "upper left corner", "upper right corner", "lower right corner"];
ipEdgeRopes: ARRAY [0..ipMaxEdges) OF Rope.ROPE = [
"left edge", "top edge", "right edge", "bottom edge"];
IPData: TYPE = REF IPDataObj;
IPDataObj: TYPE = RECORD [
localBox: BoundBox, -- a bounding box in local coordinates. Set once when the slice is created.
tightBox: BoundBox, -- a tight-fitting box in scene coordinates.
slice.boundBox allows for joints and stroke-width
includeByValue: BOOL, -- store the actual interpress in the Gargoyle master?
file: Rope.ROPE, -- fileName of Interpress Master
mem: Imager.Context, -- imager memory context
transform: ImagerTransformation.Transformation, -- includes default position
inverse: ImagerTransformation.Transformation,
inverseScale: Vector -- cached value of ImagerTransformation.Factor[inverse].s
];
IPParts: TYPE = REF IPPartsObj;
IPPartsObj: TYPE = RECORD [
points: IPPointArray, -- which corners of box are selected.
edges: IPEdgeArray, -- which edges of box are selected.
innards: BOOLFALSE -- FALSE => corners and edges only; no innards
];
IPHitData: TYPE = REF IPHitDataObj;
IPHitDataObj: TYPE = RECORD [
point: [-1..ipMaxPoints), -- which point of box is hit.
edge: INTEGER, -- which edge of box is hit.
hitPoint: Point
];
IPHitDataObj: TYPE = RECORD [
point: [-1..ipMaxPoints), -- which point of box is hit.
edge: [-1..ipMaxEdges) -- which edge of box is hit. THIS DEF CAUSES A FATAL COMPILER ERROR IN PASS FIVE !!
];
Routines peculiar to IPSlices
BuildIPSliceClass: PUBLIC PROC [] RETURNS [class: SliceClass] = {
OPEN GGSlice;
class ← NEW[SliceClassObj ← [
type: $IP,
Fundamentals
getBoundBox: IPBoundBox,
getTightBox: IPTightBox,
copy: IPCopy,
Drawing
drawParts: IPDrawParts,
drawTransform: IPDrawTransform,
drawSelectionFeedback: IPDrawSelectionFeedback,
drawAttractorFeedback: NoOpDrawAttractorFeedback,
Transforming
transform: IPTransform,
Textual Description
describe: IPDescribe,
describeHit: IPDescribeHit,
fileout: IPFileout,
filein: IPFilein,
Parts
isEmptyParts: IPIsEmptyParts,
isCompleteParts: IPIsCompleteParts,
newParts: IPNewParts,
unionParts: IPUnionParts,
differenceParts: IPDiffParts,
movingParts: IPMovingParts,
augmentParts: IPAugmentParts,
setSelectedFields: NoOpSetSelectedFields,
Part Generators
pointsInDescriptor: IPPointsInDescriptor,
pointPairsInDescriptor: IPPointPairsInDescriptor,
segmentsInDescriptor: NoOpSegmentsInDescriptor,
walkSegments: NoOpWalkSegments,
nextPoint: IPNextPoint,
nextPointPair: IPNextPointPair,
nextSegment: NoOpNextSegment,
Hit Testing
closestPoint: IPClosestPoint,
closestJointToHitData: NoOpClosestJointToHitData,
closestPointAndTangent: NoOpClosestPointAndTangent,
closestSegment: IPClosestSegment,
lineIntersection: NoOpLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: NoOpHitDataAsSimpleCurve,
Style
setDefaults: NoOpSetDefaults,
setStrokeWidth: NoOpSetStrokeWidth,
getStrokeWidth: NoOpGetStrokeWidth,
setStrokeEnd: NoOpSetStrokeEnd,
getStrokeEnd: NoOpGetStrokeEnd,
setStrokeJoint: NoOpSetStrokeJoint,
getStrokeJoint: NoOpGetStrokeJoint,
setStrokeColor: NoOpSetStrokeColor,
getStrokeColor: NoOpGetStrokeColor,
setFillColor: NoOpSetFillColor,
getFillColor: NoOpGetFillColor,
setArrows: NoOpSetArrows,
getArrows: NoOpGetArrows,
setDashed: NoOpSetDashed,
getDashed: NoOpGetDashed
]];
};
MakeIPSliceFromFile: PROC [fullName: Rope.ROPE, feedback: FeedbackData, transform: ImagerTransformation.Transformation ← NIL, localBox: BoundBox, includeByValue: BOOL] RETURNS [slice: Slice] = {
ipMaster: Interpress.Master;
success: BOOLFALSE;
[ipMaster, success] ← GGUtility.OpenInterpressOrComplain[feedback, fullName];
IF NOT success THEN RETURN;
slice ← MakeIPSliceFromMaster[ipMaster, 2834.646, fullName, feedback, transform, localBox, includeByValue];
};
MakeIPSliceFromMaster: PUBLIC PROC [ipMaster: Interpress.Master, pixelsPerUnit: REAL ← 2834.646, fullName: Rope.ROPENIL, feedback: FeedbackData, transform: ImagerTransformation.Transformation ← NIL, localBox: BoundBox ← NIL, includeByValue: BOOL] RETURNS [slice: Slice] = {
The interpress master is assumed to be in units of pixelsPerUnit pixels.
memContext: Imager.Context;
The memory context has 1 unit = 1/72.0 inch.
memContext ← ImagerMemory.NewMemoryContext[]; -- in pixels
Imager.ScaleT[memContext, pixelsPerUnit];
Interpress.DoPage[master: ipMaster, page: 1, context: memContext, log: NIL];
BEGIN -- compute the bounding box
MakeScene: PROC [context: Imager.Context] = {
ImagerMemory.Replay[memContext, context];
};
IF localBox = NIL THEN {
scene: Scene ← GGFromImager.Capture[action: MakeScene ! GGFromImager.WarningMessage => {
Feedback.Append[feedback, message, oneLiner];
RESUME;
}];
localBox ← GGScene.TightBoxOfScene[scene];
};
END;
slice ← MakeIPSliceAux[fullName, memContext, feedback, transform, includeByValue, localBox];
};
MakeIPSliceFromMaskPixel: PUBLIC PROC [pa: Imager.PixelArray, color: Color, feedback: FeedbackData, transform: ImagerTransformation.Transformation ← NIL] RETURNS [slice: Slice] = {
The transformations should be set up so that MaskPixel[pa] will look correct in a context where the units are pixels if transform is concatenated before pa is drawn. In the common case, transform will be a pure translation.
memContext: Imager.Context;
ipData: IPData;
inverse: ImagerTransformation.Transformation;
inverseScale: Imager.VEC;
localBox: BoundBox;
The memory context has 1 unit = 1/72.0 inch.
memContext ← ImagerMemory.NewMemoryContext[];
Imager.SetColor[memContext, color];
Imager.MaskPixel[memContext, pa];
transform ← IF transform=NIL THEN GGTransform.Identity[] ELSE transform;
inverse ← ImagerTransformation.Invert[transform];
inverseScale ← ImagerTransformation.Factor[inverse].s;
localBox ← GGBoundBox.BoundBoxOfPixelArray[pa];
ipData ← NEW[IPDataObj ← [localBox: localBox, includeByValue: TRUE, file: NIL, mem: memContext, transform: transform, inverse: inverse, inverseScale: inverseScale ]];
slice ← NEW[SliceObj ← [
class: GGSlice.FetchSliceClass[$IP],
data: ipData,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE],
boundBox: GGBoundBox.CreateBoundBox[0,0,0,0] -- gets set next line
]];
IPUpdateBoundBox[slice];
};
MakeIPSliceAux: PROC [fileName: Rope.ROPE, memContext: Imager.Context, feedback: FeedbackData, transform: ImagerTransformation.Transformation, includeByValue: BOOL, localBox: BoundBox] RETURNS [slice: Slice] = {
ipData: IPData;
inverse: ImagerTransformation.Transformation;
inverseScale: Imager.VEC;
transform ← IF transform=NIL THEN GGTransform.Identity[] ELSE transform;
inverse ← ImagerTransformation.Invert[transform];
inverseScale ← ImagerTransformation.Factor[inverse].s;
ipData ← NEW[IPDataObj ← [localBox: localBox, includeByValue: includeByValue, file: fileName, mem: memContext, transform: transform, inverse: inverse, inverseScale: inverseScale]];
slice ← NEW[SliceObj ← [
class: GGSlice.FetchSliceClass[$IP],
data: ipData,
parent: NIL,
selectedInFull: [FALSE, FALSE, FALSE],
boundBox: GGBoundBox.CreateBoundBox[0,0,0,0] -- gets set next line
]];
IPUpdateBoundBox[slice];
};
SetIncludeByValue: PUBLIC PROC [slice: Slice, includeByValue: BOOL] = {
ipData: IPData ← NARROW[slice.data];
ipData.includeByValue ← includeByValue;
};
GetIncludeByValue: PUBLIC PROC [slice: Slice] RETURNS [includeByValue: BOOL] = {
ipData: IPData ← NARROW[slice.data];
includeByValue ← ipData.includeByValue;
};
IP Class Routines
Fundamentals
IPUpdateBoundBox: PROC [slice: Slice] = {
ipData: IPData ← NARROW[slice.data];
ipData.tightBox ← GGBoundBox.BoundBoxOfBoundBox[ipData.localBox, ipData.transform];
slice.boundBox ← GGBoundBox.CopyBoundBox[ipData.tightBox];
GGBoundBox.AllowForJoints[slice.boundBox];
};
IPBoundBox: PROC [slice: Slice, parts: SliceParts ← NIL] RETURNS [box: BoundBox]= {
GGModelTypes.SliceBoundBoxProc
RETURN[slice.boundBox];
};
IPTightBox: PROC [slice: Slice, parts: SliceParts ← NIL] RETURNS [box: BoundBox]= {
GGModelTypes.SliceBoundBoxProc
ipData: IPData ← NARROW[slice.data];
RETURN[ipData.tightBox];
};
IPCopy: PROC [slice: Slice] RETURNS [copy: Slice ← NIL] = {
GGModelTypes.SliceCopyProc
ipData: IPData ← NARROW[slice.data];
transform: Imager.Transformation ← ImagerTransformation.Copy[ipData.transform];
copy ← MakeIPSliceAux[ipData.file, ipData.mem, NIL, transform, ipData.includeByValue, ipData.localBox];
};
Drawing
IPDrawParts: PROC [slice: Slice, parts: SliceParts ← NIL, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, quick: BOOL] = {
GGModelTypes.SliceDrawPartsProc
DoDrawInnards: PROC = {
Imager.ConcatT[dc, ipData.transform];
Imager.SetColor[dc, Imager.black]; -- otherwise the replay could end up in a random color.
ImagerMemory.Replay[ipData.mem, dc];
};
ipData: IPData ← NARROW[slice.data];
ipParts: IPParts ← NARROW[parts];
IF ipParts = NIL OR ipParts.innards THEN Imager.DoSaveAll[dc, DoDrawInnards];
};
IPDrawSelectionFeedback: PROC [slice: Slice, selectedParts: SliceParts, hotParts: SliceParts, dc: Imager.Context, camera: CameraData, dragInProgress, caretIsMoving, hideHot, quick: BOOL] = {
GGModelTypes.SliceDrawSelectionFeedbackProc
The feedback rectangle can be rotated. It is the tight bounding box of the rectangular interpress master. It is derived from the local box.
DoDrawFeedback: PROC = {
t: ImagerTransformation.Transformation ← ipData.transform;
box: BoundBox ← ipData.localBox;
thisCPisHot, thisCPisSelected: BOOL;
pts: ARRAY [0..3] OF Point;
pts[0] ← ImagerTransformation.Transform[t, [box.loX, box.loY]];
pts[1] ← ImagerTransformation.Transform[t, [box.loX, box.hiY]];
pts[2] ← ImagerTransformation.Transform[t, [box.hiX, box.hiY]];
pts[3] ← ImagerTransformation.Transform[t, [box.hiX, box.loY]];
FOR point: INTEGER IN [0..ipMaxPoints) DO
thisCPisHot ← hotIPParts#NIL AND hotIPParts.points[point];
thisCPisSelected ← normalIPParts#NIL AND normalIPParts.points[point];
IF thisCPisHot THEN GGShapes.DrawSelectedJoint[dc, pts[point], hot];
IF thisCPisSelected THEN GGShapes.DrawSelectedJoint[dc, pts[point], normal];
IF NOT thisCPisHot AND NOT thisCPisSelected THEN GGShapes.DrawCP[dc, pts[point]];
ENDLOOP;
Imager.SetStrokeJoint[dc, round];
Imager.SetStrokeWidth[dc, 1.0];
Imager.SetColor[dc, Imager.black];
IF NOT quick AND normalIPParts # NIL THEN {
IF normalIPParts # NIL THEN {
FOR edge: INTEGER IN [0..ipMaxEdges) DO
IF normalIPParts.edges[edge] THEN SELECT edge FROM
0 => Imager.MaskVector[dc, pts[0], pts[1]];
1 => Imager.MaskVector[dc, pts[1], pts[2]];
2 => Imager.MaskVector[dc, pts[2], pts[3]];
3 => Imager.MaskVector[dc, pts[3], pts[0]];
ENDCASE => ERROR;
ENDLOOP;
};
};
normalIPParts, hotIPParts: IPParts;
ipData: IPData ← NARROW[slice.data];
IF caretIsMoving OR dragInProgress THEN RETURN;
IF selectedParts = NIL AND hotParts = NIL THEN RETURN;
normalIPParts ← NARROW[selectedParts];
hotIPParts ← NARROW[hotParts];
IF camera.quality#quality THEN Imager.DoSaveAll[dc, DoDrawFeedback];
};
IPDrawTransform: PROC [sliceD: SliceDescriptor, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceDrawTransformProc
DoIPDrawTransform: PROC = {
Imager.ConcatT[dc, ImagerTransformation.Concat[ipData.transform, transform]];
Imager.SetColor[dc, Imager.black]; -- otherwise the replay could end up in a random color.
ImagerMemory.Replay[ipData.mem, dc];
};
ipData: IPData ← NARROW[sliceD.slice.data];
Imager.DoSaveAll[dc, DoIPDrawTransform];
};
Transforming
IPTransform: PROC [sliceD: SliceDescriptor, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceTransformProc
ipData: IPData ← NARROW[sliceD.slice.data];
ipData.transform ← ImagerTransformation.Concat[ipData.transform, transform];
ipData.inverse ← ImagerTransformation.Invert[ipData.transform];
ipData.inverseScale ← ImagerTransformation.Factor[ipData.inverse].s;
IPUpdateBoundBox[sliceD.slice];
};
Textual Description
IPDescribe: PROC [sliceD: SliceDescriptor] RETURNS [rope: Rope.ROPE] = {
GGModelTypes.SliceDescribeProc
prefix: Rope.ROPE;
ipParts: IPParts;
eCount, pCount: NAT ← 0;
IF sliceD.parts = NIL THEN RETURN["an IP slice"];
ipParts ← NARROW[sliceD.parts];
FOR i: INTEGER IN [0..ipMaxPoints) DO
IF ipParts.points[i] THEN {
pCount ← pCount + 1;
prefix ← ipCornerRopes[i];
};
ENDLOOP;
FOR i: INTEGER IN [0..ipMaxEdges) DO
IF ipParts.edges[i] THEN {
eCount �ount + 1;
prefix ← ipEdgeRopes[i];
};
ENDLOOP;
IF pCount+eCount>1 THEN RETURN["multiple parts of an IP slice"];
IF eCount=0 AND pCount=0 THEN RETURN["NO parts of an IP slice"];
rope ← Rope.Concat[prefix, " of an IP slice"];
};
IPDescribeHit: PROC [slice: Slice, hitData: REF ANY] RETURNS [rope: Rope.ROPE] = {
ipHitData: IPHitData ← NARROW[hitData];
prefix: Rope.ROPE;
IF ipHitData.point#-1 THEN prefix ← ipCornerRopes[ipHitData.point]
ELSE IF ipHitData.edge#-1 THEN prefix ← ipEdgeRopes[ipHitData.edge]
ELSE ERROR;
rope ← Rope.Concat[prefix, " of an IP slice"];
};
IPFileout: PROC [slice: Slice, f: IO.STREAM] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
FromMemory: PROC [dc: Imager.Context] = {
Imager.SetColor[dc, Imager.black]; -- otherwise the replay could end up in a random color.
ImagerMemory.Replay[ipData.mem, dc];
};
ipData: IPData ← NARROW[slice.data];
ipRef: ImagerInterpress.Ref;
masterStream: IO.STREAM;
masterSize: LONG CARDINAL;
masterRope: Rope.ROPE;
f.PutF["\"%q\" ", [rope[ipData.file]]];
GGParseOut.WriteTransformation[f, ipData.transform];
f.PutChar[IO.SP];
GGParseOut.WriteBox[f, ipData.localBox];
f.PutRope[" includeByValue: "];
GGParseOut.WriteBOOL[f, ipData.includeByValue];
f.PutChar[IO.CR];
IF ipData.includeByValue THEN {
Write the interpress into a Rope.ROPE.
masterStream ← IO.ROS[];
ipRef ← ImagerInterpress.CreateFromStream[masterStream, "Interpress/Xerox/3.0 "];
ImagerInterpress.DoPage[ipRef, FromMemory];
ImagerInterpress.Finish[ipRef];
masterRope ← masterStream.RopeFromROS[];
masterSize ← Rope.Length[masterRope];
Send the rope to stream f.
f.PutF["%g\n", [integer[masterSize]]];
f.PutRope[masterRope];
};
};
IPFilein: PROC [f: IO.STREAM, version: REAL, feedback: FeedbackData] RETURNS [slice: Slice ← NIL] = {
GGModelTypes.SliceFileinProc
ipMaster: Interpress.Master ← NIL;
fullName: Rope.ROPE;
success: BOOLFALSE;
transform: ImagerTransformation.Transformation;
masterText: REF TEXT;
masterSize, nBytesRead: NAT;
masterStream: IO.STREAM;
includeByValue: BOOL;
localBox: BoundBox;
fullName ← f.GetRopeLiteral[];
[fullName] ← FS.FileInfo[fullName]; -- add the version number
transform ← GGParseIn.ReadTransformation[f];
IF version >= 8701.28 THEN {
localBox ← GGParseIn.ReadBox[f];
}
ELSE localBox ← NIL;
IF version >= 8612.04 THEN {
goodValue: BOOL;
GGParseIn.ReadBlankAndRope[f, "includeByValue:"];
[includeByValue, goodValue] ← GGParseIn.ReadBOOL[f, version];
IF NOT goodValue THEN ERROR;
IF includeByValue THEN {
Read a description of yourself from stream f.
masterSize ← GGParseIn.ReadBlankAndNAT[f];
GGParseIn.ReadBlank[f];
masterText ← RefText.ObtainScratch[masterSize+2];
nBytesRead ← IO.GetBlock[f, masterText, 0, masterSize];
IF nBytesRead # masterSize THEN ERROR;
masterStream ← IO.TIS[masterText];
ipMaster ← Interpress.FromStream[masterStream, NIL];
next make an IPSlice and return it
slice ← MakeIPSliceFromMaster[ipMaster, 1.0, fullName, feedback, transform, localBox, TRUE];
}
ELSE {
slice ← MakeIPSliceFromFile[fullName, feedback, transform, localBox, FALSE];
};
}
ELSE {
slice ← MakeIPSliceFromFile[fullName, feedback, transform, localBox, FALSE];
};
};
Hit Testing
IPIsEmptyParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = {
GGModelTypes.SliceEmptyPartsProc
RETURN[IsEmpty[sliceD.parts]];
};
IPIsCompleteParts: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = {
RETURN[IsComplete[sliceD.parts]];
};
IPNewParts: PROC [slice: Slice, hitData: REF ANY, mode: SelectMode] RETURNS [sliceD: SliceDescriptor] = {
GGModelTypes.SliceNewPartsProc
ipHitData: IPHitData ← NARROW[hitData];
ipParts: IPParts ← NEW[IPPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE] ] ];
SELECT mode FROM
literal => {
IF ipHitData.point#-1 THEN ipParts.points[ipHitData.point] ← TRUE
ELSE IF ipHitData.edge#-1 THEN ipParts.edges[ipHitData.edge] ← TRUE
ELSE ERROR;
};
joint => {
IF ipHitData.point#-1 THEN {
ipParts.points[ipHitData.point] ← TRUE;
}
ELSE {
hitData: REF ANY;
pointHitData: IPHitData;
success: BOOL;
wholeD: SliceDescriptor;
wholeD ← IPNewParts[slice, NIL, slice];
[----, ----, hitData, success] ← slice.class.closestPoint[wholeD, ipHitData.hitPoint, GGUtility.plusInfinity];
IF NOT success THEN ERROR;
pointHitData ← NARROW[hitData];
IF pointHitData.point = -1 THEN ERROR;
ipParts.points[pointHitData.point] ← TRUE;
};
ipParts.innards ← TRUE;
};
segment => IF ipHitData.edge#-1 THEN{
ipParts.edges[ipHitData.edge] ← TRUE;
ipParts.innards ← TRUE;
};
controlPoint => {
ipParts.points ← ALL[TRUE];
ipParts.edges ← ALL[TRUE];
};
traj, slice, topLevel => MakeComplete[ipParts];
none => { -- leave ipParts empty
};
ENDCASE => ERROR;
sliceD ← GGSlice.DescriptorFromParts[slice, ipParts];
};
IPUnionParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aPlusB: SliceDescriptor] = {
GGModelTypes.SliceUnionPartsProc
ipPartsA: IPParts ← NARROW[partsA.parts];
ipPartsB: IPParts ← NARROW[partsB.parts];
newParts: IPParts;
IF partsA = NIL OR partsB = NIL THEN ERROR;
IF partsA.parts = NIL THEN RETURN[partsB];
IF partsB.parts = NIL THEN RETURN[partsA];
newParts ← NEW[IPPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE] ] ];
FOR i: INTEGER IN [0..ipMaxPoints) DO
newParts.points[i] ← ipPartsA.points[i] OR ipPartsB.points[i];
ENDLOOP;
FOR i: INTEGER IN [0..ipMaxEdges) DO
newParts.edges[i] ← ipPartsA.edges[i] OR ipPartsB.edges[i];
ENDLOOP;
newParts.innards ← ipPartsA.innards OR ipPartsB.innards;
aPlusB ← GGSlice.DescriptorFromParts[partsA.slice, newParts];
};
IPDiffParts: PROC [partsA: SliceDescriptor, partsB: SliceDescriptor] RETURNS [aMinusB: SliceDescriptor] = {
ipPartsA: IPParts ← NARROW[partsA.parts];
ipPartsB: IPParts ← NARROW[partsB.parts];
newParts: IPParts;
IF partsA = NIL OR partsB = NIL THEN ERROR;
IF partsA.parts = NIL OR partsB.parts = NIL THEN RETURN[partsA];
newParts ← NEW[IPPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE] ] ];
FOR i: INTEGER IN [0..ipMaxPoints) DO
newParts.points[i] ← IF ipPartsB.points[i] THEN FALSE ELSE ipPartsA.points[i];
ENDLOOP;
FOR i: INTEGER IN [0..ipMaxEdges) DO
newParts.edges[i] ← IF ipPartsB.edges[i] THEN FALSE ELSE ipPartsA.edges[i];
ENDLOOP;
newParts.innards ← IF ipPartsB.innards THEN FALSE ELSE ipPartsA.innards;
aMinusB ← GGSlice.DescriptorFromParts[partsA.slice, newParts];
};
IPMovingParts: PROC [slice: Slice, selectedParts: SliceParts] RETURNS [background, overlay, rubber, drag: SliceDescriptor] = {
GGModelTypes.SliceMovingPartsProc
If anything is moving, everything is dragging. Otherwise on background.
dragParts: IPParts ← NEW[IPPartsObj ← [points: ALL[TRUE], edges: ALL[TRUE], innards: TRUE ] ];
backgroundParts: IPParts ← NEW[IPPartsObj ← [points: ALL[TRUE], edges: ALL[TRUE], innards: TRUE ] ];
IF IsEmpty[selectedParts] THEN { -- all on background
dragParts.edges ← ALL[FALSE];
dragParts.points ← ALL[FALSE];
dragParts.innards ← FALSE;
}
ELSE { -- all dragging
backgroundParts.edges ← ALL[FALSE];
backgroundParts.points ← ALL[FALSE];
backgroundParts.innards ← FALSE;
};
background ← GGSlice.DescriptorFromParts[slice, backgroundParts];
overlay ← rubber ← GGSlice.DescriptorFromParts[slice, NIL];
drag ← GGSlice.DescriptorFromParts[slice, dragParts];
};
IPAugmentParts: PROC [sliceD: SliceDescriptor, selectClass: SelectionClass] RETURNS [more: SliceDescriptor] = {
GGModelTypes.SliceAugmentPartsProc
ipParts: IPParts ← NARROW[sliceD.parts];
newParts: IPParts ← NEW[IPPartsObj ← [points: ipParts.points, edges: ipParts.edges] ];
For every edge, add its corresponding points
FOR i: INTEGER IN [0..ipMaxEdges) DO
IF ipParts.edges[i] THEN SELECT i FROM
0 => {newParts.points[0] ← TRUE; newParts.points[1] ← TRUE;};
1 => {newParts.points[1] ← TRUE; newParts.points[2] ← TRUE;};
2 => {newParts.points[2] ← TRUE; newParts.points[3] ← TRUE;};
3 => {newParts.points[3] ← TRUE; newParts.points[0] ← TRUE;};
ENDCASE => ERROR;
ENDLOOP;
more ← GGSlice.DescriptorFromParts[sliceD.slice, newParts];
};
IPPointsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointGen: PointGenerator] = {
parts: IPParts ← NARROW[sliceD.parts];
pointGen ← NEW[PointGeneratorObj ← [sliceD, 0, 0, NIL] ];
FOR point: INTEGER IN [0..ipMaxPoints) DO
IF parts.points[point] THEN pointGen.toGo ← pointGen.toGo + 1;
ENDLOOP;
};
IPPointPairsInDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [pointPairGen: PointPairGenerator] = {
parts: IPParts ← NARROW[sliceD.parts];
pointPairGen ← NEW[PointPairGeneratorObj ← [sliceD, 0, 0, NIL] ]; -- toGo and index not used
FOR edge: INTEGER IN [0..ipMaxEdges) DO
IF parts.edges[edge] THEN pointPairGen.toGo ← pointPairGen.toGo + 1;
ENDLOOP;
};
IPNextPoint: PROC [pointGen: PointGenerator] RETURNS [pointAndDone: GGModelTypes.PointAndDone] = {
IF pointGen=NIL OR pointGen.toGo = 0 THEN {
pointAndDone.done ← TRUE;
RETURN;
}
ELSE {
sliceD: SliceDescriptor ← pointGen.sliceD;
ipData: IPData ← NARROW[sliceD.slice.data];
ipParts: IPParts ← NARROW[sliceD.parts];
index: INTEGER ← -1;
box: BoundBox ← ipData.localBox;
pointAndDone.done ← FALSE;
FOR index ← pointGen.index, index+1 UNTIL index >=ipMaxPoints DO
IF ipParts.points[index] THEN EXIT; -- index will point to next available point
ENDLOOP;
pointAndDone.point ← ImagerTransformation.Transform[ipData.transform, SELECT index FROM
0 => [box.loX, box.loY],
1 => [box.loX, box.hiY],
2 => [box.hiX, box.hiY],
3 => [box.hiX, box.loY],
ENDCASE => [0.0, 0.0] -- should not happen
ENDCASE => ERROR
];
pointGen.toGo ← pointGen.toGo-1;
pointGen.index ← IF pointGen.toGo=0 THEN 99 ELSE index+1; -- bump to the next available point in the generator
};
};
IPNextPointPair: PROC [pointPairGen: PointPairGenerator] RETURNS [pointPairAndDone: GGModelTypes.PointPairAndDone] = {
IF pointPairGen=NIL OR pointPairGen.toGo = 0 THEN {
pointPairAndDone.done ← TRUE;
RETURN;
}
ELSE {
sliceD: SliceDescriptor ← pointPairGen.sliceD;
ipData: IPData ← NARROW[sliceD.slice.data];
ipParts: IPParts ← NARROW[sliceD.parts];
box: BoundBox ← ipData.localBox;
t: ImagerTransformation.Transformation ← ipData.transform;
index: INTEGER ← -1;
pointPairAndDone.done ← FALSE;
FOR index ← pointPairGen.index, index+1 UNTIL index >=ipMaxEdges DO
IF ipParts.edges[index] THEN EXIT; -- index will point to next availabe edge
ENDLOOP;
SELECT index FROM -- index in [0..ipMaxEdges) of next available edge
0 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [box.loX, box.loY] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [box.loX, box.hiY] ];
};
1 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [box.loX, box.hiY] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [box.hiX, box.hiY] ];
};
2 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [box.hiX, box.hiY] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [box.hiX, box.loY] ];
};
3 => {
pointPairAndDone.lo ← ImagerTransformation.Transform[t, [box.hiX, box.loY] ];
pointPairAndDone.hi ← ImagerTransformation.Transform[t, [box.loX, box.loY] ];
};
ENDCASE => SIGNAL Feedback.Problem[msg: "Broken Invariant"];
pointPairGen.toGo ← pointPairGen.toGo - 1;
pointPairGen.index ← IF pointPairGen.toGo=0 THEN 99 ELSE index+1; -- bump to the next available pair in the generator
};
};
IPClosestPoint: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestPointProc
hitData ← NIL; -- automatically done by compiler
index: NAT ← 9999;
slice: Slice ← sliceD.slice;
ipData: IPData ← NARROW[slice.data];
toleranceBox: GGBasicTypes.BoundBoxObj ← [ipData.tightBox.loX-tolerance, ipData.tightBox.loY-tolerance, ipData.tightBox.hiX+tolerance, ipData.tightBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox] THEN {
ipHitData: IPHitData;
ipParts: IPParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[ipData.inverse, testPoint];
localTolerance: REALABS[tolerance*MAX[ipData.inverseScale.x, ipData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← GGBoundBox.NearestPoint[ipData.localBox, localTestpoint, localTolerance, ipParts.points];
IF success THEN {
bestPoint ← GGTransform.Transform[ipData.transform, bestPoint];
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
hitData ← ipHitData ← NEW[IPHitDataObj ← [point: index, edge: -1, hitPoint: bestPoint] ];
};
};
};
IPClosestSegment: PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance: REAL] RETURNS [bestPoint: Point, bestDist: REAL, hitData: REF ANY, success: BOOLFALSE] = {
GGModelTypes.SliceClosestSegmentProc
hitData ← NIL; -- automatically done by compiler
slice: Slice ← sliceD.slice;
ipData: IPData ← NARROW[slice.data];
toleranceBox: GGBasicTypes.BoundBoxObj ← [ipData.tightBox.loX-tolerance, ipData.tightBox.loY-tolerance, ipData.tightBox.hiX+tolerance, ipData.tightBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox] THEN {
ipHitData: IPHitData;
index: NAT ← 9999;
ipData: IPData ← NARROW[slice.data];
ipParts: IPParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[ipData.inverse, testPoint]; -- transfrom point to "IP space"
localTolerance: REALABS[tolerance*MAX[ipData.inverseScale.x, ipData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← GGBoundBox.NearestSegment[ipData.localBox, localTestpoint, localTolerance, ipParts.points];
IF success THEN {
bestPoint ← GGTransform.Transform[ipData.transform, bestPoint];
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
hitData ← ipHitData ← NEW[IPHitDataObj ← [point: -1, edge: index, hitPoint: bestPoint] ];
};
};
};
fontExtentRope: Rope.ROPE; -- filled in by Init
Init: PROC = {
fontExtentRope ← "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
};
Init[];
END.