GGSliceImplB.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last edited by Pier on November 19, 1986 6:29:39 pm PST
Last edited by Bier on January 15, 1987 1:23:35 am PST
Contents: Implements various slice classes in Gargoyle.
DIRECTORY
Atom, Convert, FileNames, FS, GGBasicTypes, GGBoundBox, GGError, GGFromImager, GGInterfaceTypes, GGLines, GGModelTypes, GGObjects, GGParseOut, GGParseIn, GGSlice, GGShapes, GGTransform, GGUtility, GGVector, Imager, ImagerFont, ImagerInterpress, ImagerMemory, ImagerTransformation, Interpress, IO, IPMaster, NodeStyle, NodeStyleFont, RealFns, RefText, Rope, ViewerClasses;
GGSliceImplB:
CEDAR
PROGRAM
IMPORTS Atom, Convert, FileNames, FS, GGBoundBox, GGError, GGFromImager, GGLines, GGObjects, GGParseIn, GGParseOut, GGSlice, GGShapes, GGTransform, GGUtility, GGVector, Imager, ImagerFont, ImagerInterpress, ImagerMemory, ImagerTransformation, Interpress, IO, NodeStyleFont, RealFns, RefText, Rope EXPORTS GGSlice = BEGIN
BoundBox: TYPE = GGModelTypes.BoundBox;
CameraData: TYPE = GGInterfaceTypes.CameraData;
Color: TYPE = Imager.Color;
DisplayStyle: TYPE = GGSlice.DisplayStyle;
ExtendMode: TYPE = GGModelTypes.ExtendMode;
GargoyleData: TYPE = GGInterfaceTypes.GargoyleData;
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;
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.
TextPoints: TYPE = REF TextPointsData;
TextPointsData: TYPE = ARRAY [0..textMaxPoints) OF Point; -- see picture
textMaxPoints: INTEGER = 12;
textMaxEdges: INTEGER = 7;
TextData: TYPE = REF TextDataObj;
TextDataObj:
TYPE =
RECORD [
box: BoundBox ← NIL, -- not a bounding box but part of the text representation
feedbackBox: BoundBox ← NIL, -- both a bounding box and part of the text representation
transform: ImagerTransformation.Transformation,
amplifySpace: REAL, -- because the Imager allows space correction (see Imager.mesa)
inverse: ImagerTransformation.Transformation, -- inverse of transform
inverseScale: Vector, -- cached value of ImagerTransformation.Factor[inverse].s
points: TextPoints, -- cache for "joints"
color: Imager.Color ← NIL,
rope: Rope.ROPE ← NIL,
fontName: Rope.ROPE ← NIL, -- font description: e.g. Helvetica11BI
fontFamily: ATOM,
fontFace: NodeStyle.FontFace ← Regular,
fontSize: REAL ← 10.0,
printFont: ImagerFont.Font ← NIL,
screenFont: ImagerFont.Font ← NIL,
dropShadowOn: BOOL ← FALSE,
dropShadowOffset: Vector,
shadowColor: Imager.Color ← NIL
];
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,
fileout: TextFileout,
filein: TextFilein,
Parts
emptyParts: TextEmptyParts,
newParts: TextNewParts,
unionParts: TextUnionParts,
differenceParts: TextDiffParts,
movingParts: TextMovingParts,
fixedParts: TextFixedParts,
augmentParts: TextAugmentParts,
Hit Testing
pointsInDescriptor: TextPointsInDescriptor,
pointPairsInDescriptor: TextPointPairsInDescriptor,
nextPoint: TextNextPoint,
nextPointPair: TextNextPointPair,
closestPoint: TextClosestPoint,
closestPointAndTangent: NIL,
closestSegment: TextClosestSegment,
lineIntersection: NoOpLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: NoOpHitDataAsSimpleCurve,
Style
setStrokeWidth: TextSetStrokeWidth,
getStrokeWidth: TextGetStrokeWidth,
setStrokeColor: TextSetStrokeColor,
getStrokeColor: TextGetStrokeColor,
setFillColor: TextSetFillColor,
getFillColor: TextGetFillColor
]];
};
MakeTextSlice:
PUBLIC
PROC [text: Rope.
ROPE, fontName: Rope.
ROPE, color: Imager.Color ← Imager.black, transform: ImagerTransformation.Transformation, amplifySpace:
REAL, dropShadowsOn:
BOOL ←
FALSE, dropShadowOffset: Vector ← [0.0, 0.0], shadowColor: Imager.Color ← Imager.black, feedback: Viewer]
RETURNS [slice: Slice] = {
inverse: ImagerTransformation.Transformation ← ImagerTransformation.Invert[transform];
textData: TextData ←
NEW[TextDataObj ← [
box: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox
feedbackBox: GGBoundBox.NullBoundBox[], -- filled in by call to TextSetBoundBox
transform: transform,
amplifySpace: amplifySpace,
inverse: inverse,
inverseScale: ImagerTransformation.Factor[inverse].s,
points: NEW[TextPointsData], -- filled in by call to TextSetBoundBox
color: color,
rope: text,
fontName: NIL, -- font description: e.g. Helvetica11BI
dropShadowOn: dropShadowsOn,
dropShadowOffset: dropShadowOffset,
shadowColor: shadowColor
]];
other font related values to be filled in by 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
]];
SetTextFont[slice, fontName, feedback]; -- updates the boundBox
};
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];
};
FontNameFromFontParams:
PROC [fontFamily:
ATOM, fontFace: NodeStyle.FontFace, fontSize:
REAL]
RETURNS [fontName: Rope.
ROPE ←
NIL] = {
sizeRope: Rope.ROPE ← Convert.RopeFromReal[fontSize];
faceRope: Rope.
ROPE ←
SELECT fontFace
FROM
BoldItalic => "BI",
Bold => "B",
Italic => "I",
ENDCASE => "";
fontName ← Rope.Cat[Atom.GetPName[fontFamily], sizeRope, faceRope];
};
FontParamsFromFontName:
PUBLIC
PROC [fontName: Rope.
ROPE]
RETURNS [fontFamily:
ATOM, fontFace: NodeStyle.FontFace, 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
};
};
familyName, face: Rope.ROPE;
bold, italic: BOOL ← FALSE;
fontStream: IO.STREAM ← IO.RIS[fontName];
[familyName, ----] ← 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 ← ""; CONTINUE; };];
fontFamily ← Atom.MakeAtom[familyName];
SELECT Rope.Length[face]
FROM
0 => NULL;
1 => {
char: CHAR ← Rope.Fetch[base: face, index: 0];
bold ← char='B OR char='b;
italic ← char='I OR char='i;
};
2 => {
char0: CHAR ← Rope.Fetch[base: face, index: 0];
char1: CHAR ← Rope.Fetch[base: face, index: 1];
bold ← char0='B OR char0='b OR char1='B OR char1='b;
italic ← char0='I OR char0='i OR char1='I OR char1='i;
};
ENDCASE => NULL;
fontFace ←
SELECT
TRUE
FROM
bold AND italic => BoldItalic,
bold => Bold,
italic => Italic,
ENDCASE => Regular;
};
SetTextFont:
PUBLIC
PROC [slice: Slice, fontName: Rope.
ROPE, feedback: Viewer] = {
A fontName is a font designation with family, size, and face, like Helvetica23BI or TimesRoman6
textData: TextData;
fontFamily: ATOM;
fontFace: NodeStyle.FontFace;
fontSize: REAL;
IF slice.class.type#$Text OR fontName=NIL THEN RETURN;
textData ← NARROW[slice.data];
[fontFamily, fontFace, fontSize] ← FontParamsFromFontName[fontName ! FontNameError => GOTO Abort; ];
IF fontSize=0.0 THEN ERROR;
IF fontSize=0.0
THEN {
scale: REAL ← ImagerTransformation.Factor[textData.transform].s.x;
GGError.Append[feedback, Rope.Cat["Font size 0.0 ignored for ", fontName], oneLiner];
GGError.Blink[feedback] ;
Now, what do you want to do?? Answer: see some text in your picture. How?? Answer: manufacture a font size that will be big enough to see.
fontSize ← 10.0*(IF scale>1.0 THEN scale ELSE 1.0/scale); -- oh, well
};
textData.printFont ← NodeStyleFont.FontFromStyleParams[prefix: printPrefix, family: fontFamily, face: fontFace, size: fontSize, alphabets: CapsAndLower];
IF Rope.Find[s1: textData.
printFont.name, s2: "xerox/xc2-2-2/modern", case:
FALSE]#-1
THEN {
FontFromStyleParams has failed to find the requested font and substituted "xerox/xc2-2-2/modern". We don't want a Xerox font for a print font, so we will substitute Xerox/PressFonts/TimesRoman instead
oldName: Rope.ROPE;
fontFamily ← $TimesRoman;
fontFace ← Regular;
oldName ← fontName;
fontName ← FontNameFromFontParams[fontFamily, fontFace, fontSize];
textData.printFont ← NodeStyleFont.FontFromStyleParams[prefix: printPrefix, family: fontFamily, face: fontFace, size: fontSize, alphabets: CapsAndLower];
GGError.Append[feedback, Rope.Cat["Font ", textData.printFont.name, " used for ", oldName], oneLiner];
GGError.Blink[feedback];
};
textData.screenFont ← NodeStyleFont.FontFromStyleParams[prefix: screenPrefix, family: fontFamily, face: fontFace, size: fontSize, alphabets: CapsAndLower];
IF Rope.Find[s1: textData.screenFont.name, s2: "Tioga10", case: FALSE]#-1 THEN textData.screenFont ← textData.printFont; -- undesired font substitution
textData.fontName ← fontName;
textData.fontFamily ← fontFamily;
textData.fontFace ← fontFace;
textData.fontSize ← fontSize;
TextSetBoundBox[slice];
EXITS
Abort => {
GGError.Append[feedback, Rope.Concat["FontNameError: ", fontName], oneLiner];
GGError.Blink[feedback] ;
};
};
GetTextFont:
PUBLIC
PROC [slice: Slice]
RETURNS [font: ImagerFont.Font, fontName: Rope.
ROPE] = {
textData: TextData;
IF slice.class.type#$Text THEN RETURN[NIL, NIL];
textData ← NARROW[slice.data];
RETURN[textData.printFont, textData.fontName];
};
SetTextAmplifySpace:
PUBLIC
PROC [slice: Slice, amplifySpace:
REAL, feedback: ViewerClasses.Viewer] = {
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
];
};
Text slice class procs
TextBoundBox:
PROC [slice: Slice, parts: SliceParts]
RETURNS [box: BoundBox]= {
GGModelTypes.SliceBoundBoxProc
RETURN[slice.boundBox];
};
TextTightBox:
PROC [slice: Slice, parts: SliceParts]
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.Width[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];
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.screenFont, textData.rope, textData.amplifySpace].x;
middle ← (left + right)/2.0;
fontExtents ← ImagerFont.RopeBoundingBox[textData.screenFont, "acbdefghijklmnopqrstuvwxyzACBDEFGHIJKLMNOPQRSTUVWXYZ"];
top ← fontExtents.ascent;
base ← 0.0;
bottom ← -fontExtents.descent;
center ← (top + bottom)/2.0;
GGBoundBox.UpdateBoundBox[textData.box, left, bottom, right, top];
GGBoundBox.EnlargeByOffset[textData.box, halfCP];
textData.box ← GGBoundBox.BoundBoxOfBoundBox[textData.box, textData.transform];
GGBoundBox.AllowForJoints[textData.box];
IF textData.dropShadowOn THEN GGBoundBox.EnlargeByVector[textData.box, textData.dropShadowOffset];
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.printFont, textData.rope, textData.amplifySpace].x;
middle ← (left + right)/2.0;
fontExtents ← ImagerFont.RopeBoundingBox[textData.printFont, "acbdefghijklmnopqrstuvwxyzACBDEFGHIJKLMNOPQRSTUVWXYZ"];
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, textData.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
textData: TextData ← NARROW[slice.data];
copy ← MakeTextSlice[textData.rope, textData.fontName, textData.color, ImagerTransformation.Copy[textData.transform], textData.amplifySpace, textData.dropShadowOn, textData.dropShadowOffset, textData.shadowColor, NIL];
};
Drawing
TextDrawParts:
PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, quick:
BOOL] = {
GGModelTypes.SliceDrawPartsProc
DoDrawText:
PROC = {
Imager.ConcatT[dc, textData.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 ← textData.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];
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.printFont];
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 = {
Imager.TranslateT[dc, textData.dropShadowOffset];
Imager.SetFont[dc, IF camera.displayStyle=screen THEN textData.screenFont ELSE textData.printFont];
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.ShowRope[dc, textData.rope];
};
Imager.DoSaveAll[dc, DoDrawDropShadow];
};
Text
DrawTransform:
PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceDrawTransformProc
DoText
DrawTransform:
PROC = {
Imager.ConcatT[dc, ImagerTransformation.Concat[textData.transform, transform]];
IF textData.dropShadowOn THEN DrawDropShadow[dc, textData, camera];
DrawText[dc, textData, camera];
};
textData: TextData ← NARROW[slice.data];
Imager.DoSaveAll[dc, DoTextDrawTransform];
};
Transforming
TextTransform:
PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceTransformProc
textData: TextData ← NARROW[slice.data];
textData.transform ← ImagerTransformation.Concat[textData.transform, transform];
textData.inverse ← ImagerTransformation.Invert[textData.transform];
textData.inverseScale ← ImagerTransformation.Factor[textData.inverse].s;
TextSetBoundBox[slice];
};
Textual Description
TextDescribe:
PROC [slice: Slice, parts: SliceParts]
RETURNS [rope: Rope.
ROPE] = {
GGModelTypes.SliceDescribeProc
prefix: Rope.ROPE;
textParts: TextParts ← NARROW[parts];
eCount, pCount: NAT ← 0;
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"];
};
TextFileout:
PROC [slice: Slice, f:
IO.
STREAM] = {
GGModelTypes.SliceFileoutProc
Write a description of yourself onto stream f.
textData: TextData ← NARROW[slice.data];
f.PutF["\"%q\" %g %g", [rope[textData.rope]], [rope[textData.fontName]], [real[textData.fontSize]] ];
f.PutChar[IO.SP];
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.PutChar[IO.SP];
GGParseOut.WriteTransformation[f, textData.transform];
f.PutChar[IO.SP];
f.PutF["%g", [real[textData.amplifySpace]]];
};
TextFilein:
PROC [f:
IO.
STREAM, version:
REAL, feedback: Viewer]
RETURNS [slice: Slice] = {
GGModelTypes.SliceFileinProc
Read a description of yourself from stream f.
rope, fontName: Rope.ROPE;
fontSize: REAL;
point: Point;
shadowColor: Imager.Color ← Imager.black;
textColor: Imager.Color ← Imager.black;
dropShadowOn, good: BOOL;
dropShadowOffset: Vector ← [0.0, 0.0];
amplifySpace: REAL;
transform: ImagerTransformation.Transformation;
Strings now surronded by quotes. Formerly surrounded by parens.
IF version > 8601.22
THEN {
rope ← f.GetRopeLiteral[];
}
ELSE {
GGParseIn.ReadBlankAndRope[f, "("];
rope ← GGParseIn.ReadBlankAndWord[f];
GGParseIn.ReadBlankAndRope[f, ")"];
};
We keep evoloving font names
IF version <= 8601.06
THEN {
-- no font name at all
fontName ← "Helvetica10";
fontSize ← 10.0;
}
ELSE
IF version <= 8601.27
THEN {
-- a simple name like "Helvetica" or "Gacha"
fontName ← GGParseIn.ReadBlankAndWord[f];
fontName ← Rope.Concat[fontName, "10"];
fontSize ← 10.0;
}
ELSE
IF version <= 8605.12
THEN {
-- a complex name like "xerox/pressfonts/Helvetica-BIR"
fontName ← GGParseIn.ReadBlankAndWord[f];
fontSize ← GGParseIn.ReadBlankAndReal[f];
[] ← GGParseIn.ReadBlankAndWord[f];
fontName ← UnpackComplexFontName[fontName, fontSize];
}
ELSE
IF version <= 8605.28
THEN {
-- a mixed mode name like Helvetica7BI or TimesRoman12
fontName ← GGParseIn.ReadBlankAndWord[f];
fontSize ← GGParseIn.ReadBlankAndReal[f];
[] ← GGParseIn.ReadBlankAndWord[f];
}
ELSE {
-- a mixed mode name like Helvetica7BI or TimesRoman12. No colorName, real text color
fontName ← GGParseIn.ReadBlankAndWord[f];
fontSize ← GGParseIn.ReadBlankAndReal[f];
GGParseIn.ReadBlank[f];
textColor ← GGParseIn.ReadColor[f];
};
Handle Drop Shadows
GGParseIn.ReadBlank[f];
IF version >= 8609.26
THEN {
[dropShadowOn, good] ← GGParseIn.ReadBOOL[f];
IF NOT good THEN ERROR;
IF dropShadowOn
THEN {
GGParseIn.ReadBlank[f];
dropShadowOffset ← GGParseIn.ReadPoint[f];
GGParseIn.ReadBlank[f];
shadowColor ← GGParseIn.ReadColor[f];
};
}
ELSE dropShadowOn ←
FALSE;
GGParseIn.ReadBlank[f];
IF version <= 8607.22
THEN {
-- a point rather than a transform
point ← GGParseIn.ReadPoint[f];
transform ← ImagerTransformation.Translate[point];
}
ELSE transform ← GGParseIn.ReadTransformation[f];
IF version >= 8701.13
THEN {
amplifySpace ← GGParseIn.ReadBlankAndReal[f];
}
ELSE amplifySpace ← 1.0;
slice ← MakeTextSlice[rope, fontName, textColor, transform, amplifySpace, dropShadowOn, dropShadowOffset, shadowColor, feedback];
};
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;
};
TextEmptyParts:
PROC [slice: Slice, parts: SliceParts]
RETURNS [
BOOL] = {
GGModelTypes.SliceEmptyPartsProc
RETURN[IsEmpty[parts]];
};
TextNewParts:
PROC [slice: Slice, hitData:
REF
ANY, mode: SelectMode]
RETURNS [parts: SliceParts] = {
GGModelTypes.SliceNewPartsProc
textHitData: TextHitData ← NARROW[hitData];
textParts: TextParts ← NEW[TextPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE], includeText: FALSE ] ];
SELECT mode
FROM
joint => {
IF textHitData.point#-1
THEN {
textParts.points[textHitData.point] ← TRUE;
}
ELSE {
hitData: REF ANY;
pointHitData: TextHitData;
success: BOOL;
wholeParts: SliceParts;
wholeD: SliceDescriptor;
wholeParts ← TextNewParts[slice, NIL, topLevel];
wholeD ← NEW[SliceDescriptorObj ← [slice, wholeParts]];
[----, ----, 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 => {
-- prefer corner to edge if both are true
IF textHitData.point#-1 THEN textParts.points[textHitData.point] ← TRUE
ELSE IF textHitData.edge#-1 THEN textParts.edges[textHitData.edge] ← TRUE;
};
ENDCASE => ERROR;
parts ← textParts; -- RETURN[textParts]
};
TextUnionParts:
PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts]
RETURNS [aPlusB: SliceParts] = {
GGModelTypes.SliceUnionPartsProc
textPartsA: TextParts ← NARROW[partsA];
textPartsB: TextParts ← NARROW[partsB];
newParts: TextParts ← 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 ← newParts; -- RETURN[newParts]
};
TextDiffParts:
PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts]
RETURNS [aMinusB: SliceParts] = {
textPartsA: TextParts ← NARROW[partsA];
textPartsB: TextParts ← NARROW[partsB];
newParts: TextParts ← NEW[TextPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE], includeText: FALSE ] ];
IF partsB=NIL THEN {newParts^ ← textPartsA^; aMinusB ← newParts; RETURN;};
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 ← newParts; -- RETURN[newParts]
};
TextMovingParts:
PROC [slice: Slice, parts: SliceParts]
RETURNS [moving: SliceParts] = {
GGModelTypes.SliceMovingPartsProc
If anything is moving, everything is moving
newParts: TextParts ← NEW[TextPartsObj ← [points: ALL[TRUE], edges: ALL[TRUE], includeText: TRUE ] ];
IF IsEmpty[parts]
THEN {
-- nothing at all is moving
newParts.edges ← ALL[FALSE];
newParts.points ← ALL[FALSE];
newParts.includeText ← FALSE;
};
moving ← newParts; -- RETURN[newParts]
};
TextFixedParts:
PROC [slice: Slice, parts: SliceParts, selectedList:
LIST
OF
REF
ANY]
RETURNS [fixed: SliceParts] = {
GGModelTypes.SliceFixedPartsProc
Start with every part and remove the ones that are moving
moving: SliceParts ← TextMovingParts[slice, parts];
allParts: TextParts ← NEW[TextPartsObj];
MakeComplete[allParts];
fixed ← TextDiffParts[slice, allParts, moving];
};
TextAugmentParts:
PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass]
RETURNS [more: SliceParts] = {
GGModelTypes.SliceAugmentPartsProc
textParts: TextParts ← NARROW[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 ← 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];
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[textData.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.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 GGError.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.transform, bestPoint];
bestDist ← GGVector.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.transform, bestPoint];
bestDist ← GGVector.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 ← GGVector.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 ← GGLines.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 => GGLines.FillEdge[points[0], points[3], scratchEdge ];
1 => GGLines.FillEdge[points[3], points[11], scratchEdge ];
2 => GGLines.FillEdge[points[11], points[8], scratchEdge ];
3 => GGLines.FillEdge[points[8], points[0], scratchEdge ];
4 => GGLines.FillEdge[points[1], points[9], scratchEdge ];
5 => GGLines.FillEdge[points[2], points[10], scratchEdge ];
6 => GGLines.FillEdge[points[4], points[7], scratchEdge ];
ENDCASE => ERROR;
thisPoint ← GGLines.NearestPointOnEdge[testPoint, scratchEdge];
thisDist2 ← GGVector.DistanceSquared[thisPoint, testPoint];
IF thisDist2 < bestDist2
THEN {
bestDist2 ← thisDist2;
bestSeg ← index;
bestPoint ← thisPoint;
};
};
ENDLOOP;
bestDist ← RealFns.SqRt[bestDist2];
IF bestDist < tolerance THEN success ← TRUE;
};
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"
];
Style
TextSetStrokeWidth:
PROC [slice: Slice, parts: SliceParts, strokeWidth:
REAL] = {};
TextGetStrokeWidth:
PROC [slice: Slice, parts: SliceParts]
RETURNS [strokeWidth:
REAL] = {
RETURN[0.0];
};
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;
};
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
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: [-1..ipMaxEdges) -- which edge of box is hit. THIS DEF CAUSES A FATAL COMPILER ERROR IN PASS FIVE !!
];
IPHitDataObj:
TYPE =
RECORD [
point: [-1..ipMaxPoints), -- which point of box is hit.
edge: INTEGER, -- which edge of box is hit.
hitPoint: Point
];
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,
fileout: IPFileout,
emptyParts: IPEmptyParts,
newParts: IPNewParts,
unionParts: IPUnionParts,
differenceParts: IPDiffParts,
movingParts: IPMovingParts,
fixedParts: IPFixedParts,
augmentParts: IPAugmentParts,
Part Generators
pointsInDescriptor: IPPointsInDescriptor,
pointPairsInDescriptor: IPPointPairsInDescriptor,
nextPoint: IPNextPoint,
nextPointPair: IPNextPointPair,
Hit Testing
closestPoint: IPClosestPoint,
closestPointAndTangent: NIL,
closestSegment: IPClosestSegment,
lineIntersection: NoOpLineIntersection,
circleIntersection: NoOpCircleIntersection,
hitDataAsSimpleCurve: NoOpHitDataAsSimpleCurve,
Style
setStrokeWidth: IPSetStrokeWidth,
getStrokeWidth: IPGetStrokeWidth,
setStrokeColor: IPSetStrokeColor,
getStrokeColor: IPGetStrokeColor,
setFillColor: IPSetFillColor,
getFillColor: IPGetFillColor
]];
};
MakeIPSliceFromFile:
PUBLIC
PROC [fullName: Rope.
ROPE, feedback: Viewer, transform: ImagerTransformation.Transformation ←
NIL, 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, includeByValue];
MakeIPSliceFromMaster:
PUBLIC
PROC [ipMaster: Interpress.Master, pixelsPerUnit:
REAL ← 2834.646, fullName: Rope.
ROPE ←
NIL, feedback: Viewer, transform: ImagerTransformation.Transformation ←
NIL, includeByValue:
BOOL]
RETURNS [slice: Slice] = {
The interpress master is assumed to be in units of pixelsPerUnit pixels.
memContext: Imager.Context;
localBox: BoundBox;
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];
};
scene: Scene;
scene ← GGFromImager.Capture[action: MakeScene ! GGFromImager.Warning => {
GGError.Append[feedback, message, oneLiner];
RESUME;
}];
localBox ← GGObjects.TightBoxOfScene[scene];
END;
slice ← MakeIPSliceAux[fullName, memContext, feedback, transform, includeByValue, localBox];
};
MakeIPSliceFromMaskPixel:
PUBLIC
PROC [pa: Imager.PixelArray, color: Color, feedback: Viewer, 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: Viewer, 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]
RETURNS [box: BoundBox]= {
GGModelTypes.SliceBoundBoxProc
RETURN[slice.boundBox];
};
IPTightBox:
PROC [slice: Slice, parts: SliceParts]
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, 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 [slice: Slice, parts: SliceParts, 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[slice.data];
Imager.DoSaveAll[dc, DoIPDrawTransform];
};
Transforming
IPTransform:
PROC [slice: Slice, parts: SliceParts, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceTransformProc
ipData: IPData ← NARROW[slice.data];
ipData.transform ← ImagerTransformation.Concat[ipData.transform, transform];
ipData.inverse ← ImagerTransformation.Invert[ipData.transform];
ipData.inverseScale ← ImagerTransformation.Factor[ipData.inverse].s;
IPUpdateBoundBox[slice];
};
Textual Description
IPDescribe:
PROC [slice: Slice, parts: SliceParts]
RETURNS [rope: Rope.
ROPE] = {
GGModelTypes.SliceDescribeProc
prefix: Rope.ROPE;
ipParts: IPParts ← NARROW[parts];
eCount, pCount: NAT ← 0;
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"];
};
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: NAT;
masterRope: Rope.ROPE;
f.PutF["\"%q\" ", [rope[ipData.file]]];
GGParseOut.WriteTransformation[f, ipData.transform];
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: Viewer]
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;
fullName ← f.GetRopeLiteral[];
GGParseIn.ReadBlank[f];
transform ← GGParseIn.ReadTransformation[f];
IF version >= 8612.04 THEN {
goodValue: BOOL;
GGParseIn.ReadBlankAndRope[f, "includeByValue:"];
GGParseIn.ReadBlank[f];
[includeByValue, goodValue] ← GGParseIn.ReadBOOL[f];
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 ← GGSlice.MakeIPSliceFromMaster[ipMaster, 1.0, fullName, feedback, transform, TRUE];
}
ELSE {
slice ← MakeIPSliceFromFile[fullName, feedback, transform, FALSE];
};
}
ELSE {
slice ← MakeIPSliceFromFile[fullName, feedback, transform, FALSE];
};
};
GetInterpressFileName:
PROC [ipName: Rope.
ROPE, feedback: Viewer]
RETURNS [fullName: Rope.
ROPE ←
NIL, success:
BOOL ←
TRUE] = {
cp: FS.ComponentPositions;
IF Rope.Length[ipName]=0 OR Rope.Equal[ipName, ""] THEN RETURN[NIL, FALSE];
[fullName, cp, ] ←
FS.ExpandName[ipName !
FS.Error => {
GGError.Append[feedback, "Gargoyle: FS Error during name expansion", oneLiner];
GGError.Blink[feedback];
success ← FALSE;
CONTINUE;
}; ];
IF success AND cp.ext.length=0 THEN fullName ← Rope.Concat[fullName, ".IP"]; -- add IP extension
};
Hit Testing
IPEmptyParts:
PROC [slice: Slice, parts: SliceParts]
RETURNS [
BOOL] = {
GGModelTypes.SliceEmptyPartsProc
RETURN[IsEmpty[parts]];
};
IPNewParts:
PROC [slice: Slice, hitData:
REF
ANY, mode: SelectMode]
RETURNS [parts: SliceParts] = {
GGModelTypes.SliceNewPartsProc
ipHitData: IPHitData ← NARROW[hitData];
ipParts: IPParts ← NEW[IPPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE] ] ];
SELECT mode
FROM
joint =>
{
IF ipHitData.point#-1
THEN {
ipParts.points[ipHitData.point] ← TRUE;
}
ELSE {
hitData: REF ANY;
pointHitData: IPHitData;
success: BOOL;
wholeD: SliceDescriptor;
wholeParts: SliceParts;
wholeParts ← IPNewParts[slice, NIL, slice];
wholeD ← NEW[SliceDescriptorObj ← [slice, wholeParts]];
[----, ----, 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 => {
-- prefer corner to edge if both are true
IF ipHitData.point#-1 THEN ipParts.points[ipHitData.point] ← TRUE
ELSE IF ipHitData.edge#-1 THEN ipParts.edges[ipHitData.edge] ← TRUE;
};
ENDCASE => ERROR;
parts ← ipParts; -- RETURN[ipParts]
};
IPUnionParts:
PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts]
RETURNS [aPlusB: SliceParts] = {
GGModelTypes.SliceUnionPartsProc
ipPartsA: IPParts ← NARROW[partsA];
ipPartsB: IPParts ← NARROW[partsB];
newParts: IPParts ← 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 ← newParts; -- RETURN[newParts]
};
IPDiffParts:
PROC [slice: Slice, partsA: SliceParts, partsB: SliceParts]
RETURNS [aMinusB: SliceParts] = {
ipPartsA: IPParts ← NARROW[partsA];
ipPartsB: IPParts ← NARROW[partsB];
newParts: IPParts ← NEW[IPPartsObj ← [points: ALL[FALSE], edges: ALL[FALSE] ] ];
IF partsB=NIL THEN {newParts^ ← ipPartsA^; aMinusB ← newParts; RETURN;};
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 ← newParts; -- RETURN[newParts]
};
IPMovingParts:
PROC [slice: Slice, parts: SliceParts]
RETURNS [moving: SliceParts] = {
GGModelTypes.SliceMovingPartsProc
If anything is moving, everything is moving
newParts: IPParts ← NEW[IPPartsObj ← [points: ALL[TRUE], edges: ALL[TRUE], innards: TRUE ] ];
IF IsEmpty[parts]
THEN {
-- nothing at all is moving
newParts.edges ← ALL[FALSE];
newParts.points ← ALL[FALSE];
newParts.innards ← FALSE;
};
moving ← newParts; -- RETURN[newParts]
};
IPFixedParts:
PROC [slice: Slice, parts: SliceParts, selectedList:
LIST
OF
REF
ANY]
RETURNS [fixed: SliceParts] = {
GGModelTypes.SliceFixedPartsProc
Start with every part and remove the ones that are moving
moving: SliceParts ← IPMovingParts[slice, parts];
allParts: IPParts ← NEW[IPPartsObj];
MakeComplete[allParts];
fixed ← IPDiffParts[slice, allParts, moving];
};
IPAugmentParts:
PROC [slice: Slice, parts: SliceParts, selectClass: SelectionClass]
RETURNS [more: SliceParts] = {
GGModelTypes.SliceAugmentPartsProc
ipParts: IPParts ← NARROW[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 ← 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 GGError.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 ← GGVector.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 ← GGVector.Distance[testPoint, bestPoint];
hitData ← ipHitData ← NEW[IPHitDataObj ← [point: -1, edge: index, hitPoint: bestPoint] ];
};
};
};
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"];
Style
IPSetStrokeColor: PROC [slice: Slice, parts: SliceParts, color: Imager.Color] = {};
IPGetStrokeColor: PROC [slice: Slice, parts: SliceParts] RETURNS [color: Imager.Color ← NIL] = {};
IPSetFillColor: PROC [slice: Slice, color: Imager.Color] = {};
IPGetFillColor: PROC [slice: Slice] RETURNS [color: Imager.Color ← NIL] = {};
IPSetStrokeWidth: PROC [slice: Slice, parts: SliceParts, strokeWidth: REAL] = {};
IPGetStrokeWidth:
PROC [slice: Slice, parts: SliceParts]
RETURNS [strokeWidth:
REAL ← 0.0] = {};
printPrefix: ATOM;
screenPrefix: ATOM;
Init:
PRIVATE
PROC [] = {
printPrefix ← Atom.MakeAtom["xerox/pressfonts/"];
screenPrefix 𡤊tom.MakeAtom ["xerox/tiogafonts/"];
};
Init[];
END.