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
[Artwork node; type 'ArtworkInterpress on' to command tool]
Text Points as shown
Text Edges: 0 => left, 1=> top, 2=> right, 3=> bottom, 4 => baseline, 5 => horizontal centerline, 6 => vertical centerline
BoundBox: TYPE = GGModelTypes.BoundBox;
CameraData: TYPE = GGInterfaceTypes.CameraData;
Color: TYPE = Imager.Color;
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.ROPENIL,
fontName: Rope.ROPENIL, -- font description: e.g. Helvetica11BI
fontFamily: ATOM,
fontFace: NodeStyle.FontFace ← Regular,
fontSize: REAL ← 10.0,
printFont: ImagerFont.Font ← NIL,
screenFont: ImagerFont.Font ← NIL,
dropShadowOn: BOOLFALSE,
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,
copy: TextCopy,
Drawing
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: BOOLFALSE, 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.ROPENIL] = {
sizeRope: Rope.ROPE ← Convert.RopeFromReal[fontSize];
faceRope: Rope.ROPESELECT 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: BOOLFALSE;
fontStream: IO.STREAMIO.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.screenFonttextData.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: INTINT.LAST] RETURNS [ImagerFont.VEC] ~ {
string: ImagerFont.XStringProc ~ { ImagerFont.MapRope[rope, start, len, charAction] };
RETURN[StringEscapement[font, string, amplifySpace]];
};
TextSetBoundBox: PROC [slice: Slice] = {
textData: TextData ← NARROW[slice.data];
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];
};
TextDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceDrawTransformProc
DoTextDrawTransform: 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: BOOLFALSE] = {
GGModelTypes.SliceClosestPointProc
index: NAT ← 9999;
textData: TextData ← NARROW[sliceD.slice.data];
bigBox: GGBasicTypes.BoundBoxObj ← [textData.feedbackBox.loX-tolerance, textData.feedbackBox.loY-tolerance, textData.feedbackBox.hiX+tolerance, textData.feedbackBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, bigBox] THEN {
textParts: TextParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[textData.inverse, testPoint];
localTolerance: REALABS[tolerance*MAX[textData.inverseScale.x, textData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← TextNearestPoint[textData.points, localTestpoint, localTolerance, textParts.points];
IF success THEN {
bestPoint ← GGTransform.Transform[textData.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: BOOLFALSE] = {
GGModelTypes.SliceClosestSegmentProc
hitData ← NIL; -- automatically done by compiler
textData: TextData ← NARROW[sliceD.slice.data];
bigBox: GGBasicTypes.BoundBoxObj ← [textData.feedbackBox.loX-tolerance, textData.feedbackBox.loY-tolerance, textData.feedbackBox.hiX+tolerance, textData.feedbackBox.hiY+tolerance, FALSE, FALSE];
IF PointIsInBox[testPoint, bigBox] THEN {
seg: NAT ← 9999;
textHitData: TextHitData;
textParts: TextParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[textData.inverse, testPoint];
localTolerance: REALABS[tolerance*MAX[textData.inverseScale.x, textData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, seg, bestPoint, success] ← TextNearestEdge[textData.points, localTestpoint, localTolerance, textParts.edges];
IF success THEN {
bestPoint ← GGTransform.Transform[textData.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: BOOLFALSE] = {
Finds the point which is in mask and nearest to testPoint (and its distance from testPoint). Number the points as shown in the picture above
thisPoint: Point;
thisDist2, bestDist2: REAL;
tolerance2: REAL ← tolerance*tolerance;
index: NAT ← 0;
bestXY ← [-1.0, -1.0];
bestDist2 ← 1E12; -- better be big enough
bestPoint ← 9999;
FOR index IN [0..textMaxPoints) DO
IF mask[index] THEN {
thisPoint ← points[index]; -- use feedbackBox relative coordinates
thisDist2 ← 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: BOOLFALSE] = {
Finds the edge which is in mask and nearest to testPoint (and its distance from testPoint).
thisPoint: Point;
thisDist2, bestDist2: REAL;
tolerance2: REAL ← tolerance*tolerance;
index: NAT ← 0;
bestPoint ← [-1.0, -1.0];
bestDist2 ← 1E12; -- better be big enough
bestSeg ← 9999;
FOR index IN [0..textMaxEdges) DO
IF mask[index] THEN {
SELECT index FROM
0 => 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: BOOLFALSE -- FALSE => corners and edges only; no innards
];
IPHitData: TYPE = REF IPHitDataObj;
IPHitDataObj: TYPE = RECORD [
point: [-1..ipMaxPoints), -- which point of box is hit.
edge: [-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 ← [
type: $IP,
Fundamentals
getBoundBox: IPBoundBox,
getTightBox: IPTightBox,
copy: IPCopy,
Drawing
drawParts: IPDrawParts,
drawTransform: IPDrawTransform,
drawSelectionFeedback: IPDrawSelectionFeedback,
drawAttractorFeedback: NoOpDrawAttractorFeedback,
Transforming
transform: IPTransform,
Textual Description
describe: IPDescribe,
fileout: IPFileout,
filein: IPFilein,
Parts
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: BOOLFALSE;
[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.ROPENIL, 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];
};
IPDrawTransform: PROC [slice: Slice, parts: SliceParts, dc: Imager.Context, camera: GGInterfaceTypes.CameraData, transform: ImagerTransformation.Transformation] = {
GGModelTypes.SliceDrawTransformProc
DoIPDrawTransform: PROC = {
Imager.ConcatT[dc, ImagerTransformation.Concat[ipData.transform, transform]];
Imager.SetColor[dc, Imager.black]; -- otherwise the replay could end up in a random color.
ImagerMemory.Replay[ipData.mem, dc];
};
ipData: IPData ← NARROW[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: BOOLFALSE;
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.ROPENIL, success: BOOLTRUE] = {
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: BOOLFALSE] = {
GGModelTypes.SliceClosestPointProc
hitData ← NIL; -- automatically done by compiler
index: NAT ← 9999;
slice: Slice ← sliceD.slice;
ipData: IPData ← NARROW[slice.data];
toleranceBox: GGBasicTypes.BoundBoxObj ← [ipData.tightBox.loX-tolerance, ipData.tightBox.loY-tolerance, ipData.tightBox.hiX+tolerance, ipData.tightBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox] THEN {
ipHitData: IPHitData;
ipParts: IPParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[ipData.inverse, testPoint];
localTolerance: REALABS[tolerance*MAX[ipData.inverseScale.x, ipData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← GGBoundBox.NearestPoint[ipData.localBox, localTestpoint, localTolerance, ipParts.points];
IF success THEN {
bestPoint ← GGTransform.Transform[ipData.transform, bestPoint];
bestDist ← 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: BOOLFALSE] = {
GGModelTypes.SliceClosestSegmentProc
hitData ← NIL; -- automatically done by compiler
slice: Slice ← sliceD.slice;
ipData: IPData ← NARROW[slice.data];
toleranceBox: GGBasicTypes.BoundBoxObj ← [ipData.tightBox.loX-tolerance, ipData.tightBox.loY-tolerance, ipData.tightBox.hiX+tolerance, ipData.tightBox.hiY+tolerance, FALSE, FALSE]; -- box in GG coordinates
IF PointIsInBox[testPoint, toleranceBox] THEN {
ipHitData: IPHitData;
index: NAT ← 9999;
ipData: IPData ← NARROW[slice.data];
ipParts: IPParts ← NARROW[sliceD.parts];
localTestpoint: Point ← GGTransform.Transform[ipData.inverse, testPoint]; -- transfrom point to "IP space"
localTolerance: REALABS[tolerance*MAX[ipData.inverseScale.x, ipData.inverseScale.y]]; -- MAX function is not correct when text is skewed.
[bestDist, index, bestPoint, success] ← GGBoundBox.NearestSegment[ipData.localBox, localTestpoint, localTolerance, ipParts.points];
IF success THEN {
bestPoint ← GGTransform.Transform[ipData.transform, bestPoint];
bestDist ← 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.