LCOV - code coverage report
Current view: top level - gfx/skia/skia/src/core - SkRegion_path.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 267 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 25 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * Copyright 2006 The Android Open Source Project
       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 "SkRegionPriv.h"
       9             : #include "SkBlitter.h"
      10             : #include "SkScan.h"
      11             : #include "SkTSort.h"
      12             : #include "SkTDArray.h"
      13             : #include "SkPath.h"
      14             : 
      15             : // The rgnbuilder caller *seems* to pass short counts, possible often seens early failure, so
      16             : // we may not want to promote this to a "std" routine just yet.
      17           0 : static bool sk_memeq32(const int32_t* SK_RESTRICT a, const int32_t* SK_RESTRICT b, int count) {
      18           0 :     for (int i = 0; i < count; ++i) {
      19           0 :         if (a[i] != b[i]) {
      20           0 :             return false;
      21             :         }
      22             :     }
      23           0 :     return true;
      24             : }
      25             : 
      26             : class SkRgnBuilder : public SkBlitter {
      27             : public:
      28             :     SkRgnBuilder();
      29             :     ~SkRgnBuilder() override;
      30             : 
      31             :     // returns true if it could allocate the working storage needed
      32             :     bool init(int maxHeight, int maxTransitions, bool pathIsInverse);
      33             : 
      34           0 :     void done() {
      35           0 :         if (fCurrScanline != nullptr) {
      36           0 :             fCurrScanline->fXCount = (SkRegion::RunType)((int)(fCurrXPtr - fCurrScanline->firstX()));
      37           0 :             if (!this->collapsWithPrev()) { // flush the last line
      38           0 :                 fCurrScanline = fCurrScanline->nextScanline();
      39             :             }
      40             :         }
      41           0 :     }
      42             : 
      43             :     int     computeRunCount() const;
      44             :     void    copyToRect(SkIRect*) const;
      45             :     void    copyToRgn(SkRegion::RunType runs[]) const;
      46             : 
      47             :     void blitH(int x, int y, int width) override;
      48           0 :     void blitAntiH(int x, int y, const SkAlpha antialias[], const int16_t runs[]) override {
      49           0 :         SkDEBUGFAIL("blitAntiH not implemented");
      50           0 :     }
      51             : 
      52             : #ifdef SK_DEBUG
      53             :     void dump() const {
      54             :         SkDebugf("SkRgnBuilder: Top = %d\n", fTop);
      55             :         const Scanline* line = (Scanline*)fStorage;
      56             :         while (line < fCurrScanline) {
      57             :             SkDebugf("SkRgnBuilder::Scanline: LastY=%d, fXCount=%d", line->fLastY, line->fXCount);
      58             :             for (int i = 0; i < line->fXCount; i++) {
      59             :                 SkDebugf(" %d", line->firstX()[i]);
      60             :             }
      61             :             SkDebugf("\n");
      62             : 
      63             :             line = line->nextScanline();
      64             :         }
      65             :     }
      66             : #endif
      67             : private:
      68             :     /*
      69             :      *  Scanline mimics a row in the region, nearly. A row in a region is:
      70             :      *      [Bottom IntervalCount [L R]... Sentinel]
      71             :      *  while a Scanline is
      72             :      *      [LastY XCount [L R]... uninitialized]
      73             :      *  The two are the same length (which is good), but we have to transmute
      74             :      *  the scanline a little when we convert it to a region-row.
      75             :      *
      76             :      *  Potentially we could recode this to exactly match the row format, in
      77             :      *  which case copyToRgn() could be a single memcpy. Not sure that is worth
      78             :      *  the effort.
      79             :      */
      80             :     struct Scanline {
      81             :         SkRegion::RunType fLastY;
      82             :         SkRegion::RunType fXCount;
      83             : 
      84           0 :         SkRegion::RunType* firstX() const { return (SkRegion::RunType*)(this + 1); }
      85           0 :         Scanline* nextScanline() const {
      86             :             // add final +1 for the x-sentinel
      87           0 :             return (Scanline*)((SkRegion::RunType*)(this + 1) + fXCount + 1);
      88             :         }
      89             :     };
      90             :     SkRegion::RunType*  fStorage;
      91             :     Scanline*           fCurrScanline;
      92             :     Scanline*           fPrevScanline;
      93             :     //  points at next avialable x[] in fCurrScanline
      94             :     SkRegion::RunType*  fCurrXPtr;
      95             :     SkRegion::RunType   fTop;           // first Y value
      96             : 
      97             :     int fStorageCount;
      98             : 
      99           0 :     bool collapsWithPrev() {
     100           0 :         if (fPrevScanline != nullptr &&
     101           0 :             fPrevScanline->fLastY + 1 == fCurrScanline->fLastY &&
     102           0 :             fPrevScanline->fXCount == fCurrScanline->fXCount &&
     103           0 :             sk_memeq32(fPrevScanline->firstX(), fCurrScanline->firstX(), fCurrScanline->fXCount))
     104             :         {
     105             :             // update the height of fPrevScanline
     106           0 :             fPrevScanline->fLastY = fCurrScanline->fLastY;
     107           0 :             return true;
     108             :         }
     109           0 :         return false;
     110             :     }
     111             : };
     112             : 
     113           0 : SkRgnBuilder::SkRgnBuilder()
     114           0 :     : fStorage(nullptr) {
     115           0 : }
     116             : 
     117           0 : SkRgnBuilder::~SkRgnBuilder() {
     118           0 :     sk_free(fStorage);
     119           0 : }
     120             : 
     121           0 : bool SkRgnBuilder::init(int maxHeight, int maxTransitions, bool pathIsInverse) {
     122           0 :     if ((maxHeight | maxTransitions) < 0) {
     123           0 :         return false;
     124             :     }
     125             : 
     126           0 :     if (pathIsInverse) {
     127             :         // allow for additional X transitions to "invert" each scanline
     128             :         // [ L' ... normal transitions ... R' ]
     129             :         //
     130           0 :         maxTransitions += 2;
     131             :     }
     132             : 
     133             :     // compute the count with +1 and +3 slop for the working buffer
     134           0 :     int64_t count = sk_64_mul(maxHeight + 1, 3 + maxTransitions);
     135             : 
     136           0 :     if (pathIsInverse) {
     137             :         // allow for two "empty" rows for the top and bottom
     138             :         //      [ Y, 1, L, R, S] == 5 (*2 for top and bottom)
     139           0 :         count += 10;
     140             :     }
     141             : 
     142           0 :     if (count < 0 || !sk_64_isS32(count)) {
     143           0 :         return false;
     144             :     }
     145           0 :     fStorageCount = sk_64_asS32(count);
     146             : 
     147           0 :     int64_t size = sk_64_mul(fStorageCount, sizeof(SkRegion::RunType));
     148           0 :     if (size < 0 || !sk_64_isS32(size)) {
     149           0 :         return false;
     150             :     }
     151             : 
     152           0 :     fStorage = (SkRegion::RunType*)sk_malloc_flags(sk_64_asS32(size), 0);
     153           0 :     if (nullptr == fStorage) {
     154           0 :         return false;
     155             :     }
     156             : 
     157           0 :     fCurrScanline = nullptr;    // signal empty collection
     158           0 :     fPrevScanline = nullptr;    // signal first scanline
     159           0 :     return true;
     160             : }
     161             : 
     162           0 : void SkRgnBuilder::blitH(int x, int y, int width) {
     163           0 :     if (fCurrScanline == nullptr) {  // first time
     164           0 :         fTop = (SkRegion::RunType)(y);
     165           0 :         fCurrScanline = (Scanline*)fStorage;
     166           0 :         fCurrScanline->fLastY = (SkRegion::RunType)(y);
     167           0 :         fCurrXPtr = fCurrScanline->firstX();
     168             :     } else {
     169           0 :         SkASSERT(y >= fCurrScanline->fLastY);
     170             : 
     171           0 :         if (y > fCurrScanline->fLastY) {
     172             :             // if we get here, we're done with fCurrScanline
     173           0 :             fCurrScanline->fXCount = (SkRegion::RunType)((int)(fCurrXPtr - fCurrScanline->firstX()));
     174             : 
     175           0 :             int prevLastY = fCurrScanline->fLastY;
     176           0 :             if (!this->collapsWithPrev()) {
     177           0 :                 fPrevScanline = fCurrScanline;
     178           0 :                 fCurrScanline = fCurrScanline->nextScanline();
     179             : 
     180             :             }
     181           0 :             if (y - 1 > prevLastY) {  // insert empty run
     182           0 :                 fCurrScanline->fLastY = (SkRegion::RunType)(y - 1);
     183           0 :                 fCurrScanline->fXCount = 0;
     184           0 :                 fCurrScanline = fCurrScanline->nextScanline();
     185             :             }
     186             :             // setup for the new curr line
     187           0 :             fCurrScanline->fLastY = (SkRegion::RunType)(y);
     188           0 :             fCurrXPtr = fCurrScanline->firstX();
     189             :         }
     190             :     }
     191             :     //  check if we should extend the current run, or add a new one
     192           0 :     if (fCurrXPtr > fCurrScanline->firstX() && fCurrXPtr[-1] == x) {
     193           0 :         fCurrXPtr[-1] = (SkRegion::RunType)(x + width);
     194             :     } else {
     195           0 :         fCurrXPtr[0] = (SkRegion::RunType)(x);
     196           0 :         fCurrXPtr[1] = (SkRegion::RunType)(x + width);
     197           0 :         fCurrXPtr += 2;
     198             :     }
     199           0 :     SkASSERT(fCurrXPtr - fStorage < fStorageCount);
     200           0 : }
     201             : 
     202           0 : int SkRgnBuilder::computeRunCount() const {
     203           0 :     if (fCurrScanline == nullptr) {
     204           0 :         return 0;
     205             :     }
     206             : 
     207           0 :     const SkRegion::RunType*  line = fStorage;
     208           0 :     const SkRegion::RunType*  stop = (const SkRegion::RunType*)fCurrScanline;
     209             : 
     210           0 :     return 2 + (int)(stop - line);
     211             : }
     212             : 
     213           0 : void SkRgnBuilder::copyToRect(SkIRect* r) const {
     214           0 :     SkASSERT(fCurrScanline != nullptr);
     215             :     // A rect's scanline is [bottom intervals left right sentinel] == 5
     216           0 :     SkASSERT((const SkRegion::RunType*)fCurrScanline - fStorage == 5);
     217             : 
     218           0 :     const Scanline* line = (const Scanline*)fStorage;
     219           0 :     SkASSERT(line->fXCount == 2);
     220             : 
     221           0 :     r->set(line->firstX()[0], fTop, line->firstX()[1], line->fLastY + 1);
     222           0 : }
     223             : 
     224           0 : void SkRgnBuilder::copyToRgn(SkRegion::RunType runs[]) const {
     225           0 :     SkASSERT(fCurrScanline != nullptr);
     226           0 :     SkASSERT((const SkRegion::RunType*)fCurrScanline - fStorage > 4);
     227             : 
     228           0 :     const Scanline* line = (const Scanline*)fStorage;
     229           0 :     const Scanline* stop = fCurrScanline;
     230             : 
     231           0 :     *runs++ = fTop;
     232           0 :     do {
     233           0 :         *runs++ = (SkRegion::RunType)(line->fLastY + 1);
     234           0 :         int count = line->fXCount;
     235           0 :         *runs++ = count >> 1;   // intervalCount
     236           0 :         if (count) {
     237           0 :             memcpy(runs, line->firstX(), count * sizeof(SkRegion::RunType));
     238           0 :             runs += count;
     239             :         }
     240           0 :         *runs++ = SkRegion::kRunTypeSentinel;
     241           0 :         line = line->nextScanline();
     242           0 :     } while (line < stop);
     243           0 :     SkASSERT(line == stop);
     244           0 :     *runs = SkRegion::kRunTypeSentinel;
     245           0 : }
     246             : 
     247           0 : static unsigned verb_to_initial_last_index(unsigned verb) {
     248             :     static const uint8_t gPathVerbToInitialLastIndex[] = {
     249             :         0,  //  kMove_Verb
     250             :         1,  //  kLine_Verb
     251             :         2,  //  kQuad_Verb
     252             :         2,  //  kConic_Verb
     253             :         3,  //  kCubic_Verb
     254             :         0,  //  kClose_Verb
     255             :         0   //  kDone_Verb
     256             :     };
     257           0 :     SkASSERT((unsigned)verb < SK_ARRAY_COUNT(gPathVerbToInitialLastIndex));
     258           0 :     return gPathVerbToInitialLastIndex[verb];
     259             : }
     260             : 
     261           0 : static unsigned verb_to_max_edges(unsigned verb) {
     262             :     static const uint8_t gPathVerbToMaxEdges[] = {
     263             :         0,  //  kMove_Verb
     264             :         1,  //  kLine_Verb
     265             :         2,  //  kQuad_VerbB
     266             :         2,  //  kConic_VerbB
     267             :         3,  //  kCubic_Verb
     268             :         0,  //  kClose_Verb
     269             :         0   //  kDone_Verb
     270             :     };
     271           0 :     SkASSERT((unsigned)verb < SK_ARRAY_COUNT(gPathVerbToMaxEdges));
     272           0 :     return gPathVerbToMaxEdges[verb];
     273             : }
     274             : 
     275             : // If returns 0, ignore itop and ibot
     276           0 : static int count_path_runtype_values(const SkPath& path, int* itop, int* ibot) {
     277           0 :     SkPath::Iter    iter(path, true);
     278             :     SkPoint         pts[4];
     279             :     SkPath::Verb    verb;
     280             : 
     281           0 :     int maxEdges = 0;
     282           0 :     SkScalar    top = SkIntToScalar(SK_MaxS16);
     283           0 :     SkScalar    bot = SkIntToScalar(SK_MinS16);
     284             : 
     285           0 :     while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
     286           0 :         maxEdges += verb_to_max_edges(verb);
     287             : 
     288           0 :         int lastIndex = verb_to_initial_last_index(verb);
     289           0 :         if (lastIndex > 0) {
     290           0 :             for (int i = 1; i <= lastIndex; i++) {
     291           0 :                 if (top > pts[i].fY) {
     292           0 :                     top = pts[i].fY;
     293           0 :                 } else if (bot < pts[i].fY) {
     294           0 :                     bot = pts[i].fY;
     295             :                 }
     296             :             }
     297           0 :         } else if (SkPath::kMove_Verb == verb) {
     298           0 :             if (top > pts[0].fY) {
     299           0 :                 top = pts[0].fY;
     300           0 :             } else if (bot < pts[0].fY) {
     301           0 :                 bot = pts[0].fY;
     302             :             }
     303             :         }
     304             :     }
     305           0 :     if (0 == maxEdges) {
     306           0 :         return 0;   // we have only moves+closes
     307             :     }
     308             : 
     309           0 :     SkASSERT(top <= bot);
     310           0 :     *itop = SkScalarRoundToInt(top);
     311           0 :     *ibot = SkScalarRoundToInt(bot);
     312           0 :     return maxEdges;
     313             : }
     314             : 
     315           0 : static bool check_inverse_on_empty_return(SkRegion* dst, const SkPath& path, const SkRegion& clip) {
     316           0 :     if (path.isInverseFillType()) {
     317           0 :         return dst->set(clip);
     318             :     } else {
     319           0 :         return dst->setEmpty();
     320             :     }
     321             : }
     322             : 
     323           0 : bool SkRegion::setPath(const SkPath& path, const SkRegion& clip) {
     324           0 :     SkDEBUGCODE(this->validate();)
     325             : 
     326           0 :     if (clip.isEmpty()) {
     327           0 :         return this->setEmpty();
     328             :     }
     329             : 
     330           0 :     if (path.isEmpty()) {
     331           0 :         return check_inverse_on_empty_return(this, path, clip);
     332             :     }
     333             : 
     334             :     //  compute worst-case rgn-size for the path
     335             :     int pathTop, pathBot;
     336           0 :     int pathTransitions = count_path_runtype_values(path, &pathTop, &pathBot);
     337           0 :     if (0 == pathTransitions) {
     338           0 :         return check_inverse_on_empty_return(this, path, clip);
     339             :     }
     340             : 
     341             :     int clipTop, clipBot;
     342           0 :     int clipTransitions = clip.count_runtype_values(&clipTop, &clipBot);
     343             : 
     344           0 :     int top = SkMax32(pathTop, clipTop);
     345           0 :     int bot = SkMin32(pathBot, clipBot);
     346           0 :     if (top >= bot) {
     347           0 :         return check_inverse_on_empty_return(this, path, clip);
     348             :     }
     349             : 
     350           0 :     SkRgnBuilder builder;
     351             : 
     352           0 :     if (!builder.init(bot - top,
     353             :                       SkMax32(pathTransitions, clipTransitions),
     354           0 :                       path.isInverseFillType())) {
     355             :         // can't allocate working space, so return false
     356           0 :         return this->setEmpty();
     357             :     }
     358             : 
     359           0 :     SkScan::FillPath(path, clip, &builder);
     360           0 :     builder.done();
     361             : 
     362           0 :     int count = builder.computeRunCount();
     363           0 :     if (count == 0) {
     364           0 :         return this->setEmpty();
     365           0 :     } else if (count == kRectRegionRuns) {
     366           0 :         builder.copyToRect(&fBounds);
     367           0 :         this->setRect(fBounds);
     368             :     } else {
     369           0 :         SkRegion tmp;
     370             : 
     371           0 :         tmp.fRunHead = RunHead::Alloc(count);
     372           0 :         builder.copyToRgn(tmp.fRunHead->writable_runs());
     373           0 :         tmp.fRunHead->computeRunBounds(&tmp.fBounds);
     374           0 :         this->swap(tmp);
     375             :     }
     376           0 :     SkDEBUGCODE(this->validate();)
     377           0 :     return true;
     378             : }
     379             : 
     380             : /////////////////////////////////////////////////////////////////////////////////////////////////
     381             : /////////////////////////////////////////////////////////////////////////////////////////////////
     382             : 
     383             : struct Edge {
     384             :     enum {
     385             :         kY0Link = 0x01,
     386             :         kY1Link = 0x02,
     387             : 
     388             :         kCompleteLink = (kY0Link | kY1Link)
     389             :     };
     390             : 
     391             :     SkRegion::RunType fX;
     392             :     SkRegion::RunType fY0, fY1;
     393             :     uint8_t fFlags;
     394             :     Edge*   fNext;
     395             : 
     396           0 :     void set(int x, int y0, int y1) {
     397           0 :         SkASSERT(y0 != y1);
     398             : 
     399           0 :         fX = (SkRegion::RunType)(x);
     400           0 :         fY0 = (SkRegion::RunType)(y0);
     401           0 :         fY1 = (SkRegion::RunType)(y1);
     402           0 :         fFlags = 0;
     403           0 :         SkDEBUGCODE(fNext = nullptr;)
     404           0 :     }
     405             : 
     406           0 :     int top() const {
     407           0 :         return SkFastMin32(fY0, fY1);
     408             :     }
     409             : };
     410             : 
     411           0 : static void find_link(Edge* base, Edge* stop) {
     412           0 :     SkASSERT(base < stop);
     413             : 
     414           0 :     if (base->fFlags == Edge::kCompleteLink) {
     415           0 :         SkASSERT(base->fNext);
     416           0 :         return;
     417             :     }
     418             : 
     419           0 :     SkASSERT(base + 1 < stop);
     420             : 
     421           0 :     int y0 = base->fY0;
     422           0 :     int y1 = base->fY1;
     423             : 
     424           0 :     Edge* e = base;
     425           0 :     if ((base->fFlags & Edge::kY0Link) == 0) {
     426             :         for (;;) {
     427           0 :             e += 1;
     428           0 :             if ((e->fFlags & Edge::kY1Link) == 0 && y0 == e->fY1) {
     429           0 :                 SkASSERT(nullptr == e->fNext);
     430           0 :                 e->fNext = base;
     431           0 :                 e->fFlags = SkToU8(e->fFlags | Edge::kY1Link);
     432           0 :                 break;
     433             :             }
     434             :         }
     435             :     }
     436             : 
     437           0 :     e = base;
     438           0 :     if ((base->fFlags & Edge::kY1Link) == 0) {
     439             :         for (;;) {
     440           0 :             e += 1;
     441           0 :             if ((e->fFlags & Edge::kY0Link) == 0 && y1 == e->fY0) {
     442           0 :                 SkASSERT(nullptr == base->fNext);
     443           0 :                 base->fNext = e;
     444           0 :                 e->fFlags = SkToU8(e->fFlags | Edge::kY0Link);
     445           0 :                 break;
     446             :             }
     447             :         }
     448             :     }
     449             : 
     450           0 :     base->fFlags = Edge::kCompleteLink;
     451             : }
     452             : 
     453           0 : static int extract_path(Edge* edge, Edge* stop, SkPath* path) {
     454           0 :     while (0 == edge->fFlags) {
     455           0 :         edge++; // skip over "used" edges
     456             :     }
     457             : 
     458           0 :     SkASSERT(edge < stop);
     459             : 
     460           0 :     Edge* base = edge;
     461           0 :     Edge* prev = edge;
     462           0 :     edge = edge->fNext;
     463           0 :     SkASSERT(edge != base);
     464             : 
     465           0 :     int count = 1;
     466           0 :     path->moveTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY0));
     467           0 :     prev->fFlags = 0;
     468           0 :     do {
     469           0 :         if (prev->fX != edge->fX || prev->fY1 != edge->fY0) { // skip collinear
     470           0 :             path->lineTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY1));    // V
     471           0 :             path->lineTo(SkIntToScalar(edge->fX), SkIntToScalar(edge->fY0));    // H
     472             :         }
     473           0 :         prev = edge;
     474           0 :         edge = edge->fNext;
     475           0 :         count += 1;
     476           0 :         prev->fFlags = 0;
     477           0 :     } while (edge != base);
     478           0 :     path->lineTo(SkIntToScalar(prev->fX), SkIntToScalar(prev->fY1));    // V
     479           0 :     path->close();
     480           0 :     return count;
     481             : }
     482             : 
     483             : struct EdgeLT {
     484           0 :     bool operator()(const Edge& a, const Edge& b) const {
     485           0 :         return (a.fX == b.fX) ? a.top() < b.top() : a.fX < b.fX;
     486             :     }
     487             : };
     488             : 
     489           0 : bool SkRegion::getBoundaryPath(SkPath* path) const {
     490             :     // path could safely be nullptr if we're empty, but the caller shouldn't
     491             :     // *know* that
     492           0 :     SkASSERT(path);
     493             : 
     494           0 :     if (this->isEmpty()) {
     495           0 :         return false;
     496             :     }
     497             : 
     498           0 :     const SkIRect& bounds = this->getBounds();
     499             : 
     500           0 :     if (this->isRect()) {
     501             :         SkRect  r;
     502           0 :         r.set(bounds);      // this converts the ints to scalars
     503           0 :         path->addRect(r);
     504           0 :         return true;
     505             :     }
     506             : 
     507           0 :     SkRegion::Iterator  iter(*this);
     508           0 :     SkTDArray<Edge>     edges;
     509             : 
     510           0 :     for (const SkIRect& r = iter.rect(); !iter.done(); iter.next()) {
     511           0 :         Edge* edge = edges.append(2);
     512           0 :         edge[0].set(r.fLeft, r.fBottom, r.fTop);
     513           0 :         edge[1].set(r.fRight, r.fTop, r.fBottom);
     514             :     }
     515             : 
     516           0 :     int count = edges.count();
     517           0 :     Edge* start = edges.begin();
     518           0 :     Edge* stop = start + count;
     519           0 :     SkTQSort<Edge>(start, stop - 1, EdgeLT());
     520             : 
     521             :     Edge* e;
     522           0 :     for (e = start; e != stop; e++) {
     523           0 :         find_link(e, stop);
     524             :     }
     525             : 
     526             : #ifdef SK_DEBUG
     527           0 :     for (e = start; e != stop; e++) {
     528           0 :         SkASSERT(e->fNext != nullptr);
     529           0 :         SkASSERT(e->fFlags == Edge::kCompleteLink);
     530             :     }
     531             : #endif
     532             : 
     533           0 :     path->incReserve(count << 1);
     534           0 :     do {
     535           0 :         SkASSERT(count > 1);
     536           0 :         count -= extract_path(start, stop, path);
     537           0 :     } while (count > 0);
     538             : 
     539           0 :     return true;
     540             : }

Generated by: LCOV version 1.13