GlyphSet::GlyphSet(const SkPaint& paint, const UChar* lower, const UChar* upper, size_t byteLength) { SkPaint clonePaint(paint); clonePaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); mTypeface = paint.getTypeface(); mCount = clonePaint.textToGlyphs(lower, byteLength, NULL); if (mCount > MAX_STORAGE_COUNT) { mLowerGlyphs = new uint16_t[2*mCount]; } else { mLowerGlyphs = &mStorage[0]; } // Use one array, and have mUpperGlyphs point to a portion of it, // so that we can reduce the number of new/deletes mUpperGlyphs = mLowerGlyphs + mCount; int count2 = clonePaint.textToGlyphs(lower, byteLength, mLowerGlyphs); SkASSERT(mCount == count2); count2 = clonePaint.textToGlyphs(upper, byteLength, mUpperGlyphs); SkASSERT(mCount == count2); }
void FindCanvas::drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, const SkPaint& paint) { SkPaint::TextEncoding encoding = paint.getTextEncoding(); //For complex text, transform utf16 to glyph if (encoding == SkPaint::kUTF16_TextEncoding) { int mCount = 0; uint16_t *glyphBuf = NULL; glyphBuf = new uint16_t[byteLength]; mCount = paint.textToGlyphs(text, byteLength, glyphBuf, byteLength); SkPaint clonePaint(paint); clonePaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); findHelper(glyphBuf, 2*mCount, clonePaint, &x, y, &FindCanvas::addMatchNormal); delete glyphBuf; return; } findHelper(text, byteLength, paint, &x, y, &FindCanvas::addMatchNormal); }
void FindCanvas::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)) { SkASSERT(paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding); SkASSERT(mMatches); GlyphSet* glyphSet = getGlyphs(paint); const int count = glyphSet->getCount(); int numCharacters = byteLength >> 1; const uint16_t* chars = (const uint16_t*) text; // This block will check to see if we are continuing from another line. If // so, the user needs to have added a space, which we do not draw. if (mWorkingIndex) { SkPoint newY; getTotalMatrix().mapXY(0, y, &newY); SkIRect workingBounds = mWorkingRegion.getBounds(); int newYInt = SkScalarRound(newY.fY); if (workingBounds.fTop > newYInt) { // The new text is above the working region, so we know it's not // a continuation. resetWorkingCanvas(); mWorkingIndex = 0; mWorkingRegion.setEmpty(); } else if (workingBounds.fBottom < newYInt) { // Now we know that this line is lower than our partial match. SkPaint clonePaint(paint); clonePaint.setTextEncoding(SkPaint::kUTF8_TextEncoding); uint16_t space; clonePaint.textToGlyphs(" ", 1, &space); if (glyphSet->characterMatches(space, mWorkingIndex)) { mWorkingIndex++; if (mWorkingIndex == count) { // We already know that it is not clipped out because we // checked for that before saving the working region. insertMatchInfo(mWorkingRegion); resetWorkingCanvas(); mWorkingIndex = 0; mWorkingRegion.setEmpty(); // We have found a match, so continue on this line from // scratch. } } else { resetWorkingCanvas(); mWorkingIndex = 0; mWorkingRegion.setEmpty(); } } // If neither one is true, then we are likely continuing on the same // line, but are in a new draw call because the paint has changed. In // this case, we can continue without adding a space. } // j is the position in the search text // Start off with mWorkingIndex in case we are continuing from a prior call int j = mWorkingIndex; // index is the position in the drawn text int index = 0; for ( ; index != numCharacters; index++) { if (glyphSet->characterMatches(chars[index], j)) { // The jth character in the search text matches the indexth position // in the drawn text, so increase j. j++; if (j != count) { continue; } // The last count characters match, so we found the entire // search string. int remaining = count - mWorkingIndex; int matchIndex = index - remaining + 1; // Set up a pointer to the matching text in 'chars'. const uint16_t* glyphs = chars + matchIndex; SkRect rect = (this->*addMatch)(matchIndex, paint, remaining, glyphs, positions, y); // We need an SkIRect for SkRegion operations. SkIRect iRect; rect.roundOut(&iRect); // If the rectangle is partially clipped, assume that the text is // not visible, so skip this match. if (getTotalClip().contains(iRect)) { // Want to outset the drawn rectangle by the same amount as // mOutset iRect.inset(-INTEGER_OUTSET, -INTEGER_OUTSET); SkRegion regionToAdd(iRect); if (!mWorkingRegion.isEmpty()) { // If this is on the same line as our working region, make // sure that they are close enough together that they are // supposed to be part of the same text string. // The width of two spaces has arbitrarily been chosen. const SkIRect& workingBounds = mWorkingRegion.getBounds(); if (workingBounds.fTop <= iRect.fBottom && workingBounds.fBottom >= iRect.fTop && SkIntToScalar(iRect.fLeft - workingBounds.fRight) > approximateSpaceWidth(paint)) { index = -1; // Will increase to 0 on next run // In this case, we need to start from the beginning of // the text being searched and our search term. j = 0; mWorkingIndex = 0; mWorkingRegion.setEmpty(); continue; } // Add the mWorkingRegion, which contains rectangles from // the previous line(s). regionToAdd.op(mWorkingRegion, SkRegion::kUnion_Op); } insertMatchInfo(regionToAdd); #if INCLUDE_SUBSTRING_MATCHES // Reset index to the location of the match and reset j to the // beginning, so that on the next iteration of the loop, index // will advance by 1 and we will compare the next character in // chars to the first character in the GlyphSet. index = matchIndex; #endif } else { // This match was clipped out, so begin looking at the next // character from our hidden match index = matchIndex; } // Whether the clip contained it or not, we need to start over // with our recording canvas resetWorkingCanvas(); } else { // Index needs to be set to index - j + 1. // This is a ridiculous case, but imagine the situation where the // user is looking for the string "jjog" in the drawText call for // "jjjog". The first two letters match. However, when the index // is 2, and we discover that 'o' and 'j' do not match, we should go // back to 1, where we do, in fact, have a match // FIXME: This does not work if (as in our example) "jj" is in one // draw call and "jog" is in the next. Doing so would require a // stack, keeping track of multiple possible working indeces and // regions. This is likely an uncommon case. index = index - j; // index will be increased by one on the next // iteration } // We reach here in one of two cases: // 1) We just completed a match, so any working rectangle/index is no // longer needed, and we will start over from the beginning // 2) The glyphs do not match, so we start over at the beginning of // the search string. j = 0; mWorkingIndex = 0; mWorkingRegion.setEmpty(); } // At this point, we have searched all of the text in the current drawText // call. // Keep track of a partial match that may start on this line. if (j > 0) { // if j is greater than 0, we have a partial match int relativeCount = j - mWorkingIndex; // Number of characters in this // part of the match. int partialIndex = index - relativeCount; // Index that starts our // partial match. const uint16_t* partialGlyphs = chars + partialIndex; SkRect partial = (this->*addMatch)(partialIndex, paint, relativeCount, partialGlyphs, positions, y); partial.inset(mOutset, mOutset); SkIRect dest; partial.roundOut(&dest); // Only save a partial if it is in the current clip. if (getTotalClip().contains(dest)) { mWorkingRegion.op(dest, SkRegion::kUnion_Op); mWorkingIndex = j; return; } } // This string doesn't go into the next drawText, so reset our working // variables mWorkingRegion.setEmpty(); mWorkingIndex = 0; }