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 ®ion, 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