• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 APPLE COMPUTER, INC. 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 #include "config.h"
27 #include "FindCanvas.h"
28 
29 #include "SkRect.h"
30 
31 // MatchInfo methods
32 ////////////////////////////////////////////////////////////////////////////////
33 
MatchInfo()34 MatchInfo::MatchInfo() {
35     m_picture = 0;
36 }
37 
~MatchInfo()38 MatchInfo::~MatchInfo() {
39     m_picture->safeUnref();
40 }
41 
MatchInfo(const MatchInfo & src)42 MatchInfo::MatchInfo(const MatchInfo& src) {
43     m_location = src.m_location;
44     m_picture = src.m_picture;
45     m_picture->safeRef();
46 }
47 
set(const SkRegion & region,SkPicture * pic)48 void MatchInfo::set(const SkRegion& region, SkPicture* pic) {
49     m_picture->safeUnref();
50     m_location = region;
51     m_picture = pic;
52     SkASSERT(pic);
53     pic->ref();
54 }
55 
56 // GlyphSet methods
57 ////////////////////////////////////////////////////////////////////////////////
58 
GlyphSet(const SkPaint & paint,const UChar * lower,const UChar * upper,size_t byteLength)59 GlyphSet::GlyphSet(const SkPaint& paint, const UChar* lower, const UChar* upper,
60             size_t byteLength) {
61     SkPaint clonePaint(paint);
62     clonePaint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
63     mTypeface = paint.getTypeface();
64     mCount = clonePaint.textToGlyphs(lower, byteLength, NULL);
65     if (mCount > MAX_STORAGE_COUNT) {
66         mLowerGlyphs = new uint16_t[2*mCount];
67     } else {
68         mLowerGlyphs = &mStorage[0];
69     }
70     // Use one array, and have mUpperGlyphs point to a portion of it,
71     // so that we can reduce the number of new/deletes
72     mUpperGlyphs = mLowerGlyphs + mCount;
73     int count2 = clonePaint.textToGlyphs(lower, byteLength, mLowerGlyphs);
74     SkASSERT(mCount == count2);
75     count2 = clonePaint.textToGlyphs(upper, byteLength, mUpperGlyphs);
76     SkASSERT(mCount == count2);
77 }
78 
~GlyphSet()79 GlyphSet::~GlyphSet() {
80     // Do not need to delete mTypeface, which is not owned by us.
81     if (mCount > MAX_STORAGE_COUNT) {
82         delete[] mLowerGlyphs;
83     }   // Otherwise, we just used local storage space, so no need to delete
84     // Also do not need to delete mUpperGlyphs, which simply points to a
85     // part of mLowerGlyphs
86 }
87 
operator =(GlyphSet & src)88 GlyphSet::GlyphSet& GlyphSet::operator=(GlyphSet& src) {
89     mTypeface = src.mTypeface;
90     mCount = src.mCount;
91     if (mCount > MAX_STORAGE_COUNT) {
92         mLowerGlyphs = new uint16_t[2*mCount];
93     } else {
94         mLowerGlyphs = &mStorage[0];
95     }
96     memcpy(mLowerGlyphs, src.mLowerGlyphs, 2*mCount*sizeof(uint16_t));
97     mUpperGlyphs = mLowerGlyphs + mCount;
98     return *this;
99 }
100 
characterMatches(uint16_t c,int index)101 bool GlyphSet::characterMatches(uint16_t c, int index) {
102     SkASSERT(index < mCount && index >= 0);
103     return c == mLowerGlyphs[index] || c == mUpperGlyphs[index];
104 }
105 
106 // FindCanvas methods
107 ////////////////////////////////////////////////////////////////////////////////
108 
FindCanvas(int width,int height,const UChar * lower,const UChar * upper,size_t byteLength)109 FindCanvas::FindCanvas(int width, int height, const UChar* lower,
110         const UChar* upper, size_t byteLength)
111         : mLowerText(lower)
112         , mUpperText(upper)
113         , mLength(byteLength)
114         , mNumFound(0) {
115 
116     setBounder(&mBounder);
117     mOutset = -SkIntToScalar(2);
118     mMatches = new WTF::Vector<MatchInfo>();
119     mWorkingIndex = 0;
120     mWorkingCanvas = 0;
121     mWorkingPicture = 0;
122 }
123 
~FindCanvas()124 FindCanvas::~FindCanvas() {
125     setBounder(NULL);
126     /* Just in case getAndClear was not called. */
127     delete mMatches;
128     mWorkingPicture->safeUnref();
129 }
130 
131 // Each version of addMatch returns a rectangle for a match.
132 // 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)133 SkRect FindCanvas::addMatchNormal(int index,
134         const SkPaint& paint, int count, const uint16_t* glyphs,
135         const SkScalar pos[], SkScalar y) {
136     const uint16_t* lineStart = glyphs - index;
137     /* Use the original paint, since "text" is in glyphs */
138     SkScalar before = paint.measureText(lineStart, index * sizeof(uint16_t), 0);
139     SkRect rect;
140     rect.fLeft = pos[0] + before;
141     int countInBytes = count * sizeof(uint16_t);
142     rect.fRight = paint.measureText(glyphs, countInBytes, 0) + rect.fLeft;
143     SkPaint::FontMetrics fontMetrics;
144     paint.getFontMetrics(&fontMetrics);
145     SkScalar baseline = y;
146     rect.fTop = baseline + fontMetrics.fAscent;
147     rect.fBottom = baseline + fontMetrics.fDescent;
148     const SkMatrix& matrix = getTotalMatrix();
149     matrix.mapRect(&rect);
150     // Add the text to our picture.
151     SkCanvas* canvas = getWorkingCanvas();
152     int saveCount = canvas->save();
153     canvas->concat(matrix);
154     canvas->drawText(glyphs, countInBytes, pos[0] + before, y, paint);
155     canvas->restoreToCount(saveCount);
156     return rect;
157 }
158 
addMatchPos(int index,const SkPaint & paint,int count,const uint16_t * glyphs,const SkScalar xPos[],SkScalar)159 SkRect FindCanvas::addMatchPos(int index,
160         const SkPaint& paint, int count, const uint16_t* glyphs,
161         const SkScalar xPos[], SkScalar /* y */) {
162     SkRect r;
163     r.setEmpty();
164     const SkPoint* temp = reinterpret_cast<const SkPoint*> (xPos);
165     const SkPoint* points = &temp[index];
166     int countInBytes = count * sizeof(uint16_t);
167     SkPaint::FontMetrics fontMetrics;
168     paint.getFontMetrics(&fontMetrics);
169     // Need to check each character individually, since the heights may be
170     // different.
171     for (int j = 0; j < count; j++) {
172         SkRect bounds;
173         bounds.fLeft = points[j].fX;
174         bounds.fRight = bounds.fLeft +
175                 paint.measureText(&glyphs[j], sizeof(uint16_t), 0);
176         SkScalar baseline = points[j].fY;
177         bounds.fTop = baseline + fontMetrics.fAscent;
178         bounds.fBottom = baseline + fontMetrics.fDescent;
179         /* Accumulate and then add the resulting rect to mMatches */
180         r.join(bounds);
181     }
182     SkMatrix matrix = getTotalMatrix();
183     matrix.mapRect(&r);
184     SkCanvas* canvas = getWorkingCanvas();
185     int saveCount = canvas->save();
186     canvas->concat(matrix);
187     canvas->drawPosText(glyphs, countInBytes, points, paint);
188     canvas->restoreToCount(saveCount);
189     return r;
190 }
191 
addMatchPosH(int index,const SkPaint & paint,int count,const uint16_t * glyphs,const SkScalar position[],SkScalar constY)192 SkRect FindCanvas::addMatchPosH(int index,
193         const SkPaint& paint, int count, const uint16_t* glyphs,
194         const SkScalar position[], SkScalar constY) {
195     SkRect r;
196     // We only care about the positions starting at the index of our match
197     const SkScalar* xPos = &position[index];
198     // This assumes that the position array is monotonic increasing
199     // The left bounds will be the position of the left most character
200     r.fLeft = xPos[0];
201     // The right bounds will be the position of the last character plus its
202     // width
203     int lastIndex = count - 1;
204     r.fRight = paint.measureText(&glyphs[lastIndex], sizeof(uint16_t), 0)
205             + xPos[lastIndex];
206     // Grab font metrics to determine the top and bottom of the bounds
207     SkPaint::FontMetrics fontMetrics;
208     paint.getFontMetrics(&fontMetrics);
209     r.fTop = constY + fontMetrics.fAscent;
210     r.fBottom = constY + fontMetrics.fDescent;
211     const SkMatrix& matrix = getTotalMatrix();
212     matrix.mapRect(&r);
213     SkCanvas* canvas = getWorkingCanvas();
214     int saveCount = canvas->save();
215     canvas->concat(matrix);
216     canvas->drawPosTextH(glyphs, count * sizeof(uint16_t), xPos, constY, paint);
217     canvas->restoreToCount(saveCount);
218     return r;
219 }
220 
drawText(const void * text,size_t byteLength,SkScalar x,SkScalar y,const SkPaint & paint)221 void FindCanvas::drawText(const void* text, size_t byteLength, SkScalar x,
222                           SkScalar y, const SkPaint& paint) {
223     findHelper(text, byteLength, paint, &x, y, &FindCanvas::addMatchNormal);
224 }
225 
drawPosText(const void * text,size_t byteLength,const SkPoint pos[],const SkPaint & paint)226 void FindCanvas::drawPosText(const void* text, size_t byteLength,
227                              const SkPoint pos[], const SkPaint& paint) {
228     // Pass in the first y coordinate for y so that we can check to see whether
229     // it is lower than the last draw call (to check if we are continuing to
230     // another line).
231     findHelper(text, byteLength, paint, (const SkScalar*) pos, pos[0].fY,
232             &FindCanvas::addMatchPos);
233 }
234 
drawPosTextH(const void * text,size_t byteLength,const SkScalar xpos[],SkScalar constY,const SkPaint & paint)235 void FindCanvas::drawPosTextH(const void* text, size_t byteLength,
236                               const SkScalar xpos[], SkScalar constY,
237                               const SkPaint& paint) {
238     findHelper(text, byteLength, paint, xpos, constY,
239             &FindCanvas::addMatchPosH);
240 }
241 
242 /* The current behavior is to skip substring matches. This means that in the
243  * string
244  *      batbatbat
245  * a search for
246  *      batbat
247  * will return 1 match.  If the desired behavior is to return 2 matches, define
248  * INCLUDE_SUBSTRING_MATCHES to be 1.
249  */
250 #define INCLUDE_SUBSTRING_MATCHES 0
251 
252 // Need a quick way to know a maximum distance between drawText calls to know if
253 // they are part of the same logical phrase when searching.  By crude
254 // inspection, half the point size seems a good guess at the width of a space
255 // character.
approximateSpaceWidth(const SkPaint & paint)256 static inline SkScalar approximateSpaceWidth(const SkPaint& paint) {
257     return SkScalarHalf(paint.getTextSize());
258 }
259 
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))260 void FindCanvas::findHelper(const void* text, size_t byteLength,
261                           const SkPaint& paint, const SkScalar positions[],
262                           SkScalar y,
263                           SkRect (FindCanvas::*addMatch)(int index,
264                           const SkPaint& paint, int count,
265                           const uint16_t* glyphs,
266                           const SkScalar positions[], SkScalar y)) {
267     SkASSERT(paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
268     SkASSERT(mMatches);
269     GlyphSet* glyphSet = getGlyphs(paint);
270     const int count = glyphSet->getCount();
271     int numCharacters = byteLength >> 1;
272     const uint16_t* chars = (const uint16_t*) text;
273     // This block will check to see if we are continuing from another line.  If
274     // so, the user needs to have added a space, which we do not draw.
275     if (mWorkingIndex) {
276         SkPoint newY;
277         getTotalMatrix().mapXY(0, y, &newY);
278         SkIRect workingBounds = mWorkingRegion.getBounds();
279         int newYInt = SkScalarRound(newY.fY);
280         if (workingBounds.fTop > newYInt) {
281             // The new text is above the working region, so we know it's not
282             // a continuation.
283             resetWorkingCanvas();
284             mWorkingIndex = 0;
285             mWorkingRegion.setEmpty();
286         } else if (workingBounds.fBottom < newYInt) {
287             // Now we know that this line is lower than our partial match.
288             SkPaint clonePaint(paint);
289             clonePaint.setTextEncoding(SkPaint::kUTF8_TextEncoding);
290             uint16_t space;
291             clonePaint.textToGlyphs(" ", 1, &space);
292             if (glyphSet->characterMatches(space, mWorkingIndex)) {
293                 mWorkingIndex++;
294                 if (mWorkingIndex == count) {
295                     // We already know that it is not clipped out because we
296                     // checked for that before saving the working region.
297                     insertMatchInfo(mWorkingRegion);
298 
299                     resetWorkingCanvas();
300                     mWorkingIndex = 0;
301                     mWorkingRegion.setEmpty();
302                     // We have found a match, so continue on this line from
303                     // scratch.
304                 }
305             } else {
306                 resetWorkingCanvas();
307                 mWorkingIndex = 0;
308                 mWorkingRegion.setEmpty();
309             }
310         }
311         // If neither one is true, then we are likely continuing on the same
312         // line, but are in a new draw call because the paint has changed.  In
313         // this case, we can continue without adding a space.
314     }
315     // j is the position in the search text
316     // Start off with mWorkingIndex in case we are continuing from a prior call
317     int j = mWorkingIndex;
318     // index is the position in the drawn text
319     int index = 0;
320     for ( ; index != numCharacters; index++) {
321         if (glyphSet->characterMatches(chars[index], j)) {
322             // The jth character in the search text matches the indexth position
323             // in the drawn text, so increase j.
324             j++;
325             if (j != count) {
326                 continue;
327             }
328             // The last count characters match, so we found the entire
329             // search string.
330             int remaining = count - mWorkingIndex;
331             int matchIndex = index - remaining + 1;
332             // Set up a pointer to the matching text in 'chars'.
333             const uint16_t* glyphs = chars + matchIndex;
334             SkRect rect = (this->*addMatch)(matchIndex, paint,
335                     remaining, glyphs, positions, y);
336             rect.inset(mOutset, mOutset);
337             // We need an SkIRect for SkRegion operations.
338             SkIRect iRect;
339             rect.roundOut(&iRect);
340             // If the rectangle is partially clipped, assume that the text is
341             // not visible, so skip this match.
342             if (getTotalClip().contains(iRect)) {
343                 SkRegion regionToAdd(iRect);
344                 if (!mWorkingRegion.isEmpty()) {
345                     // If this is on the same line as our working region, make
346                     // sure that they are close enough together that they are
347                     // supposed to be part of the same text string.
348                     // The width of two spaces has arbitrarily been chosen.
349                     const SkIRect& workingBounds = mWorkingRegion.getBounds();
350                     if (workingBounds.fTop <= iRect.fBottom &&
351                             workingBounds.fBottom >= iRect.fTop &&
352                             SkIntToScalar(iRect.fLeft - workingBounds.fRight) >
353                             approximateSpaceWidth(paint)) {
354                         index = -1;     // Will increase to 0 on next run
355                         // In this case, we need to start from the beginning of
356                         // the text being searched and our search term.
357                         j = 0;
358                         mWorkingIndex = 0;
359                         mWorkingRegion.setEmpty();
360                         continue;
361                     }
362                     // Add the mWorkingRegion, which contains rectangles from
363                     // the previous line(s).
364                     regionToAdd.op(mWorkingRegion, SkRegion::kUnion_Op);
365                 }
366                 insertMatchInfo(regionToAdd);
367 #if INCLUDE_SUBSTRING_MATCHES
368                 // Reset index to the location of the match and reset j to the
369                 // beginning, so that on the next iteration of the loop, index
370                 // will advance by 1 and we will compare the next character in
371                 // chars to the first character in the GlyphSet.
372                 index = matchIndex;
373 #endif
374             } else {
375                 // This match was clipped out, so begin looking at the next
376                 // character from our hidden match
377                 index = matchIndex;
378             }
379             // Whether the clip contained it or not, we need to start over
380             // with our recording canvas
381             resetWorkingCanvas();
382         } else {
383             // Index needs to be set to index - j + 1.
384             // This is a ridiculous case, but imagine the situation where the
385             // user is looking for the string "jjog" in the drawText call for
386             // "jjjog".  The first two letters match.  However, when the index
387             // is 2, and we discover that 'o' and 'j' do not match, we should go
388             // back to 1, where we do, in fact, have a match
389             // FIXME: This does not work if (as in our example) "jj" is in one
390             // draw call and "jog" is in the next.  Doing so would require a
391             // stack, keeping track of multiple possible working indeces and
392             // regions.  This is likely an uncommon case.
393             index = index - j;  // index will be increased by one on the next
394                                 // iteration
395         }
396         // We reach here in one of two cases:
397         // 1) We just completed a match, so any working rectangle/index is no
398         // longer needed, and we will start over from the beginning
399         // 2) The glyphs do not match, so we start over at the beginning of
400         // the search string.
401         j = 0;
402         mWorkingIndex = 0;
403         mWorkingRegion.setEmpty();
404     }
405     // At this point, we have searched all of the text in the current drawText
406     // call.
407     // Keep track of a partial match that may start on this line.
408     if (j > 0) {    // if j is greater than 0, we have a partial match
409         int relativeCount = j - mWorkingIndex;  // Number of characters in this
410                                                 // part of the match.
411         int partialIndex = index - relativeCount; // Index that starts our
412                                                   // partial match.
413         const uint16_t* partialGlyphs = chars + partialIndex;
414         SkRect partial = (this->*addMatch)(partialIndex, paint, relativeCount,
415                 partialGlyphs, positions, y);
416         partial.inset(mOutset, mOutset);
417         SkIRect dest;
418         partial.roundOut(&dest);
419         // Only save a partial if it is in the current clip.
420         if (getTotalClip().contains(dest)) {
421             mWorkingRegion.op(dest, SkRegion::kUnion_Op);
422             mWorkingIndex = j;
423             return;
424         }
425     }
426     // This string doesn't go into the next drawText, so reset our working
427     // variables
428     mWorkingRegion.setEmpty();
429     mWorkingIndex = 0;
430 }
431 
getWorkingCanvas()432 SkCanvas* FindCanvas::getWorkingCanvas() {
433     if (!mWorkingPicture) {
434         mWorkingPicture = new SkPicture;
435         mWorkingCanvas = mWorkingPicture->beginRecording(0,0);
436     }
437     return mWorkingCanvas;
438 }
439 
getGlyphs(const SkPaint & paint)440 GlyphSet* FindCanvas::getGlyphs(const SkPaint& paint) {
441     SkTypeface* typeface = paint.getTypeface();
442     GlyphSet* end = mGlyphSets.end();
443     for (GlyphSet* ptr = mGlyphSets.begin();ptr != end; ptr++) {
444         if (ptr->getTypeface() == typeface) {
445             return ptr;
446         }
447     }
448 
449     GlyphSet set(paint, mLowerText, mUpperText, mLength);
450     *mGlyphSets.append() = set;
451     return &(mGlyphSets.top());
452 }
453 
insertMatchInfo(const SkRegion & region)454 void FindCanvas::insertMatchInfo(const SkRegion& region) {
455     mNumFound++;
456     mWorkingPicture->endRecording();
457     MatchInfo matchInfo;
458     mMatches->append(matchInfo);
459     mMatches->last().set(region, mWorkingPicture);
460 }
461 
resetWorkingCanvas()462 void FindCanvas::resetWorkingCanvas() {
463     mWorkingPicture->unref();
464     mWorkingPicture = 0;
465     // Do not need to reset mWorkingCanvas itself because we only access it via
466     // getWorkingCanvas.
467 }
468