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
BoundBox: TYPE = GGModelTypes.BoundBox;
CameraData: TYPE = GGInterfaceTypes.CameraData;
Color: TYPE = Imager.Color;
DefaultData: TYPE = GGModelTypes.DefaultData;
ExtendMode: TYPE = GGModelTypes.ExtendMode;
FeedbackData: TYPE = AtomButtonsTypes.FeedbackData;
GGData: TYPE = GGInterfaceTypes.GGData;
GargoyleDataObj: TYPE = GGInterfaceTypes.GargoyleDataObj;
Point: TYPE = GGBasicTypes.Point;
PointGenerator: TYPE = GGModelTypes.PointGenerator;
PointGeneratorObj: TYPE = GGModelTypes.PointGeneratorObj;
PointPairGenerator: TYPE = GGModelTypes.PointPairGenerator;
PointPairGeneratorObj: TYPE = GGModelTypes.PointPairGeneratorObj;
Scene: TYPE = GGModelTypes.Scene;
SelectedObjectData: TYPE = GGModelTypes.SelectedObjectData;
SelectionClass: TYPE = GGInterfaceTypes.SelectionClass;
SelectMode: TYPE = GGModelTypes.SelectMode;
Slice: TYPE = GGModelTypes.Slice;
SliceClass: TYPE = GGModelTypes.SliceClass;
SliceClassObj: TYPE = GGModelTypes.SliceClassObj;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj;
SliceObj: TYPE = GGModelTypes.SliceObj;
SliceParts: TYPE = GGModelTypes.SliceParts;
Vector: TYPE = GGBasicTypes.Vector;
Viewer: TYPE = ViewerClasses.Viewer;
FontData: TYPE = GGFont.FontData;
textMaxPoints: INTEGER = 12;
textMaxEdges:
INTEGER = 7;
TextPoints: TYPE = REF TextPointsData;
TextPointsData:
TYPE =
ARRAY [0..textMaxPoints)
OF Point;
-- see picture
TextEdgeArray: TYPE = PACKED ARRAY [0..textMaxEdges) OF BOOL; -- left, top, right, bottom, baseline, centerline.
TextPointArray:
TYPE =
PACKED
ARRAY [0..textMaxPoints)
OF
BOOL;
-- see picture. Used in text parts data.
textPointRopes:
ARRAY [0..textMaxPoints)
OF Rope.
ROPE = [
"lower left point", "base left point", "center left point", "upper left point",
"lower mid point", "base mid point", "center mid point", "upper mid point",
"lower right point", "base right point", "center right point", "upper right point"
];
textEdgeRopes:
ARRAY [0..textMaxEdges)
OF Rope.
ROPE = [
"left edge", "top edge", "right edge", "bottom edge",
"baseline", "horizontal center line", "vertical center line"
];
TextData: TYPE = REF TextDataObj;
TextDataObj:
TYPE =
RECORD [
box: BoundBox, -- not a bounding box but part of the text representation
feedbackBox: BoundBox, -- both a bounding box and part of the text representation
points: TextPoints, -- cache for "joints"
rope: Rope.ROPE,
color: Imager.Color,
fontData: FontData,
trueFont: ImagerFont.Font,
screenFont: ImagerFont.Font,
inverse: ImagerTransformation.Transformation, -- inverse of transform
inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s
amplifySpace: REAL ← 1.0, -- because the Imager allows space correction (see Imager.mesa)
dropShadowOn: BOOL ← FALSE,
dropShadowOffset: Vector,
shadowColor: Imager.Color
];
TextParts: TYPE = REF TextPartsObj;
TextPartsObj:
TYPE =
RECORD [
points: TextPointArray, -- which corners of text box are selected.
edges: TextEdgeArray, -- which edges of text box are selected.
includeText: BOOL -- FALSE => corners and edges only; no text
];
TextHitData: TYPE = REF TextHitDataObj;
TextHitDataObj:
TYPE =
RECORD [
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,
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:
BOOL ←
FALSE, dropShadowOffset: Vector ← [0.0, 0.0], shadowColor: Imager.Color ← Imager.black]
RETURNS [slice: Slice] = {
textData: TextData ←
NEW[TextDataObj ← [
box: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox
feedbackBox: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox
inverseScale: [-1.0, -1.0], -- dummy vector
amplifySpace: amplifySpace,
points: NEW[TextPointsData],
fontData: GGFont.CreateFontData[], -- local storage
color: Imager.black,
rope: text,
dropShadowOn: dropShadowsOn,
dropShadowOffset: dropShadowOffset,
shadowColor: shadowColor
]];
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.ROPE ← NARROW[slice.data, TextData].rope;
RETURN[Rope.Length[rope]=IO.SkipWhitespace[IO.RIS[rope]] ];
};
};
GetText:
PUBLIC
PROC [slice: Slice]
RETURNS [text: Rope.
ROPE] = {
RETURN[IF slice.class.type#$Text THEN NIL ELSE NARROW[slice.data, TextData].rope];
};
AppendText:
PUBLIC
PROC [slice: Slice, text: Rope.
ROPE] = {
textData: TextData;
IF slice.class.type#$Text THEN RETURN;
textData ← NARROW[slice.data];
textData.rope ← Rope.Concat[textData.rope, text]; -- update text
TextSetBoundBox[slice];
};
BackspaceText:
PUBLIC
PROC [slice: Slice] = {
textData: TextData;
IF slice.class.type#$Text THEN RETURN;
textData ← NARROW[slice.data];
textData.rope ← Rope.Substr[base: textData.rope, len: Rope.Length[textData.rope]-1]; -- update text
TextSetBoundBox[slice];
};
DigitBreak:
IO.BreakProc = {
RETURN[IF char IN ['0..'9] THEN break ELSE other];
};
TFontParamsFromFontName:
PROC [fontName: Rope.
ROPE]
RETURNS [fontFamily: Rope.
ROPE, fontFace: Rope.
ROPE, fontSize:
REAL] = {
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.STREAM ← IO.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:
BOOL ←
TRUE] = {
RETURN[SetTextFontAux[slice, fontData, feedback, FALSE]];
};
SetTextFontAndTransform:
PUBLIC
PROC [slice: Slice, fontData: FontData, feedback: FeedbackData]
RETURNS [ success:
BOOL ←
TRUE] = {
RETURN[SetTextFontAux[slice, fontData, feedback, TRUE]];
};
SetTextFontAux:
PROC [slice: Slice, fontData: FontData, feedback: FeedbackData, andTransform:
BOOL ←
FALSE]
RETURNS [ success:
BOOL ←
TRUE] = {
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]] ];
NotFound => {
scratch: IO.STREAM ← IO.ROS[];
IF transform#NIL THEN GGParseOut.WriteFactoredTransformationVEC[scratch, transform];
Feedback.PutF[feedback, oneLiner, "No Such Font: %g %g %g %g", [rope[fontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]] ];
success ← FALSE;
};
END;
};
GetFontData:
PUBLIC
PROC [slice: Slice]
RETURNS [fontData: FontData] = {
textData: TextData;
IF slice.class.type#$Text THEN RETURN[NIL];
textData ← NARROW[slice.data];
RETURN[textData.fontData];
};
GetFontDataRope:
PUBLIC
PROC [slice: Slice]
RETURNS [Rope.
ROPE] = {
textData: TextData;
fontData: FontData;
scratch: IO.STREAM ← IO.ROS[];
IF slice.class.type#$Text THEN RETURN[NIL];
textData ← NARROW[slice.data];
fontData ← textData.fontData;
GGParseOut.WriteFactoredTransformationVEC[scratch, fontData.transform];
IF fontData.comfortable THEN RETURN[IO.PutFLR["%g%g %g %g %g %g", LIST[ [rope[textData.fontData.prefix]], [rope[textData.fontData.userFSF]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]], [rope[IF fontData.substituted THEN "SUBSTITUTED" ELSE ""]] ]] ]
ELSE RETURN[IO.PutFR["%g %g %g %g %g", [rope[fontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]], [rope[IF fontData.substituted THEN "SUBSTITUTED" ELSE ""]] ] ];
};
GetFontLiteralDataRope:
PUBLIC
PROC [slice: Slice]
RETURNS [Rope.
ROPE] = {
textData: TextData;
fontData: FontData;
scratch: IO.STREAM ← IO.ROS[];
IF slice.class.type#$Text THEN RETURN[NIL];
textData ← NARROW[slice.data];
fontData ← textData.fontData;
GGParseOut.WriteFactoredTransformationVEC[scratch, fontData.transform];
RETURN[IO.PutFR["%g %g %g %g %g", [rope[fontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[fontData.storedSize]], [real[fontData.designSize]], [rope[IF fontData.substituted THEN "SUBSTITUTED" ELSE ""]] ] ];
};
SetTextAmplifySpace:
PUBLIC
PROC [slice: Slice, amplifySpace:
REAL, feedback: FeedbackData] = {
textData: TextData;
textData ← NARROW[slice.data];
textData.amplifySpace ← amplifySpace;
TextSetBoundBox[slice];
};
GetTextAmplifySpace:
PUBLIC
PROC [slice: Slice]
RETURNS [amplifySpace:
REAL] = {
textData: TextData;
textData ← NARROW[slice.data];
RETURN[textData.amplifySpace];
};
DropShadowOn:
PUBLIC
PROC [slice: Slice, offset: Vector] = {
textData: TextData ← NARROW[slice.data];
textData.dropShadowOn ← TRUE;
textData.dropShadowOffset ← offset;
};
DropShadowOff:
PUBLIC
PROC [slice: Slice] = {
textData: TextData ← NARROW[slice.data];
textData.dropShadowOn ← FALSE
};
UnpackComplexFontName:
PROC [fontName: Rope.
ROPE, fontSize:
REAL]
RETURNS [new: Rope.
ROPE] = {
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:
INT ←
INT.
LAST]
RETURNS [ImagerFont.
VEC] ~ {
string: ImagerFont.XStringProc ~ { ImagerFont.MapRope[rope, start, len, charAction] };
RETURN[StringEscapement[font, string, amplifySpace]];
};
TextSetBoundBox:
PROC [slice: Slice] = {
textData: TextData ← NARROW[slice.data];
fontData: FontData ← textData.fontData;
fontExtents: ImagerFont.Extents;
halfCP: REAL = GGModelTypes.halfJointSize + 1;
left, right, middle, top, base, bottom, center:
REAL;
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];
};
Text
DrawTransform:
PROC [sliceD: SliceDescriptor, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceDrawTransformProc
DoText
DrawTransform:
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: BOOL ← FALSE;
size: REAL ← 1.0;
preferredSize: REAL ← 1.0;
transform: ImagerTransformation.Transformation;
shadowColor: Imager.Color ← Imager.black;
textColor: Imager.Color ← Imager.black;
dropShadowOn, good: BOOL ← FALSE;
dropShadowOffset: Vector ← [0.0, 0.0];
amplifySpace:
REAL ← 1.0;
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: BOOL ← FALSE;
[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];
};
};
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:
BOOL ←
FALSE] = {
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: REAL ← ABS[tolerance*MAX[textData.inverseScale.x, textData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← TextNearestPoint[textData.points, localTestpoint, localTolerance, textParts.points];
IF success
THEN {
bestPoint ← GGTransform.Transform[textData.fontData.transform, bestPoint];
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
hitData ← NEW[TextHitDataObj ← [point: index, edge: -1, hitPoint: bestPoint] ];
};
};
};
TextClosestSegment:
PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance:
REAL]
RETURNS [bestPoint: Point, bestDist:
REAL, hitData:
REF
ANY, success:
BOOL ←
FALSE] = {
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: REAL ← ABS[tolerance*MAX[textData.inverseScale.x, textData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, seg, bestPoint, success] ← TextNearestEdge[textData.points, localTestpoint, localTolerance, textParts.edges];
IF success
THEN {
bestPoint ← GGTransform.Transform[textData.fontData.transform, bestPoint];
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
hitData ← textHitData ← NEW[TextHitDataObj ← [point: -1, edge: seg, hitPoint: bestPoint] ];
};
};
};
TextNearestPoint:
PROC [points: TextPoints, testPoint: Point, tolerance:
REAL ← 1E6, mask: TextPointArray ]
RETURNS [bestDist:
REAL, bestPoint:
NAT, bestXY: Point, success:
BOOL ←
FALSE] = {
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:
BOOL ←
FALSE] = {
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;
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: BOOL ← FALSE -- FALSE => corners and edges only; no innards
];
IPHitData: TYPE = REF IPHitDataObj;
IPHitDataObj:
TYPE =
RECORD [
point: [-1..ipMaxPoints), -- which point of box is hit.
edge: INTEGER, -- which edge of box is hit.
hitPoint: Point
];
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 ← [
getBoundBox: IPBoundBox,
getTightBox: IPTightBox,
drawParts: IPDrawParts,
drawTransform: IPDrawTransform,
drawSelectionFeedback: IPDrawSelectionFeedback,
drawAttractorFeedback: NoOpDrawAttractorFeedback,
Transforming
transform: IPTransform,
Textual Description
describe: IPDescribe,
describeHit: IPDescribeHit,
fileout: IPFileout,
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: BOOL ← FALSE;
[ipMaster, success] ← GGUtility.OpenInterpressOrComplain[feedback, fullName];
IF NOT success THEN RETURN;
slice ← MakeIPSliceFromMaster[ipMaster, 2834.646, fullName, feedback, transform, localBox, includeByValue];
MakeIPSliceFromMaster:
PUBLIC
PROC [ipMaster: Interpress.Master, pixelsPerUnit:
REAL ← 2834.646, fullName: Rope.
ROPE ←
NIL, feedback: FeedbackData, transform: ImagerTransformation.Transformation ←
NIL, localBox: BoundBox ←
NIL, includeByValue:
BOOL]
RETURNS [slice: Slice] = {
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];
};
IP
DrawTransform:
PROC [sliceD: SliceDescriptor, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceDrawTransformProc
DoIP
DrawTransform:
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: BOOL ← FALSE;
transform: ImagerTransformation.Transformation;
masterText: REF TEXT;
masterSize, nBytesRead: NAT;
masterStream: IO.STREAM;
includeByValue: BOOL;
localBox: BoundBox;
fullName ← f.GetRopeLiteral[];
[fullName] ← FS.FileInfo[fullName]; -- add the version number
transform ← GGParseIn.ReadTransformation[f];
IF version >= 8701.28
THEN {
localBox ← GGParseIn.ReadBox[f];
}
ELSE localBox ← NIL;
IF version >= 8612.04
THEN {
goodValue: BOOL;
GGParseIn.ReadBlankAndRope[f, "includeByValue:"];
[includeByValue, goodValue] ← GGParseIn.ReadBOOL[f, version];
IF NOT goodValue THEN ERROR;
IF includeByValue
THEN {
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:
BOOL ←
FALSE] = {
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: REAL ← ABS[tolerance*MAX[ipData.inverseScale.x, ipData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← GGBoundBox.NearestPoint[ipData.localBox, localTestpoint, localTolerance, ipParts.points];
IF success
THEN {
bestPoint ← GGTransform.Transform[ipData.transform, bestPoint];
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
hitData ← ipHitData ← NEW[IPHitDataObj ← [point: index, edge: -1, hitPoint: bestPoint] ];
};
};
};
IPClosestSegment:
PROC [sliceD: SliceDescriptor, testPoint: Point, tolerance:
REAL]
RETURNS [bestPoint: Point, bestDist:
REAL, hitData:
REF
ANY, success:
BOOL ←
FALSE] = {
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: REAL ← ABS[tolerance*MAX[ipData.inverseScale.x, ipData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← GGBoundBox.NearestSegment[ipData.localBox, localTestpoint, localTolerance, ipParts.points];
IF success
THEN {
bestPoint ← GGTransform.Transform[ipData.transform, bestPoint];
bestDist ← Vectors2d.Distance[testPoint, bestPoint];
hitData ← ipHitData ← NEW[IPHitDataObj ← [point: -1, edge: index, hitPoint: bestPoint] ];
};
};
};
fontExtentRope: Rope.ROPE; -- filled in by Init
Init:
PROC = {
fontExtentRope ← "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
};
Init[];
END.