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; 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]; }; }; 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] = { 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] = { newInfo _ NEW[SearchInfoObj _ info^]; }; MakeEmpty: PROC [info: SearchInfo] RETURNS [newInfo: SearchInfo] = { newInfo _ CopySearchInfo[info]; newInfo.empty _ TRUE; }; CacheSegsOfFirstTraj: PUBLIC PROC [info: SearchInfo] = { 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]]; 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; }; 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] = { 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; }; 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]; 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 }; 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]; }; 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 }; 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]; }; 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; 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] = { 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; 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; 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] = { 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; }; FlipLooks: PROC [looksList: LIST OF LooksInfo] RETURNS [newList: LIST OF LooksInfo] = { newList _ NARROW[GList.Reverse[looksList], LIST OF LooksInfo]; }; SimpleMatch: PROC [sliceD: SliceDescriptor, looks: LooksInfo, matchD: MatchDescriptor _ NIL, checkShape: BOOL _ TRUE] RETURNS [match: BOOL _ TRUE] = { 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] = { 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] = { itemID.slice _ sliceD.slice; IF GGSliceOps.GetType[sliceD.slice] = $Outline THEN { childD: SliceDescriptor _ GGParent.FirstIncludedChild[sliceD.slice, sliceD.parts, first]; itemID.traj _ childD.slice; }; }; ComplexOutlineMatch: PROC [sliceD: SliceDescriptor, sliceLooks: LIST OF LIST OF LooksInfo, matchD: MatchDescriptor] RETURNS [BOOL _ FALSE] = { 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. 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] = { 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] = { trajData: TrajData _ NARROW[traj.data]; segGen: SegmentGenerator _ GGSequence.SegmentsInTraj[trajData]; looks: LooksInfo _ trajLooks.first; localMapping: LIST OF ItemMatch _ NIL; IF looks.typeDef THEN { 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]; 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 }; ComplexBoxMatch: PROC [sliceD: SliceDescriptor, sliceLooks: LIST OF LIST OF LooksInfo, matchD: MatchDescriptor] RETURNS [BOOL _ TRUE] = { 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 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! [] _ 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] = { 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] = { 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]; }; LooksEqual: PUBLIC PROC [l1, l2: LooksInfo] RETURNS [BOOL _ TRUE] = { 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] = { 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; }; SearchDisjInit: PUBLIC PROC [them: GGData, event: LIST OF REF ANY] = { matchData: MatchData _ MatchViewer.GetMatchData[]; searchState: SearchState _ Match.GetSearchState[]; 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 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, 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 }; 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] = { 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 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]; }; }; RemoveSelected: PUBLIC PROC [infoList: LIST OF SearchInfo, sliceD: SliceDescriptor] = { 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; }; HeightSort: PUBLIC PROC [slices: LIST OF Slice, ascending: BOOL _ FALSE] RETURNS [LIST OF Slice] = { 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 = { 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. DMatchImplA.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 Top-level search routines Utilities for building and querying SearchInfos Creates a new, complete SearchInfo for the specified slice Only non-destructive list ops are permitted on SearchInfo fields, so just copy the fields. Stores in info.segs all of the segments of the first trajectory of the shape that info represents. Fill in closedTraj field (is this ever used? -- Bier) Extracting Looks Information Returns the Looks of the slice that are specified in choice (if value = TRUE) or not specified (if value = FALSE) Matching a domain slice to the from looks. Make a sliceD for the match, and a rest Now we have to splice out the used segments from searchInfo.segs Make a sliceD out of it and a rest, and return Now we have to splice out the used segments from searchInfo.segs Utilities for the above routines IF reverse AND looks.typeDef THEN segList _ NARROW[GList.Reverse[segList]]; looks.shapeDef is TRUE, looks.typeDef is FALSE Find longest run, starting at subList.first, for which we have a basic match Now we must find the longest sub-run with a shape match in matchList We're being called, since typeDef is TRUE (shapeDef may be TRUE) No matches found (signaled by length _ 0) And more to come!! Compare two single-path objects. At this point it is known that the slice is either a box or an outline, and sliceLooks is of complex form NOTE: only works for SliceDescriptors of entire slices (if slice is not an outline) or SliceDesriptors of single trajectories (if slice is an outline) Must include a single trajectory. Outline utilities We're matching on the shape or type (or both) of the Outline trajectories Try to match traj with looks, for every possible orientation... We're matching on the shape or type (or both) of the Outline trajectories. We're matching on shape or type (or both) of Outline trajectories First do traj & slice specific checks Next do lower level segment checks if typeDef is TRUE Box utilities Object type or shape is set but object class may not be set. Consider the box to be a set of $Line segments. First do slice specific checks Looks good so far. Now we match against the individual segments Segment-Level checks Slice-Level checks Comparing Look Properties Doesn't compare the shape fields Uses the criteria of GGEvent.SelectMatchingData Disjunctive Search Routines Find any object that matches an object in the From viewer (searchOp = disjunction) If searching in new viewer or caret position changed or starting search from top, then build a new description of the target scene. Match-select all normal-selected slices, so they're not included in search. matchSelectD _ GGSelect.FindSelectedSlice[firstSlice, them.scene, match]; Otherwise, leave searchState alone. 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. If the next SearchInfo is Empty, then deselect the entire slice described by first Bookkeeping to keep track of what parts of a slice have been visited, when a substitution or change of search direction has occurred. 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. General Search Utilities 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. [ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison] Κ6Γ– "cedar" style˜Icode™™;K™BKšœ@Οk™CKšœ ™ K™$—K˜š ˜ Jšœ–˜–K˜—šΟn œœ˜JšœΙ˜ΠKšœ ˜K˜Kšœ œ˜'Kšœœ˜%Kšœ œ˜*Kšœ œ˜ Kšœ œ˜&Kšœœ˜'Kšœœ˜Kšœ œ˜"Kšœœ˜(Kšœ œ˜"Kšœœ˜(Kšœ œ˜(Kšœœ˜.Kšœ œ˜"Kšœœ"˜:Kšœœ˜!Kšœ œ˜$Kšœœ˜*Kšœ œ˜&Kšœ œ˜'Kšœœ!˜7Kšœ œ˜'Kšœœ˜!Kšœœ˜!Kšœœ ˜5Kšœœ)˜GKšœœ#˜;Kšœœ˜3Kšœ œœœ˜Kšœœ˜Kšœ œ˜'Kšœœ˜.Kšœ œ˜*—K˜Kšœ™K™šžœœœœœœœ œœœ œœ˜vK˜2K˜2šœ˜šœ˜KšΟbœΟc˜@KšœŸœ/˜ZK˜—šœ˜KšŸžœ˜(Kšœ1ŸœC˜ˆK˜—šœ˜ Kšœ?˜?Kšœœ˜K˜——K˜K˜—™/K™—š žœœœœœœ˜RKšœœœœœœœ˜AKš œ1œœœœœ˜_K˜K˜—š žœœœœœœ˜eK™:K˜2Kšœ œD˜Tšœ˜%˜ Kšœ9˜9Kš œœœ!œœ˜bKšœ!œ"˜IK˜—Kšœœ!œ"˜QKšœ˜—K˜K˜—šžœœœ˜IK™ZKšœ œ˜%K˜K˜—šž œœœ˜DK˜Kšœœ˜K˜K˜—šžœœœ˜8K™bKšœ œ˜šœ ˜*˜ K˜ K˜Kš œ œœœ ˜5Kšœ˜Kšœ œ ˜šœœœ˜,KšŸ ΠbkŸ*˜:Kšœ˜—Kšœœœ œ˜GK™5š˜Kšœ˜šœ˜Kšœ˜Kšœ7˜7——Kšœ˜Kšœœ˜K˜—˜ šœœœ˜Kšœ œ4˜DKšœ˜—Kš œœœ œœœ ˜XKšœœ˜K˜—Kšœœ˜—K˜K˜—K™™K™—šž œœœœ œœœœœ˜Hš žœœœœœ˜DKšœ œ4˜DK˜—K˜2K˜-Kšœ?˜?Kšœ˜K˜—šž œœœœ œœœœœ˜Fš žœœœœœ˜DKšœ œ2˜BK˜—K˜2K˜)Kšœ=˜=Kšœ˜K˜—šžœœœ+œœœœœ˜iKšœHœœ™qKšœ5œ˜GKšœœ˜%šœ&œ!˜Kšœ˜Kš œ œœœœ<˜VKšœ ˜K˜——Kšœ ˜/šœ2œ Πbc˜CKšœœ˜Kšœ(˜(K˜—šœ1œ ’˜AKšœœ˜Kšœ'˜'K˜—šœ2œ ’˜CKšœœ˜K˜1K˜—šœ2œ ’˜CKšœœ˜KšœB˜BK˜—šœ6œ ’ ˜LKšœœ˜KšœD˜DK˜—šœ3œ ’˜Ešœ#œ˜+Kšœœ˜K˜&K˜—Kšœœ˜K˜—šœ1œ ’˜AKšœœ˜K˜(K˜—šœ6œ ’˜RKšœœ˜K˜-K˜—šœ1œ ’˜CKšœœ˜Kšœœ˜ Kšœ’˜’K˜—šœ3œ ’˜EKšœœ˜KšœI˜IK˜—šœ1œ ’˜AKšœœ˜KšœC˜CK˜—šœ2œ ’˜CKšœœ˜KšœH˜HK˜—Kšœ ˜K˜K˜—šžœœ+œœœ œœœœ œ˜Žš ž œœœœœ˜@Kšœ œ1˜AJ˜—Kšœ˜Kšœ œ F˜XKšœœ ˜@šœ&œ˜.Kšœ'Οtœ£˜=K˜—šœ˜Kšž œ%œœ˜RKšœ œœ œ˜Kšœ œœ œ˜!Kšœ2œ,˜dK˜0š œœœœœ˜BKšœ œŸœœI˜Kšœ˜—Kšœ œ˜%Kšœ˜—Kš œ œœœœœ ˜IK˜K˜—š žœœœE œœ˜€Kšœœ˜K˜K˜K˜šœ2œ ’˜CKšœœ˜K˜K˜—šœ2œ ’˜CKšœœ˜Kšœ(˜(K˜—šœ1œ ’˜AKšœœ˜Kšœ˜K˜—šœ2œ ’˜CKšœœ˜K˜K˜—šœ6œ ’ ˜LKšœœ˜Kšœ1œ˜˜~Kš œ œœœœ˜$K™'Kšœ œ6˜Gšœœœ˜Kšœ9œ œ˜SKšœ˜—šœœœ˜ Kšœœ ˜)K˜—šœ˜Kšœ ˜ Kšœ(œ  ˜TK˜—K™@K˜AKšœ"˜"Kšœ œ+œœ ˜NK˜K˜K˜—šžœœ%œœœœœ/˜₯K˜Kšœ#˜#K˜Kšœœ 1˜EKšœ œ M˜_Kšœ œ˜K˜(Kšœ@ >˜~šœ œ 8˜MKšœ'˜'Kšœ!˜!Kšœœ˜K˜—K™.Kšœ œ6˜GKšœ$˜$šœœœ˜Kšœ)œ œ˜BKšœ˜—K˜šœœœ˜ Kšœœ ˜)K˜—šœ˜Kšœ ˜ Kšœ(œ  ˜TK˜—K™@š˜Kšœœ ˜'Kšœœ1œœ ˜ZKšœc˜cKšœK˜KKšœ˜—Kšœ˜K˜K˜—šžœœ%œœœœœ/˜‘K˜Kš œœœœœ ˜(K˜(š œœœ$œœ˜HK˜ Kšœ˜šœ œœœœœ œ˜Nšœœ˜Kšœ5œ˜;š œ œœœœ˜'Kšœ˜Kšœ˜K˜—Kšœœ˜—šœœœ  ˜(Kšœœ˜.šœ3˜3Kšœ ˜ Kšœ#˜#Kšœ˜—K˜XKšœ$œœ˜FK˜—šœ˜šœœœœœ%œœ˜Ošœ,œ˜4Kšœœ˜.šœ3˜3Kšœ ˜ Kšœ#˜#K˜—K˜FKšœ˜K˜—Kšœ˜—K˜—Kšœ˜—Kšœ˜—Kšœœ˜K˜K˜—K˜™ K™—šžœœœœ˜K™K˜K˜K™—šž œœGœœœœ œœ˜–K™ K˜Kš œœ(œœœ˜OKš œœ)œœœ˜QKš œœœ_œœœ˜‹Kš œœœaœœœ˜‘Kšœœ$œœ3œœœ˜†Kš œœœ<œœœ˜gKš œœœMœœœ˜}šœœ˜Kšœœ˜&Kšœ“˜“Kš œœ#œœœ˜ 2˜K™?Kš œœ#œœœœ˜Cšœœœ˜šœEœ˜MKšœ˜Kšœœ˜ K˜—Kšœœœ˜K˜—K˜’šœ+œ˜6K˜šœEœ˜MKšœ˜Kšœœ˜ K˜—Kšœ˜—K˜—šœ˜KšœG˜GKšœ˜—Kšœ˜K˜—šžœœ œœœœœœ œœœœ˜£K™JKš œœœœœ  ˜,šœ œ˜K˜Kšœœ ˜'Kšœ œœ˜0Kšœ#˜#Kšœ?˜?Kšœœ˜2Kš œœ#œœœœ˜CKš œœœtœœœ˜ Kš œœEœœœ˜]Kšœ :˜QK˜—šœ˜KšœG˜GKšœœ˜Kšœ˜—Kšœ˜K˜—šž œœœœ1œœœœœ˜†K™AKšœœ ˜'Kšœ?˜?Kšœ#˜#Kšœœœ œ˜&šœœ˜Kšœ%™%Kš œœœœœ˜@Kš œœ0œœœœ˜jKšœœœRœ œœœ˜Kšœ1™5Kšœ-œœœ˜BšœOœœ˜dKš œœ$œœœ˜=Kšœœœw ˜’K˜Kšœ˜—K˜—šœ 9˜@šœ,˜,K˜Kšœ#˜#K˜—K˜SKš œœ$œœœœ˜DKšœœœT˜kK˜—Kšœ œœœ}œœœ˜ΉKšœœ. ˜T˜K˜——K™K™ K™šžœœ'œœœœ%œœœ˜‰K™mKšž œ%œœ˜PKšœ œœ œ˜K˜K˜Kš œ$œœœ T˜ŽK™K˜Kš œœœhœœœ˜™Kš œœœœœœ +˜ΪK™@K˜0Kšœ œ˜*Kš œœ)œœœ˜Cš œ œœ.œ œ˜\KšœU˜UK˜Kšœ ˜—K˜K˜—šž œœ œœœœ œœœ˜aKš œ-œœœ ,˜ošœ!œ œ˜8Kš œœ*œœœ˜CK˜Kšœ˜—K˜K˜—š ž œœ"œœœ˜MK™Kš œœœœœ˜DKš œœœ4œœœ˜`šœœ˜Kšœœc˜{Kš œœ#œœœ˜Kšœœœœ!œœœ˜‚šœœœ˜#Kšœœœœ˜4Kšœ˜—K˜K˜—Kšœ™K˜šžœœœœœœœ˜FKšœR™RK˜2K˜2KšŸ’N™QKšŸ1™1šœœ7œ#œ˜‚Kšœ;œ˜AK˜Kšœ6˜6Kšœ˜—šœœ.œ ˜WKš’K™Kš œ œœœœ˜JK˜1J˜—Kšœ œ˜$Kšœ1£œ£œ£˜Ešœœœ˜!Kšœ2˜2KšœI™IKšœ=˜=Kšœœœ0œ˜UK˜—Kšœ;œ˜AKšœ2 b˜”Kšœ˜—KšŸ#™#Kšœœ#œœ˜iK˜K˜—šžœœœœœœœ œœœœœ œ˜«Kšœœ˜K˜2Kšœœ˜#Kš œ œœœœ˜,K˜8š˜šœœœ +˜RKšœŸœ&˜LKšŸœ.˜@Kšœ œœ  ˜2šœ ˜"K˜-K˜8Kšœœ8Ÿœ+˜€KšœŸœŸœ"˜jKšœd˜dKšœQœ˜VKšœ œxœœ˜¦K˜&Kš œ œœœ /˜NKšœœœ˜*K˜—K˜—šœ %˜*šœœœ˜Kšœ œœM˜`Kšœ˜Kšœ˜K˜—šœ˜K˜8Kšœ œxœœ˜¦Kšœœ˜#Kšœœœ˜K˜——Kšœ˜—K˜K˜—šžœœœ œœ  œœ œœ˜„K™Φšžœœœœœ œœ œ˜hK˜K˜1šœ ˜˜š ž œœœœœ˜CKšœ1œ œ˜[J˜—Kšœ$£œ £˜5K˜—˜š ž œœœœœ˜CKšœ1œ œ˜[J˜—Kšœ$£œ £˜5K˜—˜š žœœœœœ˜EKšœ œ˜#K˜—Kšœ$£œ£˜7K˜—Kšœœ˜—Kš œ"œœœœœ˜TK˜—K˜4Kšœœœ Ÿœ˜?Kšœœ/ ˜\š œœœœ œ˜BKšœœA˜OKšœ œ5˜DKšœ˜—K˜K˜—šžœœ,˜DKšžœœ"˜RK™RKšœœ8˜Wšœ E˜Lšœ!˜+˜ KšœT˜TKšœ8˜8K˜1K˜—˜ KšœT˜Tš œ œœ&œ œ˜RKšœœ˜1˜,Kšœ˜Kšœ#˜#K˜—K˜WKšœ:˜:Kšœ˜—Kšœ8˜8K˜1K˜—Kšœ<˜C—K˜—K˜K˜—K˜K™…K™š žœœœ œœ)˜WK™7Kšœ>™>Kšœ&™&KšœW™WKšœ œœœ˜š œœœ#œ œ˜IKšœ"œ/˜WKšœ˜—K˜K˜—šžœœœ0˜Ušž œœ0˜@Kšœœœ œ˜(K˜Cšœsœœ˜ˆKšœ œ˜Kšœ 4˜=—Kšœ œ˜+Kšœ œ˜š œ œœ"œ œ˜NKšœ'œ˜2Kšœ œ˜1Kšœ˜—K˜—K˜KKšœœœ ,˜VKšœ!œœ ˜Dšœ"˜,˜ Kšœœœ˜MKšœ ˜7K˜—˜ Kšœœ˜3šœœœ˜%Kšœ œœœ˜:Kšœ œ˜š œœœœ œ˜CKšœS˜SKšœœœ˜-Kšœ œ˜.Kšœ˜—K˜—šœ ˜Kšœ œœ˜#Kšœ œœœ˜:Kšœ œ˜š œœœœ œ˜CKšœS˜SKšœ œ˜)Kšœœœœ˜.Kšœ˜—šœ"œ˜*Kšœ œ˜Kšœ˜K˜—Kšœ˜K˜—K˜—Kšœœ˜—˜K˜——K™Kšœ™K™šž œœœ œœ œœœœ ˜dKšœNœZœœ2™όšž œœ œœ˜@Kšœœœœœœ œ˜BK˜—šž œ˜"Kš œœœœœœ™:Kšœœ˜Kšœœ˜Kšœ œ  ˜&Kšœ œ&˜5Kšœ œ&˜5šœœœ ˜AKšœ0 ˜OKšœ/˜/K˜—Kšœ œœž œ˜;Kšœœž œ˜/K˜—Kšœ œ"˜1Kšœ ˜K˜K˜—K˜Kšœ˜—…—Β