OSDN Git Service

improve text selection
authorCary Clark <cary@android.com>
Tue, 14 Dec 2010 13:04:18 +0000 (08:04 -0500)
committerCary Clark <cary@android.com>
Tue, 14 Dec 2010 21:31:07 +0000 (16:31 -0500)
LayerAndroid.*
Adjust the hit-test (x,y) by the layer position.
Keep track of the adjusted (x,y) and store the best
to be returned when the best layer is found.

CachedRoot.cpp
Remove code that adjusted (x,y) by layer bounds.

SelectText.*
Detect columns of text and prefer new characters in the same
column as the existing selection.

Don't extend the selection until the tap point moves past
the word anchors.

There's more work to do on selecting text in layers.

bug:3275625
bug:3271730
bug:3191699
Change-Id: Ib3c2b35e5eebe30c6032f484cf76d388e94293e0

WebCore/platform/graphics/android/LayerAndroid.cpp
WebCore/platform/graphics/android/LayerAndroid.h
WebKit/android/nav/CachedRoot.cpp
WebKit/android/nav/SelectText.cpp
WebKit/android/nav/SelectText.h

index f02136a..58d8772 100644 (file)
@@ -255,7 +255,7 @@ protected:
 
     virtual bool onIRectGlyph(const SkIRect& , const SkBounder::GlyphRec& )
     {
-        m_drewText = true;
+        m_drew = m_drewText = true;
         return false;
     }
 
@@ -281,6 +281,8 @@ public:
     FindState(int x, int y)
         : m_x(x)
         , m_y(y)
+        , m_bestX(x)
+        , m_bestY(y)
         , m_best(0)
     {
         m_bitmap.setConfig(SkBitmap::kARGB_8888_Config, TOUCH_SLOP * 2,
@@ -290,6 +292,8 @@ public:
     }
 
     const LayerAndroid* best() const { return m_best; }
+    int bestX() const { return m_bestX; }
+    int bestY() const { return m_bestY; }
 
     bool drew(SkPicture* picture, const SkRect& localBounds) {
         m_findCheck.reset();
@@ -301,7 +305,11 @@ public:
 
     bool drewText() { return m_findCheck.drewText(); }
 
-    void setBest(const LayerAndroid* best) { m_best = best; }
+    void setBest(const LayerAndroid* best) { 
+        m_best = best;
+        m_bestX = m_x;
+        m_bestY = m_y;
+    }
     int x() const { return m_x; }
     int y() const { return m_y; }
 
@@ -313,6 +321,8 @@ public:
 protected:
     int m_x;
     int m_y;
+    int m_bestX;
+    int m_bestY;
     const LayerAndroid* m_best;
     FindCheck m_findCheck;
     SkBitmap m_bitmap;
@@ -340,14 +350,16 @@ void LayerAndroid::findInner(LayerAndroid::FindState& state) const
     state.setBest(this); // set last match (presumably on top)
 }
 
-const LayerAndroid* LayerAndroid::find(int x, int y, SkPicture* root) const
+const LayerAndroid* LayerAndroid::find(int* xPtr, int* yPtr, SkPicture* root) const
 {
-    FindState state(x, y);
+    FindState state(*xPtr, *yPtr);
     SkRect rootBounds;
     rootBounds.setEmpty();
     if (root && state.drew(root, rootBounds) && state.drewText())
         return 0; // use the root picture only if it contains the text
     findInner(state);
+    *xPtr = state.bestX();
+    *yPtr = state.bestY();
     return state.best();
 }
 
index 510014b..575ada7 100644 (file)
@@ -163,7 +163,7 @@ public:
     void updatePositions();
 
     void clipArea(SkTDArray<SkRect>* region) const;
-    const LayerAndroid* find(int x, int y, SkPicture* root) const;
+    const LayerAndroid* find(int* xPtr, int* yPtr, SkPicture* root) const;
     const LayerAndroid* findById(int uniqueID) const {
         return const_cast<LayerAndroid*>(this)->findById(uniqueID);
     }
index 7f4f06f..7bedd4f 100644 (file)
@@ -1674,16 +1674,12 @@ SkPicture* CachedRoot::pictureAt(int* xPtr, int* yPtr, int* id) const
 {
 #if USE(ACCELERATED_COMPOSITING)
     if (mRootLayer) {
-        const LayerAndroid* layer = mRootLayer->find(*xPtr, *yPtr, mPicture);
+        const LayerAndroid* layer = mRootLayer->find(xPtr, yPtr, mPicture);
         if (layer) {
             SkPicture* picture = layer->picture();
             DBG_NAV_LOGD("layer %d picture=%p (%d,%d)", layer->uniqueId(),
                 picture, picture ? picture->width() : 0,
                 picture ? picture->height() : 0);
-            SkRect localBounds;
-            layer->bounds(&localBounds);
-            *xPtr -= localBounds.fLeft;
-            *yPtr -= localBounds.fTop;
             if (picture) {
                 if (id)
                     *id = layer->uniqueId();
index 6c9646c..6a22c73 100644 (file)
@@ -182,12 +182,11 @@ public:
 
 class CommonCheck : public SkBounder {
 public:
-    CommonCheck(int width, int height)
-        : mHeight(height)
+    CommonCheck(const SkIRect& area)
+        : mArea(area)
         , mLastUni(0)
         , mMatrix(0)
         , mPaint(0)
-        , mWidth(width)
     {
         mLastGlyph.fGlyphID = static_cast<uint16_t>(-1);
         reset();
@@ -246,6 +245,10 @@ public:
         mLastUni = mLastUniCandidate;
     }
 
+    const SkIRect& getArea() const {
+        return mArea;
+    }
+
     SkUnichar getUniChar(const SkBounder::GlyphRec& rec)
     {
         SkUnichar unichar;
@@ -280,7 +283,7 @@ public:
         test[0] = mLastGlyph.fGlyphID;
         test[1] = rec.fGlyphID;
         SkIRect area;
-        area.set(0, 0, mWidth, mHeight);
+        area.set(0, 0, area.width(), area.height());
         SpaceCanvas spaceChecker(area);
         spaceChecker.drawText(test, sizeof(test),
             SkFixedToScalar(mLastGlyph.fLSB.fX),
@@ -346,6 +349,12 @@ public:
         reset();
     }
 
+    void setGlyph(CommonCheck& check)
+    {
+        mLastGlyph = check.mLastGlyph;
+        mLastUni = check.mLastUni;
+    }
+
     void setUp(const SkPaint& paint, const SkMatrix& matrix, SkScalar y,
             const void* text)
     {
@@ -376,7 +385,7 @@ public:
 #endif
 
 protected:
-    int mHeight;
+    SkIRect mArea;
     SkBounder::GlyphRec mLastCandidate;
     SkBounder::GlyphRec mLastGlyph;
     SkUnichar mLastUni;
@@ -384,7 +393,6 @@ protected:
     const SkMatrix* mMatrix;
     const SkPaint* mPaint;
     const uint16_t* mText;
-    int mWidth;
     SkScalar mY;
 private:
     int mBase;
@@ -394,27 +402,144 @@ private:
     friend class EdgeCheck;
 };
 
-class FirstCheck : public CommonCheck {
+// generate the limit area for the new selection
+class LineCheck : public CommonCheck {
+public:
+    LineCheck(int x, int y, const SkIRect& area)
+        : INHERITED(area)
+        , mX(x)
+        , mY(y)
+        , mInBetween(false)
+    {
+        mLast.setEmpty();
+    }
+
+    void finish(const SkRegion& selectedRgn)
+    {
+        processLine();
+        bool above = false;
+        bool below = false;
+        bool selected = false;
+        SkRegion localRgn(selectedRgn);
+        localRgn.translate(-mArea.fLeft, -mArea.fTop, &localRgn);
+        DBG_NAV_LOGD("localRgn=(%d,%d,%d,%d)",
+            localRgn.getBounds().fLeft, localRgn.getBounds().fTop,
+            localRgn.getBounds().fRight, localRgn.getBounds().fBottom);
+        for (int index = 0; index < mParagraphs.count(); index++) {
+            const SkIRect& rect = mParagraphs[index];
+            bool localSelected = localRgn.intersects(rect);
+            DBG_NAV_LOGD("[%d] rect=(%d,%d,%d,%d)", index, rect.fLeft, rect.fTop,
+                rect.fRight, rect.fBottom);
+            if (localSelected) {
+                DBG_NAV_LOGD("[%d] localSelected=true", index);
+                *mSelected.append() = rect;
+            }
+            if (rect.fRight <= mX || rect.fLeft >= mX)
+                continue;
+            if (mY > rect.fBottom) {
+                below = true;
+                selected |= localSelected;
+                DBG_NAV_LOGD("[%d] below=true localSelected=%s", index,
+                    localSelected ? "true" : "false");
+           }
+            if (mY < rect.fTop) {
+                above = true;
+                selected |= localSelected;
+                DBG_NAV_LOGD("[%d] above=true localSelected=%s", index,
+                    localSelected ? "true" : "false");
+            }
+        }
+        DBG_NAV_LOGD("mX=%d mY=%d above=%s below=%s selected=%s",
+            mX, mY, above ? "true" : "false", below ? "true" : "false",
+            selected ? "true" : "false");
+        mInBetween = above && below && selected;
+    }
+
+    bool inBetween() const
+    {
+        return mInBetween;
+    }
+
+    bool inColumn(const SkIRect& test) const
+    {
+        for (int index = 0; index < mSelected.count(); index++) {
+            const SkIRect& rect = mSelected[index];
+            if (rect.fRight > test.fLeft && rect.fLeft < test.fRight)
+                return true;
+        }
+        return false;
+    }
+
+    virtual bool onIRectGlyph(const SkIRect& rect, const SkBounder::GlyphRec& )
+    {
+        SkIRect bounds;
+        bounds.set(rect.fLeft, top(), rect.fRight, bottom());
+        // assume that characters must be consecutive to describe spaces
+        // (i.e., don't join rects drawn at different times)
+        if (bounds.fTop != mLast.fTop || bounds.fBottom != mLast.fBottom
+            || bounds.fLeft > mLast.fRight + minSpaceWidth()
+            || bounds.fLeft < mLast.fLeft) {
+            processLine();
+            mLast = bounds;
+        } else
+            mLast.join(bounds);
+        return false;
+    }
+
+    void processLine()
+    {
+        // assume line spacing of 1.5
+        int lineHeight = bottom() - top();
+        mLast.inset(0, -lineHeight >> 1);
+        // collect arrays of rectangles making up glyphs below or above this one
+        for (int index = 0; index < mParagraphs.count(); index++) {
+            SkIRect& rect = mParagraphs[index];
+            if (SkIRect::Intersects(rect, mLast)) {
+                rect.join(mLast);
+                return;
+            }
+        }
+        *mParagraphs.append() = mLast;
+    }
+
+protected:
+    int mX;
+    int mY;
+    SkIRect mLast;
+    SkTDArray<SkIRect> mParagraphs;
+    SkTDArray<SkIRect> mSelected;
+    SkTDArray<SkIRect> mInColumn;
+    bool mInBetween;
+private:
+    typedef CommonCheck INHERITED;
+};
+
+class SelectText::FirstCheck : public CommonCheck {
 public:
     FirstCheck(int x, int y, const SkIRect& area)
-        : INHERITED(area.width(), area.height())
+        : INHERITED(area)
+        , mLineCheck(0)
         , mFocusX(x - area.fLeft)
         , mFocusY(y - area.fTop)
+        , mBestInColumn(false)
         , mRecordGlyph(false)
     {
         reset();
     }
 
-    const SkIRect& adjustedBounds(const SkIRect& area, int* base)
+    const SkIRect& adjustedBounds(int* base)
     {
-        *base = mBestBase + area.fTop;
-        mBestBounds.offset(area.fLeft, area.fTop);
+        *base = mBestBase + mArea.fTop;
+        mBestBounds.offset(mArea.fLeft, mArea.fTop);
         DBG_NAV_LOGD("FirstCheck mBestBounds:(%d, %d, %d, %d) mTop=%d mBottom=%d",
             mBestBounds.fLeft, mBestBounds.fTop, mBestBounds.fRight, 
             mBestBounds.fBottom, topDebug(), bottomDebug());
         return mBestBounds;
     }
 
+    int focusX() const { return mFocusX; }
+    int focusY() const { return mFocusY; }
+
     virtual bool onIRectGlyph(const SkIRect& rect,
         const SkBounder::GlyphRec& rec)
     {
@@ -441,25 +566,43 @@ public:
                 overlaps ? "true" : "false", ch < 0x7f ? ch : '?');
         }
 #endif
-        if ((mDy > dy && !overlaps)
-            || ((mDy == dy || overlaps) && mDx > dx)) {
-            mBestBase = base();
-            mBestBounds = testBounds;
+        if ((mDy <= dy || overlaps)
+            && ((mDy != dy && !overlaps) || mDx <= dx)) {
+            return false;
+        }
+        bool testInColumn = false;
+        bool inBetween = false;
+        if (mLineCheck) {
+            testInColumn = mLineCheck->inColumn(testBounds);
+            inBetween = mLineCheck->inBetween();
+        }
+#ifdef EXTRA_NOISY_LOGGING
+        if (dy < 10) {
+            DBG_NAV_LOGD("FirstCheck bestIn=%s testIn=%s focusIn=%s",
+                mBestInColumn ? "true" : "false", testInColumn ? "true" : "false",
+                inBetween ? "true" : "false");
+        }
+#endif
+        if ((mBestInColumn || inBetween) && !testInColumn)
+            return false;
+        mBestBase = base();
+        mBestBounds = testBounds;
+        mBestInColumn = testInColumn;
 #ifndef EXTRA_NOISY_LOGGING
-            if (dy < 10 && dx < 10)
+        if (dy < 10 && dx < 10)
 #endif
-            {
-                DBG_NAV_LOGD("FirstCheck dx/y=(%d,%d) mFocus=(%d,%d)"
-                    " mBestBounds={%d,%d,r=%d,b=%d}",
-                    dx, dy, mFocusX, mFocusY,
-                    mBestBounds.fLeft, mBestBounds.fTop,
-                    mBestBounds.fRight, mBestBounds.fBottom);
-            }
-            mDx = dx;
-            mDy = dy;
-            if (mRecordGlyph)
-                recordGlyph(rec);
+        {
+            DBG_NAV_LOGD("FirstCheck dx/y=(%d,%d) mFocus=(%d,%d)"
+                " mBestBounds={%d,%d,r=%d,b=%d} inColumn=%s",
+                dx, dy, mFocusX, mFocusY,
+                mBestBounds.fLeft, mBestBounds.fTop,
+                mBestBounds.fRight, mBestBounds.fBottom,
+                mBestInColumn ? "true" : "false");
         }
+        mDx = dx;
+        mDy = dy;
+        if (mRecordGlyph)
+            recordGlyph(rec);
         return false;
     }
 
@@ -469,33 +612,32 @@ public:
         mDx = mDy = INT_MAX;
     }
 
-    void setRecordGlyph()
-    {
-        mRecordGlyph = true;
-    }
+    void setLines(const LineCheck* lineCheck) { mLineCheck = lineCheck; }
+    void setRecordGlyph() { mRecordGlyph = true; }
 
 protected:
+    const LineCheck* mLineCheck;
     int mBestBase;
     SkIRect mBestBounds;
     int mDx;
     int mDy;
     int mFocusX;
     int mFocusY;
+    bool mBestInColumn;
     bool mRecordGlyph;
 private:
     typedef CommonCheck INHERITED;
 };
 
-class EdgeCheck : public FirstCheck {
+class SelectText::EdgeCheck : public SelectText::FirstCheck {
 public:
     EdgeCheck(int x, int y, const SkIRect& area, CommonCheck& last, bool left)
         : INHERITED(x, y, area)
-        , mLast(area.width(), area.height())
+        , mLast(area)
         , mLeft(left)
     {
         mLast.set(last);
-        mLastGlyph = last.mLastGlyph;
-        mLastUni = last.mLastUni;
+        setGlyph(last);
     }
 
     bool adjacent()
@@ -556,15 +698,15 @@ protected:
     CommonCheck mLast;
     bool mLeft;
 private:
-    typedef FirstCheck INHERITED;
+    typedef SelectText::FirstCheck INHERITED;
 };
 
 class FindFirst : public CommonCheck {
 public:
-    FindFirst(int width, int height)
-        : INHERITED(width, height)
+    FindFirst(const SkIRect& area)
+        : INHERITED(area)
     {
-        mBestBounds.set(width, height, width, height);
+        mBestBounds.set(area.width(), area.height(), area.width(), area.height());
     }
 
     const SkIRect& bestBounds(int* base)
@@ -591,8 +733,8 @@ private:
 
 class FindLast : public FindFirst {
 public:
-    FindLast(int width, int height)
-        : INHERITED(width, height)
+    FindLast(const SkIRect& area)
+        : INHERITED(area)
     {
         mBestBounds.setEmpty();
     }
@@ -625,7 +767,7 @@ protected:
 
     BuilderCheck(const SkIRect& start, int startBase, const SkIRect& end,
         int endBase, const SkIRect& area)
-        : INHERITED(area.width(), area.height())
+        : INHERITED(area)
         , mCapture(false)
         , mEnd(end)
         , mEndBase(endBase)
@@ -1020,10 +1162,11 @@ private:
 class TextCanvas : public ParseCanvas {
 public:
 
-    TextCanvas(CommonCheck* bounder, const SkIRect& area)
+    TextCanvas(CommonCheck* bounder)
             : mBounder(*bounder) {
         setBounder(bounder);
         SkBitmap bitmap;
+        const SkIRect& area = bounder->getArea();
         bitmap.setConfig(SkBitmap::kARGB_8888_Config, area.width(), 
             area.height());
         setBitmapDevice(bitmap);
@@ -1093,11 +1236,11 @@ static bool buildSelection(const SkPicture& picture, const SkIRect& area,
         selStart.fLeft, selStart.fTop, selStart.fRight, selStart.fBottom,
         selEnd.fLeft, selEnd.fTop, selEnd.fRight, selEnd.fBottom);
     MultilineBuilder builder(selStart, startBase, selEnd, endBase, area, region);
-    TextCanvas checker(&builder, area);
+    TextCanvas checker(&builder);
     checker.drawPicture(const_cast<SkPicture&>(picture));
     bool flipped = builder.flipped();
     if (flipped) {
-        TextCanvas checker(&builder, area);
+        TextCanvas checker(&builder);
         checker.drawPicture(const_cast<SkPicture&>(picture));
     }
     builder.finish();
@@ -1105,103 +1248,32 @@ static bool buildSelection(const SkPicture& picture, const SkIRect& area,
     return flipped;
 }
 
-static SkIRect findClosest(FirstCheck& _check, const SkPicture& picture,
-        const SkIRect& area, int* base)
-{
-    DBG_NAV_LOGD("area=(%d, %d, %d, %d)", area.fLeft, area.fTop,
-        area.fRight, area.fBottom);
-    TextCanvas checker(&_check, area);
-    checker.drawPicture(const_cast<SkPicture&>(picture));
-    _check.finishGlyph();
-    return _check.adjustedBounds(area, base);
-}
-
-static SkIRect findEdge(const SkPicture& picture, const SkIRect& area,
-        int x, int y, bool left, int* base)
-{
-    SkIRect result;
-    result.setEmpty();
-    FirstCheck center(x, y, area);
-    center.setRecordGlyph();
-    int closestBase;
-    SkIRect closest = findClosest(center, picture, area, &closestBase);
-    closest.inset(-TOUCH_SLOP, -TOUCH_SLOP);
-    if (!closest.contains(x, y)) {
-        DBG_NAV_LOGD("closest=(%d, %d, %d, %d) area=(%d, %d, %d, %d) x/y=%d,%d",
-            closest.fLeft, closest.fTop, closest.fRight, closest.fBottom,
-            area.fLeft, area.fTop, area.fRight, area.fBottom, x, y);
-        return result;
-    }
-    EdgeCheck edge(x, y, area, center, left);
-    do { // detect left or right until there's a gap
-        DBG_NAV_LOGD("edge=%p picture=%p area=%d,%d,%d,%d",
-            &edge, &picture, area.fLeft, area.fTop, area.fRight, area.fBottom);
-        TextCanvas checker(&edge, area);
-        checker.drawPicture(const_cast<SkPicture&>(picture));
-        edge.finishGlyph();
-        if (!edge.adjacent()) {
-            DBG_NAV_LOG("adjacent break");
-            break;
-        }
-        int nextBase;
-        const SkIRect& next = edge.bestBounds(&nextBase);
-        if (next.isEmpty()) {
-            DBG_NAV_LOG("empty");
-            break;
-        }
-        if (result == next) {
-            DBG_NAV_LOG("result == next");
-            break;
-        }
-        *base = nextBase;
-        result = next;
-        edge.shiftStart(result);
-    } while (true);
-    if (!result.isEmpty()) {
-        *base += area.fTop;
-        result.offset(area.fLeft, area.fTop);
-    }
-    return result;
-}
-
 static SkIRect findFirst(const SkPicture& picture, int* base)
 {
-    FindFirst finder(picture.width(), picture.height());
     SkIRect area;
     area.set(0, 0, picture.width(), picture.height());
-    TextCanvas checker(&finder, area);
+    FindFirst finder(area);
+    TextCanvas checker(&finder);
     checker.drawPicture(const_cast<SkPicture&>(picture));
     return finder.bestBounds(base);
 }
 
 static SkIRect findLast(const SkPicture& picture, int* base)
 {
-    FindLast finder(picture.width(), picture.height());
     SkIRect area;
     area.set(0, 0, picture.width(), picture.height());
-    TextCanvas checker(&finder, area);
+    FindLast finder(area);
+    TextCanvas checker(&finder);
     checker.drawPicture(const_cast<SkPicture&>(picture));
     return finder.bestBounds(base);
 }
 
-static SkIRect findLeft(const SkPicture& picture, const SkIRect& area,
-        int x, int y, int* base)
-{
-    return findEdge(picture, area, x, y, true, base);
-}
-
-static SkIRect findRight(const SkPicture& picture, const SkIRect& area,
-        int x, int y, int* base)
-{
-    return findEdge(picture, area, x, y, false, base);
-}
-
 static WTF::String text(const SkPicture& picture, const SkIRect& area,
         const SkIRect& start, int startBase, const SkIRect& end,
         int endBase, bool flipped)
 {
     TextExtractor extractor(start, startBase, end, endBase, area, flipped);
-    TextCanvas checker(&extractor, area);
+    TextCanvas checker(&extractor);
     checker.drawPicture(const_cast<SkPicture&>(picture));
     return extractor.text();
 }
@@ -1469,7 +1541,7 @@ void SelectText::extendSelection(const IntRect& vis, int x, int y)
         DBG_NAV_LOGD("selStart clip=(%d,%d,%d,%d)", clipRect.fLeft,
             clipRect.fTop, clipRect.fRight, clipRect.fBottom);
         FirstCheck center(m_original.fX, m_original.fY, clipRect);
-        m_selStart = m_selEnd = findClosest(center, *m_picture, clipRect, &base);
+        m_selStart = m_selEnd = findClosest(center, *m_picture, &base);
         m_startBase = m_endBase = base;
         m_startSelection = false;
         m_extendSelection = true;
@@ -1484,10 +1556,33 @@ void SelectText::extendSelection(const IntRect& vis, int x, int y)
         clipRect.sort();
         clipRect.inset(-m_visibleRect.width(), -m_visibleRect.height());
     }
-    DBG_NAV_LOGD("extend clip=(%d,%d,%d,%d)", clipRect.fLeft,
-        clipRect.fTop, clipRect.fRight, clipRect.fBottom);
+    DBG_NAV_LOGD("extend clip=(%d,%d,%d,%d) wordSel=%s outsideWord=%s",
+        clipRect.fLeft, clipRect.fTop, clipRect.fRight, clipRect.fBottom,
+        m_wordSelection ? "true" : "false", m_outsideWord ? "true" : "false");
     FirstCheck extension(x, y, clipRect);
-    SkIRect found = findClosest(extension, *m_picture, clipRect, &base);
+    SkIRect found = findClosest(extension, *m_picture, &base);
+    if (m_wordSelection) {
+        SkIRect wordBounds = m_wordBounds;
+        if (!m_outsideWord)
+            wordBounds.inset(-TOUCH_SLOP, -TOUCH_SLOP);
+        DBG_NAV_LOGD("x=%d y=%d wordBounds=(%d,%d,r=%d,b=%d)"
+            " found=(%d,%d,r=%d,b=%d)", x, y, wordBounds.fLeft, wordBounds.fTop,
+            wordBounds.fRight, wordBounds.fBottom, found.fLeft, found.fTop,
+            found.fRight, found.fBottom);
+        if (wordBounds.contains(x, y)) {
+            DBG_NAV_LOG("wordBounds.contains=true");
+            m_outsideWord = false;
+            return;
+        }
+        m_outsideWord = true;
+        if (found.fBottom <= wordBounds.fTop)
+            m_hitTopLeft = true;
+        else if (found.fTop >= wordBounds.fBottom)
+            m_hitTopLeft = false;
+        else
+            m_hitTopLeft = (found.fLeft + found.fRight)
+                < (wordBounds.fLeft + wordBounds.fRight);
+    }
     DBG_NAV_LOGD("x=%d y=%d m_startSelection=%s %s=(%d, %d, %d, %d)"
         " m_extendSelection=%s",
         x, y, m_startSelection ? "true" : "false",
@@ -1504,6 +1599,80 @@ void SelectText::extendSelection(const IntRect& vis, int x, int y)
     swapAsNeeded();
 }
 
+SkIRect SelectText::findClosest(FirstCheck& check, const SkPicture& picture,
+        int* base)
+{
+    LineCheck lineCheck(check.focusX(), check.focusY(), check.getArea());
+    TextCanvas lineChecker(&lineCheck);
+    lineChecker.drawPicture(const_cast<SkPicture&>(picture));
+    lineCheck.finish(m_selRegion);
+    check.setLines(&lineCheck);
+    TextCanvas checker(&check);
+    checker.drawPicture(const_cast<SkPicture&>(picture));
+    check.finishGlyph();
+    return check.adjustedBounds(base);
+}
+
+SkIRect SelectText::findEdge(const SkPicture& picture, const SkIRect& area,
+        int x, int y, bool left, int* base)
+{
+    SkIRect result;
+    result.setEmpty();
+    FirstCheck center(x, y, area);
+    center.setRecordGlyph();
+    int closestBase;
+    SkIRect closest = findClosest(center, picture, &closestBase);
+    closest.inset(-TOUCH_SLOP, -TOUCH_SLOP);
+    if (!closest.contains(x, y)) {
+        DBG_NAV_LOGD("closest=(%d, %d, %d, %d) area=(%d, %d, %d, %d) x/y=%d,%d",
+            closest.fLeft, closest.fTop, closest.fRight, closest.fBottom,
+            area.fLeft, area.fTop, area.fRight, area.fBottom, x, y);
+        return result;
+    }
+    EdgeCheck edge(x, y, area, center, left);
+    do { // detect left or right until there's a gap
+        DBG_NAV_LOGD("edge=%p picture=%p area=%d,%d,%d,%d",
+            &edge, &picture, area.fLeft, area.fTop, area.fRight, area.fBottom);
+        TextCanvas checker(&edge);
+        checker.drawPicture(const_cast<SkPicture&>(picture));
+        edge.finishGlyph();
+        if (!edge.adjacent()) {
+            DBG_NAV_LOG("adjacent break");
+            break;
+        }
+        int nextBase;
+        const SkIRect& next = edge.bestBounds(&nextBase);
+        if (next.isEmpty()) {
+            DBG_NAV_LOG("empty");
+            break;
+        }
+        if (result == next) {
+            DBG_NAV_LOG("result == next");
+            break;
+        }
+        *base = nextBase;
+        result = next;
+        edge.shiftStart(result);
+    } while (true);
+    if (!result.isEmpty()) {
+        *base += area.fTop;
+        result.offset(area.fLeft, area.fTop);
+    }
+    return result;
+}
+
+SkIRect SelectText::findLeft(const SkPicture& picture, const SkIRect& area,
+        int x, int y, int* base)
+{
+    return findEdge(picture, area, x, y, true, base);
+}
+
+SkIRect SelectText::findRight(const SkPicture& picture, const SkIRect& area,
+        int x, int y, int* base)
+{
+    return findEdge(picture, area, x, y, false, base);
+}
+
 const String SelectText::getSelection()
 {
     if (!m_picture)
@@ -1579,7 +1748,7 @@ void SelectText::moveSelection(const IntRect& vis, int x, int y)
     clipRect.join(m_selEnd);
     FirstCheck center(x, y, clipRect);
     int base;
-    SkIRect found = findClosest(center, *m_picture, clipRect, &base);
+    SkIRect found = findClosest(center, *m_picture, &base);
     if (m_hitTopLeft || !m_extendSelection) {
         m_startBase = base;
         m_selStart = found;
@@ -1631,7 +1800,7 @@ int SelectText::selectionY() const
 
 void SelectText::setVisibleRect(const IntRect& vis)
 {
-    DBG_NAV_LOGD("vis=(%d,%d,w=%d,h=%d) offset=(%g,%g)",
+    DBG_NAV_LOGD("vis=(%d,%d,w=%d,h=%d) offset=(%d,%d)",
         vis.x(), vis.y(), vis.width(), vis.height(), m_startOffset.fX,
         m_startOffset.fY);
     m_visibleRect = vis;
@@ -1641,9 +1810,13 @@ void SelectText::setVisibleRect(const IntRect& vis)
 bool SelectText::startSelection(const CachedRoot* root, const IntRect& vis,
     int x, int y)
 {
+    m_wordSelection = false;
     m_startOffset.set(x, y);
+    DBG_NAV_LOGD("x/y=(%d,%d)", x, y);
     m_picture->safeUnref();
     m_picture = root->pictureAt(&x, &y, &m_layerId);
+    DBG_NAV_LOGD("m_picture=%p m_layerId=%d x/y=(%d,%d)", m_picture, m_layerId,
+        x, y);
     if (!m_picture) {
         DBG_NAV_LOG("picture==0");
         return false;
@@ -1711,7 +1884,10 @@ bool SelectText::wordSelection()
         m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom,
         m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom);
     if (!left.isEmpty() || !right.isEmpty()) {
-        m_extendSelection = true;
+        m_wordBounds = m_selStart;
+        m_wordBounds.join(m_selEnd);
+        m_extendSelection = m_wordSelection = true;
+        m_outsideWord = false;
         return true;
     }
     return false;
index 32e1728..666cdd1 100644 (file)
@@ -60,8 +60,17 @@ public:
     int m_selectX;
     int m_selectY;
 private:
+    class FirstCheck;
+    class EdgeCheck;
     void drawSelectionPointer(SkCanvas* , IntRect* );
     void drawSelectionRegion(SkCanvas* , IntRect* );
+    SkIRect findClosest(FirstCheck& , const SkPicture& , int* base);
+    SkIRect findEdge(const SkPicture& , const SkIRect& area,
+        int x, int y, bool left, int* base);
+    SkIRect findLeft(const SkPicture& picture, const SkIRect& area,
+        int x, int y, int* base);
+    SkIRect findRight(const SkPicture& picture, const SkIRect& area,
+        int x, int y, int* base);
     static void getSelectionArrow(SkPath* );
     void getSelectionCaret(SkPath* );
     bool hitCorner(int cx, int cy, int x, int y) const;
@@ -73,6 +82,7 @@ private:
     SkIRect m_selEnd;
     SkIRect m_lastStart;
     SkIRect m_lastEnd;
+    SkIRect m_wordBounds;
     int m_startBase;
     int m_endBase;
     int m_layerId;
@@ -86,6 +96,8 @@ private:
     bool m_flipped;
     bool m_hitTopLeft;
     bool m_startSelection;
+    bool m_wordSelection;
+    bool m_outsideWord;
 };
 
 }