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