1/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7#ifndef SkOpSegment_DEFINE
8#define SkOpSegment_DEFINE
9
10#include "SkOpAngle.h"
11#include "SkOpSpan.h"
12#include "SkPathOpsBounds.h"
13#include "SkPathOpsCurve.h"
14#include "SkTArray.h"
15#include "SkTDArray.h"
16
17#if defined(SK_DEBUG) || !FORCE_RELEASE
18#include "SkThread.h"
19#endif
20
21struct SkCoincidence;
22class SkPathWriter;
23
24class SkOpSegment {
25public:
26    SkOpSegment() {
27#if defined(SK_DEBUG) || !FORCE_RELEASE
28        fID = sk_atomic_inc(&SkPathOpsDebug::gSegmentID);
29#endif
30    }
31
32    bool operator<(const SkOpSegment& rh) const {
33        return fBounds.fTop < rh.fBounds.fTop;
34    }
35
36    struct AlignedSpan  {
37        double fOldT;
38        double fT;
39        SkPoint fOldPt;
40        SkPoint fPt;
41        const SkOpSegment* fSegment;
42        const SkOpSegment* fOther1;
43        const SkOpSegment* fOther2;
44    };
45
46    const SkPathOpsBounds& bounds() const {
47        return fBounds;
48    }
49
50    // OPTIMIZE
51    // when the edges are initially walked, they don't automatically get the prior and next
52    // edges assigned to positions t=0 and t=1. Doing that would remove the need for this check,
53    // and would additionally remove the need for similar checks in condition edges. It would
54    // also allow intersection code to assume end of segment intersections (maybe?)
55    bool complete() const {
56        int count = fTs.count();
57        return count > 1 && fTs[0].fT == 0 && fTs[--count].fT == 1;
58    }
59
60    int count() const {
61        return fTs.count();
62    }
63
64    bool done() const {
65        SkASSERT(fDoneSpans <= fTs.count());
66        return fDoneSpans == fTs.count();
67    }
68
69    bool done(int min) const {
70        return fTs[min].fDone;
71    }
72
73    bool done(const SkOpAngle* angle) const {
74        return done(SkMin32(angle->start(), angle->end()));
75    }
76
77    SkDPoint dPtAtT(double mid) const {
78        return (*CurveDPointAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, mid);
79    }
80
81    SkVector dxdy(int index) const {
82        return (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, fTs[index].fT);
83    }
84
85    SkScalar dy(int index) const {
86        return dxdy(index).fY;
87    }
88
89    bool hasMultiples() const {
90        return fMultiples;
91    }
92
93    bool hasSmall() const {
94        return fSmall;
95    }
96
97    bool hasTiny() const {
98        return fTiny;
99    }
100
101    bool intersected() const {
102        return fTs.count() > 0;
103    }
104
105    bool isCanceled(int tIndex) const {
106        return fTs[tIndex].fWindValue == 0 && fTs[tIndex].fOppValue == 0;
107    }
108
109    bool isConnected(int startIndex, int endIndex) const {
110        return fTs[startIndex].fWindSum != SK_MinS32 || fTs[endIndex].fWindSum != SK_MinS32;
111    }
112
113    bool isHorizontal() const {
114        return fBounds.fTop == fBounds.fBottom;
115    }
116
117    bool isVertical() const {
118        return fBounds.fLeft == fBounds.fRight;
119    }
120
121    bool isVertical(int start, int end) const {
122        return (*CurveIsVertical[SkPathOpsVerbToPoints(fVerb)])(fPts, start, end);
123    }
124
125    bool operand() const {
126        return fOperand;
127    }
128
129    int oppSign(const SkOpAngle* angle) const {
130        SkASSERT(angle->segment() == this);
131        return oppSign(angle->start(), angle->end());
132    }
133
134    int oppSign(int startIndex, int endIndex) const {
135        int result = startIndex < endIndex ? -fTs[startIndex].fOppValue : fTs[endIndex].fOppValue;
136#if DEBUG_WIND_BUMP
137        SkDebugf("%s oppSign=%d\n", __FUNCTION__, result);
138#endif
139        return result;
140    }
141
142    int oppSum(int tIndex) const {
143        return fTs[tIndex].fOppSum;
144    }
145
146    int oppSum(const SkOpAngle* angle) const {
147        int lesser = SkMin32(angle->start(), angle->end());
148        return fTs[lesser].fOppSum;
149    }
150
151    int oppValue(int tIndex) const {
152        return fTs[tIndex].fOppValue;
153    }
154
155    int oppValue(const SkOpAngle* angle) const {
156        int lesser = SkMin32(angle->start(), angle->end());
157        return fTs[lesser].fOppValue;
158    }
159
160#if DEBUG_VALIDATE
161    bool oppXor() const {
162        return fOppXor;
163    }
164#endif
165
166    SkPoint ptAtT(double mid) const {
167        return (*CurvePointAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, mid);
168    }
169
170    const SkPoint* pts() const {
171        return fPts;
172    }
173
174    void reset() {
175        init(NULL, (SkPath::Verb) -1, false, false);
176        fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
177        fTs.reset();
178    }
179
180    bool reversePoints(const SkPoint& p1, const SkPoint& p2) const;
181
182    void setOppXor(bool isOppXor) {
183        fOppXor = isOppXor;
184    }
185
186    void setUpWinding(int index, int endIndex, int* maxWinding, int* sumWinding) {
187        int deltaSum = spanSign(index, endIndex);
188        *maxWinding = *sumWinding;
189        *sumWinding -= deltaSum;
190    }
191
192    const SkOpSpan& span(int tIndex) const {
193        return fTs[tIndex];
194    }
195
196    const SkOpAngle* spanToAngle(int tStart, int tEnd) const {
197        SkASSERT(tStart != tEnd);
198        const SkOpSpan& span = fTs[tStart];
199        return tStart < tEnd ? span.fToAngle : span.fFromAngle;
200    }
201
202    // FIXME: create some sort of macro or template that avoids casting
203    SkOpAngle* spanToAngle(int tStart, int tEnd) {
204        const SkOpAngle* cAngle = (const_cast<const SkOpSegment*>(this))->spanToAngle(tStart, tEnd);
205        return const_cast<SkOpAngle*>(cAngle);
206    }
207
208    int spanSign(const SkOpAngle* angle) const {
209        SkASSERT(angle->segment() == this);
210        return spanSign(angle->start(), angle->end());
211    }
212
213    int spanSign(int startIndex, int endIndex) const {
214        int result = startIndex < endIndex ? -fTs[startIndex].fWindValue : fTs[endIndex].fWindValue;
215#if DEBUG_WIND_BUMP
216        SkDebugf("%s spanSign=%d\n", __FUNCTION__, result);
217#endif
218        return result;
219    }
220
221    double t(int tIndex) const {
222        return fTs[tIndex].fT;
223    }
224
225    double tAtMid(int start, int end, double mid) const {
226        return fTs[start].fT * (1 - mid) + fTs[end].fT * mid;
227    }
228
229    void updatePts(const SkPoint pts[]) {
230        fPts = pts;
231    }
232
233    SkPath::Verb verb() const {
234        return fVerb;
235    }
236
237    int windSum(int tIndex) const {
238        return fTs[tIndex].fWindSum;
239    }
240
241    int windValue(int tIndex) const {
242        return fTs[tIndex].fWindValue;
243    }
244
245#if defined(SK_DEBUG) || DEBUG_WINDING
246    SkScalar xAtT(int index) const {
247        return xAtT(&fTs[index]);
248    }
249#endif
250
251#if DEBUG_VALIDATE
252    bool _xor() const {  // FIXME: used only by SkOpAngle::debugValidateLoop()
253        return fXor;
254    }
255#endif
256
257    const SkPoint& xyAtT(const SkOpSpan* span) const {
258        return span->fPt;
259    }
260
261    const SkPoint& xyAtT(int index) const {
262        return xyAtT(&fTs[index]);
263    }
264
265#if defined(SK_DEBUG) || DEBUG_WINDING
266    SkScalar yAtT(int index) const {
267        return yAtT(&fTs[index]);
268    }
269#endif
270
271    const SkOpAngle* activeAngle(int index, int* start, int* end, bool* done,
272                                 bool* sortable) const;
273    SkPoint activeLeftTop(int* firstT) const;
274    bool activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, SkPathOp op);
275    bool activeWinding(int index, int endIndex);
276    void addCubic(const SkPoint pts[4], bool operand, bool evenOdd);
277    void addCurveTo(int start, int end, SkPathWriter* path, bool active) const;
278    void addEndSpan(int endIndex);
279    void addLine(const SkPoint pts[2], bool operand, bool evenOdd);
280    void addOtherT(int index, double otherT, int otherIndex);
281    void addQuad(const SkPoint pts[3], bool operand, bool evenOdd);
282    void addSimpleAngle(int endIndex);
283    int addSelfT(const SkPoint& pt, double newT);
284    void addStartSpan(int endIndex);
285    int addT(SkOpSegment* other, const SkPoint& pt, double newT);
286    void addTCancel(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other);
287    bool addTCoincident(const SkPoint& startPt, const SkPoint& endPt, double endT,
288                        SkOpSegment* other);
289    const SkOpSpan* addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind,
290                             const SkPoint& pt);
291    const SkOpSpan* addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind,
292                             const SkPoint& pt, const SkPoint& oPt);
293    void alignMultiples(SkTDArray<AlignedSpan>* aligned);
294    bool alignSpan(int index, double thisT, const SkPoint& thisPt);
295    void alignSpanState(int start, int end);
296    bool betweenTs(int lesser, double testT, int greater) const;
297    void blindCancel(const SkCoincidence& coincidence, SkOpSegment* other);
298    void blindCoincident(const SkCoincidence& coincidence, SkOpSegment* other);
299    bool calcAngles();
300    double calcMissingTEnd(const SkOpSegment* ref, double loEnd, double min, double max,
301                           double hiEnd, const SkOpSegment* other, int thisEnd);
302    double calcMissingTStart(const SkOpSegment* ref, double loEnd, double min, double max,
303                             double hiEnd, const SkOpSegment* other, int thisEnd);
304    void checkDuplicates();
305    void checkEnds();
306    void checkMultiples();
307    void checkSmall();
308    bool checkSmall(int index) const;
309    void checkTiny();
310    int computeSum(int startIndex, int endIndex, SkOpAngle::IncludeType includeType);
311    bool containsPt(const SkPoint& , int index, int endIndex) const;
312    int crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT, bool* hitSomething,
313                     double mid, bool opp, bool current) const;
314    bool findCoincidentMatch(const SkOpSpan* span, const SkOpSegment* other, int oStart, int oEnd,
315                             int step, SkPoint* startPt, SkPoint* endPt, double* endT) const;
316    SkOpSegment* findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd,
317                            bool* unsortable, SkPathOp op, int xorMiMask, int xorSuMask);
318    SkOpSegment* findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd,
319                                 bool* unsortable);
320    SkOpSegment* findNextXor(int* nextStart, int* nextEnd, bool* unsortable);
321    int findExactT(double t, const SkOpSegment* ) const;
322    int findOtherT(double t, const SkOpSegment* ) const;
323    int findT(double t, const SkPoint& , const SkOpSegment* ) const;
324    SkOpSegment* findTop(int* tIndex, int* endIndex, bool* unsortable, bool firstPass);
325    void fixOtherTIndex();
326    void initWinding(int start, int end, SkOpAngle::IncludeType angleIncludeType);
327    void initWinding(int start, int end, double tHit, int winding, SkScalar hitDx, int oppWind,
328                     SkScalar hitOppDx);
329    bool isMissing(double startT, const SkPoint& pt) const;
330    bool isTiny(const SkOpAngle* angle) const;
331    bool joinCoincidence(SkOpSegment* other, double otherT, const SkPoint& otherPt, int step,
332                         bool cancel);
333    SkOpSpan* markAndChaseDoneBinary(int index, int endIndex);
334    SkOpSpan* markAndChaseDoneUnary(int index, int endIndex);
335    SkOpSpan* markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding);
336    SkOpSpan* markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding,
337                        const SkOpAngle* angle);
338    void markDone(int index, int winding);
339    void markDoneBinary(int index);
340    void markDoneUnary(int index);
341    bool nextCandidate(int* start, int* end) const;
342    int nextSpan(int from, int step) const;
343    void pinT(const SkPoint& pt, double* t);
344    void setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding,
345            int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding);
346    void sortAngles();
347    bool subDivide(int start, int end, SkPoint edge[4]) const;
348    bool subDivide(int start, int end, SkDCubic* result) const;
349    void undoneSpan(int* start, int* end);
350    int updateOppWindingReverse(const SkOpAngle* angle) const;
351    int updateWindingReverse(const SkOpAngle* angle) const;
352    static bool UseInnerWinding(int outerWinding, int innerWinding);
353    static bool UseInnerWindingReverse(int outerWinding, int innerWinding);
354    int windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const;
355    int windSum(const SkOpAngle* angle) const;
356// available for testing only
357#if defined(SK_DEBUG) || !FORCE_RELEASE
358    int debugID() const {
359        return fID;
360    }
361#else
362    int debugID() const {
363        return -1;
364    }
365#endif
366#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
367    void debugShowActiveSpans() const;
368#endif
369#if DEBUG_CONCIDENT
370    void debugShowTs(const char* prefix) const;
371#endif
372#if DEBUG_SHOW_WINDING
373    int debugShowWindingValues(int slotCount, int ofInterest) const;
374#endif
375    const SkTDArray<SkOpSpan>& debugSpans() const;
376    void debugValidate() const;
377    // available to testing only
378    const SkOpAngle* debugLastAngle() const;
379    void dumpAngles() const;
380    void dumpContour(int firstID, int lastID) const;
381    void dumpPts() const;
382    void dumpSpans() const;
383
384private:
385    struct MissingSpan  {
386        double fT;
387        double fEndT;
388        SkOpSegment* fSegment;
389        SkOpSegment* fOther;
390        double fOtherT;
391        SkPoint fPt;
392    };
393
394    const SkOpAngle* activeAngleInner(int index, int* start, int* end, bool* done,
395                                      bool* sortable) const;
396    const SkOpAngle* activeAngleOther(int index, int* start, int* end, bool* done,
397                                      bool* sortable) const;
398    bool activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, SkPathOp op,
399                  int* sumMiWinding, int* sumSuWinding);
400    bool activeWinding(int index, int endIndex, int* sumWinding);
401    void addCancelOutsides(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other);
402    void addCoinOutsides(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other);
403    SkOpAngle* addSingletonAngleDown(SkOpSegment** otherPtr, SkOpAngle** );
404    SkOpAngle* addSingletonAngleUp(SkOpSegment** otherPtr, SkOpAngle** );
405    SkOpAngle* addSingletonAngles(int step);
406    void alignSpan(const SkPoint& newPt, double newT, const SkOpSegment* other, double otherT,
407                   const SkOpSegment* other2, SkOpSpan* oSpan, SkTDArray<AlignedSpan>* );
408    bool betweenPoints(double midT, const SkPoint& pt1, const SkPoint& pt2) const;
409    void bumpCoincidentBlind(bool binary, int index, int last);
410    void bumpCoincidentThis(const SkOpSpan& oTest, bool binary, int* index,
411                           SkTArray<SkPoint, true>* outsideTs);
412    void bumpCoincidentOBlind(int index, int last);
413    void bumpCoincidentOther(const SkOpSpan& oTest, int* index,
414                           SkTArray<SkPoint, true>* outsideTs);
415    bool bumpSpan(SkOpSpan* span, int windDelta, int oppDelta);
416    bool calcLoopSpanCount(const SkOpSpan& thisSpan, int* smallCounts);
417    bool checkForSmall(const SkOpSpan* span, const SkPoint& pt, double newT,
418                       int* less, int* more) const;
419    void checkLinks(const SkOpSpan* ,
420                    SkTArray<MissingSpan, true>* missingSpans) const;
421    static void CheckOneLink(const SkOpSpan* test, const SkOpSpan* oSpan,
422                             const SkOpSpan* oFirst, const SkOpSpan* oLast,
423                             const SkOpSpan** missingPtr,
424                             SkTArray<MissingSpan, true>* missingSpans);
425    int checkSetAngle(int tIndex) const;
426    void checkSmallCoincidence(const SkOpSpan& span, SkTArray<MissingSpan, true>* );
427    bool coincidentSmall(const SkPoint& pt, double t, const SkOpSegment* other) const;
428    bool clockwise(int tStart, int tEnd, bool* swap) const;
429    static void ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle,
430                              SkOpAngle::IncludeType );
431    static void ComputeOneSumReverse(const SkOpAngle* baseAngle, SkOpAngle* nextAngle,
432                                     SkOpAngle::IncludeType );
433    bool containsT(double t, const SkOpSegment* other, double otherT) const;
434    bool decrementSpan(SkOpSpan* span);
435    int findEndSpan(int endIndex) const;
436    int findStartSpan(int startIndex) const;
437    int firstActive(int tIndex) const;
438    const SkOpSpan& firstSpan(const SkOpSpan& thisSpan) const;
439    void init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd);
440    bool inCoincidentSpan(double t, const SkOpSegment* other) const;
441    bool inLoop(const SkOpAngle* baseAngle, int spanCount, int* indexPtr) const;
442#if OLD_CHASE
443    bool isSimple(int end) const;
444#else
445    SkOpSegment* isSimple(int* end, int* step);
446#endif
447    bool isTiny(int index) const;
448    const SkOpSpan& lastSpan(const SkOpSpan& thisSpan) const;
449    void matchWindingValue(int tIndex, double t, bool borrowWind);
450    SkOpSpan* markAndChaseDone(int index, int endIndex, int winding);
451    SkOpSpan* markAndChaseDoneBinary(const SkOpAngle* angle, int winding, int oppWinding);
452    SkOpSpan* markAndChaseWinding(const SkOpAngle* angle, int winding);
453    SkOpSpan* markAndChaseWinding(int index, int endIndex, int winding);
454    SkOpSpan* markAndChaseWinding(int index, int endIndex, int winding, int oppWinding);
455    SkOpSpan* markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle);
456    void markDoneBinary(int index, int winding, int oppWinding);
457    SkOpSpan* markAndChaseDoneUnary(const SkOpAngle* angle, int winding);
458    void markOneDone(const char* funName, int tIndex, int winding);
459    void markOneDoneBinary(const char* funName, int tIndex);
460    void markOneDoneBinary(const char* funName, int tIndex, int winding, int oppWinding);
461    void markOneDoneUnary(const char* funName, int tIndex);
462    SkOpSpan* markOneWinding(const char* funName, int tIndex, int winding);
463    SkOpSpan* markOneWinding(const char* funName, int tIndex, int winding, int oppWinding);
464    void markWinding(int index, int winding);
465    void markWinding(int index, int winding, int oppWinding);
466    bool monotonicInY(int tStart, int tEnd) const;
467
468    bool multipleEnds() const { return fTs[count() - 2].fT == 1; }
469    bool multipleStarts() const { return fTs[1].fT == 0; }
470
471    SkOpSegment* nextChase(int* index, int* step, int* min, SkOpSpan** last);
472    int nextExactSpan(int from, int step) const;
473    bool serpentine(int tStart, int tEnd) const;
474    void setCoincidentRange(const SkPoint& startPt, const SkPoint& endPt,  SkOpSegment* other);
475    void setFromAngle(int endIndex, SkOpAngle* );
476    void setToAngle(int endIndex, SkOpAngle* );
477    void setUpWindings(int index, int endIndex, int* sumMiWinding,
478            int* maxWinding, int* sumWinding);
479    void subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const;
480    static void TrackOutsidePair(SkTArray<SkPoint, true>* outsideTs, const SkPoint& endPt,
481            const SkPoint& startPt);
482    static void TrackOutside(SkTArray<SkPoint, true>* outsideTs, const SkPoint& startPt);
483    int updateOppWinding(int index, int endIndex) const;
484    int updateOppWinding(const SkOpAngle* angle) const;
485    int updateWinding(int index, int endIndex) const;
486    int updateWinding(const SkOpAngle* angle) const;
487    int updateWindingReverse(int index, int endIndex) const;
488    SkOpSpan* verifyOneWinding(const char* funName, int tIndex);
489    SkOpSpan* verifyOneWindingU(const char* funName, int tIndex);
490
491    SkScalar xAtT(const SkOpSpan* span) const {
492        return xyAtT(span).fX;
493    }
494
495    SkScalar yAtT(const SkOpSpan* span) const {
496        return xyAtT(span).fY;
497    }
498
499    void zeroSpan(SkOpSpan* span);
500
501#if DEBUG_SWAP_TOP
502    bool controlsContainedByEnds(int tStart, int tEnd) const;
503#endif
504    void debugAddAngle(int start, int end);
505#if DEBUG_CONCIDENT
506    void debugAddTPair(double t, const SkOpSegment& other, double otherT) const;
507#endif
508#if DEBUG_ANGLE
509    void debugCheckPointsEqualish(int tStart, int tEnd) const;
510#endif
511#if DEBUG_SWAP_TOP
512    int debugInflections(int index, int endIndex) const;
513#endif
514#if DEBUG_MARK_DONE || DEBUG_UNSORTABLE
515    void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding);
516    void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding, int oppWinding);
517#endif
518#if DEBUG_WINDING
519    static char as_digit(int value) {
520        return value < 0 ? '?' : value <= 9 ? '0' + value : '+';
521    }
522#endif
523    // available to testing only
524    void debugConstruct();
525    void debugConstructCubic(SkPoint shortQuad[4]);
526    void debugConstructLine(SkPoint shortQuad[2]);
527    void debugConstructQuad(SkPoint shortQuad[3]);
528    void debugReset();
529    void dumpDPts() const;
530    void dumpSpan(int index) const;
531
532    const SkPoint* fPts;
533    SkPathOpsBounds fBounds;
534    // FIXME: can't convert to SkTArray because it uses insert
535    SkTDArray<SkOpSpan> fTs;  // 2+ (always includes t=0 t=1) -- at least (number of spans) + 1
536    SkOpAngleSet fAngles;  // empty or 2+ -- (number of non-zero spans) * 2
537    // OPTIMIZATION: could pack donespans, verb, operand, xor into 1 int-sized value
538    int fDoneSpans;  // quick check that segment is finished
539    // OPTIMIZATION: force the following to be byte-sized
540    SkPath::Verb fVerb;
541    bool fLoop;   // set if cubic intersects itself
542    bool fMultiples;  // set if curve intersects multiple other curves at one interior point
543    bool fOperand;
544    bool fXor;  // set if original contour had even-odd fill
545    bool fOppXor;  // set if opposite operand had even-odd fill
546    bool fSmall;  // set if some span is small
547    bool fTiny;  // set if some span is tiny
548#if defined(SK_DEBUG) || !FORCE_RELEASE
549    int fID;
550#endif
551
552    friend class PathOpsSegmentTester;
553};
554
555#endif
556