MatchImplA.mesa
Contents: Routines for extracting looks and matching looks.
Copyright Ó 1988, 1990 by Xerox Corporation. All rights reserved.
Last edited by: David Kurlander - September 6, 1987 7:29:54 pm PDT
Bier, April 8, 1991 10:29 pm PDT
Pier, August 19, 1988 5:10:55 pm PDT
DIRECTORY
Basics, AtomButtons, GGBasicTypes, GGCaret, GGCoreOps, GGFont, GGInterfaceTypes, GGModelTypes, GGOutline, GGParent, GGScene, GGSegmentTypes, GGSelect, GGSequence, GGSlice, GGSliceOps, GGTraj, GGWindow, GList, ImagerTransformation, Match, MatchTurtle, MatchViewer, RealFns, Rope;
MatchImplA:
CEDAR
PROGRAM
IMPORTS AtomButtons, GGCaret, GGCoreOps, GGOutline, GGParent, GGScene, GGSelect, GGSequence, GGSlice, GGSliceOps, GGTraj, GGWindow, GList, ImagerTransformation, Match, MatchTurtle, MatchViewer, RealFns, Rope
EXPORTS Match = BEGIN
BoundBox: TYPE = GGModelTypes.BoundBox;
Caret: TYPE = GGInterfaceTypes.Caret;
ChoiceData: TYPE = MatchViewer.ChoiceData;
DashInfo: TYPE = Match.DashInfo;
DashInfoObj: TYPE = Match.DashInfoObj;
GGData: TYPE = GGInterfaceTypes.GGData;
ItemID: TYPE = Match.ItemID;
ItemMatch: TYPE = Match.ItemMatch;
ItemMatchObj: TYPE = Match.ItemMatchObj;
LooksInfo: TYPE = Match.LooksInfo;
LooksInfoObj: TYPE = Match.LooksInfoObj;
MatchData: TYPE = MatchViewer.MatchData;
MatchDescriptor: TYPE = Match.MatchDescriptor;
MatchDObj: TYPE = Match.MatchDObj;
PositionDescriptor: TYPE = MatchTurtle.PositionDescriptor;
Point: TYPE = GGBasicTypes.Point;
SearchInfo: TYPE = Match.SearchInfo;
SearchInfoObj: TYPE = Match.SearchInfoObj;
SearchState: TYPE = Match.SearchState;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
Sequence: TYPE = GGModelTypes.Sequence;
Scene: TYPE = GGModelTypes.Scene;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator;
SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj;
SliceGenerator: TYPE = GGModelTypes.SliceGenerator;
SliceParts: TYPE = REF ANY;
Traj: TYPE = GGModelTypes.Traj;
TrajData: TYPE = GGModelTypes.TrajData;
TurtleHeader: TYPE = MatchTurtle.TurtleHeader;
TurtleInfo: TYPE = MatchTurtle.TurtleInfo;
Top-level search routines
Search:
PUBLIC
PROC [toSearch: GGData, event:
LIST
OF
REF
ANY, refresh:
BOOL ←
TRUE]
RETURNS [found:
BOOL ←
FALSE] = {
matchData: MatchData ← MatchViewer.GetMatchData[];
searchState: SearchState ← Match.GetSearchState[];
SELECT matchData.searchOp
FROM
disjunction => {
SearchDisjInit[toSearch, event]; -- initialize searchInfos et al
[searchState.ahead, found] ← SearchDisjNext[searchState.ahead, event.rest.first, refresh];
};
conjunction => {
Match.InitializeSearch[toSearch, event];
[searchState.ahead, searchState.behind, found] ← Match.SearchConjNext[searchState.ahead, searchState.behind, event.rest.first, refresh];
};
ENDCASE => {
MatchViewer.ErrorFeedback["This SearchOp is not implemented."];
RETURN[FALSE];
};
};
Utilities for building and querying SearchInfos
IsEmptySearchInfo:
PUBLIC
PROC [searchInfo: SearchInfo]
RETURNS [
BOOL ←
FALSE] = {
IF searchInfo = NIL OR searchInfo.empty = TRUE THEN RETURN[TRUE];
IF GGSliceOps.GetType[searchInfo.slice] = $Outline AND searchInfo.kids = NIL THEN RETURN[TRUE];
};
CreateSearchInfo:
PUBLIC
PROC [slice: Slice, direction:
REF
ANY]
RETURNS [searchInfo: SearchInfo] = {
Creates a new, complete SearchInfo for the specified slice
matchData: MatchData ← MatchViewer.GetMatchData[];
searchInfo ← NEW[SearchInfoObj ← [slice: slice, reverse: direction = $SearchAbove]];
SELECT GGSliceOps.GetType[slice]
FROM
$Outline => {
searchInfo.kids ← GGOutline.TrajectoriesOfOutline[slice];
IF searchInfo.reverse THEN searchInfo.kids ← NARROW[GList.Reverse[searchInfo.kids], LIST OF Traj];
IF matchData.matchLevel = anywhere THEN CacheSegsOfFirstTraj[searchInfo];
};
$Box => IF matchData.matchLevel = anywhere THEN CacheSegsOfFirstTraj[searchInfo];
ENDCASE;
};
CopySearchInfo:
PROC [info: SearchInfo]
RETURNS [newInfo: SearchInfo] = {
Only non-destructive list ops are permitted on SearchInfo fields, so just copy the fields.
newInfo ← NEW[SearchInfoObj ← info^];
};
MakeEmpty:
PROC [info: SearchInfo]
RETURNS [newInfo: SearchInfo] = {
newInfo ← CopySearchInfo[info];
newInfo.empty ← TRUE;
};
CacheSegsOfFirstTraj:
PUBLIC
PROC [info: SearchInfo] = {
Stores in info.segs all of the segments of the first trajectory of the shape that info represents.
info.segs ← NIL;
SELECT GGSliceOps.GetType[info.slice]
FROM
$Outline => {
traj: Slice;
trajData: TrajData;
IF info.kids = NIL THEN RETURN; -- nothing to fill in
traj ← info.kids.first;
trajData ← NARROW[traj.data];
FOR i:
INT
IN [0..GGTraj.HiSegment[traj]]
DO
info.segs ← CONS[GGTraj.FetchSegment[traj, i], info.segs];
ENDLOOP;
IF NOT info.reverse THEN info.segs ← NARROW[GList.DReverse[info.segs]];
Fill in closedTraj field (is this ever used? -- Bier)
IF
trajData.role # open OR
ManhattanDistance[
GGTraj.FetchJointPos[traj, 0],
GGTraj.FetchJointPos[traj, GGTraj.HiJoint[traj]]] < 1.0
THEN info.closedTraj ← TRUE
ELSE info.closedTraj ← FALSE;
};
$Box => {
FOR i:
INT
IN [0..4)
DO
info.segs ← CONS[GGSlice.BoxFetchSegment[info.slice, i], info.segs];
ENDLOOP;
IF NOT info.reverse THEN info.segs ← NARROW[GList.DReverse[info.segs], LIST OF Segment];
info.closedTraj ← TRUE;
};
ENDCASE => ERROR;
};
Extracting Looks Information
GetFromLooks:
PUBLIC
PROC
RETURNS [looksList:
LIST
OF
REF
ANY ←
NIL] = {
AddLooksToList:
PROC [slice: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
looksList ← CONS[GetLooksOfSlice[slice, matchData.from], looksList];
};
matchData: MatchData ← MatchViewer.GetMatchData[];
fromData: GGData ← MatchViewer.GetFromData[];
[] ← GGScene.WalkSlices[fromData.scene, first, AddLooksToList];
};
GetToLooks:
PUBLIC
PROC
RETURNS [looksList:
LIST
OF
REF
ANY ←
NIL] = {
AddLooksToList:
PROC [slice: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
looksList ← CONS[GetLooksOfSlice[slice, matchData.to], looksList];
};
matchData: MatchData ← MatchViewer.GetMatchData[];
toData: GGData ← MatchViewer.GetToData[];
[] ← GGScene.WalkSlices[toData.scene, first, AddLooksToList];
};
GetLooksOfSlice:
PUBLIC
PROC [slice: Slice, choice: ChoiceData, value:
BOOL ←
TRUE]
RETURNS [
REF
ANY] = {
Returns the Looks of the slice that are specified in choice (if value = TRUE) or not specified (if value = FALSE)
targetParts: SliceParts ← GGSliceOps.NewParts[slice, NIL, slice].parts;
looks: LooksInfo ← NEW[LooksInfoObj];
IF GGSliceOps.GetType[slice] = $Outline
OR GGSliceOps.GetType[slice] = $Box
THEN {
looksList: LIST OF LIST OF LooksInfo ← GetCompositeLooksOfSlice[slice, choice, value];
RETURN[looksList];
};
looks.owner.slice ← slice; -- this will suffice
IF AtomButtons.GetBinaryState[choice.class] = value
THEN {
--
Class
looks.classDef ← TRUE;
looks.class ← GGSliceOps.GetType[slice];
};
IF AtomButtons.GetBinaryState[choice.type] = value
THEN {
--
Type
looks.typeDef ← TRUE;
looks.type ← GGSliceOps.GetType[slice];
};
IF AtomButtons.GetBinaryState[choice.shape] = value
THEN {
--
Shape
looks.shapeDef ← TRUE;
looks.shape ← MatchTurtle.GetShapeOfSlice[slice];
};
IF AtomButtons.GetBinaryState[choice.color] = value
THEN {
--
Color
looks.colorDef ← TRUE;
looks.color ← GGSliceOps.GetStrokeColor[slice, targetParts].color;
};
IF AtomButtons.GetBinaryState[choice.fillColor] = value
THEN {
--
Fill Color
looks.fillColorDef ← TRUE;
looks.fillColor ← GGSliceOps.GetFillColor[slice, targetParts].color;
};
IF AtomButtons.GetBinaryState[choice.string] = value
THEN {
--
String
IF GGSliceOps.GetType[slice] = $Text
THEN {
looks.stringDef ← TRUE;
looks.string ← GGSlice.GetText[slice];
}
ELSE looks.string ← NIL;
};
IF AtomButtons.GetBinaryState[choice.font] = value
THEN {
--
Font
looks.fontDef ← TRUE;
looks.font ← GGSlice.GetFontData[slice];
};
IF AtomButtons.GetBinaryState[choice.fontTform] = value
THEN {
--
Font & Transform
looks.fontTformDef ← TRUE;
looks.fontTform ← GGSlice.GetFontData[slice];
};
IF AtomButtons.GetBinaryState[choice.dash] = value
THEN {
--
Dashes
looks.dashesDef ← TRUE;
looks.dashes ← NEW[DashInfoObj];
[dashed: looks.dashes.dashed, pattern: looks.dashes.pattern, offset: looks.dashes.offset, length: looks.dashes.length] ← GGSliceOps.GetDashed[slice, targetParts];
};
IF AtomButtons.GetBinaryState[choice.joints] = value
THEN {
--
Joints
looks.jointsDef ← TRUE;
looks.joints ← GGSliceOps.GetStrokeJoint[slice, targetParts].strokeJoint;
};
IF AtomButtons.GetBinaryState[choice.ends] = value
THEN {
--
Ends
looks.endsDef ← TRUE;
looks.ends ← GGSliceOps.GetStrokeEnd[slice, targetParts].strokeEnd;
};
IF AtomButtons.GetBinaryState[choice.width] = value
THEN {
--
Width
looks.widthDef ← TRUE;
looks.width ← GGSliceOps.GetStrokeWidth[slice, targetParts].strokeWidth;
};
RETURN [looks];
};
GetCompositeLooksOfSlice:
PROC [slice: Slice, choice: ChoiceData, value:
BOOL ←
TRUE]
RETURNS [looksList:
LIST
OF
LIST
OF LooksInfo ←
NIL] = {
DoConsLooks:
PROC [traj: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
looksList ← CONS[GetLooksOfTraj[traj, choice, value], looksList];
};
shape: TurtleHeader;
traj: Traj ← NIL; -- must be defined here so it's defined at GetLooksInSegment call-back
class: ATOM ← GGSliceOps.GetType[slice]; -- for use in call-back
IF GGSliceOps.GetType[slice] = $Outline
THEN {
[] ← GGParent.WalkChildren[slice, first, DoConsLooks, $Traj];
}
ELSE {
GatherSegs: GGModelTypes.WalkProc = {segList ← CONS[seg, segList]; keep ← FALSE;};
segList: LIST OF Segment ← NIL;
subList: LIST OF LooksInfo ← NIL;
IF AtomButtons.GetBinaryState[choice.shape] = value THEN shape ← MatchTurtle.GetShapeOfSlice[slice];
[] ← GGSliceOps.WalkSegments[slice, GatherSegs];
FOR segs:
LIST
OF Segment ← segList, segs.rest
UNTIL segs =
NIL
DO
subList ← CONS[GetLooksOfSegment[slice: slice, traj: NIL, seg: segs.first, choice: choice, value: value, shape: shape], subList];
ENDLOOP;
looksList ← CONS[subList, looksList];
};
looksList ← NARROW[GList.DReverse[looksList], LIST OF LIST OF LooksInfo];
};
GetLooksOfSegment:
PUBLIC
PROC [slice: Slice, traj: Traj, seg: Segment, choice: ChoiceData, value:
BOOL ← FALSE, shape: TurtleHeader]
RETURNS [looks: LooksInfo] = {
looks ← NEW[LooksInfoObj];
looks.owner.slice ← slice;
looks.owner.traj ← traj;
looks.owner.seg ← seg;
IF AtomButtons.GetBinaryState[choice.shape] = value
THEN {
--
Shape
looks.shapeDef ← TRUE;
looks.shape ← shape;
};
IF AtomButtons.GetBinaryState[choice.class] = value
THEN {
--
Class
looks.classDef ← TRUE;
looks.class ← GGSliceOps.GetType[slice];
};
IF AtomButtons.GetBinaryState[choice.type] = value
THEN {
--
Type
looks.typeDef ← TRUE;
looks.type ← seg.class.type;
};
IF AtomButtons.GetBinaryState[choice.color] = value
THEN {
--
Color
looks.colorDef ← TRUE;
looks.color ← seg.color;
};
IF AtomButtons.GetBinaryState[choice.fillColor] = value
THEN {
--
Fill Color
looks.fillColorDef ← TRUE;
looks.fillColor ← GGSliceOps.GetFillColor[slice, NIL].color;
};
IF AtomButtons.GetBinaryState[choice.dash] = value
THEN {
--
Dashes
looks.dashesDef ← TRUE;
looks.dashes ← NEW[DashInfoObj ← [dashed: seg.dashed, pattern: seg.pattern, offset: seg.offset, length: seg.length]];
};
IF AtomButtons.GetBinaryState[choice.joints] = value
THEN {
--
Joints
looks.jointsDef ← TRUE;
looks.joints ← IF traj = NIL THEN GGSliceOps.GetStrokeJoint[slice, GGSliceOps.NewParts[slice, NIL, slice]].strokeJoint ELSE GGSliceOps.GetStrokeJoint[traj, NIL].strokeJoint;
};
IF AtomButtons.GetBinaryState[choice.ends] = value
THEN {
--
Ends
looks.endsDef ← TRUE;
looks.ends ← seg.strokeEnd;
};
IF AtomButtons.GetBinaryState[choice.width] = value
THEN {
--
Width
looks.widthDef ← TRUE;
looks.width ← seg.strokeWidth;
};
};
GetLooksOfTraj:
PUBLIC
PROC [traj: Traj, choice: ChoiceData, value:
BOOL ←
TRUE]
RETURNS [trajLooks:
LIST
OF LooksInfo ←
NIL] = {
shape: TurtleHeader ← IF AtomButtons.GetBinaryState[choice.shape] = value THEN MatchTurtle.TrajToTurtle[traj] ELSE NIL;
FOR i:
INT
DECREASING
IN [0..GGTraj.HiSegment[traj]]
DO
trajLooks ← CONS[GetLooksOfSegment[slice: traj.parent, traj: traj, seg: GGTraj.FetchSegment[traj, i], choice: choice, value: value, shape: shape], trajLooks];
ENDLOOP;
};
Matching a domain slice to the from looks.
FindMatchInSlice:
PUBLIC
PROC [searchInfo: SearchInfo, fromLooks:
LIST
OF
REF
ANY, direction:
REF
ANY, matchD: MatchDescriptor]
RETURNS [match: SliceDescriptor ←
NIL, rest: SearchInfo ←
NIL] = {
IF searchInfo.empty THEN RETURN[NIL, searchInfo];
SELECT GGSliceOps.GetType[searchInfo.slice]
FROM
$Circle => [match, rest] ← FindMatchInCircle[searchInfo, fromLooks, matchD];
$Text => [match, rest] ← FindMatchInText[searchInfo, fromLooks, matchD];
$Box => [match, rest] ← FindMatchInBox[searchInfo, fromLooks, matchD];
$Outline => [match, rest] ← FindMatchInOutline[searchInfo, fromLooks, matchD];
ENDCASE; -- can't match on this kind of slice
FindMatchInCircle:
PROC [searchInfo: SearchInfo, fromLooks:
LIST
OF
REF
ANY, matchD: MatchDescriptor]
RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
entireCircleD: SliceDescriptor ← GGSliceOps.NewParts[searchInfo.slice, NIL, slice];
looks: LooksInfo;
complexLooks: LIST OF LIST OF LooksInfo;
FOR fromLooks ← fromLooks, fromLooks.rest
UNTIL fromLooks=
NIL
DO
WITH fromLooks.first
SELECT
FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex:
LIST
OF
LIST
OF LooksInfo => {
looks ← complex.first.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF GList.Length[complexLooks] > 1 THEN LOOP;
IF looks.typeDef AND complexLooks#NIL THEN LOOP;
IF SimpleMatch[entireCircleD, looks, matchD] THEN RETURN[entireCircleD, MakeEmpty[searchInfo]];
ENDLOOP;
RETURN[NIL, MakeEmpty[searchInfo]];
};
FindMatchInText:
PROC [searchInfo: SearchInfo, fromLooks:
LIST
OF
REF
ANY, matchD: MatchDescriptor]
RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
entireTextD: SliceDescriptor ← GGSliceOps.NewParts[searchInfo.slice, NIL, slice];
looks: LooksInfo;
complexLooks: LIST OF LIST OF LooksInfo;
FOR fromLooks ← fromLooks, fromLooks.rest
UNTIL fromLooks=
NIL
DO
WITH fromLooks.first
SELECT
FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex:
LIST
OF
LIST
OF LooksInfo => {
looks ← complex.first.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF GList.Length[complexLooks] > 1 THEN LOOP;
IF looks.typeDef AND complexLooks#NIL THEN LOOP;
IF SimpleMatch[entireTextD, looks, matchD] THEN RETURN[entireTextD, MakeEmpty[searchInfo]];
ENDLOOP;
RETURN[NIL, MakeEmpty[searchInfo]];
};
FindMatchInBox:
PROC [searchInfo: SearchInfo, fromLooks:
LIST
OF
REF
ANY, matchD: MatchDescriptor]
RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
matchLevel: MatchViewer.MatchLevel ← MatchViewer.GetMatchData[].matchLevel;
entireBoxD: SliceDescriptor ← GGSliceOps.NewParts[searchInfo.slice, NIL, slice];
looks: LooksInfo;
complexLooks: LIST OF LIST OF LooksInfo;
IF matchLevel = anywhere
THEN {
[match, rest] ← FindAnywhereMatchInBox[searchInfo, fromLooks, matchD];
RETURN[match, rest];
};
FOR fromLooks ← fromLooks, fromLooks.rest
UNTIL fromLooks=
NIL
DO
WITH fromLooks.first
SELECT
FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex:
LIST
OF
LIST
OF LooksInfo => {
looks ← complex.first.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF matchLevel = trajLevel
OR matchLevel = clusterLevel
THEN {
complexLength: NAT ← GList.Length[complexLooks];
IF looks.shapeDef AND complexLength > 1 THEN LOOP; -- boxes are connected entities
IF looks.typeDef AND complexLength # 1 THEN LOOP;
IF looks.typeDef
THEN {
IF ComplexMatch[entireBoxD, complexLooks, matchD] THEN RETURN[entireBoxD, MakeEmpty[searchInfo]];
}
ELSE {
IF SimpleMatch[entireBoxD, looks, matchD] THEN RETURN[entireBoxD, MakeEmpty[searchInfo]];
};
}
ELSE ERROR;
ENDLOOP;
RETURN[NIL, MakeEmpty[searchInfo]];
};
FindMatchInOutline:
PROC [searchInfo: SearchInfo, fromLooks:
LIST
OF
REF
ANY, matchD: MatchDescriptor]
RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
entireSliceD: SliceDescriptor ← GGSliceOps.NewParts[searchInfo.slice, NIL, slice];
looks: LooksInfo;
complexLooks: LIST OF LIST OF LooksInfo;
matchLevel: MatchViewer.MatchLevel ← MatchViewer.GetMatchData[].matchLevel;
IF matchLevel = trajLevel
THEN {
[match, rest] ← FindTrajMatchInOutline[searchInfo, fromLooks, matchD];
RETURN[match, rest];
};
IF matchLevel = anywhere
THEN {
[match, rest] ← FindAnywhereMatchInOutline[searchInfo, fromLooks, matchD];
RETURN[match, rest];
};
FOR fromLooks ← fromLooks, fromLooks.rest
UNTIL fromLooks=
NIL
DO
WITH fromLooks.first
SELECT
FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex:
LIST
OF
LIST
OF LooksInfo => {
looks ← complex.first.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF matchLevel = clusterLevel
THEN {
trajList: LIST OF Traj ← GGOutline.TrajectoriesOfOutline[searchInfo.slice];
IF (looks.typeDef
OR looks.shapeDef)
AND complexLooks #
NIL
THEN {
IF GList.Length[complexLooks] # GList.Length[trajList] THEN LOOP; -- different traj count
IF ComplexMatch[entireSliceD, complexLooks, matchD] THEN RETURN[entireSliceD, MakeEmpty[searchInfo]];
}
ELSE {
IF SimpleMatch[entireSliceD, looks, matchD] THEN RETURN[entireSliceD, MakeEmpty[searchInfo]];
};
}
ENDLOOP;
RETURN[NIL, MakeEmpty[searchInfo]];
};
FindAnywhereMatchInBox:
PROC [searchInfo: SearchInfo, fromLooks:
LIST
OF
REF
ANY, matchD: MatchDescriptor]
RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
SelectInRange: GGModelTypes.WalkProc = {
keep ← IF segNum >= first AND segNum <= last THEN TRUE ELSE FALSE;
segNum ← segNum + 1;
};
segCount: NAT = 4; -- 4 segs per box
segNum: NAT ← 0;
start, length: NAT; -- refer to the span of segments on searchInfo.segs
first, last: NAT; -- refer to the span of segments relative to the box
startSeg: Segment;
[start, length] ← SegLooksMatch[searchInfo, fromLooks, matchD]; -- note: start is relative to start of list, not start of traj
IF length < 1 THEN RETURN[NIL, NIL];
Make a sliceD for the match, and a rest
startSeg ← NARROW[GList.NthElement[searchInfo.segs, start+1], Segment];
FOR i:
INT
IN [0..4)
DO
IF GGSlice.BoxFetchSegment[searchInfo.slice, i] = startSeg THEN {first ← i; EXIT;};
ENDLOOP;
IF
NOT searchInfo.reverse
THEN {
last ← (first + length - 1) MOD segCount;
}
ELSE {
last ← first;
first ← (first - length + 1 + segCount) MOD segCount; -- + segCount to keep positive
};
Now we have to splice out the used segments from searchInfo.segs
match ← GGSliceOps.WalkSegments[searchInfo.slice, SelectInRange];
rest ← CopySearchInfo[searchInfo];
rest.segs ← NARROW[GList.NthTail[rest.segs, start + length], LIST OF Segment];
};
FindAnywhereMatchInOutline:
PROC [searchInfo: SearchInfo, fromLooks:
LIST
OF
REF
ANY, matchD: MatchDescriptor]
RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
matchSeq: Sequence;
traj: Traj ← searchInfo.kids.first;
startSeg: Segment;
start, length: NAT; -- refer to a span of segments on searchInfo.segs
first, last: NAT; -- refer to a span of segments in the new sequence (ie, relative to the traj)
segCount: NAT;
searchInfo ← CopySearchInfo[searchInfo];
[start, length] ← SegLooksMatch[searchInfo, fromLooks, matchD]; -- note: start is relative to start of list, not start of traj
IF length < 1
THEN {
-- nothing on interest on this traj, go directly to next
searchInfo.kids ← searchInfo.kids.rest;
CacheSegsOfFirstTraj[searchInfo];
RETURN[NIL, searchInfo];
};
Make a sliceD out of it and a rest, and return
startSeg ← NARROW[GList.NthElement[searchInfo.segs, start+1], Segment];
segCount ← GGTraj.HiSegment[traj]+1;
FOR i:
INT
IN [0..segCount)
DO
IF GGTraj.FetchSegment[traj, i] = startSeg THEN {first ← i; EXIT};
ENDLOOP;
IF
NOT searchInfo.reverse
THEN {
last ← (first + length - 1) MOD segCount;
}
ELSE {
last ← first;
first ← (first - length + 1 + segCount) MOD segCount; -- + segCount to keep positive
};
Now we have to splice out the used segments from searchInfo.segs
BEGIN
trajData: TrajData ← NARROW[traj.data];
searchInfo.segs ← NARROW[GList.NthTail[searchInfo.segs, start + length], LIST OF Segment];
matchSeq ← GGSlice.DescriptorFromParts[traj, GGSequence.CreateFromSegments[trajData, first, last]];
match ← GGParent.DescriptorFromChildDescriptor[searchInfo.slice, matchSeq];
END;
RETURN[match, searchInfo];
};
FindTrajMatchInOutline:
PROC [searchInfo: SearchInfo, fromLooks:
LIST
OF
REF
ANY, matchD: MatchDescriptor]
RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
looks: LooksInfo;
complexLooks: LIST OF LIST OF LooksInfo;
searchInfo ← CopySearchInfo[searchInfo];
FOR trajs:
LIST
OF Traj ← searchInfo.kids, trajs.rest
UNTIL trajs=
NIL
DO
currentTraj: Traj ← trajs.first;
searchInfo.kids ← trajs.rest;
FOR fromList:
LIST
OF
REF
ANY ← fromLooks, fromList.rest
UNTIL fromList=
NIL
DO
WITH fromList.first
SELECT
FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex:
LIST
OF
LIST
OF LooksInfo => {
looks ← complex.first.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF complexLooks=
NIL
THEN {
-- ie, simple
trajData: TrajData ← NARROW[currentTraj.data];
seq: SliceDescriptor ← GGSlice.DescriptorFromParts[
currentTraj,
GGSequence.CreateComplete[trajData]
];
sliceD: SliceDescriptor ← GGParent.DescriptorFromChildDescriptor[searchInfo.slice, seq];
IF SimpleMatch[sliceD, looks, matchD] THEN RETURN[sliceD, searchInfo];
}
ELSE {
FOR list:
LIST
OF
LIST
OF LooksInfo ← complexLooks, list.rest
UNTIL list=
NIL
DO
IF TrajMatch[currentTraj, list.first, matchD]
THEN {
trajData: TrajData ← NARROW[currentTraj.data];
seq: SliceDescriptor ← GGSlice.DescriptorFromParts[
currentTraj,
GGSequence.CreateComplete[trajData]
];
match ← GGParent.DescriptorFromChildDescriptor[searchInfo.slice, seq];
RETURN[match, searchInfo];
};
ENDLOOP;
};
ENDLOOP;
ENDLOOP;
RETURN[NIL, searchInfo];
};
Utilities for the above routines
ManhattanDistance:
PROC [pt1, pt2: Point]
RETURNS [
REAL] = {
RETURN[ABS[pt2.x - pt1.x] + ABS[pt2.y - pt1.y]];
};
SegLooksMatch:
PROC [searchInfo: SearchInfo, fromLooks:
LIST
OF
REF
ANY, matchD: MatchDescriptor]
RETURNS [start, length:
NAT ← 0] = {
noneFound: BOOL ← TRUE;
newStart, newLength: NAT;
looks: LooksInfo;
complexLooks: LIST OF LooksInfo;
reverse: BOOL ← FALSE;
mapping: LIST OF ItemMatch ← NIL;
saveDObj, bestDObj: Match.MatchDObj;
fromLooks ← FlattenLooks[fromLooks];
FOR fromLooks ← fromLooks, fromLooks.rest
UNTIL fromLooks=
NIL
DO
WITH fromLooks.first
SELECT
FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex:
LIST
OF LooksInfo => {
-- not LIST OF LIST OF, since we've flattened slices
looks ← complex.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF NOT SliceLevelMatch[searchInfo.slice, IF searchInfo.kids = NIL THEN NIL ELSE searchInfo.kids.first, looks] THEN LOOP;
saveDObj ← matchD^;
IF NOT looks.typeDef THEN [newStart, newLength, reverse] ← SimpleSegMatch[searchInfo, looks, matchD]
ELSE [newStart, newLength, reverse] ← CompoundSegMatch[searchInfo, complexLooks, matchD];
IF newLength > 0
THEN {
-- we've found a match, but is it the (closest, longest) match?
IF start = newStart AND newLength > length THEN length ← newLength;
IF noneFound
OR newStart < start
THEN {
start ← newStart;
length ← newLength;
mapping ← MakeSegMapping[searchInfo, start, length, reverse, looks, complexLooks];
bestDObj ← saveDObj;
};
noneFound ← FALSE;
};
ENDLOOP;
IF length > 0
THEN {
matchD^ ← bestDObj;
matchD.mapping ← NARROW[GList.Append[mapping, matchD.mapping]];
};
};
FlattenLooks:
PROC [looksList:
LIST
OF
REF
ANY]
RETURNS [newList:
LIST
OF
REF
ANY] = {
FOR looksList ← looksList, looksList.rest
UNTIL looksList=
NIL
DO
WITH looksList.first
SELECT
FROM
simple: LooksInfo => newList ← CONS[simple, newList];
complex:
LIST
OF
LIST
OF LooksInfo => {
FOR complex ← complex, complex.rest
UNTIL complex=
NIL
DO
newList ← CONS[complex.first, newList];
ENDLOOP;
};
ENDCASE => ERROR;
ENDLOOP;
newList ← NARROW[GList.DReverse[newList], LIST OF REF ANY];
};
MakeSegMapping:
PROC [searchInfo: SearchInfo, start, length:
NAT, reverse:
BOOL ← FALSE, looks: LooksInfo, complexLooks:
LIST
OF LooksInfo]
RETURNS [mapping:
LIST
OF ItemMatch ←
NIL] = {
segList: LIST OF Segment ← NARROW[GList.NthTail[searchInfo.segs, start]];
traj: Traj ← IF GGSliceOps.GetType[searchInfo.slice] = $Outline THEN searchInfo.kids.first ELSE NIL;
IF reverse AND looks.typeDef THEN segList ← NARROW[GList.Reverse[segList]];
THROUGH [0..length)
DO
newItem: ItemMatch;
IF looks.typeDef
THEN {
newItem ← NEW[ItemMatchObj ← [matcher: complexLooks.first.owner, matchee: [slice: searchInfo.slice, traj: traj, seg: segList.first], backwards: reverse]];
complexLooks ← complexLooks.rest;
}
ELSE {
newItem ← NEW[ItemMatchObj ← [matcher: looks.owner, matchee: [slice: searchInfo.slice, traj: traj, seg: segList.first], backwards: reverse]];
};
mapping ← CONS[newItem, mapping];
segList ← segList.rest;
ENDLOOP;
};
SimpleSegMatch:
PROC [searchInfo: SearchInfo, looks: LooksInfo, matchD: MatchDescriptor]
RETURNS [start, length:
NAT ← 0, reverse:
BOOL ← FALSE] = {
IF
NOT looks.shapeDef
THEN {
-- Grab first run that fits description
index: NAT ← 0;
FOR segs:
LIST
OF Segment ← searchInfo.segs, segs.rest
UNTIL segs=
NIL
DO
match: BOOL ← SegmentMatch[segs.first, looks];
IF match AND length < 1 THEN start ← index;
IF match THEN length ← length + 1;
IF length > 0 AND NOT match THEN EXIT;
index ← index + 1;
ENDLOOP;
}
ELSE {
[start, length, reverse] ← ShapeDrivenSegMatch[searchInfo, looks, matchD];
RETURN[start, length, reverse];
};
};
ShapeDrivenSegMatch:
PROC [searchInfo: SearchInfo, looks: LooksInfo, matchD: MatchDescriptor]
RETURNS [start, length:
NAT ← 0, reverse:
BOOL ← FALSE] = {
looks.shapeDef is TRUE, looks.typeDef is FALSE
matchData: MatchData ← MatchViewer.GetMatchData[];
revCheck: BOOL ← NOT AtomButtons.GetBinaryState[matchData.polarity];
matchList: LIST OF Segment ← NIL;
IF looks.shapeDef AND looks.shape.closed THEN revCheck ← FALSE; -- can't flip closed turtles!
FOR segs:
LIST
OF Segment ← searchInfo.segs, segs.rest
UNTIL segs =
NIL
DO
tail: LIST OF TurtleInfo ← NIL;
ends: LIST OF LIST OF TurtleInfo ← NIL;
Find longest run, starting at subList.first, for which we have a basic match
FOR subList:
LIST
OF Segment ← segs, subList.rest
UNTIL subList=
NIL
DO
IF SegmentMatch[subList.first, looks]
THEN {
matchList ← CONS[subList.first, matchList];
tail ← MatchTurtle.SegmentToTurtle[subList.first, tail, searchInfo.reverse];
ends ← CONS[MatchTurtle.CopyTurtleTail[tail], ends]; -- list of backwards turtleTails
}
ELSE EXIT;
ENDLOOP;
Now we must find the longest sub-run with a shape match in matchList
FOR matchList ← matchList, matchList.rest
UNTIL matchList =
NIL
DO
-- matchList is backwards
turtle: TurtleHeader ← MatchTurtle.PackageTurtleTail[ends.first];
revTurtle: TurtleHeader ← turtle;
IF searchInfo.reverse THEN turtle ← MatchTurtle.FlipTurtle[turtle] -- so polarity works right
ELSE IF revCheck THEN revTurtle ← MatchTurtle.FlipTurtle[revTurtle];
IF MatchTurtle.TurtleEqual[looks.shape, turtle, MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[start, GList.Length[matchList], FALSE];
IF revCheck AND MatchTurtle.TurtleEqual[looks.shape, revTurtle, MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[start, GList.Length[matchList], TRUE];
ends ← ends.rest;
ENDLOOP;
start ← start + 1;
ENDLOOP;
};
CompoundSegMatch:
PROC [searchInfo: SearchInfo, looks:
LIST
OF LooksInfo, matchD: MatchDescriptor]
RETURNS [start, length:
NAT ← 0, reverse:
BOOL ←
FALSE] = {
We're being called, since typeDef is TRUE (shapeDef may be TRUE)
matchData: MatchData ← MatchViewer.GetMatchData[];
revCheck: BOOL ← NOT AtomButtons.GetBinaryState[matchData.polarity];
single: LooksInfo;
matchLength: NAT ← GList.Length[looks];
revLooks: LIST OF LooksInfo;
IF looks=NIL THEN RETURN ELSE single ← looks.first; -- if looks is NIL, target not compound
IF single.shapeDef AND single.shape.closed THEN revCheck ← FALSE; -- can't flip closed turtles!
revLooks ← IF revCheck THEN FlipLooks[looks] ELSE NIL;
FOR subList:
LIST
OF Segment ← searchInfo.segs, subList.rest
UNTIL subList =
NIL
DO
foreMatch: BOOL ← TRUE;
backMatch: BOOL ← revCheck;
foreLooks: LIST OF LooksInfo ← looks;
backLooks: LIST OF LooksInfo ← revLooks;
segs: LIST OF Segment ← subList;
tail: LIST OF TurtleInfo ← NIL;
turtle: TurtleHeader;
THROUGH [0..matchLength)
DO
IF segs=NIL THEN RETURN; -- No matches found (signaled by length ← 0)
IF foreMatch AND NOT SegmentMatch[segs.first, foreLooks.first] THEN foreMatch ← FALSE;
IF backMatch AND NOT SegmentMatch[segs.first, backLooks.first] THEN backMatch ← FALSE;
IF NOT foreMatch AND NOT backMatch THEN EXIT;
IF single.shapeDef THEN tail ← MatchTurtle.SegmentToTurtle[segs.first, tail, searchInfo.reverse];
segs ← segs.rest; -- do list bookkeeping . . .
foreLooks ← foreLooks.rest;
IF backLooks # NIL THEN backLooks ← backLooks.rest;
ENDLOOP;
IF single.shapeDef
AND (foreMatch
OR backMatch)
THEN {
turtle ← MatchTurtle.PackageTurtleTail[tail];
IF searchInfo.reverse THEN turtle ← MatchTurtle.FlipTurtle[turtle];
};
IF foreMatch
THEN {
IF NOT single.shapeDef OR MatchTurtle.TurtleEqual[single.shape, turtle, MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[start, matchLength, FALSE];
};
IF backMatch
THEN {
IF NOT single.shapeDef OR MatchTurtle.TurtleEqual[single.shape, MatchTurtle.FlipTurtle[turtle], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[start, matchLength, TRUE];
};
start ← start + 1;
ENDLOOP;
No matches found (signaled by length ← 0)
};
FlipLooks:
PROC [looksList:
LIST
OF LooksInfo]
RETURNS [newList:
LIST
OF LooksInfo] = {
newList ← NARROW[GList.Reverse[looksList], LIST OF LooksInfo];
And more to come!!
};
SimpleMatch:
PROC [sliceD: SliceDescriptor, looks: LooksInfo, matchD: MatchDescriptor ←
NIL, checkShape:
BOOL ←
TRUE]
RETURNS [match:
BOOL ←
TRUE] = {
Compare two single-path objects.
slice: Slice ← sliceD.slice;
IF looks.typeDef AND looks.type # GGSliceOps.GetType[slice] THEN RETURN[FALSE];
IF looks.classDef AND looks.class # GGSliceOps.GetType[slice] THEN RETURN[FALSE];
IF looks.colorDef AND NOT GGCoreOps.EquivalentColors[looks.color, GGSliceOps.GetStrokeColor[slice, sliceD.parts].color] THEN RETURN[FALSE];
IF looks.fillColorDef AND NOT GGCoreOps.EquivalentColors[looks.fillColor, GGSliceOps.GetFillColor[slice, sliceD.parts].color] THEN RETURN[FALSE];
IF looks.stringDef AND (GGSliceOps.GetType[slice] # $Text OR NOT Rope.Equal[GGSlice.GetText[slice], looks.string]) THEN RETURN[FALSE];
IF looks.fontDef AND NOT FontEqual[looks.font, GGSlice.GetFontData[slice].fontData] THEN RETURN[FALSE];
IF looks.fontTformDef AND NOT FontAndTransformEqual[looks.fontTform, GGSlice.GetFontData[slice].fontData] THEN RETURN[FALSE];
IF looks.dashesDef
THEN {
dashInfo: DashInfo ← NEW[DashInfoObj];
[dashed: dashInfo.dashed, pattern: dashInfo.pattern, offset: dashInfo.offset, length: dashInfo.length] ← GGSliceOps.GetDashed[slice, sliceD.parts];
IF NOT DashEqual[dashInfo, looks.dashes] THEN RETURN[FALSE];
};
IF looks.jointsDef AND looks.joints # GGSliceOps.GetStrokeJoint[slice, sliceD.parts].strokeJoint THEN RETURN[FALSE];
IF looks.endsDef AND looks.ends # GGSliceOps.GetStrokeEnd[slice, sliceD.parts].strokeEnd THEN RETURN[FALSE];
IF looks.widthDef AND looks.width # GGSliceOps.GetStrokeWidth[slice, sliceD.parts].strokeWidth THEN RETURN[FALSE];
IF checkShape
AND looks.shapeDef
THEN {
IF GGSliceOps.GetType[slice] = $Outline
THEN {
seqs: LIST OF SliceDescriptor ← GGParent.ListIncludedChildren[sliceD.slice, sliceD.parts, first];
IF seqs=NIL OR seqs.rest#NIL THEN RETURN[FALSE]; -- can't match multiple spans
IF NOT MatchTurtle.TurtleEqualEitherWay[looks.shape, MatchTurtle.TrajToTurtle[seqs.first.slice], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[FALSE];
}
ELSE IF NOT MatchTurtle.TurtleEqualEitherWay[looks.shape, MatchTurtle.GetShapeOfSlice[slice], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[FALSE];
};
IF checkShape THEN AddMapping[matchD, looks.owner, SliceDToItemID[sliceD]];
};
AddMapping:
PROC [matchD: MatchDescriptor, matcher, matchee: ItemID, backwards:
BOOL ←
FALSE] = {
newMatch: ItemMatch ← NEW[ItemMatchObj ← [matcher: matcher, matchee: matchee, backwards: backwards]];
matchD.mapping ← CONS[newMatch, matchD.mapping];
};
ComplexMatch:
PROC [sliceD: SliceDescriptor, sliceLooks:
LIST
OF
LIST
OF LooksInfo, matchD: MatchDescriptor]
RETURNS [
BOOL ←
TRUE] = {
At this point it is known that the slice is either a box or an outline, and sliceLooks is of complex form
SELECT GGSliceOps.GetType[sliceD.slice]
FROM
$Outline => RETURN[ComplexOutlineMatch[sliceD, sliceLooks, matchD]];
$Box => RETURN[ComplexBoxMatch[sliceD, sliceLooks, matchD]];
ENDCASE => ERROR;
};
SliceDToItemID:
PROC [sliceD: SliceDescriptor]
RETURNS [itemID: ItemID] = {
NOTE: only works for SliceDescriptors of entire slices (if slice is not an outline) or SliceDesriptors of single trajectories (if slice is an outline)
itemID.slice ← sliceD.slice;
IF GGSliceOps.GetType[sliceD.slice] = $Outline
THEN {
childD: SliceDescriptor ← GGParent.FirstIncludedChild[sliceD.slice, sliceD.parts, first];
Must include a single trajectory.
itemID.traj ← childD.slice;
};
};
Outline utilities
ComplexOutlineMatch:
PROC [sliceD: SliceDescriptor, sliceLooks:
LIST
OF
LIST
OF LooksInfo, matchD: MatchDescriptor]
RETURNS [
BOOL ←
FALSE] = {
We're matching on the shape or type (or both) of the Outline trajectories
matchData: MatchData ← MatchViewer.GetMatchData[];
structure: BOOL ← TRUE;
trajs: LIST OF Traj ← GGOutline.TrajectoriesOfOutline[sliceD.slice]; -- sliceD is complete
IF structure
THEN {
copyD: MatchDescriptor ← NEW[MatchDObj ← matchD^];
traj: Traj ← trajs.first;
trajLooks: LIST OF LooksInfo ← sliceLooks.first;
looks: LooksInfo ← trajLooks.first;
matcher: MatchTurtle.Matcher;
next: PositionDescriptor;
IF matchD.posD.valid THEN RETURN[OutlineMatchOriented[trajs, sliceLooks, structure, matchD]]; -- ie, we already have an orientation to match on.
Try to match traj with looks, for every possible orientation...
IF NOT TrajMatch[traj, trajLooks, copyD, FALSE] THEN RETURN[FALSE];
IF
NOT looks.shapeDef
THEN {
IF OutlineMatchOriented[trajs.rest, sliceLooks.rest, structure, copyD]
THEN {
matchD^ ← copyD^;
RETURN[TRUE];
}
ELSE RETURN [FALSE];
};
matcher ← MatchTurtle.CreateMatcher[looks.shape, MatchTurtle.TrajToTurtle[traj], MatchViewer.GetMatchTolerance[], AtomButtons.GetBinaryState[matchData.polarity]];
WHILE (next ← MatchTurtle.NextMatch[matcher]) #
NIL
DO
copyD.posD ← next;
IF OutlineMatchOriented[trajs.rest, sliceLooks.rest, structure, copyD]
THEN {
matchD^ ← copyD^;
RETURN[TRUE];
};
ENDLOOP;
}
ELSE {
MatchViewer.ErrorFeedback["Only can deal with structure matches now."];
};
};
OutlineMatchOriented:
PROC [trajs:
LIST
OF Traj, sliceLooks:
LIST
OF
LIST
OF LooksInfo, structure:
BOOL ← FALSE, matchD: MatchDescriptor]
RETURNS [
BOOL ←
TRUE] = {
We're matching on the shape or type (or both) of the Outline trajectories.
IF trajs=NIL THEN RETURN[TRUE]; -- base case
IF structure
THEN {
traj: Traj ← trajs.first;
trajData: TrajData ← NARROW[traj.data];
trajLooks: LIST OF LooksInfo ← sliceLooks.first;
looks: LooksInfo ← trajLooks.first;
segGen: SegmentGenerator ← GGSequence.SegmentsInTraj[trajData];
copyD: MatchDescriptor ← NEW[MatchDObj ← matchD^];
IF NOT TrajMatch[traj, trajLooks, copyD, FALSE] THEN RETURN[FALSE];
IF looks.shapeDef AND NOT MatchTurtle.TurtleEqual[looks.shape, MatchTurtle.TrajToTurtle[traj], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[FALSE];
IF NOT OutlineMatchOriented[trajs.rest, sliceLooks.rest, structure, copyD] THEN RETURN[FALSE]
ELSE matchD^ ← copyD^; -- only make changes in the descriptor if there's a match!
}
ELSE {
MatchViewer.ErrorFeedback["Only can deal with structure matches now."];
RETURN[FALSE];
};
};
TrajMatch:
PROC [traj: Traj, trajLooks:
LIST
OF LooksInfo, matchD: MatchDescriptor, checkShape:
BOOL ←
TRUE]
RETURNS [
BOOL ←
TRUE] = {
We're matching on shape or type (or both) of Outline trajectories
trajData: TrajData ← NARROW[traj.data];
segGen: SegmentGenerator ← GGSequence.SegmentsInTraj[trajData];
looks: LooksInfo ← trajLooks.first;
localMapping: LIST OF ItemMatch ← NIL;
IF looks.typeDef
THEN {
First do traj & slice specific checks
IF looks.classDef AND looks.class # $Outline THEN RETURN[FALSE];
IF looks.jointsDef AND looks.joints # GGSliceOps.GetStrokeJoint[traj, NIL].strokeJoint THEN RETURN[FALSE];
IF looks.fillColorDef AND NOT GGCoreOps.EquivalentColors[looks.fillColor, GGSliceOps.GetFillColor[traj.parent, NIL].color] THEN RETURN [FALSE];
Next do lower level segment checks if typeDef is TRUE
IF trajData.segCount # GList.Length[trajLooks] THEN RETURN[FALSE];
FOR seg: Segment ← GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen]
UNTIL seg =
NIL
DO
IF NOT SegmentMatch[seg, trajLooks.first] THEN RETURN[FALSE];
localMapping ← CONS[NEW[ItemMatchObj ← [matcher: trajLooks.first.owner, matchee: [slice: traj.parent, traj: traj, seg: seg]]], localMapping]; -- looks good so far
trajLooks ← trajLooks.rest;
ENDLOOP;
}
ELSE {
-- match the looks characteristics on the traj as a whole
seq: Sequence ← GGSlice.DescriptorFromParts[
traj,
GGSequence.CreateComplete[trajData]
];
sliceD: SliceDescriptor ← GGParent.DescriptorFromChildDescriptor[traj.parent, seq];
IF NOT SimpleMatch[sliceD, looks, matchD, FALSE] THEN RETURN[FALSE];
localMapping ← LIST[NEW[ItemMatchObj ← [matcher: looks.owner, matchee: [slice: traj.parent, traj: traj]]]];
};
IF checkShape AND looks.shapeDef AND NOT MatchTurtle.TurtleEqualEitherWay[looks.shape, MatchTurtle.TrajToTurtle[traj], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN [FALSE];
matchD.mapping ← NARROW[GList.Append[localMapping, matchD.mapping]]; -- save mapping
Box utilities
ComplexBoxMatch:
PROC [sliceD: SliceDescriptor, sliceLooks:
LIST
OF
LIST
OF LooksInfo, matchD: MatchDescriptor]
RETURNS [
BOOL ←
TRUE] = {
Object type or shape is set but object class may not be set. Consider the box to be a set of $Line segments.
GatherSegs: GGModelTypes.WalkProc = {segList ← CONS[seg, segList]; keep ← TRUE};
segList: LIST OF Segment ← NIL;
looks: LooksInfo;
slice: Slice ← sliceD.slice;
IF GList.Length[sliceLooks.first] # 4 THEN RETURN[FALSE]; -- boxes have 4 segs. We may have to change this later on if we allow wildcard segs
First do slice specific checks
looks ← sliceLooks.first.first;
IF looks.fillColorDef AND NOT GGCoreOps.EquivalentColors[looks.fillColor, GGSliceOps.GetFillColor[sliceD.slice, sliceD.parts].color] THEN RETURN [FALSE];
IF looks.shapeDef AND NOT MatchTurtle.TurtleEqualEitherWay[looks.shape, MatchTurtle.GetShapeOfSlice[slice], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN [FALSE]; -- shape may be out of phase with segments!
Looks good so far. Now we match against the individual segments
[] ← GGSliceOps.WalkSegments[slice, GatherSegs];
segList ← NARROW[GList.DReverse[segList]];
IF NOT SegListMatch[segList, sliceLooks.first] THEN RETURN [FALSE];
FOR looksList:
LIST
OF LooksInfo ← sliceLooks.first, looksList.rest
UNTIL looksList =
NIL
DO
AddMapping[matchD, looksList.first.owner, [slice: sliceD.slice, seg: segList.first]];
segList ← segList.rest;
ENDLOOP; -- register matches
};
SegListMatch:
PROC [segList:
LIST
OF Segment, looks:
LIST
OF LooksInfo]
RETURNS [
BOOL ←
TRUE] = {
IF GList.Length[segList] # GList.Length[looks] THEN RETURN[FALSE]; -- this will change with advent of wildcards
FOR segList ← segList, segList.rest
UNTIL segList=
NIL
DO
IF NOT SegmentMatch[segList.first, looks.first] THEN RETURN[FALSE];
looks ← looks.rest;
ENDLOOP;
};
SegmentMatch:
PROC [seg: Segment, looks: LooksInfo]
RETURNS [
BOOL ←
TRUE] = {
Segment-Level checks
IF looks.typeDef AND looks.type # seg.class.type THEN RETURN[FALSE];
IF looks.colorDef AND NOT GGCoreOps.EquivalentColors[looks.color, seg.color] THEN RETURN[FALSE];
IF looks.dashesDef
THEN {
dashInfo: DashInfo ← NEW[DashInfoObj ← [dashed: seg.dashed, pattern: seg.pattern, offset: seg.offset, length: seg.length]];
IF NOT DashEqual[dashInfo, looks.dashes] THEN RETURN[FALSE];
};
IF looks.endsDef AND looks.ends # seg.strokeEnd THEN RETURN[FALSE];
IF looks.widthDef AND looks.width # seg.strokeWidth THEN RETURN[FALSE];
};
SliceLevelMatch:
PROC [slice: Slice, traj: Traj ←
NIL, looks: LooksInfo]
RETURNS [
BOOL ←
TRUE] = {
Slice-Level checks
IF looks.classDef AND looks.class # GGSliceOps.GetType[slice] THEN RETURN[FALSE];
IF looks.fillColorDef AND NOT GGCoreOps.EquivalentColors[looks.fillColor, GGSliceOps.GetFillColor[slice, NIL].color] THEN RETURN[FALSE];
IF GGSliceOps.GetType[slice] = $Outline AND looks.jointsDef AND traj # NIL AND looks.joints # GGSliceOps.GetStrokeJoint[traj, NIL].strokeJoint THEN RETURN[FALSE];
};
Comparing Look Properties
LooksEqual:
PUBLIC
PROC [l1, l2: LooksInfo]
RETURNS [
BOOL ←
TRUE] = {
Doesn't compare the shape fields
IF l1 = l2 THEN RETURN[TRUE];
IF l1 = NIL OR l2 = NIL THEN RETURN[FALSE];
IF (l1.classDef # l2.classDef) OR (l1.typeDef # l2.typeDef) OR (l1.colorDef # l2.colorDef) OR (l1.fillColorDef # l2.fillColorDef) OR (l1.stringDef # l2.stringDef) OR (l1.fontDef # l2.fontDef) OR (l1.fontTformDef # l2.fontTformDef) OR (l1.dashesDef # l2.dashesDef) OR (l1.jointsDef # l2.jointsDef) OR (l1.endsDef # l2.endsDef) OR (l1.widthDef # l2.widthDef) THEN RETURN[FALSE];
IF l1.classDef AND l1.class # l2.class THEN RETURN[FALSE];
IF l1.typeDef AND l1.type # l2.type THEN RETURN[FALSE];
IF l1.colorDef AND NOT GGCoreOps.EquivalentColors[l1.color, l2.color] THEN RETURN[FALSE];
IF l1.fillColorDef AND NOT GGCoreOps.EquivalentColors[l1.fillColor, l2.fillColor] THEN RETURN[FALSE];
IF l1.stringDef AND NOT Rope.Equal[l1.string, l2.string] THEN RETURN[FALSE];
IF l1.fontDef AND NOT FontEqual[l1.font, l2.font] THEN RETURN[FALSE];
IF l1.fontTformDef AND NOT FontAndTransformEqual[l1.fontTform, l2.fontTform] THEN RETURN[FALSE];
IF l1.dashesDef AND NOT DashEqual[l1.dashes, l2.dashes] THEN RETURN[FALSE];
IF l1.jointsDef AND l1.joints # l2.joints THEN RETURN[FALSE];
IF l1.endsDef AND l1.ends # l2.ends THEN RETURN[FALSE];
IF l1.widthDef AND l1.width # l2.width THEN RETURN[FALSE];
};
FontAndTransformEqual:
PROC [f1, f2: GGFont.FontData]
RETURNS [match:
BOOL ←
FALSE] = {
Uses the criteria of GGEvent.SelectMatchingData
maxPixels: REAL = 10000.0;
IF f1 # NIL AND f2 # NIL AND Rope.Equal[f1.literal, f2.literal, FALSE] AND RealFns.AlmostEqual[f1.storedSize, f2.storedSize, -9] AND ImagerTransformation.CloseToTranslation[f1.transform, f2.transform, maxPixels] THEN RETURN[TRUE];
};
FontEqual:
PROC [f1, f2: GGFont.FontData]
RETURNS [match:
BOOL ←
FALSE] = {
IF f1 # NIL AND f2 # NIL AND Rope.Equal[f1.literal, f2.literal, FALSE] AND RealFns.AlmostEqual[f1.storedSize, f2.storedSize, -9] THEN RETURN[TRUE];
};
DashEqual:
PROC [d1, d2: DashInfo]
RETURNS [match:
BOOL ←
TRUE] = {
IF d1.dashed = FALSE AND d2.dashed = FALSE THEN RETURN [TRUE];
IF d1.dashed # d2.dashed OR d1.offset # d2.offset OR d1.length # d2.length OR d1.pattern.len # d2.pattern.len THEN RETURN [FALSE];
FOR i:
INT
IN [0..d1.pattern.len)
DO
IF d1.pattern[i] # d2.pattern[i] THEN RETURN[FALSE];
ENDLOOP;
};
Disjunctive Search Routines
SearchDisjInit:
PUBLIC
PROC [them: GGData, event:
LIST
OF
REF
ANY] = {
Find any object that matches an object in the From viewer (searchOp = disjunction)
matchData: MatchData ← MatchViewer.GetMatchData[];
searchState: SearchState ← Match.GetSearchState[];
If searching in new viewer or caret position changed or starting search from top,
then build a new description of the target scene.
IF them # matchData.theirData
OR GGCaret.GetPoint[them.caret] # matchData.lastCaretPos
OR event.rest.first = $SearchFromTop
THEN {
searchState.ahead ← RemakeDisjList[them, event.rest.first, TRUE];
matchData.theirData ← them;
matchData.lastCaretPos ← GGCaret.GetPoint[them.caret];
}
ELSE
IF event.rest.first # searchState.lastDirection
THEN {
-- reverse search direction
Match-select all normal-selected slices, so they're not included in search.
DoSelect:
PROC [normalD: SliceDescriptor]
RETURNS [done:
BOOL ←
FALSE] = {
GGSelect.SelectSlice[normalD, them.scene, match];
};
matchSelectD: SliceDescriptor ← NIL;
[] ← GGScene.WalkSelectedSlices[them.scene, first, DoSelect, normal];
IF searchState.ahead #
NIL
THEN {
firstSlice: Slice ← searchState.ahead.first.slice;
matchSelectD ← GGSelect.FindSelectedSlice[firstSlice, them.scene, match];
matchSelectD ← GGSelect.FindSelectedSlice[firstSlice, match];
IF matchSelectD = NIL THEN matchSelectD ← GGSliceOps.NewParts[firstSlice, NIL, none];
};
searchState.ahead ← RemakeDisjList[them, event.rest.first, TRUE];
RemoveSelected[searchState.ahead, matchSelectD]; --do bookkeeping so that search will begin (in the other direction) where the last search left off
};
Otherwise, leave searchState alone.
searchState.lastDirection ← IF event.rest.first = $SearchFromTop THEN $SearchBelow ELSE event.rest.first;
};
SearchDisjNext:
PUBLIC
PROC [searchList:
LIST
OF SearchInfo, direction:
REF
ANY, refresh:
BOOL ←
TRUE]
RETURNS [newSearchList:
LIST
OF SearchInfo, found:
BOOL ← FALSE] = {
target: SliceDescriptor ← NIL;
matchData: MatchData ← MatchViewer.GetMatchData[];
check, checkNext: SearchInfo ← NIL;
fromLooks: LIST OF REF ANY ← GetFromLooks[];
matchD: MatchDescriptor ← Match.CreateMatchDescriptor[];
DO
IF
NOT IsEmptySearchInfo[check]
THEN {
-- something left of this slice to match on
[target, checkNext] ← FindMatchInSlice[check, fromLooks, direction, matchD];
DisjClearMatchBits[check, checkNext, matchData.theirData.scene];
IF target = NIL THEN check ← checkNext -- no match
ELSE {
-- we've matched something!
fromData: GGData ← MatchViewer.GetFromData[];
GGSelect.DeselectAll[matchData.theirData.scene, normal];
IF NOT AtomButtons.GetBinaryState[matchData.contextSensitive] THEN Match.SelectMapped[matchData.theirData.scene, matchD.mapping]
ELSE Match.SelectMapped[matchData.theirData.scene, Match.SelectionFilter[fromData.scene, matchD.mapping]];
matchData.lastCaretPos ← [GGCaret.GetPoint[matchData.theirData.caret].x, target.slice.boundBox.hiY];
GGCaret.SetAttractor[matchData.theirData.caret, matchData.lastCaretPos, [0, -1], NIL];
IF refresh THEN GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: matchData.theirData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
Match.RememberMatchDescriptor[matchD];
IF checkNext = NIL THEN ERROR; -- we no longer consider nil SearchInfo's empty
RETURN[CONS[checkNext, searchList], TRUE];
};
}
ELSE
-- no more interesting stuff in slice
IF searchList #
NIL
THEN {
IF check # NIL THEN GGSelect.DeselectEntireSlice[check.slice, matchData.theirData.scene, match];
check ← searchList.first;
searchList ← searchList.rest;
}
ELSE {
GGSelect.DeselectAll[matchData.theirData.scene, normal];
IF refresh THEN GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: matchData.theirData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
Match.RememberMatchDescriptor[NIL];
RETURN[NIL, FALSE];
};
ENDLOOP;
};
RemakeDisjList:
PUBLIC
PROC [theirData: GGData, direction:
REF
ANY, select:
BOOL ← FALSE]
RETURNS [toSearch:
LIST
OF SearchInfo] = {
toSearch will hold SearchInfo's for objects whose boundBox is below the caret if the search is progressing in a downwards direction, and above the caret if the search is progressing upwards. If select is true, then all slices ahead will be match selected (and those behind will be cleared). This routine is called during disjunction search.
GetOrderedSlices:
PROC [ggData: GGData, direction:
REF
ANY]
RETURNS [sliceList:
LIST
OF Slice ←
NIL] = {
scene: Scene ← ggData.scene;
caretPos: Point ← GGCaret.GetPoint[ggData.caret];
SELECT direction
FROM
$SearchBelow => {
DoSearchBelow:
PROC [slice: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
IF caretPos.y >= GGSliceOps.GetBoundBox[slice].hiY THEN sliceList ← CONS[slice, sliceList];
};
[] ← GGScene.WalkSlices[scene, first, DoSearchBelow];
};
$SearchAbove => {
DoSearchAbove:
PROC [slice: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
IF caretPos.y <= GGSliceOps.GetBoundBox[slice].hiY THEN sliceList ← CONS[slice, sliceList];
};
[] ← GGScene.WalkSlices[scene, first, DoSearchAbove];
};
$SearchFromTop => {
DoSearchFromTop:
PROC [slice: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
sliceList ← CONS[slice, sliceList];
};
[] ← GGScene.WalkSlices[scene, first, DoSearchFromTop];
};
ENDCASE => ERROR;
sliceList ← HeightSort[sliceList, IF direction = $SearchAbove THEN FALSE ELSE TRUE];
};
caretPos: Point ← GGCaret.GetPoint[theirData.caret];
slices: LIST OF Slice ← GetOrderedSlices[theirData, direction];
IF select THEN GGSelect.DeselectAll[theirData.scene, match]; -- speeds subsequent selections
FOR sList:
LIST
OF Slice ← slices, sList.rest
UNTIL sList =
NIL
DO
IF select THEN GGSelect.SelectEntireSlice[sList.first, theirData.scene, match];
toSearch ← CONS[CreateSearchInfo[sList.first, direction], toSearch];
ENDLOOP;
};
DisjClearMatchBits:
PROC [first, next: SearchInfo, scene: Scene] = {
SegmentsInInfo: GGModelTypes.WalkProc = { RETURN[GList.Member[seg, next.segs]]; };
If the next SearchInfo is Empty, then deselect the entire slice described by first
IF IsEmptySearchInfo[next] THEN GGSelect.DeselectEntireSlice[first.slice, scene, match]
ELSE {
-- a bit more complicated (part of next still remains to be searched)
SELECT GGSliceOps.GetType[first.slice]
FROM
$Box => {
newSelection: SliceDescriptor ← GGSliceOps.WalkSegments[next.slice, SegmentsInInfo];
GGSelect.DeselectEntireSlice[first.slice, scene, match];
GGSelect.SelectSlice[newSelection, scene, match];
};
$Outline => {
newSelection: SliceDescriptor ← GGSliceOps.WalkSegments[next.slice, SegmentsInInfo];
FOR trajList:
LIST
OF Traj ← next.kids.rest, trajList.rest
UNTIL trajList =
NIL
DO
trajData: TrajData ← NARROW[trajList.first.data];
seq: Sequence ← GGSlice.DescriptorFromParts[
trajList.first,
GGSequence.CreateComplete[trajData]
];
trajD: SliceDescriptor ← GGParent.DescriptorFromChildDescriptor[seq.slice.parent, seq];
newSelection ← GGSliceOps.UnionParts[newSelection, trajD];
ENDLOOP;
GGSelect.DeselectEntireSlice[first.slice, scene, match];
GGSelect.SelectSlice[newSelection, scene, match];
};
ENDCASE => GGSelect.DeselectEntireSlice[first.slice, scene, match];
};
};
Bookkeeping to keep track of what parts of a slice have been visited, when a substitution or change of search direction has occurred.
RemoveSelected:
PUBLIC
PROC [infoList:
LIST
OF SearchInfo, sliceD: SliceDescriptor] = {
We have done a replace or changed the search direction.
sliceD is the SliceDescriptor of the "in progress" SearchInfo.
infoList is the ahead SearchInfo list.
We remove the sliceD pieces from the ahead list since they have already been looked at.
IF sliceD = NIL THEN RETURN;
FOR iList:
LIST
OF SearchInfo ← infoList, iList.rest
UNTIL iList =
NIL
DO
IF sliceD.slice = iList.first.slice THEN RemoveDescriptorFromInfo[sliceD, iList.first];
ENDLOOP;
};
RemoveDescriptorFromInfo:
PUBLIC
PROC [sliceD: SliceDescriptor, info: SearchInfo] = {
FilterSegs:
PROC [sliceD: SliceDescriptor, info: SearchInfo] = {
segList, revSegs: LIST OF Segment ← NIL;
segGen: SegmentGenerator ← GGSliceOps.SegmentsInDescriptor[sliceD];
FOR seg: Segment ← GGSliceOps.NextSegment[sliceD.slice, segGen].seg, GGSliceOps.NextSegment[sliceD.slice, segGen].seg
UNTIL seg =
NIL
DO
segList ← CONS [seg, segList];
ENDLOOP; -- we've formed a list of segments in the descriptor
revSegs ← NARROW[GList.Reverse[info.segs]];
info.segs ← NIL;
FOR infoSegs:
LIST
OF Segment ← revSegs, infoSegs.rest
UNTIL infoSegs =
NIL
DO
IF GList.Member[infoSegs.first, segList] THEN EXIT
ELSE info.segs ← CONS[infoSegs.first, info.segs];
ENDLOOP;
};
matchLevel: MatchViewer.MatchLevel ← MatchViewer.GetMatchData[].matchLevel;
IF info.slice # sliceD.slice THEN RETURN; -- sliceD and info must relate to same slice
IF GGSliceOps.IsEmptyParts[sliceD] THEN RETURN; -- nothing to remove
SELECT GGSliceOps.GetType[sliceD.slice]
FROM
$Box => {
IF matchLevel = clusterLevel OR matchLevel = trajLevel THEN info.empty ← TRUE
ELSE FilterSegs[sliceD, info]; -- matchLevel = anywhere
};
$Outline => {
IF matchLevel = clusterLevel THEN info.empty ← TRUE
ELSE
IF matchLevel = trajLevel
THEN {
revTrajs: LIST OF Traj ← NARROW[GList.Reverse[info.kids]];
info.kids ← NIL;
FOR trajs:
LIST
OF Traj ← revTrajs, trajs.rest
UNTIL trajs =
NIL
DO
seq: SliceDescriptor ← GGParent.ChildDescriptorFromDescriptor[sliceD, trajs.first];
IF NOT GGSliceOps.IsEmptyParts[seq] THEN EXIT
ELSE info.kids ← CONS[trajs.first, info.kids];
ENDLOOP;
}
ELSE {
-- matchLevel = anywhere
oldTrajs: LIST OF Traj ← info.kids;
revTrajs: LIST OF Traj ← NARROW[GList.Reverse[info.kids]];
info.kids ← NIL;
FOR trajs:
LIST
OF Traj ← revTrajs, trajs.rest
UNTIL trajs =
NIL
DO
seq: SliceDescriptor ← GGParent.ChildDescriptorFromDescriptor[sliceD, trajs.first];
info.kids ← CONS[trajs.first, info.kids];
IF NOT GGSliceOps.IsEmptyParts[seq] THEN EXIT;
ENDLOOP;
IF info.kids.first # oldTrajs.first
THEN {
info.segs ← NIL;
CacheSegsOfFirstTraj[info];
};
FilterSegs[sliceD, info];
};
};
ENDCASE => info.empty ← TRUE;
General Search Utilities
HeightSort:
PUBLIC
PROC [slices:
LIST
OF Slice, ascending:
BOOL ← FALSE]
RETURNS [
LIST
OF Slice] = {
Height sorts slices by upper left of bounding box (according to the ascending BOOL). Breaks ties by using loX values (ordered according to the complement of the ascending BOOL), so if ascending = FALSE, search will proceed downwards and left to right.
CompareReal:
PROC [r1, r2:
REAL]
RETURNS [Basics.Comparison] ~ {
RETURN [IF r1=r2 THEN equal ELSE IF r1>r2 THEN greater ELSE less];
};
CompareProc: GList.CompareProc = {
[ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]
slice1: Slice ← NARROW[ref1];
slice2: Slice ← NARROW[ref2];
epsilon: REAL ← 0.072; -- 0.001 inches
priority1: REAL ← GGSliceOps.GetBoundBox[slice1].hiY;
priority2: REAL ← GGSliceOps.GetBoundBox[slice2].hiY;
IF
ABS[priority1 - priority2] <= 0.0
THEN {
-- sort left to right
priority1 ← GGSliceOps.GetBoundBox[slice2].loX; -- slices swapped intentionally
priority2 ← GGSliceOps.GetBoundBox[slice1].loX;
};
IF ascending THEN RETURN[CompareReal[priority1, priority2]]
ELSE RETURN[CompareReal[priority2, priority1]];
};
slices ← NARROW[GList.Sort[slices, CompareProc]];
RETURN[slices];
};
END.