OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2012 Google Inc. | 2 * Copyright 2012 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 #include "SkIntersections.h" | 7 #include "SkIntersections.h" |
8 #include "SkOpSegment.h" | 8 #include "SkOpSegment.h" |
9 #include "SkPathWriter.h" | 9 #include "SkPathWriter.h" |
10 #include "SkTSort.h" | 10 #include "SkTSort.h" |
(...skipping 1280 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1291 return bestTIndex; | 1291 return bestTIndex; |
1292 } | 1292 } |
1293 if (fBounds.fLeft == fBounds.fRight) { | 1293 if (fBounds.fLeft == fBounds.fRight) { |
1294 // if vertical, and directly above test point, wait for another one | 1294 // if vertical, and directly above test point, wait for another one |
1295 return AlmostEqualUlps(basePt.fX, fBounds.fLeft) ? SK_MinS32 : bestTInde
x; | 1295 return AlmostEqualUlps(basePt.fX, fBounds.fLeft) ? SK_MinS32 : bestTInde
x; |
1296 } | 1296 } |
1297 // intersect ray starting at basePt with edge | 1297 // intersect ray starting at basePt with edge |
1298 SkIntersections intersections; | 1298 SkIntersections intersections; |
1299 // OPTIMIZE: use specialty function that intersects ray with curve, | 1299 // OPTIMIZE: use specialty function that intersects ray with curve, |
1300 // returning t values only for curve (we don't care about t on ray) | 1300 // returning t values only for curve (we don't care about t on ray) |
| 1301 intersections.allowNear(false); |
1301 int pts = (intersections.*CurveVertical[SkPathOpsVerbToPoints(fVerb)]) | 1302 int pts = (intersections.*CurveVertical[SkPathOpsVerbToPoints(fVerb)]) |
1302 (fPts, top, bottom, basePt.fX, false); | 1303 (fPts, top, bottom, basePt.fX, false); |
1303 if (pts == 0 || (current && pts == 1)) { | 1304 if (pts == 0 || (current && pts == 1)) { |
1304 return bestTIndex; | 1305 return bestTIndex; |
1305 } | 1306 } |
1306 if (current) { | 1307 if (current) { |
1307 SkASSERT(pts > 1); | 1308 SkASSERT(pts > 1); |
1308 int closestIdx = 0; | 1309 int closestIdx = 0; |
1309 double closest = fabs(intersections[0][0] - mid); | 1310 double closest = fabs(intersections[0][0] - mid); |
1310 for (int idx = 1; idx < pts; ++idx) { | 1311 for (int idx = 1; idx < pts; ++idx) { |
(...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1413 ; | 1414 ; |
1414 int otherCount = other->fTs.count(); | 1415 int otherCount = other->fTs.count(); |
1415 int peekLast = span.fOtherIndex; | 1416 int peekLast = span.fOtherIndex; |
1416 while (++peekLast < otherCount && other->fTs[peekLast].fT == otherT) | 1417 while (++peekLast < otherCount && other->fTs[peekLast].fT == otherT) |
1417 ; | 1418 ; |
1418 if (++peekStart == --peekLast) { // if there isn't a range, there's noth
ing to do | 1419 if (++peekStart == --peekLast) { // if there isn't a range, there's noth
ing to do |
1419 continue; | 1420 continue; |
1420 } | 1421 } |
1421 // t start/last describe the range of spans that match the t of this spa
n | 1422 // t start/last describe the range of spans that match the t of this spa
n |
1422 double t = span.fT; | 1423 double t = span.fT; |
1423 int tStart = index; | 1424 double tBottom = -1; |
1424 while (--tStart >= 0 && (t == fTs[tStart].fT || fTs[tStart].fTiny)) | 1425 int tStart = -1; |
1425 ; | 1426 int tLast = count; |
1426 int tLast = index; | 1427 bool lastSmall = false; |
1427 while (fTs[tLast].fTiny) { | 1428 double afterT = t; |
1428 ++tLast; | 1429 for (int inner = 0; inner < count; ++inner) { |
| 1430 double innerT = fTs[inner].fT; |
| 1431 if (innerT <= t && innerT > tBottom) { |
| 1432 if (innerT < t || !lastSmall) { |
| 1433 tStart = inner - 1; |
| 1434 } |
| 1435 tBottom = innerT; |
| 1436 } |
| 1437 if (innerT > afterT) { |
| 1438 if (t == afterT && lastSmall) { |
| 1439 afterT = innerT; |
| 1440 } else { |
| 1441 tLast = inner; |
| 1442 break; |
| 1443 } |
| 1444 } |
| 1445 lastSmall = innerT <= t ? fTs[inner].fSmall : false; |
1429 } | 1446 } |
1430 while (++tLast < count && t == fTs[tLast].fT) | |
1431 ; | |
1432 for (int peekIndex = peekStart; peekIndex <= peekLast; ++peekIndex) { | 1447 for (int peekIndex = peekStart; peekIndex <= peekLast; ++peekIndex) { |
1433 if (peekIndex == span.fOtherIndex) { // skip the other span pointed
to by this span | 1448 if (peekIndex == span.fOtherIndex) { // skip the other span pointed
to by this span |
1434 continue; | 1449 continue; |
1435 } | 1450 } |
1436 const SkOpSpan& peekSpan = other->fTs[peekIndex]; | 1451 const SkOpSpan& peekSpan = other->fTs[peekIndex]; |
1437 SkOpSegment* match = peekSpan.fOther; | 1452 SkOpSegment* match = peekSpan.fOther; |
1438 if (match->done()) { | 1453 if (match->done()) { |
1439 continue; // if the edge has already been eaten (likely coincid
ence), ignore it | 1454 continue; // if the edge has already been eaten (likely coincid
ence), ignore it |
1440 } | 1455 } |
1441 const double matchT = peekSpan.fOtherT; | 1456 const double matchT = peekSpan.fOtherT; |
(...skipping 247 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1689 MissingSpan& missing = missingSpans[index]; | 1704 MissingSpan& missing = missingSpans[index]; |
1690 missing.fSegment->addTPair(missing.fT, missing.fOther, missing.fOtherT,
false, missing.fPt); | 1705 missing.fSegment->addTPair(missing.fT, missing.fOther, missing.fOtherT,
false, missing.fPt); |
1691 } | 1706 } |
1692 for (int index = 0; index < missingCount; ++index) { | 1707 for (int index = 0; index < missingCount; ++index) { |
1693 MissingSpan& missing = missingSpans[index]; | 1708 MissingSpan& missing = missingSpans[index]; |
1694 missing.fSegment->fixOtherTIndex(); | 1709 missing.fSegment->fixOtherTIndex(); |
1695 missing.fOther->fixOtherTIndex(); | 1710 missing.fOther->fixOtherTIndex(); |
1696 } | 1711 } |
1697 } | 1712 } |
1698 | 1713 |
| 1714 bool SkOpSegment::findCoincidentMatch(const SkOpSpan* span, const SkOpSegment* o
ther, int oStart, |
| 1715 int oEnd, int step, SkPoint* startPt, SkPoint* endPt, double* endT) cons
t { |
| 1716 SkASSERT(span->fT == 0 || span->fT == 1); |
| 1717 SkASSERT(span->fOtherT == 0 || span->fOtherT == 1); |
| 1718 const SkOpSpan* otherSpan = &other->span(oEnd); |
| 1719 double refT = otherSpan->fT; |
| 1720 const SkPoint& refPt = otherSpan->fPt; |
| 1721 const SkOpSpan* lastSpan = &other->span(step > 0 ? other->count() - 1 : 0); |
| 1722 do { |
| 1723 const SkOpSegment* match = span->fOther; |
| 1724 if (match == otherSpan->fOther) { |
| 1725 // find start of respective spans and see if both have winding |
| 1726 int startIndex, endIndex; |
| 1727 if (span->fOtherT == 1) { |
| 1728 endIndex = span->fOtherIndex; |
| 1729 startIndex = match->nextExactSpan(endIndex, -1); |
| 1730 } else { |
| 1731 startIndex = span->fOtherIndex; |
| 1732 endIndex = match->nextExactSpan(startIndex, 1); |
| 1733 } |
| 1734 const SkOpSpan& startSpan = match->span(startIndex); |
| 1735 if (startSpan.fWindValue != 0) { |
| 1736 // draw ray from endSpan.fPt perpendicular to end tangent and me
asure distance |
| 1737 // to other segment. |
| 1738 const SkOpSpan& endSpan = match->span(endIndex); |
| 1739 SkDLine ray; |
| 1740 SkVector dxdy; |
| 1741 if (span->fOtherT == 1) { |
| 1742 ray.fPts[0].set(startSpan.fPt); |
| 1743 dxdy = match->dxdy(startIndex); |
| 1744 } else { |
| 1745 ray.fPts[0].set(endSpan.fPt); |
| 1746 dxdy = match->dxdy(endIndex); |
| 1747 } |
| 1748 ray.fPts[1].fX = ray.fPts[0].fX + dxdy.fY; |
| 1749 ray.fPts[1].fY = ray.fPts[0].fY - dxdy.fX; |
| 1750 SkIntersections i; |
| 1751 int roots = (i.*CurveRay[SkPathOpsVerbToPoints(other->verb())])(
other->pts(), ray); |
| 1752 for (int index = 0; index < roots; ++index) { |
| 1753 if (ray.fPts[0].approximatelyEqual(i.pt(index))) { |
| 1754 double matchMidT = (match->span(startIndex).fT |
| 1755 + match->span(endIndex).fT) / 2; |
| 1756 SkPoint matchMidPt = match->ptAtT(matchMidT); |
| 1757 double otherMidT = (i[0][index] + other->span(oStart).fT
) / 2; |
| 1758 SkPoint otherMidPt = other->ptAtT(otherMidT); |
| 1759 if (SkDPoint::ApproximatelyEqual(matchMidPt, otherMidPt)
) { |
| 1760 *startPt = startSpan.fPt; |
| 1761 *endPt = endSpan.fPt; |
| 1762 *endT = endSpan.fT; |
| 1763 return true; |
| 1764 } |
| 1765 } |
| 1766 } |
| 1767 } |
| 1768 return false; |
| 1769 } |
| 1770 if (otherSpan == lastSpan) { |
| 1771 break; |
| 1772 } |
| 1773 otherSpan += step; |
| 1774 } while (otherSpan->fT == refT || otherSpan->fPt == refPt); |
| 1775 return false; |
| 1776 } |
| 1777 |
1699 /* | 1778 /* |
1700 The M and S variable name parts stand for the operators. | 1779 The M and S variable name parts stand for the operators. |
1701 Mi stands for Minuend (see wiki subtraction, analogous to difference) | 1780 Mi stands for Minuend (see wiki subtraction, analogous to difference) |
1702 Su stands for Subtrahend | 1781 Su stands for Subtrahend |
1703 The Opp variable name part designates that the value is for the Opposite operat
or. | 1782 The Opp variable name part designates that the value is for the Opposite operat
or. |
1704 Opposite values result from combining coincident spans. | 1783 Opposite values result from combining coincident spans. |
1705 */ | 1784 */ |
1706 SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart
, int* nextEnd, | 1785 SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart
, int* nextEnd, |
1707 bool* unsortable, SkPathOp op, const int xo
rMiMask, | 1786 bool* unsortable, SkPathOp op, const int xo
rMiMask, |
1708 const int xorSuMask) { | 1787 const int xorSuMask) { |
(...skipping 360 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2069 const SkOpAngle* angle = sorted[angleIndex]; | 2148 const SkOpAngle* angle = sorted[angleIndex]; |
2070 if (angle->segment() == this && angle->start() == end && | 2149 if (angle->segment() == this && angle->start() == end && |
2071 angle->end() == start) { | 2150 angle->end() == start) { |
2072 firstIndex = angleIndex; | 2151 firstIndex = angleIndex; |
2073 break; | 2152 break; |
2074 } | 2153 } |
2075 } | 2154 } |
2076 return firstIndex; | 2155 return firstIndex; |
2077 } | 2156 } |
2078 | 2157 |
| 2158 int SkOpSegment::findT(double t, const SkOpSegment* match) const { |
| 2159 int count = this->count(); |
| 2160 for (int index = 0; index < count; ++index) { |
| 2161 const SkOpSpan& span = fTs[index]; |
| 2162 if (span.fT == t && span.fOther == match) { |
| 2163 return index; |
| 2164 } |
| 2165 } |
| 2166 SkASSERT(0); |
| 2167 return -1; |
| 2168 } |
| 2169 |
2079 // FIXME: either: | 2170 // FIXME: either: |
2080 // a) mark spans with either end unsortable as done, or | 2171 // a) mark spans with either end unsortable as done, or |
2081 // b) rewrite findTop / findTopSegment / findTopContour to iterate further | 2172 // b) rewrite findTop / findTopSegment / findTopContour to iterate further |
2082 // when encountering an unsortable span | 2173 // when encountering an unsortable span |
2083 | 2174 |
2084 // OPTIMIZATION : for a pair of lines, can we compute points at T (cached) | 2175 // OPTIMIZATION : for a pair of lines, can we compute points at T (cached) |
2085 // and use more concise logic like the old edge walker code? | 2176 // and use more concise logic like the old edge walker code? |
2086 // FIXME: this needs to deal with coincident edges | 2177 // FIXME: this needs to deal with coincident edges |
2087 SkOpSegment* SkOpSegment::findTop(int* tIndexPtr, int* endIndexPtr, bool* unsort
able, | 2178 SkOpSegment* SkOpSegment::findTop(int* tIndexPtr, int* endIndexPtr, bool* unsort
able, |
2088 bool onlySortable) { | 2179 bool onlySortable) { |
(...skipping 203 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2292 double t = fTs[end].fT; | 2383 double t = fTs[end].fT; |
2293 if (approximately_less_than_zero(t)) { | 2384 if (approximately_less_than_zero(t)) { |
2294 return !approximately_less_than_zero(fTs[1].fT); | 2385 return !approximately_less_than_zero(fTs[1].fT); |
2295 } | 2386 } |
2296 if (approximately_greater_than_one(t)) { | 2387 if (approximately_greater_than_one(t)) { |
2297 return !approximately_greater_than_one(fTs[count - 2].fT); | 2388 return !approximately_greater_than_one(fTs[count - 2].fT); |
2298 } | 2389 } |
2299 return false; | 2390 return false; |
2300 } | 2391 } |
2301 | 2392 |
| 2393 bool SkOpSegment::isTiny(const SkOpAngle* angle) const { |
| 2394 int start = angle->start(); |
| 2395 int end = angle->end(); |
| 2396 const SkOpSpan& mSpan = fTs[SkMin32(start, end)]; |
| 2397 return mSpan.fTiny; |
| 2398 } |
| 2399 |
| 2400 bool SkOpSegment::isTiny(int index) const { |
| 2401 return fTs[index].fTiny; |
| 2402 } |
| 2403 |
| 2404 // look pair of active edges going away from coincident edge |
| 2405 // one of them should be the continuation of other |
| 2406 // if both are active, look to see if they both the connect to another coinciden
t pair |
| 2407 // if one at least one is a line, then make the pair coincident |
| 2408 // if neither is a line, test for coincidence |
| 2409 bool SkOpSegment::joinCoincidence(bool end, SkOpSegment* other, double otherT, i
nt step, |
| 2410 bool cancel) { |
| 2411 int otherTIndex = other->findT(otherT, this); |
| 2412 int next = other->nextExactSpan(otherTIndex, step); |
| 2413 int otherMin = SkTMin(otherTIndex, next); |
| 2414 int otherWind = other->span(otherMin).fWindValue; |
| 2415 if (otherWind == 0) { |
| 2416 return false; |
| 2417 } |
| 2418 SkASSERT(next >= 0); |
| 2419 if (end) { |
| 2420 int tIndex = count() - 1; |
| 2421 do { |
| 2422 SkOpSpan* test = &fTs[tIndex]; |
| 2423 SkASSERT(test->fT == 1); |
| 2424 if (test->fOther == other || test->fOtherT != 0) { |
| 2425 continue; |
| 2426 } |
| 2427 SkPoint startPt, endPt; |
| 2428 double endT; |
| 2429 if (findCoincidentMatch(test, other, otherTIndex, next, step, &start
Pt, &endPt, &endT)) { |
| 2430 SkOpSegment* match = test->fOther; |
| 2431 if (cancel) { |
| 2432 match->addTCancel(startPt, endPt, other); |
| 2433 } else { |
| 2434 match->addTCoincident(startPt, endPt, endT, other); |
| 2435 } |
| 2436 return true; |
| 2437 } |
| 2438 } while (fTs[--tIndex].fT == 1); |
| 2439 } else { |
| 2440 int tIndex = 0; |
| 2441 do { |
| 2442 SkOpSpan* test = &fTs[tIndex]; |
| 2443 SkASSERT(test->fT == 0); |
| 2444 if (test->fOther == other || test->fOtherT != 1) { |
| 2445 continue; |
| 2446 } |
| 2447 SkPoint startPt, endPt; |
| 2448 double endT; |
| 2449 if (findCoincidentMatch(test, other, otherTIndex, next, step, &start
Pt, &endPt, &endT)) { |
| 2450 SkOpSegment* match = test->fOther; |
| 2451 if (cancel) { |
| 2452 match->addTCancel(startPt, endPt, other); |
| 2453 } else { |
| 2454 match->addTCoincident(startPt, endPt, endT, other); |
| 2455 } |
| 2456 return true; |
| 2457 } |
| 2458 } while (fTs[++tIndex].fT == 0); |
| 2459 } |
| 2460 return false; |
| 2461 } |
| 2462 |
2302 // this span is excluded by the winding rule -- chase the ends | 2463 // this span is excluded by the winding rule -- chase the ends |
2303 // as long as they are unambiguous to mark connections as done | 2464 // as long as they are unambiguous to mark connections as done |
2304 // and give them the same winding value | 2465 // and give them the same winding value |
2305 SkOpSpan* SkOpSegment::markAndChaseDone(int index, int endIndex, int winding) { | 2466 SkOpSpan* SkOpSegment::markAndChaseDone(int index, int endIndex, int winding) { |
2306 int step = SkSign32(endIndex - index); | 2467 int step = SkSign32(endIndex - index); |
2307 int min = SkMin32(index, endIndex); | 2468 int min = SkMin32(index, endIndex); |
2308 markDone(min, winding); | 2469 markDone(min, winding); |
2309 SkOpSpan* last; | 2470 SkOpSpan* last; |
2310 SkOpSegment* other = this; | 2471 SkOpSegment* other = this; |
2311 while ((other = other->nextChase(&index, step, &min, &last))) { | 2472 while ((other = other->nextChase(&index, step, &min, &last))) { |
(...skipping 699 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3011 } | 3172 } |
3012 return true; | 3173 return true; |
3013 } | 3174 } |
3014 | 3175 |
3015 void SkOpSegment::subDivideBounds(int start, int end, SkPathOpsBounds* bounds) c
onst { | 3176 void SkOpSegment::subDivideBounds(int start, int end, SkPathOpsBounds* bounds) c
onst { |
3016 SkPoint edge[4]; | 3177 SkPoint edge[4]; |
3017 subDivide(start, end, edge); | 3178 subDivide(start, end, edge); |
3018 (bounds->*SetCurveBounds[SkPathOpsVerbToPoints(fVerb)])(edge); | 3179 (bounds->*SetCurveBounds[SkPathOpsVerbToPoints(fVerb)])(edge); |
3019 } | 3180 } |
3020 | 3181 |
3021 bool SkOpSegment::isTiny(const SkOpAngle* angle) const { | |
3022 int start = angle->start(); | |
3023 int end = angle->end(); | |
3024 const SkOpSpan& mSpan = fTs[SkMin32(start, end)]; | |
3025 return mSpan.fTiny; | |
3026 } | |
3027 | |
3028 bool SkOpSegment::isTiny(int index) const { | |
3029 return fTs[index].fTiny; | |
3030 } | |
3031 | |
3032 void SkOpSegment::TrackOutsidePair(SkTArray<SkPoint, true>* outsidePts, const Sk
Point& endPt, | 3182 void SkOpSegment::TrackOutsidePair(SkTArray<SkPoint, true>* outsidePts, const Sk
Point& endPt, |
3033 const SkPoint& startPt) { | 3183 const SkPoint& startPt) { |
3034 int outCount = outsidePts->count(); | 3184 int outCount = outsidePts->count(); |
3035 if (outCount == 0 || endPt != (*outsidePts)[outCount - 2]) { | 3185 if (outCount == 0 || endPt != (*outsidePts)[outCount - 2]) { |
3036 outsidePts->push_back(endPt); | 3186 outsidePts->push_back(endPt); |
3037 outsidePts->push_back(startPt); | 3187 outsidePts->push_back(startPt); |
3038 } | 3188 } |
3039 } | 3189 } |
3040 | 3190 |
3041 void SkOpSegment::TrackOutside(SkTArray<SkPoint, true>* outsidePts, const SkPoin
t& startPt) { | 3191 void SkOpSegment::TrackOutside(SkTArray<SkPoint, true>* outsidePts, const SkPoin
t& startPt) { |
(...skipping 509 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3551 SkASSERT(done == fDoneSpans); | 3701 SkASSERT(done == fDoneSpans); |
3552 #endif | 3702 #endif |
3553 } | 3703 } |
3554 | 3704 |
3555 #ifdef SK_DEBUG | 3705 #ifdef SK_DEBUG |
3556 void SkOpSegment::dumpPts() const { | 3706 void SkOpSegment::dumpPts() const { |
3557 int last = SkPathOpsVerbToPoints(fVerb); | 3707 int last = SkPathOpsVerbToPoints(fVerb); |
3558 SkDebugf("{{"); | 3708 SkDebugf("{{"); |
3559 int index = 0; | 3709 int index = 0; |
3560 do { | 3710 do { |
3561 SkDPoint::DumpSkPoint(fPts[index]); | 3711 SkDPoint::dump(fPts[index]); |
3562 SkDebugf(", "); | 3712 SkDebugf(", "); |
3563 } while (++index < last); | 3713 } while (++index < last); |
3564 SkDPoint::DumpSkPoint(fPts[index]); | 3714 SkDPoint::dump(fPts[index]); |
3565 SkDebugf("}}\n"); | 3715 SkDebugf("}}\n"); |
3566 } | 3716 } |
3567 | 3717 |
3568 void SkOpSegment::dumpDPts() const { | 3718 void SkOpSegment::dumpDPts() const { |
3569 int count = SkPathOpsVerbToPoints(fVerb); | 3719 int count = SkPathOpsVerbToPoints(fVerb); |
3570 SkDebugf("{{"); | 3720 SkDebugf("{{"); |
3571 int index = 0; | 3721 int index = 0; |
3572 do { | 3722 do { |
3573 SkDPoint dPt = {fPts[index].fX, fPts[index].fY}; | 3723 SkDPoint dPt = {fPts[index].fX, fPts[index].fY}; |
3574 dPt.dump(); | 3724 dPt.dump(); |
3575 if (index != count) { | 3725 if (index != count) { |
3576 SkDebugf(", "); | 3726 SkDebugf(", "); |
3577 } | 3727 } |
3578 } while (++index <= count); | 3728 } while (++index <= count); |
3579 SkDebugf("}}\n"); | 3729 SkDebugf("}}\n"); |
3580 } | 3730 } |
3581 | 3731 |
3582 void SkOpSegment::dumpSpans() const { | 3732 void SkOpSegment::dumpSpans() const { |
3583 int count = this->count(); | 3733 int count = this->count(); |
3584 for (int index = 0; index < count; ++index) { | 3734 for (int index = 0; index < count; ++index) { |
3585 const SkOpSpan& span = this->span(index); | 3735 const SkOpSpan& span = this->span(index); |
3586 SkDebugf("[%d] ", index); | 3736 SkDebugf("[%d] ", index); |
3587 span.dump(); | 3737 span.dump(); |
3588 } | 3738 } |
3589 } | 3739 } |
3590 #endif | 3740 #endif |
OLD | NEW |