1 /*
2 * Copyright 2007, 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 #include "CachedPrefix.h"
27 #include "android_graphics.h"
28 #include "CachedHistory.h"
29 #include "CachedInput.h"
30 #include "CachedNode.h"
31 #include "FindCanvas.h"
32 #include "FloatRect.h"
33 #include "LayerAndroid.h"
34 #include "SkBitmap.h"
35 #include "SkBounder.h"
36 #include "SkPixelRef.h"
37 #include "SkRegion.h"
38
39 #include "CachedRoot.h"
40
41 using std::min;
42 using std::max;
43
44 #ifdef DUMP_NAV_CACHE_USING_PRINTF
45 extern android::Mutex gWriteLogMutex;
46 #endif
47
48 namespace android {
49
50 class CommonCheck : public SkBounder {
51 public:
52 enum Type {
53 kNo_Type,
54 kDrawBitmap_Type,
55 kDrawGlyph_Type,
56 kDrawPaint_Type,
57 kDrawPath_Type,
58 kDrawPicture_Type,
59 kDrawPoints_Type,
60 kDrawPosText_Type,
61 kDrawPosTextH_Type,
62 kDrawRect_Type,
63 kDrawSprite_Type,
64 kDrawText_Type,
65 kDrawTextOnPath_Type
66 };
67
isTextType(Type t)68 static bool isTextType(Type t) {
69 return t == kDrawPosTextH_Type || t == kDrawText_Type;
70 }
71
CommonCheck()72 CommonCheck() : mType(kNo_Type), mAllOpaque(true), mIsOpaque(true) {
73 setEmpty();
74 }
75
doRect(Type type)76 bool doRect(Type type) {
77 mType = type;
78 return doIRect(mUnion);
79 }
80
joinGlyphs(const SkIRect & rect)81 bool joinGlyphs(const SkIRect& rect) {
82 bool isGlyph = mType == kDrawGlyph_Type;
83 if (isGlyph)
84 mUnion.join(rect);
85 return isGlyph;
86 }
87
setAllOpaque(bool opaque)88 void setAllOpaque(bool opaque) { mAllOpaque = opaque; }
setEmpty()89 void setEmpty() { mUnion.setEmpty(); }
setIsOpaque(bool opaque)90 void setIsOpaque(bool opaque) { mIsOpaque = opaque; }
setType(Type type)91 void setType(Type type) { mType = type; }
92
93 Type mType;
94 SkIRect mUnion;
95 bool mAllOpaque;
96 bool mIsOpaque;
97 };
98
99 #if DEBUG_NAV_UI
100 static const char* TypeNames[] = {
101 "kNo_Type",
102 "kDrawBitmap_Type",
103 "kDrawGlyph_Type",
104 "kDrawPaint_Type",
105 "kDrawPath_Type",
106 "kDrawPicture_Type",
107 "kDrawPoints_Type",
108 "kDrawPosText_Type",
109 "kDrawPosTextH_Type",
110 "kDrawRect_Type",
111 "kDrawSprite_Type",
112 "kDrawText_Type",
113 "kDrawTextOnPath_Type"
114 };
115 #endif
116
117 #define kMargin 16
118 #define kSlop 2
119
120 class BoundsCheck : public CommonCheck {
121 public:
BoundsCheck()122 BoundsCheck() {
123 mAllDrawnIn.setEmpty();
124 mLastAll.setEmpty();
125 mLastOver.setEmpty();
126 }
127
Area(SkIRect test)128 static int Area(SkIRect test) {
129 return test.width() * test.height();
130 }
131
checkLast()132 void checkLast() {
133 if (mAllDrawnIn.isEmpty())
134 return;
135 if (mLastAll.isEmpty() || Area(mLastAll) < Area(mAllDrawnIn)) {
136 mLastAll = mAllDrawnIn;
137 mDrawnOver.setEmpty();
138 }
139 mAllDrawnIn.setEmpty();
140 }
141
hidden()142 bool hidden() {
143 return (mLastAll.isEmpty() && mLastOver.isEmpty()) ||
144 mDrawnOver.contains(mBounds);
145 }
146
onIRect(const SkIRect & rect)147 virtual bool onIRect(const SkIRect& rect) {
148 if (joinGlyphs(rect))
149 return false;
150 bool interestingType = mType == kDrawBitmap_Type ||
151 mType == kDrawRect_Type || isTextType(mType);
152 if (SkIRect::Intersects(mBounds, rect) == false) {
153 DBG_NAV_LOGD("BoundsCheck (no intersect) rect={%d,%d,%d,%d}"
154 " mType=%s", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
155 TypeNames[mType]);
156 if (interestingType)
157 checkLast();
158 return false;
159 }
160 if (interestingType == false)
161 return false;
162 if (!mDrawnOver.contains(rect) && (mBoundsSlop.contains(rect) ||
163 (mBounds.fLeft == rect.fLeft && mBounds.fRight == rect.fRight &&
164 mBounds.fTop >= rect.fTop && mBounds.fBottom <= rect.fBottom) ||
165 (mBounds.fTop == rect.fTop && mBounds.fBottom == rect.fBottom &&
166 mBounds.fLeft >= rect.fLeft && mBounds.fRight <= rect.fRight))) {
167 mDrawnOver.setEmpty();
168 mAllDrawnIn.join(rect);
169 DBG_NAV_LOGD("BoundsCheck (contains) rect={%d,%d,%d,%d}"
170 " mAllDrawnIn={%d,%d,%d,%d} mType=%s",
171 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
172 mAllDrawnIn.fLeft, mAllDrawnIn.fTop, mAllDrawnIn.fRight,
173 mAllDrawnIn.fBottom, TypeNames[mType]);
174 } else {
175 checkLast();
176 if (!isTextType(mType)) {
177 if (
178 #if 0
179 // should the opaqueness of the bitmap disallow its ability to draw over?
180 // not sure that this test is needed
181 (mType != kDrawBitmap_Type ||
182 (mIsOpaque && mAllOpaque)) &&
183 #endif
184 mLastAll.isEmpty() == false)
185 mDrawnOver.op(rect, SkRegion::kUnion_Op);
186 } else {
187 // FIXME
188 // sometimes the text is not drawn entirely inside the cursor area, even though
189 // it is the correct text. Until I figure out why, I allow text drawn at the
190 // end that is not covered up by something else to represent the link
191 // example that triggers this that should be figured out:
192 // http://cdn.labpixies.com/campaigns/blackjack/blackjack.html?lang=en&country=US&libs=assets/feature/core
193 // ( http://tinyurl.com/ywsyzb )
194 mLastOver = rect;
195 }
196 #if DEBUG_NAV_UI
197 const SkIRect& drawnOver = mDrawnOver.getBounds();
198 DBG_NAV_LOGD("(overlaps) rect={%d,%d,%d,%d}"
199 " mDrawnOver={%d,%d,%d,%d} mType=%s mIsOpaque=%s mAllOpaque=%s",
200 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
201 drawnOver.fLeft, drawnOver.fTop, drawnOver.fRight, drawnOver.fBottom,
202 TypeNames[mType], mIsOpaque ? "true" : "false",
203 mAllOpaque ? "true" : "false");
204 #endif
205 }
206 return false;
207 }
208
209 SkIRect mBounds;
210 SkIRect mBoundsSlop;
211 SkRegion mDrawnOver;
212 SkIRect mLastOver;
213 SkIRect mAllDrawnIn;
214 SkIRect mLastAll;
215 };
216
217 class BoundsCanvas : public SkCanvas {
218 public:
219
BoundsCanvas(CommonCheck * bounder)220 BoundsCanvas(CommonCheck* bounder) : mBounder(*bounder) {
221 mTransparentLayer = 0;
222 setBounder(bounder);
223 }
224
~BoundsCanvas()225 virtual ~BoundsCanvas() {
226 setBounder(NULL);
227 }
228
drawPaint(const SkPaint & paint)229 virtual void drawPaint(const SkPaint& paint) {
230 mBounder.setType(CommonCheck::kDrawPaint_Type);
231 SkCanvas::drawPaint(paint);
232 }
233
drawPoints(PointMode mode,size_t count,const SkPoint pts[],const SkPaint & paint)234 virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[],
235 const SkPaint& paint) {
236 mBounder.setType(CommonCheck::kDrawPoints_Type);
237 SkCanvas::drawPoints(mode, count, pts, paint);
238 }
239
drawRect(const SkRect & rect,const SkPaint & paint)240 virtual void drawRect(const SkRect& rect, const SkPaint& paint) {
241 mBounder.setType(CommonCheck::kDrawRect_Type);
242 SkCanvas::drawRect(rect, paint);
243 }
244
drawPath(const SkPath & path,const SkPaint & paint)245 virtual void drawPath(const SkPath& path, const SkPaint& paint) {
246 mBounder.setType(CommonCheck::kDrawPath_Type);
247 SkCanvas::drawPath(path, paint);
248 }
249
commonDrawBitmap(const SkBitmap & bitmap,const SkMatrix & matrix,const SkPaint & paint)250 virtual void commonDrawBitmap(const SkBitmap& bitmap,
251 const SkMatrix& matrix, const SkPaint& paint) {
252 mBounder.setType(CommonCheck::kDrawBitmap_Type);
253 mBounder.setIsOpaque(bitmap.isOpaque());
254 SkCanvas::commonDrawBitmap(bitmap, matrix, paint);
255 }
256
drawSprite(const SkBitmap & bitmap,int left,int top,const SkPaint * paint=NULL)257 virtual void drawSprite(const SkBitmap& bitmap, int left, int top,
258 const SkPaint* paint = NULL) {
259 mBounder.setType(CommonCheck::kDrawSprite_Type);
260 mBounder.setIsOpaque(bitmap.isOpaque());
261 SkCanvas::drawSprite(bitmap, left, top, paint);
262 }
263
drawText(const void * text,size_t byteLength,SkScalar x,SkScalar y,const SkPaint & paint)264 virtual void drawText(const void* text, size_t byteLength, SkScalar x,
265 SkScalar y, const SkPaint& paint) {
266 mBounder.setEmpty();
267 mBounder.setType(CommonCheck::kDrawGlyph_Type);
268 SkCanvas::drawText(text, byteLength, x, y, paint);
269 mBounder.doRect(CommonCheck::kDrawText_Type);
270 }
271
drawPosText(const void * text,size_t byteLength,const SkPoint pos[],const SkPaint & paint)272 virtual void drawPosText(const void* text, size_t byteLength,
273 const SkPoint pos[], const SkPaint& paint) {
274 mBounder.setEmpty();
275 mBounder.setType(CommonCheck::kDrawGlyph_Type);
276 SkCanvas::drawPosText(text, byteLength, pos, paint);
277 mBounder.doRect(CommonCheck::kDrawPosText_Type);
278 }
279
drawPosTextH(const void * text,size_t byteLength,const SkScalar xpos[],SkScalar constY,const SkPaint & paint)280 virtual void drawPosTextH(const void* text, size_t byteLength,
281 const SkScalar xpos[], SkScalar constY,
282 const SkPaint& paint) {
283 mBounder.setEmpty();
284 mBounder.setType(CommonCheck::kDrawGlyph_Type);
285 SkCanvas::drawPosTextH(text, byteLength, xpos, constY, paint);
286 if (mBounder.mUnion.isEmpty())
287 return;
288 SkPaint::FontMetrics metrics;
289 paint.getFontMetrics(&metrics);
290 SkPoint upDown[2] = { {xpos[0], constY + metrics.fAscent},
291 {xpos[0], constY + metrics.fDescent} };
292 const SkMatrix& matrix = getTotalMatrix();
293 matrix.mapPoints(upDown, 2);
294 if (upDown[0].fX == upDown[1].fX) {
295 mBounder.mUnion.fTop = SkScalarFloor(upDown[0].fY);
296 mBounder.mUnion.fBottom = SkScalarFloor(upDown[1].fY);
297 }
298 mBounder.doRect(CommonCheck::kDrawPosTextH_Type);
299 }
300
drawTextOnPath(const void * text,size_t byteLength,const SkPath & path,const SkMatrix * matrix,const SkPaint & paint)301 virtual void drawTextOnPath(const void* text, size_t byteLength,
302 const SkPath& path, const SkMatrix* matrix,
303 const SkPaint& paint) {
304 mBounder.setEmpty();
305 mBounder.setType(CommonCheck::kDrawGlyph_Type);
306 SkCanvas::drawTextOnPath(text, byteLength, path, matrix, paint);
307 mBounder.doRect(CommonCheck::kDrawTextOnPath_Type);
308 }
309
drawPicture(SkPicture & picture)310 virtual void drawPicture(SkPicture& picture) {
311 mBounder.setType(CommonCheck::kDrawPicture_Type);
312 SkCanvas::drawPicture(picture);
313 }
314
saveLayer(const SkRect * bounds,const SkPaint * paint,SaveFlags flags)315 virtual int saveLayer(const SkRect* bounds, const SkPaint* paint,
316 SaveFlags flags) {
317 int depth = SkCanvas::saveLayer(bounds, paint, flags);
318 if (mTransparentLayer == 0 && paint && paint->getAlpha() < 255) {
319 mTransparentLayer = depth;
320 mBounder.setAllOpaque(false);
321 }
322 return depth;
323 }
324
restore()325 virtual void restore() {
326 int depth = getSaveCount();
327 if (depth == mTransparentLayer) {
328 mTransparentLayer = 0;
329 mBounder.setAllOpaque(true);
330 }
331 SkCanvas::restore();
332 }
333
334 int mTransparentLayer;
335 CommonCheck& mBounder;
336 };
337
338 /*
339 LeftCheck examines the text in a picture, within a viewable rectangle,
340 and returns via left() the position of the left edge of the paragraph.
341 It first looks at the left edge of the test point, then looks above and below
342 it for more lines of text to determine the div's left edge.
343 */
344 class LeftCheck : public CommonCheck {
345 public:
LeftCheck(int x,int y)346 LeftCheck(int x, int y) : mX(x), mY(y), mHitLeft(INT_MAX),
347 mMostLeft(INT_MAX) {
348 mHit.set(x - (HIT_SLOP << 1), y - HIT_SLOP, x, y + HIT_SLOP);
349 mPartial.setEmpty();
350 mBounds.setEmpty();
351 mPartialType = kNo_Type;
352 }
353
left()354 int left() {
355 if (isTextType(mType))
356 doRect(); // process the final line of text
357 return mMostLeft != INT_MAX ? mMostLeft : mX >> 1;
358 }
359
360 // FIXME: this is identical to CenterCheck::onIRect()
361 // refactor so that LeftCheck and CenterCheck inherit common functions
onIRect(const SkIRect & rect)362 virtual bool onIRect(const SkIRect& rect) {
363 bool opaqueBitmap = mType == kDrawBitmap_Type && mIsOpaque;
364 if (opaqueBitmap && rect.contains(mX, mY)) {
365 mMostLeft = rect.fLeft;
366 return false;
367 }
368 if (joinGlyphs(rect)) // assembles glyphs into a text string
369 return false;
370 if (!isTextType(mType) && !opaqueBitmap)
371 return false;
372 /* Text on one line may be broken into several parts. Reassemble
373 the text into a rectangle before considering it. */
374 if (rect.fTop < mPartial.fBottom
375 && rect.fBottom > mPartial.fTop
376 && mPartial.fRight + SLOP >= rect.fLeft
377 && (mPartialType != kDrawBitmap_Type
378 || mPartial.height() <= rect.height() + HIT_SLOP)) {
379 DBG_NAV_LOGD("LeftCheck join mPartial=(%d, %d, %d, %d)"
380 " rect=(%d, %d, %d, %d)",
381 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom,
382 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
383 mPartial.join(rect);
384 return false;
385 }
386 if (mPartial.isEmpty() == false) {
387 doRect(); // process the previous line of text
388 #if DEBUG_NAV_UI
389 if (mHitLeft == INT_MAX)
390 DBG_NAV_LOGD("LeftCheck disabled rect=(%d, %d, %d, %d)",
391 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
392 #endif
393 }
394 mPartial = rect;
395 mPartialType = mType;
396 return false;
397 }
398
doRect()399 void doRect()
400 {
401 /* Record the outer bounds of the lines of text that intersect the
402 touch coordinates, given some slop */
403 if (SkIRect::Intersects(mPartial, mHit)) {
404 if (mHitLeft > mPartial.fLeft)
405 mHitLeft = mPartial.fLeft;
406 DBG_NAV_LOGD("LeftCheck mHitLeft=%d", mHitLeft);
407 } else if (mHitLeft == INT_MAX)
408 return; // wait for intersect success
409 /* If text is too far away vertically, don't consider it */
410 if (!mBounds.isEmpty() && (mPartial.fTop > mBounds.fBottom + SLOP
411 || mPartial.fBottom < mBounds.fTop - SLOP)) {
412 DBG_NAV_LOGD("LeftCheck stop mPartial=(%d, %d, %d, %d)"
413 " mBounds=(%d, %d, %d, %d)",
414 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom,
415 mBounds.fLeft, mBounds.fTop, mBounds.fRight, mBounds.fBottom);
416 mHitLeft = INT_MAX; // and disable future comparisons
417 return;
418 }
419 /* If the considered text is completely to the left or right of the
420 touch coordinates, skip it, turn off further detection */
421 if (mPartial.fLeft > mX || mPartial.fRight < mX) {
422 DBG_NAV_LOGD("LeftCheck stop mX=%d mPartial=(%d, %d, %d, %d)", mX,
423 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom);
424 mHitLeft = INT_MAX;
425 return;
426 }
427 /* record the smallest margins on the left and right */
428 if (mMostLeft > mPartial.fLeft) {
429 DBG_NAV_LOGD("LeftCheck new mMostLeft=%d (old=%d)", mPartial.fLeft,
430 mMostLeft);
431 mMostLeft = mPartial.fLeft;
432 }
433 if (mBounds.isEmpty())
434 mBounds = mPartial;
435 else if (mPartial.fBottom > mBounds.fBottom) {
436 DBG_NAV_LOGD("LeftCheck new bottom=%d (old=%d)", mPartial.fBottom,
437 mBounds.fBottom);
438 mBounds.fBottom = mPartial.fBottom;
439 }
440 }
441
442 static const int HIT_SLOP = 5; // space between text parts and lines
443 static const int SLOP = 30; // space between text parts and lines
444 /* const */ SkIRect mHit; // sloppy hit rectangle
445 SkIRect mBounds; // reference bounds
446 SkIRect mPartial; // accumulated text bounds, per line
447 const int mX; // touch location
448 const int mY;
449 int mHitLeft; // touched text extremes
450 int mMostLeft; // paragraph extremes
451 Type mPartialType;
452 };
453
454 /*
455 CenterCheck examines the text in a picture, within a viewable rectangle,
456 and returns via center() the optimal amount to scroll in x to display the
457 paragraph of text.
458
459 The caller of CenterCheck has configured (but not allocated) a bitmap
460 the height and three times the width of the view. The picture is drawn centered
461 in the bitmap, so text that would be revealed, if the view was scrolled up to
462 a view-width to the left or right, is considered.
463 */
464 class CenterCheck : public CommonCheck {
465 public:
CenterCheck(int x,int y,int width)466 CenterCheck(int x, int y, int width) : mX(x), mY(y),
467 mHitLeft(x), mHitRight(x), mMostLeft(INT_MAX), mMostRight(-INT_MAX),
468 mViewLeft(width), mViewRight(width << 1) {
469 mHit.set(x - CENTER_SLOP, y - CENTER_SLOP,
470 x + CENTER_SLOP, y + CENTER_SLOP);
471 mPartial.setEmpty();
472 }
473
center()474 int center() {
475 doRect(); // process the final line of text
476 /* If the touch coordinates aren't near any text, return 0 */
477 if (mHitLeft == mHitRight) {
478 DBG_NAV_LOGD("abort: mHitLeft=%d ==mHitRight", mHitLeft);
479 return 0;
480 }
481 int leftOver = mHitLeft - mViewLeft;
482 int rightOver = mHitRight - mViewRight;
483 int center;
484 /* If the touched text is too large to entirely fit on the screen,
485 center it. */
486 if (leftOver < 0 && rightOver > 0) {
487 center = (leftOver + rightOver) >> 1;
488 DBG_NAV_LOGD("overlap: leftOver=%d rightOver=%d center=%d",
489 leftOver, rightOver, center);
490 return center;
491 }
492 center = (mMostLeft + mMostRight) >> 1; // the paragraph center
493 if (leftOver > 0 && rightOver >= 0) { // off to the right
494 if (center > mMostLeft) // move to center loses left-most text?
495 center = mMostLeft;
496 } else if (rightOver < 0 && leftOver <= 0) { // off to the left
497 if (center < mMostRight) // move to center loses right-most text?
498 center = mMostRight;
499 } else {
500 #ifdef DONT_CENTER_IF_ALREADY_VISIBLE
501 center = 0; // paragraph is already fully visible
502 #endif
503 }
504 DBG_NAV_LOGD("scroll: leftOver=%d rightOver=%d center=%d",
505 leftOver, rightOver, center);
506 return center;
507 }
508
509 protected:
onIRect(const SkIRect & rect)510 virtual bool onIRect(const SkIRect& rect) {
511 if (joinGlyphs(rect)) // assembles glyphs into a text string
512 return false;
513 if (!isTextType(mType))
514 return false;
515 /* Text on one line may be broken into several parts. Reassemble
516 the text into a rectangle before considering it. */
517 if (rect.fTop < mPartial.fBottom && rect.fBottom >
518 mPartial.fTop && mPartial.fRight + CENTER_SLOP >= rect.fLeft) {
519 DBG_NAV_LOGD("join mPartial=(%d, %d, %d, %d) rect=(%d, %d, %d, %d)",
520 mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom,
521 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
522 mPartial.join(rect);
523 return false;
524 }
525 if (mPartial.isEmpty() == false)
526 doRect(); // process the previous line of text
527 mPartial = rect;
528 return false;
529 }
530
doRect()531 void doRect()
532 {
533 /* Record the outer bounds of the lines of text that was 'hit' by the
534 touch coordinates, given some slop */
535 if (SkIRect::Intersects(mPartial, mHit)) {
536 if (mHitLeft > mPartial.fLeft)
537 mHitLeft = mPartial.fLeft;
538 if (mHitRight < mPartial.fRight)
539 mHitRight = mPartial.fRight;
540 DBG_NAV_LOGD("mHitLeft=%d mHitRight=%d", mHitLeft, mHitRight);
541 }
542 /* If the considered text is completely to the left or right of the
543 touch coordinates, skip it */
544 if (mPartial.fLeft > mX || mPartial.fRight < mX)
545 return;
546 int leftOver = mPartial.fLeft - mViewLeft;
547 int rightOver = mPartial.fRight - mViewRight;
548 /* If leftOver <= 0, the text starts off the screen.
549 If rightOver >= 0, the text ends off the screen.
550 */
551 if (leftOver <= 0 && rightOver >= 0) // discard wider than screen
552 return;
553 #ifdef DONT_CENTER_IF_ALREADY_VISIBLE
554 if (leftOver > 0 && rightOver < 0) // discard already visible
555 return;
556 #endif
557 /* record the smallest margins on the left and right */
558 if (mMostLeft > leftOver)
559 mMostLeft = leftOver;
560 if (mMostRight < rightOver)
561 mMostRight = rightOver;
562 DBG_NAV_LOGD("leftOver=%d rightOver=%d mMostLeft=%d mMostRight=%d",
563 leftOver, rightOver, mMostLeft, mMostRight);
564 }
565
566 static const int CENTER_SLOP = 10; // space between text parts and lines
567 /* const */ SkIRect mHit; // sloppy hit rectangle
568 SkIRect mPartial; // accumulated text bounds, per line
569 const int mX; // touch location
570 const int mY;
571 int mHitLeft; // touched text extremes
572 int mHitRight;
573 int mMostLeft; // paragraph extremes
574 int mMostRight;
575 const int mViewLeft; // middle third of 3x-wide view
576 const int mViewRight;
577 };
578
579 class ImageCanvas : public SkCanvas {
580 public:
ImageCanvas(SkBounder * bounder)581 ImageCanvas(SkBounder* bounder) : mURI(NULL) {
582 setBounder(bounder);
583 }
584
585 // Currently webkit's bitmap draws always seem to be cull'd before this entry
586 // point is called, so we assume that any bitmap that gets here is inside our
587 // tiny clip (may not be true in the future)
commonDrawBitmap(const SkBitmap & bitmap,const SkMatrix &,const SkPaint &)588 virtual void commonDrawBitmap(const SkBitmap& bitmap,
589 const SkMatrix& , const SkPaint& ) {
590 SkPixelRef* pixelRef = bitmap.pixelRef();
591 if (pixelRef != NULL) {
592 mURI = pixelRef->getURI();
593 }
594 }
595
596 const char* mURI;
597 };
598
599 class ImageCheck : public SkBounder {
600 public:
onIRect(const SkIRect & rect)601 virtual bool onIRect(const SkIRect& rect) {
602 return false;
603 }
604 };
605
606 class JiggleCheck : public CommonCheck {
607 public:
JiggleCheck(int delta,int width)608 JiggleCheck(int delta, int width) : mDelta(delta), mMaxX(width) {
609 mMaxJiggle = 0;
610 mMinX = mMinJiggle = abs(delta);
611 mMaxWidth = width + mMinX;
612 }
613
jiggle()614 int jiggle() {
615 if (mMinJiggle > mMaxJiggle)
616 return mDelta;
617 int avg = (mMinJiggle + mMaxJiggle + 1) >> 1;
618 return mDelta < 0 ? -avg : avg;
619 }
620
onIRect(const SkIRect & rect)621 virtual bool onIRect(const SkIRect& rect) {
622 if (joinGlyphs(rect))
623 return false;
624 if (mType != kDrawBitmap_Type && !isTextType(mType))
625 return false;
626 int min, max;
627 if (mDelta < 0) {
628 min = mMinX - rect.fLeft;
629 max = mMaxWidth - rect.fRight;
630 } else {
631 min = rect.fRight - mMaxX;
632 max = rect.fLeft;
633 }
634 if (min <= 0)
635 return false;
636 if (max >= mMinX)
637 return false;
638 if (mMinJiggle > min)
639 mMinJiggle = min;
640 if (mMaxJiggle < max)
641 mMaxJiggle = max;
642 return false;
643 }
644
645 int mDelta;
646 int mMaxJiggle;
647 int mMaxX;
648 int mMinJiggle;
649 int mMinX;
650 int mMaxWidth;
651 };
652
653 class RingCheck : public CommonCheck {
654 public:
RingCheck(const WTF::Vector<WebCore::IntRect> & rings,const WebCore::IntPoint & location)655 RingCheck(const WTF::Vector<WebCore::IntRect>& rings,
656 const WebCore::IntPoint& location) : mSuccess(true) {
657 const WebCore::IntRect* r;
658 for (r = rings.begin(); r != rings.end(); r++) {
659 SkIRect fatter = {r->x(), r->y(), r->right(), r->bottom()};
660 fatter.inset(-CURSOR_RING_HIT_TEST_RADIUS, -CURSOR_RING_HIT_TEST_RADIUS);
661 DBG_NAV_LOGD("fat=(%d,%d,r=%d,b=%d)", fatter.fLeft, fatter.fTop,
662 fatter.fRight, fatter.fBottom);
663 mRings.op(fatter, SkRegion::kUnion_Op);
664 }
665 DBG_NAV_LOGD("translate=(%d,%d)", -location.x(), -location.y());
666 mRings.translate(-location.x(), -location.y());
667 }
668
onIRect(const SkIRect & rect)669 virtual bool onIRect(const SkIRect& rect) {
670 if (mSuccess && mType == kDrawGlyph_Type) {
671 DBG_NAV_LOGD("contains (%d,%d,r=%d,b=%d) == %s",
672 rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
673 mRings.contains(rect) ? "true" : "false");
674 mSuccess &= mRings.contains(rect);
675 }
676 return false;
677 }
678
success()679 bool success() { return mSuccess; }
680 SkRegion mRings;
681 bool mSuccess;
682 };
683
adjustForScroll(BestData * best,CachedFrame::Direction direction,WebCore::IntPoint * scrollPtr,bool findClosest)684 bool CachedRoot::adjustForScroll(BestData* best, CachedFrame::Direction direction,
685 WebCore::IntPoint* scrollPtr, bool findClosest)
686 {
687 WebCore::IntRect newOutset;
688 const CachedNode* newNode = best->mNode;
689 // see if there's a middle node
690 // if the middle node is in the visited list,
691 // or if none was computed and the newNode is in the visited list,
692 // treat result as NULL
693 if (newNode != NULL && findClosest) {
694 if (best->bounds().intersects(mHistory->mPriorBounds) == false &&
695 checkBetween(best, direction))
696 newNode = best->mNode;
697 if (findClosest && maskIfHidden(best)) {
698 innerMove(document(), best, direction, scrollPtr, false);
699 return true;
700 }
701 newOutset = newNode->cursorRingBounds(best->mFrame);
702 }
703 int delta;
704 bool newNodeInView = scrollDelta(newOutset, direction, &delta);
705 if (delta && scrollPtr && (newNode == NULL || newNodeInView == false ||
706 (best->mNavOutside && best->mWorkingOutside)))
707 *scrollPtr = WebCore::IntPoint(direction & UP_DOWN ? 0 : delta,
708 direction & UP_DOWN ? delta : 0);
709 return false;
710 }
711
712
checkForCenter(int x,int y) const713 int CachedRoot::checkForCenter(int x, int y) const
714 {
715 int width = mViewBounds.width();
716 CenterCheck centerCheck(x + width - mViewBounds.x(), y - mViewBounds.y(),
717 width);
718 BoundsCanvas checker(¢erCheck);
719 SkBitmap bitmap;
720 bitmap.setConfig(SkBitmap::kARGB_8888_Config, width * 3,
721 mViewBounds.height());
722 checker.setBitmapDevice(bitmap);
723 checker.translate(SkIntToScalar(width - mViewBounds.x()),
724 SkIntToScalar(-mViewBounds.y()));
725 checker.drawPicture(*pictureAt(x, y));
726 return centerCheck.center();
727 }
728
checkForJiggle(int * xDeltaPtr) const729 void CachedRoot::checkForJiggle(int* xDeltaPtr) const
730 {
731 int xDelta = *xDeltaPtr;
732 JiggleCheck jiggleCheck(xDelta, mViewBounds.width());
733 BoundsCanvas checker(&jiggleCheck);
734 SkBitmap bitmap;
735 int absDelta = abs(xDelta);
736 bitmap.setConfig(SkBitmap::kARGB_8888_Config, mViewBounds.width() +
737 absDelta, mViewBounds.height());
738 checker.setBitmapDevice(bitmap);
739 int x = -mViewBounds.x() - (xDelta < 0 ? xDelta : 0);
740 int y = -mViewBounds.y();
741 checker.translate(SkIntToScalar(x), SkIntToScalar(y));
742 checker.drawPicture(*pictureAt(x, y));
743 *xDeltaPtr = jiggleCheck.jiggle();
744 }
745
checkRings(SkPicture * picture,const WTF::Vector<WebCore::IntRect> & rings,const WebCore::IntRect & bounds) const746 bool CachedRoot::checkRings(SkPicture* picture,
747 const WTF::Vector<WebCore::IntRect>& rings,
748 const WebCore::IntRect& bounds) const
749 {
750 if (!picture)
751 return false;
752 RingCheck ringCheck(rings, bounds.location());
753 BoundsCanvas checker(&ringCheck);
754 SkBitmap bitmap;
755 bitmap.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(),
756 bounds.height());
757 checker.setBitmapDevice(bitmap);
758 checker.translate(SkIntToScalar(-bounds.x()), SkIntToScalar(-bounds.y()));
759 checker.drawPicture(*picture);
760 DBG_NAV_LOGD("bounds=(%d,%d,r=%d,b=%d) success=%s",
761 bounds.x(), bounds.y(), bounds.right(), bounds.bottom(),
762 ringCheck.success() ? "true" : "false");
763 return ringCheck.success();
764 }
765
draw(FindCanvas & canvas) const766 void CachedRoot::draw(FindCanvas& canvas) const
767 {
768 canvas.setLayerId(-1); // overlays change the ID as their pictures draw
769 canvas.drawPicture(*mPicture);
770 #if USE(ACCELERATED_COMPOSITING)
771 if (!mRootLayer)
772 return;
773 canvas.drawLayers(mRootLayer);
774 #endif
775 }
776
findAt(const WebCore::IntRect & rect,const CachedFrame ** framePtr,int * x,int * y,bool checkForHidden) const777 const CachedNode* CachedRoot::findAt(const WebCore::IntRect& rect,
778 const CachedFrame** framePtr, int* x, int* y, bool checkForHidden) const
779 {
780 int best = INT_MAX;
781 bool inside = false;
782 (const_cast<CachedRoot*>(this))->resetClippedOut();
783 const CachedFrame* directHitFramePtr;
784 const CachedNode* directHit = NULL;
785 const CachedNode* node = findBestAt(rect, &best, &inside, &directHit,
786 &directHitFramePtr, framePtr, x, y, checkForHidden);
787 DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(),
788 node == NULL ? NULL : node->nodePointer());
789 if (node == NULL) {
790 node = findBestHitAt(rect, framePtr, x, y);
791 DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(),
792 node == NULL ? NULL : node->nodePointer());
793 }
794 if (node == NULL) {
795 *framePtr = findBestFrameAt(rect.x() + (rect.width() >> 1),
796 rect.y() + (rect.height() >> 1));
797 }
798 return node;
799 }
800
cursorLocation() const801 WebCore::IntPoint CachedRoot::cursorLocation() const
802 {
803 const WebCore::IntRect& bounds = mHistory->mNavBounds;
804 return WebCore::IntPoint(bounds.x() + (bounds.width() >> 1),
805 bounds.y() + (bounds.height() >> 1));
806 }
807
focusLocation() const808 WebCore::IntPoint CachedRoot::focusLocation() const
809 {
810 return WebCore::IntPoint(mFocusBounds.x() + (mFocusBounds.width() >> 1),
811 mFocusBounds.y() + (mFocusBounds.height() >> 1));
812 }
813
814 // These reset the values because we only want to get the selection the first time.
815 // After that, the selection is no longer accurate.
getAndResetSelectionEnd()816 int CachedRoot::getAndResetSelectionEnd()
817 {
818 int end = mSelectionEnd;
819 mSelectionEnd = -1;
820 return end;
821 }
822
getAndResetSelectionStart()823 int CachedRoot::getAndResetSelectionStart()
824 {
825 int start = mSelectionStart;
826 mSelectionStart = -1;
827 return start;
828 }
829
getBlockLeftEdge(int x,int y,float scale) const830 int CachedRoot::getBlockLeftEdge(int x, int y, float scale) const
831 {
832 DBG_NAV_LOGD("x=%d y=%d scale=%g mViewBounds=(%d,%d,%d,%d)", x, y, scale,
833 mViewBounds.x(), mViewBounds.y(), mViewBounds.width(),
834 mViewBounds.height());
835 // if (x, y) is in a textArea or textField, return that
836 const int slop = 1;
837 WebCore::IntRect rect = WebCore::IntRect(x - slop, y - slop,
838 slop * 2, slop * 2);
839 const CachedFrame* frame;
840 int fx, fy;
841 const CachedNode* node = findAt(rect, &frame, &fx, &fy, true);
842 if (node && node->wantsKeyEvents()) {
843 DBG_NAV_LOGD("x=%d (%s)", node->bounds(frame).x(),
844 node->isTextInput() ? "text" : "plugin");
845 return node->bounds(frame).x();
846 }
847 SkPicture* picture = node ? frame->picture(node) : pictureAt(x, y);
848 if (!picture)
849 return x;
850 int halfW = (int) (mViewBounds.width() * scale * 0.5f);
851 int fullW = halfW << 1;
852 int halfH = (int) (mViewBounds.height() * scale * 0.5f);
853 int fullH = halfH << 1;
854 LeftCheck leftCheck(fullW, halfH);
855 BoundsCanvas checker(&leftCheck);
856 SkBitmap bitmap;
857 bitmap.setConfig(SkBitmap::kARGB_8888_Config, fullW, fullH);
858 checker.setBitmapDevice(bitmap);
859 checker.translate(SkIntToScalar(fullW - x), SkIntToScalar(halfH - y));
860 checker.drawPicture(*picture);
861 int result = x + leftCheck.left() - fullW;
862 DBG_NAV_LOGD("halfW=%d halfH=%d mMostLeft=%d x=%d",
863 halfW, halfH, leftCheck.mMostLeft, result);
864 return result;
865 }
866
getSimulatedMousePosition(WebCore::IntPoint * point) const867 void CachedRoot::getSimulatedMousePosition(WebCore::IntPoint* point) const
868 {
869 #ifndef NDEBUG
870 ASSERT(CachedFrame::mDebug.mInUse);
871 #endif
872 const WebCore::IntRect& mouseBounds = mHistory->mMouseBounds;
873 int x = mouseBounds.x();
874 int y = mouseBounds.y();
875 int width = mouseBounds.width();
876 int height = mouseBounds.height();
877 point->setX(x + (width >> 1)); // default to box center
878 point->setY(y + (height >> 1));
879 #if DEBUG_NAV_UI
880 const WebCore::IntRect& navBounds = mHistory->mNavBounds;
881 DBG_NAV_LOGD("mHistory->mNavBounds={%d,%d,%d,%d} "
882 "mHistory->mMouseBounds={%d,%d,%d,%d} point={%d,%d}",
883 navBounds.x(), navBounds.y(), navBounds.width(), navBounds.height(),
884 mouseBounds.x(), mouseBounds.y(), mouseBounds.width(),
885 mouseBounds.height(), point->x(), point->y());
886 #endif
887 }
888
init(WebCore::Frame * frame,CachedHistory * history)889 void CachedRoot::init(WebCore::Frame* frame, CachedHistory* history)
890 {
891 CachedFrame::init(this, -1, frame);
892 reset();
893 mHistory = history;
894 mPicture = NULL;
895 }
896
innerDown(const CachedNode * test,BestData * bestData) const897 bool CachedRoot::innerDown(const CachedNode* test, BestData* bestData) const
898 {
899 ASSERT(minWorkingVertical() >= mViewBounds.x());
900 ASSERT(maxWorkingVertical() <= mViewBounds.right());
901 setupScrolledBounds();
902 // (line up)
903 mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll);
904 int testTop = mScrolledBounds.y();
905 int viewBottom = mViewBounds.bottom();
906 const WebCore::IntRect& navBounds = mHistory->mNavBounds;
907 if (navBounds.isEmpty() == false &&
908 navBounds.bottom() > viewBottom && viewBottom < mContents.height())
909 return false;
910 if (navBounds.isEmpty() == false) {
911 int navTop = navBounds.y();
912 int scrollBottom;
913 if (testTop < navTop && navTop < (scrollBottom = mScrolledBounds.bottom())) {
914 mScrolledBounds.setHeight(scrollBottom - navTop);
915 mScrolledBounds.setY(navTop);
916 }
917 }
918 setCursorCache(0, mMaxYScroll);
919 frameDown(test, NULL, bestData);
920 return true;
921 }
922
innerLeft(const CachedNode * test,BestData * bestData) const923 bool CachedRoot::innerLeft(const CachedNode* test, BestData* bestData) const
924 {
925 ASSERT(minWorkingHorizontal() >= mViewBounds.y());
926 ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom());
927 setupScrolledBounds();
928 mScrolledBounds.setX(mScrolledBounds.x() - mMaxXScroll);
929 mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll);
930 int testRight = mScrolledBounds.right();
931 int viewLeft = mViewBounds.x();
932 const WebCore::IntRect& navBounds = mHistory->mNavBounds;
933 if (navBounds.isEmpty() == false &&
934 navBounds.x() < viewLeft && viewLeft > mContents.x())
935 return false;
936 if (navBounds.isEmpty() == false) {
937 int navRight = navBounds.right();
938 int scrollLeft;
939 if (testRight > navRight && navRight > (scrollLeft = mScrolledBounds.x()))
940 mScrolledBounds.setWidth(navRight - scrollLeft);
941 }
942 setCursorCache(-mMaxXScroll, 0);
943 frameLeft(test, NULL, bestData);
944 return true;
945 }
946
947
innerMove(const CachedNode * node,BestData * bestData,Direction direction,WebCore::IntPoint * scroll,bool firstCall)948 void CachedRoot::innerMove(const CachedNode* node, BestData* bestData,
949 Direction direction, WebCore::IntPoint* scroll, bool firstCall)
950 {
951 bestData->reset();
952 bool outOfCursor = mCursorIndex == CURSOR_CLEARED;
953 DBG_NAV_LOGD("mHistory->didFirstLayout()=%s && mCursorIndex=%d",
954 mHistory->didFirstLayout() ? "true" : "false", mCursorIndex);
955 if (mHistory->didFirstLayout() && mCursorIndex < CURSOR_SET) {
956 mHistory->reset();
957 outOfCursor = true;
958 }
959 const CachedFrame* cursorFrame;
960 const CachedNode* cursor = currentCursor(&cursorFrame);
961 mHistory->setWorking(direction, cursorFrame, cursor, mViewBounds);
962 bool findClosest = false;
963 if (mScrollOnly == false) {
964 switch (direction) {
965 case LEFT:
966 if (outOfCursor)
967 mHistory->mNavBounds = WebCore::IntRect(mViewBounds.right(),
968 mViewBounds.y(), 1, mViewBounds.height());
969 findClosest = innerLeft(node, bestData);
970 break;
971 case RIGHT:
972 if (outOfCursor)
973 mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x() - 1,
974 mViewBounds.y(), 1, mViewBounds.height());
975 findClosest = innerRight(node, bestData);
976 break;
977 case UP:
978 if (outOfCursor)
979 mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(),
980 mViewBounds.bottom(), mViewBounds.width(), 1);
981 findClosest = innerUp(node, bestData);
982 break;
983 case DOWN:
984 if (outOfCursor)
985 mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(),
986 mViewBounds.y() - 1, mViewBounds.width(), 1);
987 findClosest = innerDown(node, bestData);
988 break;
989 case UNINITIALIZED:
990 default:
991 ASSERT(0);
992 }
993 }
994 if (firstCall)
995 mHistory->mPriorBounds = mHistory->mNavBounds; // bounds always advances, even if new node is ultimately NULL
996 bestData->setMouseBounds(bestData->bounds());
997 if (adjustForScroll(bestData, direction, scroll, findClosest))
998 return;
999 if (bestData->mNode != NULL) {
1000 mHistory->addToVisited(bestData->mNode, direction);
1001 mHistory->mNavBounds = bestData->bounds();
1002 mHistory->mMouseBounds = bestData->mouseBounds();
1003 } else if (scroll->x() != 0 || scroll->y() != 0) {
1004 WebCore::IntRect newBounds = mHistory->mNavBounds;
1005 int offsetX = scroll->x();
1006 int offsetY = scroll->y();
1007 newBounds.move(offsetX, offsetY);
1008 if (mViewBounds.x() > newBounds.x())
1009 offsetX = mViewBounds.x() - mHistory->mNavBounds.x();
1010 else if (mViewBounds.right() < newBounds.right())
1011 offsetX = mViewBounds.right() - mHistory->mNavBounds.right();
1012 if (mViewBounds.y() > newBounds.y())
1013 offsetY = mViewBounds.y() - mHistory->mNavBounds.y();
1014 else if (mViewBounds.bottom() < newBounds.bottom())
1015 offsetY = mViewBounds.bottom() - mHistory->mNavBounds.bottom();
1016 mHistory->mNavBounds.move(offsetX, offsetY);
1017 }
1018 mHistory->setDidFirstLayout(false);
1019 }
1020
innerRight(const CachedNode * test,BestData * bestData) const1021 bool CachedRoot::innerRight(const CachedNode* test, BestData* bestData) const
1022 {
1023 ASSERT(minWorkingHorizontal() >= mViewBounds.y());
1024 ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom());
1025 setupScrolledBounds();
1026 // (align)
1027 mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll);
1028 int testLeft = mScrolledBounds.x();
1029 int viewRight = mViewBounds.right();
1030 const WebCore::IntRect& navBounds = mHistory->mNavBounds;
1031 if (navBounds.isEmpty() == false &&
1032 navBounds.right() > viewRight && viewRight < mContents.width())
1033 return false;
1034 if (navBounds.isEmpty() == false) {
1035 int navLeft = navBounds.x();
1036 int scrollRight;
1037 if (testLeft < navLeft && navLeft < (scrollRight = mScrolledBounds.right())) {
1038 mScrolledBounds.setWidth(scrollRight - navLeft);
1039 mScrolledBounds.setX(navLeft);
1040 }
1041 }
1042 setCursorCache(mMaxXScroll, 0);
1043 frameRight(test, NULL, bestData);
1044 return true;
1045 }
1046
innerUp(const CachedNode * test,BestData * bestData) const1047 bool CachedRoot::innerUp(const CachedNode* test, BestData* bestData) const
1048 {
1049 ASSERT(minWorkingVertical() >= mViewBounds.x());
1050 ASSERT(maxWorkingVertical() <= mViewBounds.right());
1051 setupScrolledBounds();
1052 mScrolledBounds.setY(mScrolledBounds.y() - mMaxYScroll);
1053 mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll);
1054 int testBottom = mScrolledBounds.bottom();
1055 int viewTop = mViewBounds.y();
1056 const WebCore::IntRect& navBounds = mHistory->mNavBounds;
1057 if (navBounds.isEmpty() == false &&
1058 navBounds.y() < viewTop && viewTop > mContents.y())
1059 return false;
1060 if (navBounds.isEmpty() == false) {
1061 int navBottom = navBounds.bottom();
1062 int scrollTop;
1063 if (testBottom > navBottom && navBottom > (scrollTop = mScrolledBounds.y()))
1064 mScrolledBounds.setHeight(navBottom - scrollTop);
1065 }
1066 setCursorCache(0, -mMaxYScroll);
1067 frameUp(test, NULL, bestData);
1068 return true;
1069 }
1070
imageURI(int x,int y) const1071 WebCore::String CachedRoot::imageURI(int x, int y) const
1072 {
1073 ImageCheck imageCheck;
1074 ImageCanvas checker(&imageCheck);
1075 SkBitmap bitmap;
1076 bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1);
1077 checker.setBitmapDevice(bitmap);
1078 checker.translate(SkIntToScalar(-x), SkIntToScalar(-y));
1079 checker.drawPicture(*pictureAt(x, y));
1080 return WebCore::String(checker.mURI);
1081 }
1082
maskIfHidden(BestData * best) const1083 bool CachedRoot::maskIfHidden(BestData* best) const
1084 {
1085 const CachedNode* bestNode = best->mNode;
1086 if (bestNode->isUnclipped() || bestNode->isTransparent())
1087 return false;
1088 const CachedFrame* frame = best->mFrame;
1089 SkPicture* picture = frame->picture(bestNode);
1090 if (picture == NULL) {
1091 DBG_NAV_LOG("missing picture");
1092 return false;
1093 }
1094 // given the picture matching this nav cache
1095 // create an SkBitmap with dimensions of the cursor intersected w/ extended view
1096 const WebCore::IntRect& nodeBounds = bestNode->bounds(frame);
1097 WebCore::IntRect bounds = nodeBounds;
1098 bounds.intersect(mScrolledBounds);
1099 int leftMargin = bounds.x() == nodeBounds.x() ? kMargin : 0;
1100 int topMargin = bounds.y() == nodeBounds.y() ? kMargin : 0;
1101 int rightMargin = bounds.right() == nodeBounds.right() ? kMargin : 0;
1102 int bottomMargin = bounds.bottom() == nodeBounds.bottom() ? kMargin : 0;
1103 bool unclipped = (leftMargin & topMargin & rightMargin & bottomMargin) != 0;
1104 WebCore::IntRect marginBounds = nodeBounds;
1105 marginBounds.inflate(kMargin);
1106 marginBounds.intersect(mScrolledBounds);
1107 SkScalar offsetX = SkIntToScalar(leftMargin - bounds.x());
1108 SkScalar offsetY = SkIntToScalar(topMargin - bounds.y());
1109 #if USE(ACCELERATED_COMPOSITING)
1110 // When cached nodes are constructed in CacheBuilder.cpp, their
1111 // unclipped attribute is set so this condition won't be reached.
1112 // In the future, layers may contain nodes that can be clipped.
1113 // So to be safe, adjust the layer picture by its offset.
1114 if (bestNode->isInLayer()) {
1115 const CachedLayer* cachedLayer = frame->layer(bestNode);
1116 const LayerAndroid* layer = cachedLayer->layer(mRootLayer);
1117 SkMatrix pictMatrix;
1118 layer->localToGlobal(&pictMatrix);
1119 // FIXME: ignore scale, rotation for now
1120 offsetX += pictMatrix.getTranslateX();
1121 offsetY += pictMatrix.getTranslateY();
1122 DBG_NAV_LOGD("layer picture=%p (%g,%g)", picture,
1123 pictMatrix.getTranslateX(), pictMatrix.getTranslateY());
1124 }
1125 #endif
1126 BoundsCheck boundsCheck;
1127 BoundsCanvas checker(&boundsCheck);
1128 boundsCheck.mBounds.set(leftMargin, topMargin,
1129 leftMargin + bounds.width(), topMargin + bounds.height());
1130 boundsCheck.mBoundsSlop = boundsCheck.mBounds;
1131 boundsCheck.mBoundsSlop.inset(-kSlop, -kSlop);
1132 SkBitmap bitmap;
1133 bitmap.setConfig(SkBitmap::kARGB_8888_Config, marginBounds.width(),
1134 marginBounds.height());
1135 checker.setBitmapDevice(bitmap);
1136 // insert probes to be called when the data corresponding to this ring is drawn
1137 // need to know if ring was generated by text, image, or parent (like div)
1138 // ? need to know (like imdb menu bar) to give up sometimes (when?)
1139 checker.translate(offsetX, offsetY);
1140 checker.drawPicture(*picture);
1141 boundsCheck.checkLast();
1142 // was it not drawn or clipped out?
1143 CachedNode* node = const_cast<CachedNode*>(best->mNode);
1144 if (boundsCheck.hidden()) { // if hidden, return false so that nav can try again
1145 #if DEBUG_NAV_UI
1146 const SkIRect& m = boundsCheck.mBounds;
1147 const SkIRect& s = boundsCheck.mBoundsSlop;
1148 DBG_NAV_LOGD("hidden node:%p (%d) mBounds={%d,%d,%d,%d} mBoundsSlop="
1149 "{%d,%d,%d,%d}", node, node->index(),
1150 m.fLeft, m.fTop, m.fRight, m.fBottom,
1151 s.fLeft, s.fTop, s.fRight, s.fBottom);
1152 const SkIRect& o = boundsCheck.mDrawnOver.getBounds();
1153 const SkIRect& l = boundsCheck.mLastAll;
1154 const SkIRect& u = boundsCheck.mUnion;
1155 DBG_NAV_LOGD("hidden mDrawnOver={%d,%d,%d,%d} mLastAll={%d,%d,%d,%d}"
1156 " mUnion={%d,%d,%d,%d}",
1157 o.fLeft, o.fTop, o.fRight, o.fBottom,
1158 l.fLeft, l.fTop, l.fRight, l.fBottom,
1159 u.fLeft, u.fTop, u.fRight, u.fBottom);
1160 const SkIRect& a = boundsCheck.mAllDrawnIn;
1161 const WebCore::IntRect& c = mScrolledBounds;
1162 const WebCore::IntRect& b = nodeBounds;
1163 DBG_NAV_LOGD("hidden mAllDrawnIn={%d,%d,%d,%d}"
1164 " mScrolledBounds={%d,%d,%d,%d} nodeBounds={%d,%d,%d,%d}",
1165 a.fLeft, a.fTop, a.fRight, a.fBottom,
1166 c.x(), c.y(), c.right(), c.bottom(),
1167 b.x(), b.y(), b.right(), b.bottom());
1168 DBG_NAV_LOGD("bits.mWidth=%d bits.mHeight=%d transX=%d transY=%d",
1169 marginBounds.width(),marginBounds.height(),
1170 kMargin - bounds.x(), kMargin - bounds.y());
1171 #endif
1172 node->setDisabled(true);
1173 node->setClippedOut(unclipped == false);
1174 return true;
1175 }
1176 // was it partially occluded by later drawing?
1177 // if partially occluded, modify the bounds so that the mouse click has a better x,y
1178 const SkIRect& over = boundsCheck.mDrawnOver.getBounds();
1179 if (over.isEmpty() == false) {
1180 #if DEBUG_NAV_UI
1181 SkIRect orig = boundsCheck.mBounds;
1182 #endif
1183 SkIRect& base = boundsCheck.mBounds;
1184 if (base.fLeft < over.fRight && base.fRight > over.fRight)
1185 base.fLeft = over.fRight;
1186 else if (base.fRight > over.fLeft && base.fLeft < over.fLeft)
1187 base.fRight = over.fLeft;
1188 if (base.fTop < over.fBottom && base.fBottom > over.fBottom)
1189 base.fTop = over.fBottom;
1190 else if (base.fBottom > over.fTop && base.fTop < over.fTop)
1191 base.fBottom = over.fTop;
1192 #if DEBUG_NAV_UI
1193 const SkIRect& modded = boundsCheck.mBounds;
1194 DBG_NAV_LOGD("partially occluded node:%p (%d) old:{%d,%d,%d,%d}"
1195 " new:{%d,%d,%d,%d}", node, node->index(),
1196 orig.fLeft, orig.fTop, orig.fRight, orig.fBottom,
1197 base.fLeft, base.fTop, base.fRight, base.fBottom);
1198 #endif
1199 best->setMouseBounds(WebCore::IntRect(bounds.x() + base.fLeft - kMargin,
1200 bounds.y() + base.fTop - kMargin, base.width(), base.height()));
1201 node->clip(best->mouseBounds());
1202 }
1203 return false;
1204 }
1205
moveCursor(Direction direction,const CachedFrame ** framePtr,WebCore::IntPoint * scroll)1206 const CachedNode* CachedRoot::moveCursor(Direction direction, const CachedFrame** framePtr,
1207 WebCore::IntPoint* scroll)
1208 {
1209 #ifndef NDEBUG
1210 ASSERT(CachedFrame::mDebug.mInUse);
1211 #endif
1212 CachedRoot* frame = this;
1213 const CachedNode* node = frame->document();
1214 if (node == NULL)
1215 return NULL;
1216 if (mViewBounds.isEmpty())
1217 return NULL;
1218 resetClippedOut();
1219 setData();
1220 BestData bestData;
1221 innerMove(node, &bestData, direction, scroll, true);
1222 // if node is partially or fully concealed by layer, scroll it into view
1223 if (mRootLayer && bestData.mNode && !bestData.mNode->isInLayer()) {
1224 #if USE(ACCELERATED_COMPOSITING)
1225 #if DUMP_NAV_CACHE
1226 CachedLayer::Debug::printRootLayerAndroid(mRootLayer);
1227 #endif
1228 SkIRect original = bestData.mNode->cursorRingBounds(bestData.mFrame);
1229 DBG_NAV_LOGD("original=(%d,%d,w=%d,h=%d) scroll=(%d,%d)",
1230 original.fLeft, original.fTop, original.width(), original.height(),
1231 scroll->x(), scroll->y());
1232 original.offset(-scroll->x(), -scroll->y());
1233 SkRegion rings(original);
1234 SkTDArray<SkRect> region;
1235 mRootLayer->clipArea(®ion);
1236 SkRegion layers;
1237 for (int index = 0; index < region.count(); index++) {
1238 SkIRect enclosing;
1239 region[index].round(&enclosing);
1240 rings.op(enclosing, SkRegion::kDifference_Op);
1241 layers.op(enclosing, SkRegion::kUnion_Op);
1242 }
1243 SkIRect layerBounds(layers.getBounds());
1244 SkIRect ringBounds(rings.getBounds());
1245 int scrollX = scroll->x();
1246 int scrollY = scroll->y();
1247 if (rings.getBounds() != original) {
1248 int topOverlap = layerBounds.fBottom - original.fTop;
1249 int bottomOverlap = original.fBottom - layerBounds.fTop;
1250 int leftOverlap = layerBounds.fRight - original.fLeft;
1251 int rightOverlap = original.fRight - layerBounds.fLeft;
1252 if (direction & UP_DOWN) {
1253 if (layerBounds.fLeft < original.fLeft && leftOverlap < 0)
1254 scroll->setX(leftOverlap);
1255 if (original.fRight < layerBounds.fRight && rightOverlap > 0
1256 && -leftOverlap > rightOverlap)
1257 scroll->setX(rightOverlap);
1258 bool topSet = scrollY > topOverlap && (direction == UP
1259 || !scrollY);
1260 if (topSet)
1261 scroll->setY(topOverlap);
1262 if (scrollY < bottomOverlap && (direction == DOWN || (!scrollY
1263 && (!topSet || -topOverlap > bottomOverlap))))
1264 scroll->setY(bottomOverlap);
1265 } else {
1266 if (layerBounds.fTop < original.fTop && topOverlap < 0)
1267 scroll->setY(topOverlap);
1268 if (original.fBottom < layerBounds.fBottom && bottomOverlap > 0
1269 && -topOverlap > bottomOverlap)
1270 scroll->setY(bottomOverlap);
1271 bool leftSet = scrollX > leftOverlap && (direction == LEFT
1272 || !scrollX);
1273 if (leftSet)
1274 scroll->setX(leftOverlap);
1275 if (scrollX < rightOverlap && (direction == RIGHT || (!scrollX
1276 && (!leftSet || -leftOverlap > rightOverlap))))
1277 scroll->setX(rightOverlap);
1278 }
1279 DBG_NAV_LOGD("rings=(%d,%d,w=%d,h=%d) layers=(%d,%d,w=%d,h=%d)"
1280 " scroll=(%d,%d)",
1281 ringBounds.fLeft, ringBounds.fTop, ringBounds.width(), ringBounds.height(),
1282 layerBounds.fLeft, layerBounds.fTop, layerBounds.width(), layerBounds.height(),
1283 scroll->x(), scroll->y());
1284 }
1285 #endif
1286 }
1287 *framePtr = bestData.mFrame;
1288 return const_cast<CachedNode*>(bestData.mNode);
1289 }
1290
nextTextField(const CachedNode * start,const CachedFrame ** framePtr) const1291 const CachedNode* CachedRoot::nextTextField(const CachedNode* start,
1292 const CachedFrame** framePtr) const
1293 {
1294 bool startFound = false;
1295 return CachedFrame::nextTextField(start, framePtr, &startFound);
1296 }
1297
pictureAt(int x,int y) const1298 SkPicture* CachedRoot::pictureAt(int x, int y) const
1299 {
1300 #if USE(ACCELERATED_COMPOSITING)
1301 if (mRootLayer) {
1302 const LayerAndroid* layer = mRootLayer->find(x, y);
1303 if (layer) {
1304 SkPicture* picture = layer->picture();
1305 if (picture)
1306 return picture;
1307 }
1308 }
1309 #endif
1310 return mPicture;
1311 }
1312
reset()1313 void CachedRoot::reset()
1314 {
1315 #ifndef NDEBUG
1316 ASSERT(CachedFrame::mDebug.mInUse);
1317 #endif
1318 mContents = mViewBounds = WebCore::IntRect(0, 0, 0, 0);
1319 mMaxXScroll = mMaxYScroll = 0;
1320 mRootLayer = 0;
1321 mSelectionStart = mSelectionEnd = -1;
1322 mScrollOnly = false;
1323 }
1324
scrollDelta(WebCore::IntRect & newOutset,Direction direction,int * delta)1325 bool CachedRoot::scrollDelta(WebCore::IntRect& newOutset, Direction direction, int* delta)
1326 {
1327 switch (direction) {
1328 case LEFT:
1329 *delta = -mMaxXScroll;
1330 return newOutset.x() >= mViewBounds.x();
1331 case RIGHT:
1332 *delta = mMaxXScroll;
1333 return newOutset.right() <= mViewBounds.right();
1334 case UP:
1335 *delta = -mMaxYScroll;
1336 return newOutset.y() >= mViewBounds.y();
1337 case DOWN:
1338 *delta = mMaxYScroll;
1339 return newOutset.bottom() <= mViewBounds.bottom();
1340 default:
1341 *delta = 0;
1342 ASSERT(0);
1343 }
1344 return false;
1345 }
1346
setCachedFocus(CachedFrame * frame,CachedNode * node)1347 void CachedRoot::setCachedFocus(CachedFrame* frame, CachedNode* node)
1348 {
1349 mFocusBounds = WebCore::IntRect(0, 0, 0, 0);
1350 if (node == NULL)
1351 return;
1352 node->setIsFocus(true);
1353 mFocusBounds = node->bounds(frame);
1354 frame->setFocusIndex(node - frame->document());
1355 CachedFrame* parent;
1356 while ((parent = frame->parent()) != NULL) {
1357 parent->setFocusIndex(frame->indexInParent());
1358 frame = parent;
1359 }
1360 #if DEBUG_NAV_UI
1361 const CachedFrame* focusFrame;
1362 const CachedNode* focus = currentFocus(&focusFrame);
1363 WebCore::IntRect bounds = WebCore::IntRect(0, 0, 0, 0);
1364 if (focus)
1365 bounds = focus->bounds(focusFrame);
1366 DBG_NAV_LOGD("new focus %d (nodePointer=%p) bounds={%d,%d,%d,%d}",
1367 focus ? focus->index() : 0,
1368 focus ? focus->nodePointer() : NULL, bounds.x(), bounds.y(),
1369 bounds.width(), bounds.height());
1370 #endif
1371 }
1372
setCursor(CachedFrame * frame,CachedNode * node)1373 void CachedRoot::setCursor(CachedFrame* frame, CachedNode* node)
1374 {
1375 #if DEBUG_NAV_UI
1376 const CachedFrame* cursorFrame;
1377 const CachedNode* cursor = currentCursor(&cursorFrame);
1378 WebCore::IntRect bounds;
1379 if (cursor)
1380 bounds = cursor->bounds(cursorFrame);
1381 DBG_NAV_LOGD("old cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}",
1382 cursor ? cursor->index() : 0,
1383 cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(),
1384 bounds.width(), bounds.height());
1385 #endif
1386 clearCursor();
1387 if (node == NULL)
1388 return;
1389 node->setIsCursor(true);
1390 node->show();
1391 frame->setCursorIndex(node - frame->document());
1392 CachedFrame* parent;
1393 while ((parent = frame->parent()) != NULL) {
1394 parent->setCursorIndex(frame->indexInParent());
1395 frame = parent;
1396 }
1397 #if DEBUG_NAV_UI
1398 cursor = currentCursor(&cursorFrame);
1399 bounds = WebCore::IntRect(0, 0, 0, 0);
1400 if (cursor)
1401 bounds = cursor->bounds(cursorFrame);
1402 DBG_NAV_LOGD("new cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}",
1403 cursor ? cursor->index() : 0,
1404 cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(),
1405 bounds.width(), bounds.height());
1406 #endif
1407 }
1408
setCursorCache(int scrollX,int scrollY) const1409 void CachedRoot::setCursorCache(int scrollX, int scrollY) const
1410 {
1411 mCursor = currentCursor();
1412 if (mCursor)
1413 mCursorBounds = mCursor->bounds(this);
1414 if (!mRootLayer)
1415 return;
1416 SkRegion baseScrolled(mScrolledBounds);
1417 mBaseUncovered = SkRegion(mScrolledBounds);
1418 #if USE(ACCELERATED_COMPOSITING)
1419 #if DUMP_NAV_CACHE
1420 CachedLayer::Debug::printRootLayerAndroid(mRootLayer);
1421 #endif
1422 SkTDArray<SkRect> region;
1423 mRootLayer->clipArea(®ion);
1424 WebCore::IntSize offset(
1425 copysign(min(max(0, mContents.width() - mScrolledBounds.width()),
1426 abs(scrollX)), scrollX),
1427 copysign(min(max(0, mContents.height() - mScrolledBounds.height()),
1428 abs(scrollY)), scrollY));
1429 bool hasOffset = offset.width() || offset.height();
1430 // restrict scrollBounds to that which is not under layer
1431 for (int index = 0; index < region.count(); index++) {
1432 SkIRect less;
1433 region[index].round(&less);
1434 DBG_NAV_LOGD("less=(%d,%d,w=%d,h=%d)", less.fLeft, less.fTop,
1435 less.width(), less.height());
1436 mBaseUncovered.op(less, SkRegion::kDifference_Op);
1437 if (!hasOffset)
1438 continue;
1439 less.offset(offset.width(), offset.height());
1440 baseScrolled.op(less, SkRegion::kDifference_Op);
1441 }
1442 if (hasOffset)
1443 mBaseUncovered.op(baseScrolled, SkRegion::kUnion_Op);
1444 #endif
1445 }
1446
1447 #if DUMP_NAV_CACHE
1448
1449 #define DEBUG_PRINT_BOOL(field) \
1450 DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false")
1451
1452 #define DEBUG_PRINT_RECT(field) \
1453 { const WebCore::IntRect& r = b->field; \
1454 DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \
1455 r.x(), r.y(), r.width(), r.height()); }
1456
base() const1457 CachedRoot* CachedRoot::Debug::base() const {
1458 CachedRoot* nav = (CachedRoot*) ((char*) this - OFFSETOF(CachedRoot, mDebug));
1459 return nav;
1460 }
1461
print() const1462 void CachedRoot::Debug::print() const
1463 {
1464 #ifdef DUMP_NAV_CACHE_USING_PRINTF
1465 gWriteLogMutex.lock();
1466 ASSERT(gNavCacheLogFile == NULL);
1467 gNavCacheLogFile = fopen(NAV_CACHE_LOG_FILE, "a");
1468 #endif
1469 CachedRoot* b = base();
1470 b->CachedFrame::mDebug.print();
1471 b->mHistory->mDebug.print(b);
1472 DUMP_NAV_LOGD("// int mMaxXScroll=%d, mMaxYScroll=%d;\n",
1473 b->mMaxXScroll, b->mMaxYScroll);
1474 #ifdef DUMP_NAV_CACHE_USING_PRINTF
1475 if (gNavCacheLogFile)
1476 fclose(gNavCacheLogFile);
1477 gNavCacheLogFile = NULL;
1478 gWriteLogMutex.unlock();
1479 #endif
1480 }
1481
1482 #endif
1483
1484 }
1485