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 45 MatchInfo::MatchInfo() { 46 m_picture = 0; 47 } 48 49 MatchInfo::~MatchInfo() { 50 m_picture->safeUnref(); 51 } 52 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 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 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 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 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 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 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 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. 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 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 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 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 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 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 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. 281 static inline SkScalar approximateSpaceWidth(const SkPaint& paint) { 282 return SkScalarHalf(paint.getTextSize()); 283 } 284 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 459 SkCanvas* FindCanvas::getWorkingCanvas() { 460 if (!mWorkingPicture) { 461 mWorkingPicture = new SkPicture; 462 mWorkingCanvas = mWorkingPicture->beginRecording(0,0); 463 } 464 return mWorkingCanvas; 465 } 466 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 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 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. 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 518 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 529 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. 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 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. 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 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. 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