• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2008, The Android Open Source Project
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *  * Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  *  * Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #define LOG_TAG "webviewglue"
27 
28 #include "config.h"
29 #include "FindCanvas.h"
30 #include "LayerAndroid.h"
31 #include "IntRect.h"
32 #include "SkBlurMaskFilter.h"
33 #include "SkCornerPathEffect.h"
34 #include "SkRect.h"
35 
36 #include <utils/Log.h>
37 
38 #define INTEGER_OUTSET 2
39 
40 namespace android {
41 
42 // MatchInfo methods
43 ////////////////////////////////////////////////////////////////////////////////
44 
MatchInfo()45 MatchInfo::MatchInfo() {
46     m_picture = 0;
47 }
48 
~MatchInfo()49 MatchInfo::~MatchInfo() {
50     m_picture->safeUnref();
51 }
52 
MatchInfo(const MatchInfo & src)53 MatchInfo::MatchInfo(const MatchInfo& src) {
54     m_layerId = src.m_layerId;
55     m_location = src.m_location;
56     m_picture = src.m_picture;
57     m_picture->safeRef();
58 }
59 
set(const SkRegion & region,SkPicture * pic,int layerId)60 void MatchInfo::set(const SkRegion& region, SkPicture* pic, int layerId) {
61     m_picture->safeUnref();
62     m_layerId = layerId;
63     m_location = region;
64     m_picture = pic;
65     SkASSERT(pic);
66     pic->ref();
67 }
68 
69 // GlyphSet methods
70 ////////////////////////////////////////////////////////////////////////////////
71 
GlyphSet(const SkPaint & paint,const UChar * lower,const UChar * upper,size_t byteLength)72 GlyphSet::GlyphSet(const SkPaint& paint, const UChar* lower, const UChar* upper,
73             size_t byteLength) {
74     SkPaint clonePaint(paint);
75     clonePaint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
76     mTypeface = paint.getTypeface();
77     mCount = clonePaint.textToGlyphs(lower, byteLength, NULL);
78     if (mCount > MAX_STORAGE_COUNT) {
79         mLowerGlyphs = new uint16_t[2*mCount];
80     } else {
81         mLowerGlyphs = &mStorage[0];
82     }
83     // Use one array, and have mUpperGlyphs point to a portion of it,
84     // so that we can reduce the number of new/deletes
85     mUpperGlyphs = mLowerGlyphs + mCount;
86     int count2 = clonePaint.textToGlyphs(lower, byteLength, mLowerGlyphs);
87     SkASSERT(mCount == count2);
88     count2 = clonePaint.textToGlyphs(upper, byteLength, mUpperGlyphs);
89     SkASSERT(mCount == count2);
90 }
91 
~GlyphSet()92 GlyphSet::~GlyphSet() {
93     // Do not need to delete mTypeface, which is not owned by us.
94     if (mCount > MAX_STORAGE_COUNT) {
95         delete[] mLowerGlyphs;
96     }   // Otherwise, we just used local storage space, so no need to delete
97     // Also do not need to delete mUpperGlyphs, which simply points to a
98     // part of mLowerGlyphs
99 }
100 
operator =(GlyphSet & src)101 GlyphSet::GlyphSet& GlyphSet::operator=(GlyphSet& src) {
102     mTypeface = src.mTypeface;
103     mCount = src.mCount;
104     if (mCount > MAX_STORAGE_COUNT) {
105         mLowerGlyphs = new uint16_t[2*mCount];
106     } else {
107         mLowerGlyphs = &mStorage[0];
108     }
109     memcpy(mLowerGlyphs, src.mLowerGlyphs, 2*mCount*sizeof(uint16_t));
110     mUpperGlyphs = mLowerGlyphs + mCount;
111     return *this;
112 }
113 
characterMatches(uint16_t c,int index)114 bool GlyphSet::characterMatches(uint16_t c, int index) {
115     SkASSERT(index < mCount && index >= 0);
116     return c == mLowerGlyphs[index] || c == mUpperGlyphs[index];
117 }
118 
119 // FindCanvas methods
120 ////////////////////////////////////////////////////////////////////////////////
121 
FindCanvas(int width,int height,const UChar * lower,const UChar * upper,size_t byteLength)122 FindCanvas::FindCanvas(int width, int height, const UChar* lower,
123         const UChar* upper, size_t byteLength)
124         : mLowerText(lower)
125         , mUpperText(upper)
126         , mLength(byteLength)
127         , mNumFound(0) {
128 
129     setBounder(&mBounder);
130     mOutset = -SkIntToScalar(INTEGER_OUTSET);
131     mMatches = new WTF::Vector<MatchInfo>();
132     mWorkingIndex = 0;
133     mWorkingCanvas = 0;
134     mWorkingPicture = 0;
135 }
136 
~FindCanvas()137 FindCanvas::~FindCanvas() {
138     setBounder(NULL);
139     /* Just in case getAndClear was not called. */
140     delete mMatches;
141     mWorkingPicture->safeUnref();
142 }
143 
144 // Each version of addMatch returns a rectangle for a match.
145 // Not all of the parameters are used by each version.
addMatchNormal(int index,const SkPaint & paint,int count,const uint16_t * glyphs,const SkScalar pos[],SkScalar y)146 SkRect FindCanvas::addMatchNormal(int index,
147         const SkPaint& paint, int count, const uint16_t* glyphs,
148         const SkScalar pos[], SkScalar y) {
149     const uint16_t* lineStart = glyphs - index;
150     /* Use the original paint, since "text" is in glyphs */
151     SkScalar before = paint.measureText(lineStart, index * sizeof(uint16_t), 0);
152     SkRect rect;
153     rect.fLeft = pos[0] + before;
154     int countInBytes = count * sizeof(uint16_t);
155     rect.fRight = paint.measureText(glyphs, countInBytes, 0) + rect.fLeft;
156     SkPaint::FontMetrics fontMetrics;
157     paint.getFontMetrics(&fontMetrics);
158     SkScalar baseline = y;
159     rect.fTop = baseline + fontMetrics.fAscent;
160     rect.fBottom = baseline + fontMetrics.fDescent;
161     const SkMatrix& matrix = getTotalMatrix();
162     matrix.mapRect(&rect);
163     // Add the text to our picture.
164     SkCanvas* canvas = getWorkingCanvas();
165     int saveCount = canvas->save();
166     canvas->concat(matrix);
167     canvas->drawText(glyphs, countInBytes, pos[0] + before, y, paint);
168     canvas->restoreToCount(saveCount);
169     return rect;
170 }
171 
addMatchPos(int index,const SkPaint & paint,int count,const uint16_t * glyphs,const SkScalar xPos[],SkScalar)172 SkRect FindCanvas::addMatchPos(int index,
173         const SkPaint& paint, int count, const uint16_t* glyphs,
174         const SkScalar xPos[], SkScalar /* y */) {
175     SkRect r;
176     r.setEmpty();
177     const SkPoint* temp = reinterpret_cast<const SkPoint*> (xPos);
178     const SkPoint* points = &temp[index];
179     int countInBytes = count * sizeof(uint16_t);
180     SkPaint::FontMetrics fontMetrics;
181     paint.getFontMetrics(&fontMetrics);
182     // Need to check each character individually, since the heights may be
183     // different.
184     for (int j = 0; j < count; j++) {
185         SkRect bounds;
186         bounds.fLeft = points[j].fX;
187         bounds.fRight = bounds.fLeft +
188                 paint.measureText(&glyphs[j], sizeof(uint16_t), 0);
189         SkScalar baseline = points[j].fY;
190         bounds.fTop = baseline + fontMetrics.fAscent;
191         bounds.fBottom = baseline + fontMetrics.fDescent;
192         /* Accumulate and then add the resulting rect to mMatches */
193         r.join(bounds);
194     }
195     SkMatrix matrix = getTotalMatrix();
196     matrix.mapRect(&r);
197     SkCanvas* canvas = getWorkingCanvas();
198     int saveCount = canvas->save();
199     canvas->concat(matrix);
200     canvas->drawPosText(glyphs, countInBytes, points, paint);
201     canvas->restoreToCount(saveCount);
202     return r;
203 }
204 
addMatchPosH(int index,const SkPaint & paint,int count,const uint16_t * glyphs,const SkScalar position[],SkScalar constY)205 SkRect FindCanvas::addMatchPosH(int index,
206         const SkPaint& paint, int count, const uint16_t* glyphs,
207         const SkScalar position[], SkScalar constY) {
208     SkRect r;
209     // We only care about the positions starting at the index of our match
210     const SkScalar* xPos = &position[index];
211     // This assumes that the position array is monotonic increasing
212     // The left bounds will be the position of the left most character
213     r.fLeft = xPos[0];
214     // The right bounds will be the position of the last character plus its
215     // width
216     int lastIndex = count - 1;
217     r.fRight = paint.measureText(&glyphs[lastIndex], sizeof(uint16_t), 0)
218             + xPos[lastIndex];
219     // Grab font metrics to determine the top and bottom of the bounds
220     SkPaint::FontMetrics fontMetrics;
221     paint.getFontMetrics(&fontMetrics);
222     r.fTop = constY + fontMetrics.fAscent;
223     r.fBottom = constY + fontMetrics.fDescent;
224     const SkMatrix& matrix = getTotalMatrix();
225     matrix.mapRect(&r);
226     SkCanvas* canvas = getWorkingCanvas();
227     int saveCount = canvas->save();
228     canvas->concat(matrix);
229     canvas->drawPosTextH(glyphs, count * sizeof(uint16_t), xPos, constY, paint);
230     canvas->restoreToCount(saveCount);
231     return r;
232 }
233 
drawLayers(LayerAndroid * layer)234 void FindCanvas::drawLayers(LayerAndroid* layer) {
235 #if USE(ACCELERATED_COMPOSITING)
236     SkPicture* picture = layer->picture();
237     if (picture) {
238         setLayerId(layer->uniqueId());
239         drawPicture(*picture);
240     }
241     for (int i = 0; i < layer->countChildren(); i++)
242         drawLayers(layer->getChild(i));
243 #endif
244 }
245 
drawText(const void * text,size_t byteLength,SkScalar x,SkScalar y,const SkPaint & paint)246 void FindCanvas::drawText(const void* text, size_t byteLength, SkScalar x,
247                           SkScalar y, const SkPaint& paint) {
248     findHelper(text, byteLength, paint, &x, y, &FindCanvas::addMatchNormal);
249 }
250 
drawPosText(const void * text,size_t byteLength,const SkPoint pos[],const SkPaint & paint)251 void FindCanvas::drawPosText(const void* text, size_t byteLength,
252                              const SkPoint pos[], const SkPaint& paint) {
253     // Pass in the first y coordinate for y so that we can check to see whether
254     // it is lower than the last draw call (to check if we are continuing to
255     // another line).
256     findHelper(text, byteLength, paint, (const SkScalar*) pos, pos[0].fY,
257             &FindCanvas::addMatchPos);
258 }
259 
drawPosTextH(const void * text,size_t byteLength,const SkScalar xpos[],SkScalar constY,const SkPaint & paint)260 void FindCanvas::drawPosTextH(const void* text, size_t byteLength,
261                               const SkScalar xpos[], SkScalar constY,
262                               const SkPaint& paint) {
263     findHelper(text, byteLength, paint, xpos, constY,
264             &FindCanvas::addMatchPosH);
265 }
266 
267 /* The current behavior is to skip substring matches. This means that in the
268  * string
269  *      batbatbat
270  * a search for
271  *      batbat
272  * will return 1 match.  If the desired behavior is to return 2 matches, define
273  * INCLUDE_SUBSTRING_MATCHES to be 1.
274  */
275 #define INCLUDE_SUBSTRING_MATCHES 0
276 
277 // Need a quick way to know a maximum distance between drawText calls to know if
278 // they are part of the same logical phrase when searching.  By crude
279 // inspection, half the point size seems a good guess at the width of a space
280 // character.
approximateSpaceWidth(const SkPaint & paint)281 static inline SkScalar approximateSpaceWidth(const SkPaint& paint) {
282     return SkScalarHalf(paint.getTextSize());
283 }
284 
findHelper(const void * text,size_t byteLength,const SkPaint & paint,const SkScalar positions[],SkScalar y,SkRect (FindCanvas::* addMatch)(int index,const SkPaint & paint,int count,const uint16_t * glyphs,const SkScalar positions[],SkScalar y))285 void FindCanvas::findHelper(const void* text, size_t byteLength,
286                           const SkPaint& paint, const SkScalar positions[],
287                           SkScalar y,
288                           SkRect (FindCanvas::*addMatch)(int index,
289                           const SkPaint& paint, int count,
290                           const uint16_t* glyphs,
291                           const SkScalar positions[], SkScalar y)) {
292     SkASSERT(paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
293     SkASSERT(mMatches);
294     GlyphSet* glyphSet = getGlyphs(paint);
295     const int count = glyphSet->getCount();
296     int numCharacters = byteLength >> 1;
297     const uint16_t* chars = (const uint16_t*) text;
298     // This block will check to see if we are continuing from another line.  If
299     // so, the user needs to have added a space, which we do not draw.
300     if (mWorkingIndex) {
301         SkPoint newY;
302         getTotalMatrix().mapXY(0, y, &newY);
303         SkIRect workingBounds = mWorkingRegion.getBounds();
304         int newYInt = SkScalarRound(newY.fY);
305         if (workingBounds.fTop > newYInt) {
306             // The new text is above the working region, so we know it's not
307             // a continuation.
308             resetWorkingCanvas();
309             mWorkingIndex = 0;
310             mWorkingRegion.setEmpty();
311         } else if (workingBounds.fBottom < newYInt) {
312             // Now we know that this line is lower than our partial match.
313             SkPaint clonePaint(paint);
314             clonePaint.setTextEncoding(SkPaint::kUTF8_TextEncoding);
315             uint16_t space;
316             clonePaint.textToGlyphs(" ", 1, &space);
317             if (glyphSet->characterMatches(space, mWorkingIndex)) {
318                 mWorkingIndex++;
319                 if (mWorkingIndex == count) {
320                     // We already know that it is not clipped out because we
321                     // checked for that before saving the working region.
322                     insertMatchInfo(mWorkingRegion);
323 
324                     resetWorkingCanvas();
325                     mWorkingIndex = 0;
326                     mWorkingRegion.setEmpty();
327                     // We have found a match, so continue on this line from
328                     // scratch.
329                 }
330             } else {
331                 resetWorkingCanvas();
332                 mWorkingIndex = 0;
333                 mWorkingRegion.setEmpty();
334             }
335         }
336         // If neither one is true, then we are likely continuing on the same
337         // line, but are in a new draw call because the paint has changed.  In
338         // this case, we can continue without adding a space.
339     }
340     // j is the position in the search text
341     // Start off with mWorkingIndex in case we are continuing from a prior call
342     int j = mWorkingIndex;
343     // index is the position in the drawn text
344     int index = 0;
345     for ( ; index != numCharacters; index++) {
346         if (glyphSet->characterMatches(chars[index], j)) {
347             // The jth character in the search text matches the indexth position
348             // in the drawn text, so increase j.
349             j++;
350             if (j != count) {
351                 continue;
352             }
353             // The last count characters match, so we found the entire
354             // search string.
355             int remaining = count - mWorkingIndex;
356             int matchIndex = index - remaining + 1;
357             // Set up a pointer to the matching text in 'chars'.
358             const uint16_t* glyphs = chars + matchIndex;
359             SkRect rect = (this->*addMatch)(matchIndex, paint,
360                     remaining, glyphs, positions, y);
361             // We need an SkIRect for SkRegion operations.
362             SkIRect iRect;
363             rect.roundOut(&iRect);
364             // If the rectangle is partially clipped, assume that the text is
365             // not visible, so skip this match.
366             if (getTotalClip().contains(iRect)) {
367                 // Want to outset the drawn rectangle by the same amount as
368                 // mOutset
369                 iRect.inset(-INTEGER_OUTSET, -INTEGER_OUTSET);
370                 SkRegion regionToAdd(iRect);
371                 if (!mWorkingRegion.isEmpty()) {
372                     // If this is on the same line as our working region, make
373                     // sure that they are close enough together that they are
374                     // supposed to be part of the same text string.
375                     // The width of two spaces has arbitrarily been chosen.
376                     const SkIRect& workingBounds = mWorkingRegion.getBounds();
377                     if (workingBounds.fTop <= iRect.fBottom &&
378                             workingBounds.fBottom >= iRect.fTop &&
379                             SkIntToScalar(iRect.fLeft - workingBounds.fRight) >
380                             approximateSpaceWidth(paint)) {
381                         index = -1;     // Will increase to 0 on next run
382                         // In this case, we need to start from the beginning of
383                         // the text being searched and our search term.
384                         j = 0;
385                         mWorkingIndex = 0;
386                         mWorkingRegion.setEmpty();
387                         continue;
388                     }
389                     // Add the mWorkingRegion, which contains rectangles from
390                     // the previous line(s).
391                     regionToAdd.op(mWorkingRegion, SkRegion::kUnion_Op);
392                 }
393                 insertMatchInfo(regionToAdd);
394 #if INCLUDE_SUBSTRING_MATCHES
395                 // Reset index to the location of the match and reset j to the
396                 // beginning, so that on the next iteration of the loop, index
397                 // will advance by 1 and we will compare the next character in
398                 // chars to the first character in the GlyphSet.
399                 index = matchIndex;
400 #endif
401             } else {
402                 // This match was clipped out, so begin looking at the next
403                 // character from our hidden match
404                 index = matchIndex;
405             }
406             // Whether the clip contained it or not, we need to start over
407             // with our recording canvas
408             resetWorkingCanvas();
409         } else {
410             // Index needs to be set to index - j + 1.
411             // This is a ridiculous case, but imagine the situation where the
412             // user is looking for the string "jjog" in the drawText call for
413             // "jjjog".  The first two letters match.  However, when the index
414             // is 2, and we discover that 'o' and 'j' do not match, we should go
415             // back to 1, where we do, in fact, have a match
416             // FIXME: This does not work if (as in our example) "jj" is in one
417             // draw call and "jog" is in the next.  Doing so would require a
418             // stack, keeping track of multiple possible working indeces and
419             // regions.  This is likely an uncommon case.
420             index = index - j;  // index will be increased by one on the next
421                                 // iteration
422         }
423         // We reach here in one of two cases:
424         // 1) We just completed a match, so any working rectangle/index is no
425         // longer needed, and we will start over from the beginning
426         // 2) The glyphs do not match, so we start over at the beginning of
427         // the search string.
428         j = 0;
429         mWorkingIndex = 0;
430         mWorkingRegion.setEmpty();
431     }
432     // At this point, we have searched all of the text in the current drawText
433     // call.
434     // Keep track of a partial match that may start on this line.
435     if (j > 0) {    // if j is greater than 0, we have a partial match
436         int relativeCount = j - mWorkingIndex;  // Number of characters in this
437                                                 // part of the match.
438         int partialIndex = index - relativeCount; // Index that starts our
439                                                   // partial match.
440         const uint16_t* partialGlyphs = chars + partialIndex;
441         SkRect partial = (this->*addMatch)(partialIndex, paint, relativeCount,
442                 partialGlyphs, positions, y);
443         partial.inset(mOutset, mOutset);
444         SkIRect dest;
445         partial.roundOut(&dest);
446         // Only save a partial if it is in the current clip.
447         if (getTotalClip().contains(dest)) {
448             mWorkingRegion.op(dest, SkRegion::kUnion_Op);
449             mWorkingIndex = j;
450             return;
451         }
452     }
453     // This string doesn't go into the next drawText, so reset our working
454     // variables
455     mWorkingRegion.setEmpty();
456     mWorkingIndex = 0;
457 }
458 
getWorkingCanvas()459 SkCanvas* FindCanvas::getWorkingCanvas() {
460     if (!mWorkingPicture) {
461         mWorkingPicture = new SkPicture;
462         mWorkingCanvas = mWorkingPicture->beginRecording(0,0);
463     }
464     return mWorkingCanvas;
465 }
466 
getGlyphs(const SkPaint & paint)467 GlyphSet* FindCanvas::getGlyphs(const SkPaint& paint) {
468     SkTypeface* typeface = paint.getTypeface();
469     GlyphSet* end = mGlyphSets.end();
470     for (GlyphSet* ptr = mGlyphSets.begin();ptr != end; ptr++) {
471         if (ptr->getTypeface() == typeface) {
472             return ptr;
473         }
474     }
475 
476     GlyphSet set(paint, mLowerText, mUpperText, mLength);
477     *mGlyphSets.append() = set;
478     return &(mGlyphSets.top());
479 }
480 
insertMatchInfo(const SkRegion & region)481 void FindCanvas::insertMatchInfo(const SkRegion& region) {
482     mNumFound++;
483     mWorkingPicture->endRecording();
484     MatchInfo matchInfo;
485     mMatches->append(matchInfo);
486     LOGD("%s region=%p pict=%p layer=%d", __FUNCTION__,
487         &region, mWorkingPicture, mLayerId);
488     mMatches->last().set(region, mWorkingPicture, mLayerId);
489 }
490 
resetWorkingCanvas()491 void FindCanvas::resetWorkingCanvas() {
492     mWorkingPicture->unref();
493     mWorkingPicture = 0;
494     // Do not need to reset mWorkingCanvas itself because we only access it via
495     // getWorkingCanvas.
496 }
497 
498 // This function sets up the paints that are used to draw the matches.
setUpFindPaint()499 void FindOnPage::setUpFindPaint() {
500     // Set up the foreground paint
501     m_findPaint.setAntiAlias(true);
502     const SkScalar roundiness = SkIntToScalar(2);
503     SkCornerPathEffect* cornerEffect = new SkCornerPathEffect(roundiness);
504     m_findPaint.setPathEffect(cornerEffect);
505     m_findPaint.setARGB(255, 132, 190, 0);
506 
507     // Set up the background blur paint.
508     m_findBlurPaint.setAntiAlias(true);
509     m_findBlurPaint.setARGB(204, 0, 0, 0);
510     m_findBlurPaint.setPathEffect(cornerEffect);
511     cornerEffect->unref();
512     SkMaskFilter* blurFilter = SkBlurMaskFilter::Create(SK_Scalar1,
513             SkBlurMaskFilter::kNormal_BlurStyle);
514     m_findBlurPaint.setMaskFilter(blurFilter)->unref();
515     m_isFindPaintSetUp = true;
516 }
517 
currentMatchBounds() const518 IntRect FindOnPage::currentMatchBounds() const {
519     IntRect noBounds = IntRect(0, 0, 0, 0);
520     if (!m_matches || !m_matches->size())
521         return noBounds;
522     MatchInfo& info = (*m_matches)[m_findIndex];
523     // FIXME: this should test if the match in the layer is visible
524     if (info.isInLayer())
525         return noBounds;
526     return info.getLocation().getBounds();
527 }
528 
currentMatchIsInLayer() const529 bool FindOnPage::currentMatchIsInLayer() const {
530     if (!m_matches || !m_matches->size())
531         return false;
532     MatchInfo& info = (*m_matches)[m_findIndex];
533     return info.isInLayer();
534 }
535 
536 // This function is only used by findNext and setMatches.  In it, we store
537 // upper left corner of the match specified by m_findIndex in
538 // m_currentMatchLocation.
storeCurrentMatchLocation()539 void FindOnPage::storeCurrentMatchLocation() {
540     SkASSERT(m_findIndex < m_matches->size());
541     const SkIRect& bounds = (*m_matches)[m_findIndex].getLocation().getBounds();
542     m_currentMatchLocation.set(bounds.fLeft, bounds.fTop);
543     m_hasCurrentLocation = true;
544 }
545 
546 // Put a cap on the number of matches to draw.  If the current page has more
547 // matches than this, only draw the focused match.
548 #define MAX_NUMBER_OF_MATCHES_TO_DRAW 101
549 
draw(SkCanvas * canvas,LayerAndroid * layer)550 void FindOnPage::draw(SkCanvas* canvas, LayerAndroid* layer) {
551     if (!m_hasCurrentLocation || !m_matches || !m_matches->size())
552         return;
553     int layerId = layer->uniqueId();
554     if (m_findIndex >= m_matches->size())
555         m_findIndex = 0;
556     const MatchInfo& matchInfo = (*m_matches)[m_findIndex];
557     const SkRegion& currentMatchRegion = matchInfo.getLocation();
558 
559     // Set up the paints used for drawing the matches
560     if (!m_isFindPaintSetUp)
561         setUpFindPaint();
562 
563     // Draw the current match
564     if (matchInfo.layerId() == layerId) {
565         drawMatch(currentMatchRegion, canvas, true);
566         // Now draw the picture, so that it shows up on top of the rectangle
567         int saveCount = canvas->save();
568         SkPath matchPath;
569         currentMatchRegion.getBoundaryPath(&matchPath);
570         canvas->clipPath(matchPath);
571         canvas->drawPicture(*matchInfo.getPicture());
572         canvas->restoreToCount(saveCount);
573     }
574     // Draw the rest
575     unsigned numberOfMatches = m_matches->size();
576     if (numberOfMatches > 1
577             && numberOfMatches < MAX_NUMBER_OF_MATCHES_TO_DRAW) {
578         for(unsigned i = 0; i < numberOfMatches; i++) {
579             // The current match has already been drawn
580             if (i == m_findIndex)
581                 continue;
582             if ((*m_matches)[i].layerId() != layerId)
583                 continue;
584             const SkRegion& region = (*m_matches)[i].getLocation();
585             // Do not draw matches which intersect the current one, or if it is
586             // offscreen
587             if (currentMatchRegion.intersects(region))
588                 continue;
589             SkRect bounds;
590             bounds.set(region.getBounds());
591             if (canvas->quickReject(bounds, SkCanvas::kAA_EdgeType))
592                 continue;
593             drawMatch(region, canvas, false);
594         }
595     }
596 }
597 
598 // Draw the match specified by region to the canvas.
drawMatch(const SkRegion & region,SkCanvas * canvas,bool focused)599 void FindOnPage::drawMatch(const SkRegion& region, SkCanvas* canvas,
600         bool focused)
601 {
602     // For the match which has focus, use a filled paint.  For the others, use
603     // a stroked paint.
604     if (focused) {
605         m_findPaint.setStyle(SkPaint::kFill_Style);
606         m_findBlurPaint.setStyle(SkPaint::kFill_Style);
607     } else {
608         m_findPaint.setStyle(SkPaint::kStroke_Style);
609         m_findPaint.setStrokeWidth(SK_Scalar1);
610         m_findBlurPaint.setStyle(SkPaint::kStroke_Style);
611         m_findBlurPaint.setStrokeWidth(SkIntToScalar(2));
612     }
613     // Find the path for the current match
614     SkPath matchPath;
615     region.getBoundaryPath(&matchPath);
616     // Offset the path for a blurred shadow
617     SkPath blurPath;
618     matchPath.offset(SK_Scalar1, SkIntToScalar(2), &blurPath);
619     int saveCount = 0;
620     if (!focused) {
621         saveCount = canvas->save();
622         canvas->clipPath(matchPath, SkRegion::kDifference_Op);
623     }
624     // Draw the blurred background
625     canvas->drawPath(blurPath, m_findBlurPaint);
626     if (!focused)
627         canvas->restoreToCount(saveCount);
628     // Draw the foreground
629     canvas->drawPath(matchPath, m_findPaint);
630 }
631 
findNext(bool forward)632 void FindOnPage::findNext(bool forward)
633 {
634     if (!m_matches || !m_matches->size() || !m_hasCurrentLocation)
635         return;
636     if (forward) {
637         m_findIndex++;
638         if (m_findIndex == m_matches->size())
639             m_findIndex = 0;
640     } else {
641         if (m_findIndex == 0) {
642             m_findIndex = m_matches->size() - 1;
643         } else {
644             m_findIndex--;
645         }
646     }
647     storeCurrentMatchLocation();
648 }
649 
650 // With this call, WebView takes ownership of matches, and is responsible for
651 // deleting it.
setMatches(WTF::Vector<MatchInfo> * matches)652 void FindOnPage::setMatches(WTF::Vector<MatchInfo>* matches)
653 {
654     if (m_matches)
655         delete m_matches;
656     m_matches = matches;
657     if (m_matches->size()) {
658         if (m_hasCurrentLocation) {
659             for (unsigned i = 0; i < m_matches->size(); i++) {
660                 const SkIRect& rect = (*m_matches)[i].getLocation().getBounds();
661                 if (rect.fLeft == m_currentMatchLocation.fX
662                         && rect.fTop == m_currentMatchLocation.fY) {
663                     m_findIndex = i;
664                     return;
665                 }
666             }
667         }
668         // If we did not have a stored location, or if we were unable to restore
669         // it, store the new one.
670         m_findIndex = 0;
671         storeCurrentMatchLocation();
672     } else {
673         m_hasCurrentLocation = false;
674     }
675 }
676 
677 }
678 
679