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 47 MatchInfo::MatchInfo() { 48 m_picture = 0; 49 } 50 51 MatchInfo::~MatchInfo() { 52 SkSafeUnref(m_picture); 53 } 54 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 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 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 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 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 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 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 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. 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 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 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 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 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 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 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. 304 static inline SkScalar approximateSpaceWidth(const SkPaint& paint) { 305 return SkScalarHalf(paint.getTextSize()); 306 } 307 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 471 SkCanvas* FindCanvas::getWorkingCanvas() { 472 if (!mWorkingPicture) { 473 mWorkingPicture = new SkPicture; 474 mWorkingCanvas = mWorkingPicture->beginRecording(0,0); 475 } 476 return mWorkingCanvas; 477 } 478 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 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 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. 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 530 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 538 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 545 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. 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 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. 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 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. 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