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
8#include <cmath>
9#include "SkRRect.h"
10#include "SkScopeExit.h"
11#include "SkBuffer.h"
12#include "SkMalloc.h"
13#include "SkMatrix.h"
14#include "SkScaleToSides.h"
15
16///////////////////////////////////////////////////////////////////////////////
17
18void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
19    if (!this->initializeRect(rect)) {
20        return;
21    }
22
23    if (!SkScalarsAreFinite(xRad, yRad)) {
24        xRad = yRad = 0;    // devolve into a simple rect
25    }
26    if (xRad <= 0 || yRad <= 0) {
27        // all corners are square in this case
28        this->setRect(rect);
29        return;
30    }
31
32    if (fRect.width() < xRad+xRad || fRect.height() < yRad+yRad) {
33        SkScalar scale = SkMinScalar(fRect.width() / (xRad + xRad), fRect.height() / (yRad + yRad));
34        SkASSERT(scale < SK_Scalar1);
35        xRad *= scale;
36        yRad *= scale;
37    }
38
39    for (int i = 0; i < 4; ++i) {
40        fRadii[i].set(xRad, yRad);
41    }
42    fType = kSimple_Type;
43    if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) {
44        fType = kOval_Type;
45        // TODO: assert that all the x&y radii are already W/2 & H/2
46    }
47
48    SkASSERT(this->isValid());
49}
50
51void SkRRect::setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad,
52                           SkScalar rightRad, SkScalar bottomRad) {
53    if (!this->initializeRect(rect)) {
54        return;
55    }
56
57    const SkScalar array[4] = { leftRad, topRad, rightRad, bottomRad };
58    if (!SkScalarsAreFinite(array, 4)) {
59        this->setRect(rect);    // devolve into a simple rect
60        return;
61    }
62
63    leftRad = SkMaxScalar(leftRad, 0);
64    topRad = SkMaxScalar(topRad, 0);
65    rightRad = SkMaxScalar(rightRad, 0);
66    bottomRad = SkMaxScalar(bottomRad, 0);
67
68    SkScalar scale = SK_Scalar1;
69    if (leftRad + rightRad > fRect.width()) {
70        scale = fRect.width() / (leftRad + rightRad);
71    }
72    if (topRad + bottomRad > fRect.height()) {
73        scale = SkMinScalar(scale, fRect.height() / (topRad + bottomRad));
74    }
75
76    if (scale < SK_Scalar1) {
77        leftRad *= scale;
78        topRad *= scale;
79        rightRad *= scale;
80        bottomRad *= scale;
81    }
82
83    if (leftRad == rightRad && topRad == bottomRad) {
84        if (leftRad >= SkScalarHalf(fRect.width()) && topRad >= SkScalarHalf(fRect.height())) {
85            fType = kOval_Type;
86        } else if (0 == leftRad || 0 == topRad) {
87            // If the left and (by equality check above) right radii are zero then it is a rect.
88            // Same goes for top/bottom.
89            fType = kRect_Type;
90            leftRad = 0;
91            topRad = 0;
92            rightRad = 0;
93            bottomRad = 0;
94        } else {
95            fType = kSimple_Type;
96        }
97    } else {
98        fType = kNinePatch_Type;
99    }
100
101    fRadii[kUpperLeft_Corner].set(leftRad, topRad);
102    fRadii[kUpperRight_Corner].set(rightRad, topRad);
103    fRadii[kLowerRight_Corner].set(rightRad, bottomRad);
104    fRadii[kLowerLeft_Corner].set(leftRad, bottomRad);
105
106    SkASSERT(this->isValid());
107}
108
109// These parameters intentionally double. Apropos crbug.com/463920, if one of the
110// radii is huge while the other is small, single precision math can completely
111// miss the fact that a scale is required.
112static double compute_min_scale(double rad1, double rad2, double limit, double curMin) {
113    if ((rad1 + rad2) > limit) {
114        return SkTMin(curMin, limit / (rad1 + rad2));
115    }
116    return curMin;
117}
118
119void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) {
120    if (!this->initializeRect(rect)) {
121        return;
122    }
123
124    if (!SkScalarsAreFinite(&radii[0].fX, 8)) {
125        this->setRect(rect);    // devolve into a simple rect
126        return;
127    }
128
129    memcpy(fRadii, radii, sizeof(fRadii));
130
131    bool allCornersSquare = true;
132
133    // Clamp negative radii to zero
134    for (int i = 0; i < 4; ++i) {
135        if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) {
136            // In this case we are being a little fast & loose. Since one of
137            // the radii is 0 the corner is square. However, the other radii
138            // could still be non-zero and play in the global scale factor
139            // computation.
140            fRadii[i].fX = 0;
141            fRadii[i].fY = 0;
142        } else {
143            allCornersSquare = false;
144        }
145    }
146
147    if (allCornersSquare) {
148        this->setRect(rect);
149        return;
150    }
151
152    this->scaleRadii();
153}
154
155bool SkRRect::initializeRect(const SkRect& rect) {
156    // Check this before sorting because sorting can hide nans.
157    if (!rect.isFinite()) {
158        *this = SkRRect();
159        return false;
160    }
161    fRect = rect.makeSorted();
162    if (fRect.isEmpty()) {
163        memset(fRadii, 0, sizeof(fRadii));
164        fType = kEmpty_Type;
165        return false;
166    }
167    return true;
168}
169
170void SkRRect::scaleRadii() {
171
172    // Proportionally scale down all radii to fit. Find the minimum ratio
173    // of a side and the radii on that side (for all four sides) and use
174    // that to scale down _all_ the radii. This algorithm is from the
175    // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping
176    // Curves:
177    // "Let f = min(Li/Si), where i is one of { top, right, bottom, left },
178    //   Si is the sum of the two corresponding radii of the corners on side i,
179    //   and Ltop = Lbottom = the width of the box,
180    //   and Lleft = Lright = the height of the box.
181    // If f < 1, then all corner radii are reduced by multiplying them by f."
182    double scale = 1.0;
183
184    // The sides of the rectangle may be larger than a float.
185    double width = (double)fRect.fRight - (double)fRect.fLeft;
186    double height = (double)fRect.fBottom - (double)fRect.fTop;
187    scale = compute_min_scale(fRadii[0].fX, fRadii[1].fX, width,  scale);
188    scale = compute_min_scale(fRadii[1].fY, fRadii[2].fY, height, scale);
189    scale = compute_min_scale(fRadii[2].fX, fRadii[3].fX, width,  scale);
190    scale = compute_min_scale(fRadii[3].fY, fRadii[0].fY, height, scale);
191
192    if (scale < 1.0) {
193        SkScaleToSides::AdjustRadii(width,  scale, &fRadii[0].fX, &fRadii[1].fX);
194        SkScaleToSides::AdjustRadii(height, scale, &fRadii[1].fY, &fRadii[2].fY);
195        SkScaleToSides::AdjustRadii(width,  scale, &fRadii[2].fX, &fRadii[3].fX);
196        SkScaleToSides::AdjustRadii(height, scale, &fRadii[3].fY, &fRadii[0].fY);
197    }
198
199    // At this point we're either oval, simple, or complex (not empty or rect).
200    this->computeType();
201
202    SkASSERT(this->isValid());
203}
204
205// This method determines if a point known to be inside the RRect's bounds is
206// inside all the corners.
207bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const {
208    SkPoint canonicalPt; // (x,y) translated to one of the quadrants
209    int index;
210
211    if (kOval_Type == this->type()) {
212        canonicalPt.set(x - fRect.centerX(), y - fRect.centerY());
213        index = kUpperLeft_Corner;  // any corner will do in this case
214    } else {
215        if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX &&
216            y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) {
217            // UL corner
218            index = kUpperLeft_Corner;
219            canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX),
220                            y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY));
221            SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0);
222        } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX &&
223                   y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) {
224            // LL corner
225            index = kLowerLeft_Corner;
226            canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX),
227                            y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY));
228            SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0);
229        } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX &&
230                   y < fRect.fTop + fRadii[kUpperRight_Corner].fY) {
231            // UR corner
232            index = kUpperRight_Corner;
233            canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX),
234                            y - (fRect.fTop + fRadii[kUpperRight_Corner].fY));
235            SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0);
236        } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX &&
237                   y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) {
238            // LR corner
239            index = kLowerRight_Corner;
240            canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX),
241                            y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY));
242            SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0);
243        } else {
244            // not in any of the corners
245            return true;
246        }
247    }
248
249    // A point is in an ellipse (in standard position) if:
250    //      x^2     y^2
251    //     ----- + ----- <= 1
252    //      a^2     b^2
253    // or :
254    //     b^2*x^2 + a^2*y^2 <= (ab)^2
255    SkScalar dist =  SkScalarSquare(canonicalPt.fX) * SkScalarSquare(fRadii[index].fY) +
256                     SkScalarSquare(canonicalPt.fY) * SkScalarSquare(fRadii[index].fX);
257    return dist <= SkScalarSquare(fRadii[index].fX * fRadii[index].fY);
258}
259
260bool SkRRect::allCornersCircular(SkScalar tolerance) const {
261    return SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY, tolerance) &&
262           SkScalarNearlyEqual(fRadii[1].fX, fRadii[1].fY, tolerance) &&
263           SkScalarNearlyEqual(fRadii[2].fX, fRadii[2].fY, tolerance) &&
264           SkScalarNearlyEqual(fRadii[3].fX, fRadii[3].fY, tolerance);
265}
266
267bool SkRRect::contains(const SkRect& rect) const {
268    if (!this->getBounds().contains(rect)) {
269        // If 'rect' isn't contained by the RR's bounds then the
270        // RR definitely doesn't contain it
271        return false;
272    }
273
274    if (this->isRect()) {
275        // the prior test was sufficient
276        return true;
277    }
278
279    // At this point we know all four corners of 'rect' are inside the
280    // bounds of of this RR. Check to make sure all the corners are inside
281    // all the curves
282    return this->checkCornerContainment(rect.fLeft, rect.fTop) &&
283           this->checkCornerContainment(rect.fRight, rect.fTop) &&
284           this->checkCornerContainment(rect.fRight, rect.fBottom) &&
285           this->checkCornerContainment(rect.fLeft, rect.fBottom);
286}
287
288static bool radii_are_nine_patch(const SkVector radii[4]) {
289    return radii[SkRRect::kUpperLeft_Corner].fX == radii[SkRRect::kLowerLeft_Corner].fX &&
290           radii[SkRRect::kUpperLeft_Corner].fY == radii[SkRRect::kUpperRight_Corner].fY &&
291           radii[SkRRect::kUpperRight_Corner].fX == radii[SkRRect::kLowerRight_Corner].fX &&
292           radii[SkRRect::kLowerLeft_Corner].fY == radii[SkRRect::kLowerRight_Corner].fY;
293}
294
295// There is a simplified version of this method in setRectXY
296void SkRRect::computeType() {
297    SK_AT_SCOPE_EXIT(SkASSERT(this->isValid()));
298
299    if (fRect.isEmpty()) {
300        SkASSERT(fRect.isSorted());
301        for (size_t i = 0; i < SK_ARRAY_COUNT(fRadii); ++i) {
302            SkASSERT((fRadii[i] == SkVector{0, 0}));
303        }
304        fType = kEmpty_Type;
305        return;
306    }
307
308    bool allRadiiEqual = true; // are all x radii equal and all y radii?
309    bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY;
310
311    for (int i = 1; i < 4; ++i) {
312        if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
313            // if either radius is zero the corner is square so both have to
314            // be non-zero to have a rounded corner
315            allCornersSquare = false;
316        }
317        if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
318            allRadiiEqual = false;
319        }
320    }
321
322    if (allCornersSquare) {
323        fType = kRect_Type;
324        return;
325    }
326
327    if (allRadiiEqual) {
328        if (fRadii[0].fX >= SkScalarHalf(fRect.width()) &&
329            fRadii[0].fY >= SkScalarHalf(fRect.height())) {
330            fType = kOval_Type;
331        } else {
332            fType = kSimple_Type;
333        }
334        return;
335    }
336
337    if (radii_are_nine_patch(fRadii)) {
338        fType = kNinePatch_Type;
339    } else {
340        fType = kComplex_Type;
341    }
342}
343
344static bool matrix_only_scale_and_translate(const SkMatrix& matrix) {
345    const SkMatrix::TypeMask m = (SkMatrix::TypeMask) (SkMatrix::kAffine_Mask
346                                    | SkMatrix::kPerspective_Mask);
347    return (matrix.getType() & m) == 0;
348}
349
350bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
351    if (nullptr == dst) {
352        return false;
353    }
354
355    // Assert that the caller is not trying to do this in place, which
356    // would violate const-ness. Do not return false though, so that
357    // if they know what they're doing and want to violate it they can.
358    SkASSERT(dst != this);
359
360    if (matrix.isIdentity()) {
361        *dst = *this;
362        return true;
363    }
364
365    // If transform supported 90 degree rotations (which it could), we could
366    // use SkMatrix::rectStaysRect() to check for a valid transformation.
367    if (!matrix_only_scale_and_translate(matrix)) {
368        return false;
369    }
370
371    SkRect newRect;
372    if (!matrix.mapRect(&newRect, fRect)) {
373        return false;
374    }
375
376    // The matrix may have scaled us to zero (or due to float madness, we now have collapsed
377    // some dimension of the rect, so we need to check for that. Note that matrix must be
378    // scale and translate and mapRect() produces a sorted rect. So an empty rect indicates
379    // loss of precision.
380    if (!newRect.isFinite() || newRect.isEmpty()) {
381        return false;
382    }
383
384    // At this point, this is guaranteed to succeed, so we can modify dst.
385    dst->fRect = newRect;
386
387    // Since the only transforms that were allowed are scale and translate, the type
388    // remains unchanged.
389    dst->fType = fType;
390
391    if (kRect_Type == fType) {
392        SkASSERT(dst->isValid());
393        return true;
394    }
395    if (kOval_Type == fType) {
396        for (int i = 0; i < 4; ++i) {
397            dst->fRadii[i].fX = SkScalarHalf(newRect.width());
398            dst->fRadii[i].fY = SkScalarHalf(newRect.height());
399        }
400        SkASSERT(dst->isValid());
401        return true;
402    }
403
404    // Now scale each corner
405    SkScalar xScale = matrix.getScaleX();
406    const bool flipX = xScale < 0;
407    if (flipX) {
408        xScale = -xScale;
409    }
410    SkScalar yScale = matrix.getScaleY();
411    const bool flipY = yScale < 0;
412    if (flipY) {
413        yScale = -yScale;
414    }
415
416    // Scale the radii without respecting the flip.
417    for (int i = 0; i < 4; ++i) {
418        dst->fRadii[i].fX = fRadii[i].fX * xScale;
419        dst->fRadii[i].fY = fRadii[i].fY * yScale;
420    }
421
422    // Now swap as necessary.
423    if (flipX) {
424        if (flipY) {
425            // Swap with opposite corners
426            SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]);
427            SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]);
428        } else {
429            // Only swap in x
430            SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]);
431            SkTSwap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]);
432        }
433    } else if (flipY) {
434        // Only swap in y
435        SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]);
436        SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]);
437    }
438
439    if (!AreRectAndRadiiValid(dst->fRect, dst->fRadii)) {
440        return false;
441    }
442
443    dst->scaleRadii();
444    dst->isValid();
445
446    return true;
447}
448
449///////////////////////////////////////////////////////////////////////////////
450
451void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
452    SkRect r = fRect.makeInset(dx, dy);
453    bool degenerate = false;
454    if (r.fRight <= r.fLeft) {
455        degenerate = true;
456        r.fLeft = r.fRight = SkScalarAve(r.fLeft, r.fRight);
457    }
458    if (r.fBottom <= r.fTop) {
459        degenerate = true;
460        r.fTop = r.fBottom = SkScalarAve(r.fTop, r.fBottom);
461    }
462    if (degenerate) {
463        dst->fRect = r;
464        memset(dst->fRadii, 0, sizeof(dst->fRadii));
465        dst->fType = kEmpty_Type;
466        return;
467    }
468    if (!r.isFinite()) {
469        *dst = SkRRect();
470        return;
471    }
472
473    SkVector radii[4];
474    memcpy(radii, fRadii, sizeof(radii));
475    for (int i = 0; i < 4; ++i) {
476        if (radii[i].fX) {
477            radii[i].fX -= dx;
478        }
479        if (radii[i].fY) {
480            radii[i].fY -= dy;
481        }
482    }
483    dst->setRectRadii(r, radii);
484}
485
486///////////////////////////////////////////////////////////////////////////////
487
488size_t SkRRect::writeToMemory(void* buffer) const {
489    // Serialize only the rect and corners, but not the derived type tag.
490    memcpy(buffer, this, kSizeInMemory);
491    return kSizeInMemory;
492}
493
494void SkRRect::writeToBuffer(SkWBuffer* buffer) const {
495    // Serialize only the rect and corners, but not the derived type tag.
496    buffer->write(this, kSizeInMemory);
497}
498
499size_t SkRRect::readFromMemory(const void* buffer, size_t length) {
500    if (length < kSizeInMemory) {
501        return 0;
502    }
503
504    SkRRect raw;
505    memcpy(&raw, buffer, kSizeInMemory);
506    this->setRectRadii(raw.fRect, raw.fRadii);
507    return kSizeInMemory;
508}
509
510bool SkRRect::readFromBuffer(SkRBuffer* buffer) {
511    if (buffer->available() < kSizeInMemory) {
512        return false;
513    }
514    SkRRect storage;
515    return buffer->read(&storage, kSizeInMemory) &&
516           (this->readFromMemory(&storage, kSizeInMemory) == kSizeInMemory);
517}
518
519#include "SkString.h"
520#include "SkStringUtils.h"
521
522void SkRRect::dump(bool asHex) const {
523    SkScalarAsStringType asType = asHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
524
525    fRect.dump(asHex);
526    SkString line("const SkPoint corners[] = {\n");
527    for (int i = 0; i < 4; ++i) {
528        SkString strX, strY;
529        SkAppendScalar(&strX, fRadii[i].x(), asType);
530        SkAppendScalar(&strY, fRadii[i].y(), asType);
531        line.appendf("    { %s, %s },", strX.c_str(), strY.c_str());
532        if (asHex) {
533            line.appendf(" /* %f %f */", fRadii[i].x(), fRadii[i].y());
534        }
535        line.append("\n");
536    }
537    line.append("};");
538    SkDebugf("%s\n", line.c_str());
539}
540
541///////////////////////////////////////////////////////////////////////////////
542
543/**
544 *  We need all combinations of predicates to be true to have a "safe" radius value.
545 */
546static bool are_radius_check_predicates_valid(SkScalar rad, SkScalar min, SkScalar max) {
547    return (min <= max) && (rad <= max - min) && (min + rad <= max) && (max - rad >= min) &&
548           rad >= 0;
549}
550
551bool SkRRect::isValid() const {
552    if (!AreRectAndRadiiValid(fRect, fRadii)) {
553        return false;
554    }
555
556    bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY);
557    bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY);
558    bool allRadiiSame = true;
559
560    for (int i = 1; i < 4; ++i) {
561        if (0 != fRadii[i].fX || 0 != fRadii[i].fY) {
562            allRadiiZero = false;
563        }
564
565        if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
566            allRadiiSame = false;
567        }
568
569        if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
570            allCornersSquare = false;
571        }
572    }
573    bool patchesOfNine = radii_are_nine_patch(fRadii);
574
575    if (fType < 0 || fType > kLastType) {
576        return false;
577    }
578
579    switch (fType) {
580        case kEmpty_Type:
581            if (!fRect.isEmpty() || !allRadiiZero || !allRadiiSame || !allCornersSquare) {
582                return false;
583            }
584            break;
585        case kRect_Type:
586            if (fRect.isEmpty() || !allRadiiZero || !allRadiiSame || !allCornersSquare) {
587                return false;
588            }
589            break;
590        case kOval_Type:
591            if (fRect.isEmpty() || allRadiiZero || !allRadiiSame || allCornersSquare) {
592                return false;
593            }
594
595            for (int i = 0; i < 4; ++i) {
596                if (!SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())) ||
597                    !SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height()))) {
598                    return false;
599                }
600            }
601            break;
602        case kSimple_Type:
603            if (fRect.isEmpty() || allRadiiZero || !allRadiiSame || allCornersSquare) {
604                return false;
605            }
606            break;
607        case kNinePatch_Type:
608            if (fRect.isEmpty() || allRadiiZero || allRadiiSame || allCornersSquare ||
609                !patchesOfNine) {
610                return false;
611            }
612            break;
613        case kComplex_Type:
614            if (fRect.isEmpty() || allRadiiZero || allRadiiSame || allCornersSquare ||
615                patchesOfNine) {
616                return false;
617            }
618            break;
619    }
620
621    return true;
622}
623
624bool SkRRect::AreRectAndRadiiValid(const SkRect& rect, const SkVector radii[4]) {
625    if (!rect.isFinite() || !rect.isSorted()) {
626        return false;
627    }
628    for (int i = 0; i < 4; ++i) {
629        if (!are_radius_check_predicates_valid(radii[i].fX, rect.fLeft, rect.fRight) ||
630            !are_radius_check_predicates_valid(radii[i].fY, rect.fTop, rect.fBottom)) {
631            return false;
632        }
633    }
634    return true;
635}
636///////////////////////////////////////////////////////////////////////////////
637