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