summaryrefslogtreecommitdiffstats
path: root/Source/WebKit/android/nav/SelectText.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebKit/android/nav/SelectText.cpp')
-rw-r--r--Source/WebKit/android/nav/SelectText.cpp1983
1 files changed, 1983 insertions, 0 deletions
diff --git a/Source/WebKit/android/nav/SelectText.cpp b/Source/WebKit/android/nav/SelectText.cpp
new file mode 100644
index 0000000..f8ea799
--- /dev/null
+++ b/Source/WebKit/android/nav/SelectText.cpp
@@ -0,0 +1,1983 @@
+/*
+ * Copyright 2008, The Android Open Source Project
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define LOG_TAG "webviewglue"
+
+#include "CachedPrefix.h"
+#include "BidiResolver.h"
+#include "CachedRoot.h"
+#include "LayerAndroid.h"
+#include "ParseCanvas.h"
+#include "SelectText.h"
+#include "SkBitmap.h"
+#include "SkBounder.h"
+#include "SkGradientShader.h"
+#include "SkMatrix.h"
+#include "SkPicture.h"
+#include "SkPixelXorXfermode.h"
+#include "SkPoint.h"
+#include "SkRect.h"
+#include "SkRegion.h"
+#include "SkUtils.h"
+#include "TextRun.h"
+
+#ifdef DEBUG_NAV_UI
+#include <wtf/text/CString.h>
+#endif
+
+#define VERBOSE_LOGGING 0
+// #define EXTRA_NOISY_LOGGING 1
+
+// TextRunIterator has been copied verbatim from GraphicsContext.cpp
+namespace WebCore {
+
+class TextRunIterator {
+public:
+ TextRunIterator()
+ : m_textRun(0)
+ , m_offset(0)
+ {
+ }
+
+ TextRunIterator(const TextRun* textRun, unsigned offset)
+ : m_textRun(textRun)
+ , m_offset(offset)
+ {
+ }
+
+ TextRunIterator(const TextRunIterator& other)
+ : m_textRun(other.m_textRun)
+ , m_offset(other.m_offset)
+ {
+ }
+
+ unsigned offset() const { return m_offset; }
+ void increment() { m_offset++; }
+ bool atEnd() const { return !m_textRun || m_offset >= m_textRun->length(); }
+ UChar current() const { return (*m_textRun)[m_offset]; }
+ WTF::Unicode::Direction direction() const { return atEnd() ? WTF::Unicode::OtherNeutral : WTF::Unicode::direction(current()); }
+
+ bool operator==(const TextRunIterator& other)
+ {
+ return m_offset == other.m_offset && m_textRun == other.m_textRun;
+ }
+
+ bool operator!=(const TextRunIterator& other) { return !operator==(other); }
+
+private:
+ const TextRun* m_textRun;
+ int m_offset;
+};
+
+// ReverseBidi is a trimmed-down version of GraphicsContext::drawBidiText()
+void ReverseBidi(UChar* chars, int len) {
+ using namespace WTF::Unicode;
+ WTF::Vector<UChar> result;
+ result.reserveCapacity(len);
+ TextRun run(chars, len);
+ BidiResolver<TextRunIterator, BidiCharacterRun> bidiResolver;
+ bidiResolver.setStatus(BidiStatus(LeftToRight, LeftToRight, LeftToRight,
+ BidiContext::create(0, LeftToRight, false)));
+ bidiResolver.setPosition(TextRunIterator(&run, 0));
+ bidiResolver.createBidiRunsForLine(TextRunIterator(&run, len));
+ if (!bidiResolver.runCount())
+ return;
+ BidiCharacterRun* bidiRun = bidiResolver.firstRun();
+ while (bidiRun) {
+ int bidiStart = bidiRun->start();
+ int bidiStop = bidiRun->stop();
+ int size = result.size();
+ int bidiCount = bidiStop - bidiStart;
+ result.append(chars + bidiStart, bidiCount);
+ if (bidiRun->level() % 2) {
+ UChar* start = &result[size];
+ UChar* end = start + bidiCount;
+ // reverse the order of any RTL substrings
+ while (start < end) {
+ UChar temp = *start;
+ *start++ = *--end;
+ *end = temp;
+ }
+ start = &result[size];
+ end = start + bidiCount - 1;
+ // if the RTL substring had a surrogate pair, restore its order
+ while (start < end) {
+ UChar trail = *start++;
+ if (!U16_IS_SURROGATE(trail))
+ continue;
+ start[-1] = *start; // lead
+ *start++ = trail;
+ }
+ }
+ bidiRun = bidiRun->next();
+ }
+ bidiResolver.deleteRuns();
+ memcpy(chars, &result[0], len * sizeof(UChar));
+}
+
+}
+
+namespace android {
+
+#define HYPHEN_MINUS 0x2D // ASCII hyphen
+#define SOLIDUS 0x2F // ASCII slash
+#define REVERSE_SOLIDUS 0x5C // ASCII backslash
+#define HYPHEN 0x2010 // unicode hyphen, first in range of dashes
+#define HORZ_BAR 0x2015 // unicode horizontal bar, last in range of dashes
+#define TOUCH_SLOP 10 // additional distance from character rect when hit
+
+class CommonCheck : public SkBounder {
+public:
+ CommonCheck(const SkIRect& area)
+ : mArea(area)
+ , mLastUni(0)
+ {
+ mLastGlyph.fGlyphID = static_cast<uint16_t>(-1);
+ mLastCandidate.fGlyphID = static_cast<uint16_t>(-1);
+ mMatrix.reset();
+ reset();
+ }
+
+ /* called only while the picture is parsed */
+ int base() {
+ if (mBase == INT_MAX) {
+ SkPoint result;
+ mMatrix.mapXY(0, mY, &result);
+ mBase = SkScalarFloor(result.fY);
+ }
+ return mBase;
+ }
+
+ /* called only while the picture is parsed */
+ int bottom() {
+ if (mBottom == INT_MAX) {
+ SkPoint result;
+ SkPaint::FontMetrics metrics;
+ mPaint.getFontMetrics(&metrics);
+ mMatrix.mapXY(0, metrics.fDescent + mY, &result);
+ mBottom = SkScalarCeil(result.fY);
+ }
+ return mBottom;
+ }
+
+#if DEBUG_NAV_UI
+ // make current (possibily uncomputed) value visible for debugging
+ int bottomDebug() const
+ {
+ return mBottom;
+ }
+#endif
+
+ bool addNewLine(const SkBounder::GlyphRec& rec)
+ {
+ SkFixed lineSpacing = SkFixedAbs(mLastGlyph.fLSB.fY - rec.fLSB.fY);
+ SkFixed lineHeight = SkIntToFixed(bottom() - top());
+ return lineSpacing >= lineHeight + (lineHeight >> 1); // 1.5
+ }
+
+ bool addSpace(const SkBounder::GlyphRec& rec)
+ {
+ bool newBaseLine = mLastGlyph.fLSB.fY != rec.fLSB.fY;
+ if (((mLastUni >= HYPHEN && mLastUni <= HORZ_BAR)
+ || mLastUni == HYPHEN_MINUS || mLastUni == SOLIDUS
+ || mLastUni == REVERSE_SOLIDUS) && newBaseLine)
+ {
+ return false;
+ }
+ return isSpace(rec);
+ }
+
+ void finishGlyph()
+ {
+ mLastGlyph = mLastCandidate;
+ mLastUni = mLastUniCandidate;
+ mLastPaint = mLastPaintCandidate;
+ }
+
+ const SkIRect& getArea() const {
+ return mArea;
+ }
+
+ /* called only while the picture is parsed */
+ SkUnichar getUniChar(const SkBounder::GlyphRec& rec)
+ {
+ SkUnichar unichar;
+ SkPaint::TextEncoding save = mPaint.getTextEncoding();
+ mPaint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+ mPaint.glyphsToUnichars(&rec.fGlyphID, 1, &unichar);
+ mPaint.setTextEncoding(save);
+ return unichar;
+ }
+
+ bool isSpace(const SkBounder::GlyphRec& rec)
+ {
+ if (mLastGlyph.fGlyphID == static_cast<uint16_t>(-1))
+ return true;
+ DBG_NAV_LOGD("mLastGlyph=((%g, %g),(%g, %g), %d)"
+ " rec=((%g, %g),(%g, %g), %d) mLastUni=0x%04x '%c'",
+ SkFixedToScalar(mLastGlyph.fLSB.fX),
+ SkFixedToScalar(mLastGlyph.fLSB.fY),
+ SkFixedToScalar(mLastGlyph.fRSB.fX),
+ SkFixedToScalar(mLastGlyph.fRSB.fY), mLastGlyph.fGlyphID,
+ SkFixedToScalar(rec.fLSB.fX), SkFixedToScalar(rec.fLSB.fY),
+ SkFixedToScalar(rec.fRSB.fX), SkFixedToScalar(rec.fRSB.fY),
+ rec.fGlyphID,
+ mLastUni, mLastUni && mLastUni < 0x7f ? mLastUni : '?');
+ bool newBaseLine = mLastGlyph.fLSB.fY != rec.fLSB.fY;
+ if (newBaseLine)
+ return true;
+ SkFixed gapOne = mLastGlyph.fLSB.fX - rec.fRSB.fX;
+ SkFixed gapTwo = rec.fLSB.fX - mLastGlyph.fRSB.fX;
+ if (gapOne < 0 && gapTwo < 0)
+ return false; // overlaps
+ const SkBounder::GlyphRec& first = mLastGlyph.fLSB.fX < rec.fLSB.fX
+ ? mLastGlyph : rec;
+ const SkBounder::GlyphRec& second = mLastGlyph.fLSB.fX < rec.fLSB.fX
+ ? rec : mLastGlyph;
+ uint16_t firstGlyph = first.fGlyphID;
+ SkScalar firstWidth = mLastPaint.measureText(&firstGlyph, sizeof(firstGlyph));
+ SkFixed ceilWidth = SkIntToFixed(SkScalarCeil(firstWidth));
+ SkFixed posNoSpace = first.fLSB.fX + ceilWidth;
+ SkFixed ceilSpace = SkIntToFixed(SkFixedCeil(minSpaceWidth(mLastPaint)));
+ SkFixed posWithSpace = posNoSpace + ceilSpace;
+ SkFixed diffNoSpace = SkFixedAbs(second.fLSB.fX - posNoSpace);
+ SkFixed diffWithSpace = SkFixedAbs(second.fLSB.fX - posWithSpace);
+ DBG_NAV_LOGD("second=%g width=%g (%g) noSpace=%g (%g) withSpace=%g (%g)"
+ " fontSize=%g",
+ SkFixedToScalar(second.fLSB.fX),
+ firstWidth, SkFixedToScalar(ceilWidth),
+ SkFixedToScalar(posNoSpace), SkFixedToScalar(diffNoSpace),
+ SkFixedToScalar(posWithSpace), SkFixedToScalar(diffWithSpace),
+ mLastPaint.getTextSize());
+ return diffWithSpace <= diffNoSpace;
+ }
+
+ SkFixed minSpaceWidth(SkPaint& paint)
+ {
+ if (mMinSpaceWidth == SK_FixedMax) {
+ SkPaint::TextEncoding save = paint.getTextEncoding();
+ paint.setTextEncoding(SkPaint::kUTF8_TextEncoding);
+ SkScalar width = paint.measureText(" ", 1);
+ mMinSpaceWidth = SkScalarToFixed(width * mMatrix.getScaleX());
+ paint.setTextEncoding(save);
+ DBG_NAV_LOGV("width=%g matrix sx/sy=(%g, %g) tx/ty=(%g, %g)"
+ " mMinSpaceWidth=%g", width,
+ mMatrix.getScaleX(), mMatrix.getScaleY(),
+ mMatrix.getTranslateX(), mMatrix.getTranslateY(),
+ SkFixedToScalar(mMinSpaceWidth));
+ }
+ return mMinSpaceWidth;
+ }
+
+ void recordGlyph(const SkBounder::GlyphRec& rec)
+ {
+ mLastCandidate = rec;
+ mLastUniCandidate = getUniChar(rec);
+ mLastPaintCandidate = mPaint;
+ }
+
+ void reset()
+ {
+ mMinSpaceWidth = SK_FixedMax; // mark as uninitialized
+ mBase = mBottom = mTop = INT_MAX; // mark as uninitialized
+ }
+
+ void set(CommonCheck& check)
+ {
+ mLastGlyph = check.mLastGlyph;
+ mLastUni = check.mLastUni;
+ mMatrix = check.mMatrix;
+ mLastPaint = check.mLastPaint;
+ reset();
+ }
+
+ void setGlyph(CommonCheck& check)
+ {
+ mLastGlyph = check.mLastGlyph;
+ mLastUni = check.mLastUni;
+ mLastPaint = check.mLastPaint;
+ }
+
+ void setUp(const SkPaint& paint, const SkMatrix& matrix, SkScalar y,
+ const void* text)
+ {
+ mMatrix = matrix;
+ mPaint = paint;
+ mText = static_cast<const uint16_t*>(text);
+ mY = y;
+ reset();
+ }
+
+ /* called only while the picture is parsed */
+ int top() {
+ if (mTop == INT_MAX) {
+ SkPoint result;
+ SkPaint::FontMetrics metrics;
+ mPaint.getFontMetrics(&metrics);
+ mMatrix.mapXY(0, metrics.fAscent + mY, &result);
+ mTop = SkScalarFloor(result.fY);
+ }
+ return mTop;
+ }
+
+#if DEBUG_NAV_UI
+ // make current (possibily uncomputed) value visible for debugging
+ int topDebug() const
+ {
+ return mTop;
+ }
+#endif
+
+protected:
+ SkIRect mArea;
+ SkBounder::GlyphRec mLastCandidate;
+ SkBounder::GlyphRec mLastGlyph;
+ SkPaint mLastPaint; // available after picture has been parsed
+ SkPaint mLastPaintCandidate; // associated with candidate glyph
+ SkUnichar mLastUni;
+ SkUnichar mLastUniCandidate;
+ SkMatrix mMatrix;
+ SkPaint mPaint; // only set up while the picture is parsed
+ const uint16_t* mText;
+ SkScalar mY;
+private:
+ int mBase;
+ int mBottom;
+ SkFixed mMinSpaceWidth;
+ int mTop;
+ friend class EdgeCheck;
+};
+
+// 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)
+ {
+ if (!mParagraphs.count() && mLast.isEmpty())
+ return;
+ 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;
+ }
+
+ bool inColumn(int x, int y) const
+ {
+ for (int index = 0; index < mSelected.count(); index++) {
+ const SkIRect& rect = mSelected[index];
+ if (rect.contains(x, y))
+ return true;
+ }
+ return false;
+ }
+
+ virtual bool onIRect(const SkIRect& rect)
+ {
+ 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(mPaint)
+ || 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;
+ bool mInBetween;
+private:
+ typedef CommonCheck INHERITED;
+};
+
+class SelectText::FirstCheck : public CommonCheck {
+public:
+ FirstCheck(int x, int y, const SkIRect& area)
+ : INHERITED(area)
+ , mLineCheck(0)
+ , mFocusX(x - area.fLeft)
+ , mFocusY(y - area.fTop)
+ , mBestInColumn(false)
+ , mRecordGlyph(false)
+ {
+ reset();
+ }
+
+ const SkIRect& adjustedBounds(int* base)
+ {
+ *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)
+ {
+ /* compute distance from rectangle center.
+ * centerX = (rect.L + rect.R) / 2
+ * multiply centerX and comparison x by 2 to retain better precision
+ */
+ SkIRect testBounds = {rect.fLeft, top(), rect.fRight, bottom()};
+ // dx and dy are the distances from the tested edge
+ // The edge distance is paramount if the test point is far away
+ int dx = std::max(0, std::max(testBounds.fLeft - mFocusX,
+ mFocusX - testBounds.fRight));
+ int dy = std::max(0, std::max(testBounds.fTop - mFocusY,
+ mFocusY - testBounds.fBottom));
+ bool testInColumn = false;
+ bool inBetween = false;
+ bool inFocus = false;
+ if (mLineCheck) {
+ testInColumn = mLineCheck->inColumn(testBounds);
+ inBetween = mLineCheck->inBetween();
+ inFocus = mLineCheck->inColumn(mFocusX, mFocusY);
+ }
+#ifdef EXTRA_NOISY_LOGGING
+ if (dy < 10) {
+ SkUnichar ch = getUniChar(rec);
+ DBG_NAV_LOGD("FC dx/y=%d,%d mDx/y=%d,%d test=%d,%d,%d,%d"
+ " best=%d,%d,%d,%d bestIn=%s tween=%s testIn=%s focus=%s ch=%c",
+ dx, dy, mDx, mDy,
+ testBounds.fLeft, testBounds.fTop, testBounds.fRight,
+ testBounds.fBottom, mBestBounds.fLeft, mBestBounds.fTop,
+ mBestBounds.fRight, mBestBounds.fBottom,
+ mBestInColumn ? "true" : "false", inBetween ? "true" : "false",
+ testInColumn ? "true" : "false", inFocus ? "true" : "false",
+ ch < 0x7f ? ch : '?');
+ }
+#endif
+ if ((mBestInColumn || inBetween) && !testInColumn) {
+#ifdef EXTRA_NOISY_LOGGING
+ if (dy < 10) DBG_NAV_LOG("FirstCheck reject column");
+#endif
+ return false;
+ }
+ bool ignoreColumn = mBestInColumn == testInColumn || !inFocus;
+ if (ignoreColumn && dy > 0 && (mDy < dy
+ || (mDy == dy && dx > 0 && mDx <= dx))) {
+#ifdef EXTRA_NOISY_LOGGING
+ if (dy < 10) DBG_NAV_LOG("FirstCheck reject edge");
+#endif
+ return false;
+ }
+ // cx and cy are the distances from the tested center
+ // The center distance is used when the test point is over the text
+ int cx = std::abs(((testBounds.fLeft + testBounds.fRight) >> 1)
+ - mFocusX);
+ int cy = std::abs(((testBounds.fTop + testBounds.fBottom) >> 1)
+ - mFocusY);
+ if (ignoreColumn && dy == 0 && mDy == 0) {
+ if (mCy < cy) {
+#ifdef EXTRA_NOISY_LOGGING
+ DBG_NAV_LOGD("FirstCheck reject cy=%d mCy=%d", cy, mCy);
+#endif
+ return false;
+ }
+ if (mCy == cy) {
+ if (dx == 0 && mDx == 0) {
+ if (mCx < cx) {
+#ifdef EXTRA_NOISY_LOGGING
+ DBG_NAV_LOGD("FirstCheck reject cx=%d mCx=%d", cx, mCx);
+#endif
+ return false;
+ }
+ } else if (dx > 0 && mDx <= dx) {
+#ifdef EXTRA_NOISY_LOGGING
+ DBG_NAV_LOGD("FirstCheck reject dx=%d mDx=%d", dx, mDx);
+#endif
+ return false;
+ }
+ }
+ }
+#ifdef EXTRA_NOISY_LOGGING
+ if (dy < 10) {
+ DBG_NAV_LOGD("FirstCheck cx/y=(%d,%d)", cx, cy);
+ }
+#endif
+ mBestBase = base();
+ mBestBounds = testBounds;
+ mBestInColumn = testInColumn;
+#ifndef EXTRA_NOISY_LOGGING
+ if (dy < 10 && dx < 10)
+#endif
+ {
+#if DEBUG_NAV_UI
+ SkUnichar ch = getUniChar(rec);
+#endif
+ DBG_NAV_LOGD("FirstCheck dx/y=(%d,%d) mFocus=(%d,%d)"
+ " mBestBounds={%d,%d,r=%d,b=%d} inColumn=%s ch=%c",
+ dx, dy, mFocusX, mFocusY,
+ mBestBounds.fLeft, mBestBounds.fTop,
+ mBestBounds.fRight, mBestBounds.fBottom,
+ mBestInColumn ? "true" : "false", ch < 0x7f ? ch : '?');
+ }
+ mCx = cx;
+ mCy = cy;
+ mDx = dx;
+ mDy = dy;
+ if (mRecordGlyph)
+ recordGlyph(rec);
+ return false;
+ }
+
+ void reset()
+ {
+ mBestBounds.setEmpty();
+ mDx = mDy = mCx = mCy = INT_MAX;
+ }
+
+ void setLines(const LineCheck* lineCheck) { mLineCheck = lineCheck; }
+ void setRecordGlyph() { mRecordGlyph = true; }
+
+protected:
+ const LineCheck* mLineCheck;
+ int mBestBase;
+ SkIRect mBestBounds;
+ int mCx;
+ int mCy;
+ int mDx;
+ int mDy;
+ int mFocusX;
+ int mFocusY;
+ bool mBestInColumn;
+ bool mRecordGlyph;
+private:
+ typedef CommonCheck INHERITED;
+};
+
+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)
+ , mLeft(left)
+ {
+ mLast.set(last); // CommonCheck::set()
+ setGlyph(last);
+ }
+
+ bool adjacent()
+ {
+ return !mLast.isSpace(mLastGlyph);
+ }
+
+ const SkIRect& bestBounds(int* base)
+ {
+ *base = mBestBase;
+ return mBestBounds;
+ }
+
+ virtual bool onIRectGlyph(const SkIRect& rect,
+ const SkBounder::GlyphRec& rec)
+ {
+ int dx = mLeft ? mFocusX - rect.fRight : rect.fLeft - mFocusX;
+ int dy = ((top() + bottom()) >> 1) - mFocusY;
+ dx = abs(dx);
+ dy = abs(dy);
+ if (mLeft ? mFocusX <= rect.fLeft : mFocusX >= rect.fRight) {
+ if (dx <= 10 && dy <= 10) {
+ DBG_NAV_LOGD("EdgeCheck fLeft=%d fRight=%d mFocusX=%d dx=%d dy=%d",
+ rect.fLeft, rect.fRight, mFocusX, dx, dy);
+ }
+ return false;
+ }
+ if (mDy > dy || (mDy == dy && mDx > dx)) {
+ if (rec.fLSB == mLastGlyph.fLSB && rec.fRSB == mLastGlyph.fRSB) {
+ DBG_NAV_LOGD("dup rec.fLSB.fX=%g rec.fRSB.fX=%g",
+ SkFixedToScalar(rec.fLSB.fX), SkFixedToScalar(rec.fRSB.fX));
+ return false;
+ }
+ recordGlyph(rec);
+ mDx = dx;
+ mDy = dy;
+ mBestBase = base();
+ mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom());
+ if (dx <= 10 && dy <= 10) {
+ DBG_NAV_LOGD("EdgeCheck mBestBounds={%d,%d,r=%d,b=%d} dx/y=(%d, %d)",
+ mBestBounds.fLeft, mBestBounds.fTop,
+ mBestBounds.fRight, mBestBounds.fBottom, dx, dy);
+ }
+ }
+ return false;
+ }
+
+ void shiftStart(SkIRect bounds)
+ {
+ DBG_NAV_LOGD("EdgeCheck mFocusX=%d mLeft=%s bounds.fLeft=%d bounds.fRight=%d",
+ mFocusX, mLeft ? "true" : "false", bounds.fLeft, bounds.fRight);
+ reset();
+ mFocusX = mLeft ? bounds.fLeft : bounds.fRight;
+ mLast.set(*this); // CommonCheck::set()
+ }
+
+protected:
+ CommonCheck mLast;
+ bool mLeft;
+private:
+ typedef SelectText::FirstCheck INHERITED;
+};
+
+class FindFirst : public CommonCheck {
+public:
+ FindFirst(const SkIRect& area)
+ : INHERITED(area)
+ {
+ mBestBounds.set(area.width(), area.height(), area.width(), area.height());
+ }
+
+ const SkIRect& bestBounds(int* base)
+ {
+ *base = mBestBase;
+ return mBestBounds;
+ }
+
+ virtual bool onIRect(const SkIRect& rect)
+ {
+ if (mBestBounds.isEmpty()) {
+ mBestBase = base();
+ mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom());
+ }
+ return false;
+ }
+
+protected:
+ int mBestBase;
+ SkIRect mBestBounds;
+private:
+ typedef CommonCheck INHERITED;
+};
+
+class FindLast : public FindFirst {
+public:
+ FindLast(const SkIRect& area)
+ : INHERITED(area)
+ {
+ mBestBounds.setEmpty();
+ }
+
+ virtual bool onIRect(const SkIRect& rect)
+ {
+ mBestBase = base();
+ mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom());
+ return false;
+ }
+
+private:
+ typedef FindFirst INHERITED;
+};
+
+static bool baseLinesAgree(const SkIRect& rectA, int baseA,
+ const SkIRect& rectB, int baseB)
+{
+ return (rectA.fTop < baseB && rectA.fBottom >= baseB)
+ || (rectB.fTop < baseA && rectB.fBottom >= baseA);
+}
+
+class BuilderCheck : public CommonCheck {
+protected:
+ enum IntersectionType {
+ NO_INTERSECTION, // debugging printf expects this to equal zero
+ LAST_INTERSECTION, // debugging printf expects this to equal one
+ WAIT_FOR_INTERSECTION
+ };
+
+ BuilderCheck(const SkIRect& start, int startBase, const SkIRect& end,
+ int endBase, const SkIRect& area)
+ : INHERITED(area)
+ , mCapture(false)
+ , mEnd(end)
+ , mEndBase(endBase)
+ , mStart(start)
+ , mStartBase(startBase)
+ {
+ mEnd.offset(-area.fLeft, -area.fTop);
+ mEndBase -= area.fTop;
+ mEndExtra.setEmpty();
+ mLast.setEmpty();
+ mLastBase = INT_MAX;
+ mSelectRect.setEmpty();
+ mStart.offset(-area.fLeft, -area.fTop);
+ mStartBase -= area.fTop;
+ mStartExtra.setEmpty();
+ DBG_NAV_LOGD(" mStart=(%d,%d,r=%d,b=%d) mStartBase=%d"
+ " mEnd=(%d,%d,r=%d,b=%d) mEndBase=%d",
+ mStart.fLeft, mStart.fTop, mStart.fRight, mStart.fBottom, mStartBase,
+ mEnd.fLeft, mEnd.fTop, mEnd.fRight, mEnd.fBottom, mEndBase);
+ }
+
+ int checkFlipRect(const SkIRect& full, int fullBase) {
+ mCollectFull = false;
+ // is the text to collect between the selection top and bottom?
+ if (fullBase < mStart.fTop || fullBase > mEnd.fBottom) {
+ if (VERBOSE_LOGGING && !mLast.isEmpty()) DBG_NAV_LOGD("%s 1"
+ " full=(%d,%d,r=%d,b=%d) fullBase=%d"
+ " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d",
+ mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION",
+ full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase,
+ mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase);
+ return mLastIntersects;
+ }
+ // is the text to the left of the selection start?
+ if (baseLinesAgree(mStart, mStartBase, full, fullBase)
+ && full.fLeft < mStart.fLeft) {
+ if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 2"
+ " full=(%d,%d,r=%d,b=%d) fullBase=%d"
+ " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d"
+ " mStart=(%d,%d,r=%d,b=%d) mStartBase=%d",
+ mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION",
+ full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase,
+ mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase,
+ mStart.fLeft, mStart.fTop, mStart.fRight, mStart.fBottom, mStartBase);
+ mStartExtra.join(full);
+ return mLastIntersects;
+ }
+ // is the text to the right of the selection end?
+ if (baseLinesAgree(mEnd, mEndBase, full, fullBase)
+ && full.fRight > mEnd.fRight) {
+ if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 3"
+ " full=(%d,%d,r=%d,b=%d) fullBase=%d"
+ " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d"
+ " mEnd=(%d,%d,r=%d,b=%d) mEndBase=%d",
+ mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION",
+ full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase,
+ mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase,
+ mEnd.fLeft, mEnd.fTop, mEnd.fRight, mEnd.fBottom, mEndBase);
+ mEndExtra.join(full);
+ return mLastIntersects;
+ }
+ int spaceGap = SkFixedRound(minSpaceWidth(mPaint) * 3);
+ // should text to the left of the start be added to the selection bounds?
+ if (!mStartExtra.isEmpty()) {
+ if (VERBOSE_LOGGING) DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)"
+ " mStartExtra=(%d,%d,r=%d,b=%d)",
+ mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom,
+ mStartExtra.fLeft, mStartExtra.fTop, mStartExtra.fRight, mStartExtra.fBottom);
+ if (mStartExtra.fRight + spaceGap >= mStart.fLeft)
+ mSelectRect.join(mStartExtra);
+ mStartExtra.setEmpty();
+ }
+ // should text to the right of the end be added to the selection bounds?
+ if (!mEndExtra.isEmpty()) {
+ if (VERBOSE_LOGGING) DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)"
+ " mEndExtra=(%d,%d,r=%d,b=%d)",
+ mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom,
+ mEndExtra.fLeft, mEndExtra.fTop, mEndExtra.fRight, mEndExtra.fBottom);
+ if (mEndExtra.fLeft - spaceGap <= mEnd.fRight)
+ mSelectRect.join(mEndExtra);
+ mEndExtra.setEmpty();
+ }
+ bool sameBaseLine = baseLinesAgree(mLast, mLastBase, full, fullBase);
+ bool adjacent = (full.fLeft - mLast.fRight) < spaceGap;
+ // is this the first, or are there more characters on the same line?
+ if (mLast.isEmpty() || (sameBaseLine && adjacent)) {
+ if (VERBOSE_LOGGING) DBG_NAV_LOGD("WAIT_FOR_INTERSECTION"
+ " full=(%d,%d,r=%d,b=%d) fullBase=%d"
+ " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d"
+ " mSelectRect=(%d,%d,r=%d,b=%d)",
+ full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase,
+ mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase,
+ mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom);
+ mLast.join(full);
+ mLastIntersects = SkIRect::Intersects(mLast, mSelectRect);
+ return WAIT_FOR_INTERSECTION;
+ }
+ if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 4"
+ " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d"
+ " full=(%d,%d,r=%d,b=%d) fullBase=%d"
+ " mSelectRect=(%d,%d,r=%d,b=%d)"
+ " mStartExtra=(%d,%d,r=%d,b=%d)"
+ " mEndExtra=(%d,%d,r=%d,b=%d)",
+ mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION",
+ mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase,
+ full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase,
+ mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom,
+ mStartExtra.fLeft, mStartExtra.fTop, mStartExtra.fRight, mStartExtra.fBottom,
+ mEndExtra.fLeft, mEndExtra.fTop, mEndExtra.fRight, mEndExtra.fBottom);
+ // after the caller determines what to do with the last collection,
+ // start the collection over with full and fullBase.
+ mCollectFull = true;
+ return mLastIntersects;
+ }
+
+ bool resetLast(const SkIRect& full, int fullBase)
+ {
+ if (mCollectFull) {
+ mLast = full;
+ mLastBase = fullBase;
+ mLastIntersects = SkIRect::Intersects(mLast, mSelectRect);
+ } else {
+ mLast.setEmpty();
+ mLastBase = INT_MAX;
+ mLastIntersects = false;
+ }
+ return mCollectFull;
+ }
+
+ void setFlippedState()
+ {
+ mSelectRect = mStart;
+ mSelectRect.join(mEnd);
+ DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)",
+ mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom);
+ mLast.setEmpty();
+ mLastBase = INT_MAX;
+ mLastIntersects = NO_INTERSECTION;
+ }
+
+ bool mCapture;
+ bool mCollectFull;
+ SkIRect mEnd;
+ int mEndBase;
+ SkIRect mEndExtra;
+ bool mFlipped;
+ SkIRect mLast;
+ int mLastBase;
+ int mLastIntersects;
+ SkIRect mSelectRect;
+ SkIRect mStart;
+ SkIRect mStartExtra;
+ int mStartBase;
+private:
+ typedef CommonCheck INHERITED;
+
+};
+
+class MultilineBuilder : public BuilderCheck {
+public:
+ MultilineBuilder(const SkIRect& start, int startBase, const SkIRect& end,
+ int endBase, const SkIRect& area, SkRegion* region)
+ : INHERITED(start, startBase, end, endBase, area)
+ , mSelectRegion(region)
+ {
+ mFlipped = false;
+ }
+
+ void addLastToRegion() {
+ if (VERBOSE_LOGGING) DBG_NAV_LOGD(" mLast=(%d,%d,r=%d,b=%d)",
+ mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom);
+ mSelectRegion->op(mLast, SkRegion::kUnion_Op);
+ }
+
+ void finish() {
+ if (!mFlipped || !mLastIntersects)
+ return;
+ addLastToRegion();
+ }
+
+ // return true if capture end was not found after capture begin
+ bool flipped() {
+ DBG_NAV_LOGD("flipped=%s", mCapture ? "true" : "false");
+ if (!mCapture)
+ return false;
+ mFlipped = true;
+ setFlippedState();
+ mSelectRegion->setEmpty();
+ return true;
+ }
+
+ virtual bool onIRect(const SkIRect& rect) {
+ SkIRect full;
+ full.set(rect.fLeft, top(), rect.fRight, bottom());
+ int fullBase = base();
+ if (mFlipped) {
+ int intersectType = checkFlipRect(full, fullBase);
+ if (intersectType == LAST_INTERSECTION)
+ addLastToRegion();
+ if (intersectType != WAIT_FOR_INTERSECTION)
+ resetLast(full, fullBase);
+ return false;
+ }
+ if (full == mStart) {
+ if (VERBOSE_LOGGING) DBG_NAV_LOGD("full == mStart full=(%d,%d,r=%d,b=%d)",
+ full.fLeft, full.fTop, full.fRight, full.fBottom);
+ mCapture = true;
+ }
+ if (mCapture) {
+ bool sameLines = baseLinesAgree(mLast, mLastBase, full, fullBase);
+ if (sameLines)
+ mLast.join(full);
+ if (!sameLines || full == mEnd) {
+ if (VERBOSE_LOGGING) DBG_NAV_LOGD("finish mLast=(%d,%d,r=%d,b=%d)",
+ mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom);
+ addLastToRegion();
+ mLast = full;
+ mLastBase = fullBase;
+ }
+ }
+ if (full == mEnd) {
+ if (VERBOSE_LOGGING) DBG_NAV_LOGD("full == mEnd full=(%d,%d,r=%d,b=%d)",
+ full.fLeft, full.fTop, full.fRight, full.fBottom);
+ mCapture = false;
+ if (full == mStart)
+ addLastToRegion();
+ }
+ return false;
+ }
+
+protected:
+ SkRegion* mSelectRegion;
+private:
+ typedef BuilderCheck INHERITED;
+};
+
+static inline bool compareBounds(const SkIRect* first, const SkIRect* second)
+{
+ return first->fTop < second->fTop;
+}
+
+class TextExtractor : public BuilderCheck {
+public:
+ TextExtractor(const SkIRect& start, int startBase, const SkIRect& end,
+ int endBase, const SkIRect& area, bool flipped)
+ : INHERITED(start, startBase, end, endBase, area)
+ , mSelectStartIndex(-1)
+ , mSkipFirstSpace(true) // don't start with a space
+ {
+ mFlipped = flipped;
+ if (flipped)
+ setFlippedState();
+ }
+
+ void addCharacter(const SkBounder::GlyphRec& rec)
+ {
+ if (mSelectStartIndex < 0)
+ mSelectStartIndex = mSelectText.count();
+ if (!mSkipFirstSpace) {
+ if (addNewLine(rec)) {
+ DBG_NAV_LOG("write new line");
+ *mSelectText.append() = '\n';
+ *mSelectText.append() = '\n';
+ } else if (addSpace(rec)) {
+ DBG_NAV_LOG("write space");
+ *mSelectText.append() = ' ';
+ }
+ } else
+ mSkipFirstSpace = false;
+ recordGlyph(rec);
+ finishGlyph();
+ if (VERBOSE_LOGGING) DBG_NAV_LOGD("glyphID=%d uni=%d '%c'", rec.fGlyphID,
+ mLastUni, mLastUni && mLastUni < 0x7f ? mLastUni : '?');
+ if (mLastUni) {
+ uint16_t chars[2];
+ size_t count = SkUTF16_FromUnichar(mLastUni, chars);
+ *mSelectText.append() = chars[0];
+ if (count == 2)
+ *mSelectText.append() = chars[1];
+ }
+ }
+
+ void addLast()
+ {
+ *mSelectBounds.append() = mLast;
+ *mSelectStart.append() = mSelectStartIndex;
+ *mSelectEnd.append() = mSelectText.count();
+ }
+
+ /* Text characters are collected before it's been determined that the
+ characters are part of the selection. The bounds describe valid parts
+ of the selection, but the bounds are out of order.
+
+ This sorts the characters by sorting the bounds, then copying the
+ characters that were captured.
+ */
+ void finish()
+ {
+ if (mLastIntersects)
+ addLast();
+ Vector<SkIRect*> sortedBounds;
+ SkTDArray<uint16_t> temp;
+ int index;
+ DBG_NAV_LOGD("mSelectBounds.count=%d text=%d", mSelectBounds.count(),
+ mSelectText.count());
+ for (index = 0; index < mSelectBounds.count(); index++)
+ sortedBounds.append(&mSelectBounds[index]);
+ std::sort(sortedBounds.begin(), sortedBounds.end(), compareBounds);
+ int lastEnd = -1;
+ for (index = 0; index < mSelectBounds.count(); index++) {
+ int order = sortedBounds[index] - &mSelectBounds[0];
+ int start = mSelectStart[order];
+ int end = mSelectEnd[order];
+ DBG_NAV_LOGD("order=%d start=%d end=%d top=%d", order, start, end,
+ mSelectBounds[order].fTop);
+ int count = temp.count();
+ if (count > 0 && temp[count - 1] != '\n' && start != lastEnd) {
+ // always separate paragraphs when original text is out of order
+ DBG_NAV_LOG("write new line");
+ *temp.append() = '\n';
+ *temp.append() = '\n';
+ }
+ temp.append(end - start, &mSelectText[start]);
+ lastEnd = end;
+ }
+ mSelectText.swap(temp);
+ }
+
+ virtual bool onIRectGlyph(const SkIRect& rect,
+ const SkBounder::GlyphRec& rec)
+ {
+ SkIRect full;
+ full.set(rect.fLeft, top(), rect.fRight, bottom());
+ int fullBase = base();
+ if (mFlipped) {
+ int intersectType = checkFlipRect(full, fullBase);
+ if (WAIT_FOR_INTERSECTION == intersectType)
+ addCharacter(rec); // may not be copied
+ else {
+ if (LAST_INTERSECTION == intersectType)
+ addLast();
+ else
+ mSkipFirstSpace = true;
+ mSelectStartIndex = -1;
+ if (resetLast(full, fullBase))
+ addCharacter(rec); // may not be copied
+ }
+ return false;
+ }
+ if (full == mStart)
+ mCapture = true;
+ if (mCapture)
+ addCharacter(rec);
+ else
+ mSkipFirstSpace = true;
+ if (full == mEnd)
+ mCapture = false;
+ return false;
+ }
+
+ WTF::String text() {
+ if (mFlipped)
+ finish();
+ // the text has been copied in visual order. Reverse as needed if
+ // result contains right-to-left characters.
+ const uint16_t* start = mSelectText.begin();
+ const uint16_t* end = mSelectText.end();
+ while (start < end) {
+ SkUnichar ch = SkUTF16_NextUnichar(&start);
+ WTF::Unicode::Direction charDirection = WTF::Unicode::direction(ch);
+ if (WTF::Unicode::RightToLeftArabic == charDirection
+ || WTF::Unicode::RightToLeft == charDirection) {
+ WebCore::ReverseBidi(mSelectText.begin(), mSelectText.count());
+ break;
+ }
+ }
+ return WTF::String(mSelectText.begin(), mSelectText.count());
+ }
+
+protected:
+ SkIRect mEmpty;
+ SkTDArray<SkIRect> mSelectBounds;
+ SkTDArray<int> mSelectEnd;
+ SkTDArray<int> mSelectStart;
+ int mSelectStartIndex;
+ SkTDArray<uint16_t> mSelectText;
+ bool mSkipFirstSpace;
+private:
+ typedef BuilderCheck INHERITED;
+};
+
+class TextCanvas : public ParseCanvas {
+public:
+
+ 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);
+ translate(SkIntToScalar(-area.fLeft), SkIntToScalar(-area.fTop));
+#ifdef DEBUG_NAV_UI
+ const SkIRect& clip = getTotalClip().getBounds();
+ const SkMatrix& matrix = getTotalMatrix();
+ DBG_NAV_LOGD("bitmap=(%d,%d) clip=(%d,%d,%d,%d) matrix=(%g,%g)",
+ bitmap.width(), bitmap.height(), clip.fLeft, clip.fTop,
+ clip.fRight, clip.fBottom, matrix.getTranslateX(), matrix.getTranslateY());
+#endif
+ }
+
+ virtual void drawPaint(const SkPaint& paint) {
+ }
+
+ virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[],
+ const SkPaint& paint) {
+ }
+
+ virtual void drawRect(const SkRect& rect, const SkPaint& paint) {
+ }
+
+ virtual void drawPath(const SkPath& path, const SkPaint& paint) {
+ }
+
+ virtual void commonDrawBitmap(const SkBitmap& bitmap, const SkIRect* rect,
+ const SkMatrix& matrix, const SkPaint& paint) {
+ }
+
+ virtual void drawSprite(const SkBitmap& bitmap, int left, int top,
+ const SkPaint* paint = NULL) {
+ }
+
+ virtual void drawText(const void* text, size_t byteLength, SkScalar x,
+ SkScalar y, const SkPaint& paint) {
+ mBounder.setUp(paint, getTotalMatrix(), y, text);
+ INHERITED::drawText(text, byteLength, x, y, paint);
+ }
+
+ virtual void drawPosTextH(const void* text, size_t byteLength,
+ const SkScalar xpos[], SkScalar constY,
+ const SkPaint& paint) {
+ mBounder.setUp(paint, getTotalMatrix(), constY, text);
+ INHERITED::drawPosTextH(text, byteLength, xpos, constY, paint);
+ }
+
+ virtual void drawVertices(VertexMode vmode, int vertexCount,
+ const SkPoint vertices[], const SkPoint texs[],
+ const SkColor colors[], SkXfermode* xmode,
+ const uint16_t indices[], int indexCount,
+ const SkPaint& paint) {
+ }
+
+ CommonCheck& mBounder;
+private:
+ typedef ParseCanvas INHERITED;
+};
+
+static bool buildSelection(const SkPicture& picture, const SkIRect& area,
+ const SkIRect& selStart, int startBase,
+ const SkIRect& selEnd, int endBase, SkRegion* region)
+{
+ DBG_NAV_LOGD("area=(%d, %d, %d, %d) selStart=(%d, %d, %d, %d)"
+ " selEnd=(%d, %d, %d, %d)",
+ area.fLeft, area.fTop, area.fRight, area.fBottom,
+ 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);
+ checker.drawPicture(const_cast<SkPicture&>(picture));
+ bool flipped = builder.flipped();
+ if (flipped) {
+ TextCanvas checker(&builder);
+ checker.drawPicture(const_cast<SkPicture&>(picture));
+ }
+ builder.finish();
+ region->translate(area.fLeft, area.fTop);
+ return flipped;
+}
+
+static SkIRect findFirst(const SkPicture& picture, int* base)
+{
+ SkIRect area;
+ area.set(0, 0, picture.width(), picture.height());
+ FindFirst finder(area);
+ TextCanvas checker(&finder);
+ checker.drawPicture(const_cast<SkPicture&>(picture));
+ return finder.bestBounds(base);
+}
+
+static SkIRect findLast(const SkPicture& picture, int* base)
+{
+ SkIRect area;
+ area.set(0, 0, picture.width(), picture.height());
+ FindLast finder(area);
+ TextCanvas checker(&finder);
+ checker.drawPicture(const_cast<SkPicture&>(picture));
+ return finder.bestBounds(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);
+ checker.drawPicture(const_cast<SkPicture&>(picture));
+ return extractor.text();
+}
+
+#define CONTROL_NOTCH 16
+#define CONTROL_HEIGHT 35
+#define CONTROL_WIDTH 21
+#define STROKE_WIDTH 1.0f
+#define STROKE_OUTSET 3.5f
+#define STROKE_I_OUTSET 4 // (int) ceil(STROKE_OUTSET)
+#define STROKE_COLOR 0x66000000
+#define OUTER_COLOR 0x33000000
+#define INNER_COLOR 0xe6aae300
+
+#define SLOP 35
+
+SelectText::SelectText()
+{
+ m_picture = 0;
+ reset();
+ SkPaint paint;
+ SkRect oval;
+
+ SkPath startOuterPath;
+ oval.set(-CONTROL_WIDTH - STROKE_OUTSET, CONTROL_NOTCH - STROKE_OUTSET,
+ -CONTROL_WIDTH + STROKE_OUTSET, CONTROL_NOTCH + STROKE_OUTSET);
+ startOuterPath.arcTo(oval, 180, 45, true);
+ oval.set(-STROKE_OUTSET, -STROKE_OUTSET, STROKE_OUTSET, STROKE_OUTSET);
+ startOuterPath.arcTo(oval, 180 + 45, 135, false);
+ oval.set(-STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET,
+ STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET);
+ startOuterPath.arcTo(oval, 0, 90, false);
+ oval.set(-CONTROL_WIDTH - STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET,
+ -CONTROL_WIDTH + STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET);
+ startOuterPath.arcTo(oval, 90, 90, false);
+ startOuterPath.close();
+ SkPath startInnerPath;
+ startInnerPath.moveTo(-CONTROL_WIDTH, CONTROL_NOTCH);
+ startInnerPath.lineTo(-CONTROL_WIDTH, CONTROL_HEIGHT);
+ startInnerPath.lineTo(0, CONTROL_HEIGHT);
+ startInnerPath.lineTo(0, 0);
+ startInnerPath.close();
+ startOuterPath.addPath(startInnerPath, 0, 0);
+
+ SkCanvas* canvas = m_startControl.beginRecording(
+ CONTROL_WIDTH + STROKE_OUTSET * 2,
+ CONTROL_HEIGHT + STROKE_OUTSET * 2);
+ paint.setAntiAlias(true);
+ paint.setColor(INNER_COLOR);
+ paint.setStyle(SkPaint::kFill_Style);
+ canvas->drawPath(startInnerPath, paint);
+ paint.setColor(OUTER_COLOR);
+ canvas->drawPath(startOuterPath, paint);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(STROKE_COLOR);
+ paint.setStrokeWidth(STROKE_WIDTH);
+ canvas->drawPath(startInnerPath, paint);
+ m_startControl.endRecording();
+
+ SkPath endOuterPath;
+ oval.set(-STROKE_OUTSET, -STROKE_OUTSET, STROKE_OUTSET, STROKE_OUTSET);
+ endOuterPath.arcTo(oval, 180, 135, true);
+ oval.set(CONTROL_WIDTH - STROKE_OUTSET, CONTROL_NOTCH - STROKE_OUTSET,
+ CONTROL_WIDTH + STROKE_OUTSET, CONTROL_NOTCH + STROKE_OUTSET);
+ endOuterPath.arcTo(oval, 360 - 45, 45, false);
+ oval.set(CONTROL_WIDTH - STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET,
+ CONTROL_WIDTH + STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET);
+ endOuterPath.arcTo(oval, 0, 90, false);
+ oval.set(-STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET,
+ STROKE_OUTSET, CONTROL_HEIGHT + STROKE_OUTSET);
+ endOuterPath.arcTo(oval, 90, 90, false);
+ startOuterPath.close();
+ SkPath endInnerPath;
+ endInnerPath.moveTo(0, 0);
+ endInnerPath.lineTo(0, CONTROL_HEIGHT);
+ endInnerPath.lineTo(CONTROL_WIDTH, CONTROL_HEIGHT);
+ endInnerPath.lineTo(CONTROL_WIDTH, CONTROL_NOTCH);
+ endInnerPath.close();
+ endOuterPath.addPath(endInnerPath, 0, 0);
+
+ canvas = m_endControl.beginRecording(CONTROL_WIDTH + STROKE_OUTSET * 2,
+ CONTROL_HEIGHT + STROKE_OUTSET * 2);
+ paint.setColor(INNER_COLOR);
+ paint.setStyle(SkPaint::kFill_Style);
+ canvas->drawPath(endInnerPath, paint);
+ paint.setColor(OUTER_COLOR);
+ canvas->drawPath(endOuterPath, paint);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(STROKE_COLOR);
+ paint.setStrokeWidth(STROKE_WIDTH);
+ canvas->drawPath(endInnerPath, paint);
+ m_endControl.endRecording();
+}
+
+SelectText::~SelectText()
+{
+ SkSafeUnref(m_picture);
+}
+
+void SelectText::draw(SkCanvas* canvas, LayerAndroid* layer, IntRect* inval)
+{
+ if (m_layerId != layer->uniqueId())
+ return;
+ // reset m_picture to match m_layerId
+ SkSafeUnref(m_picture);
+ m_picture = layer->picture();
+ SkSafeRef(m_picture);
+ DBG_NAV_LOGD("m_extendSelection=%d m_drawPointer=%d layer [%d]",
+ m_extendSelection, m_drawPointer, layer->uniqueId());
+ if (m_extendSelection)
+ drawSelectionRegion(canvas, inval);
+ if (m_drawPointer)
+ drawSelectionPointer(canvas, inval);
+}
+
+static void addInval(IntRect* inval, const SkCanvas* canvas,
+ const SkRect& bounds) {
+ const SkMatrix& matrix = canvas->getTotalMatrix();
+ SkRect transformed;
+ matrix.mapRect(&transformed, bounds);
+ SkIRect iTrans;
+ transformed.round(&iTrans);
+ inval->unite(iTrans);
+}
+
+void SelectText::drawSelectionPointer(SkCanvas* canvas, IntRect* inval)
+{
+ SkPath path;
+ if (m_extendSelection)
+ getSelectionCaret(&path);
+ else
+ getSelectionArrow(&path);
+ SkPixelXorXfermode xorMode(SK_ColorWHITE);
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kStroke_Style);
+ paint.setColor(SK_ColorBLACK);
+ if (m_extendSelection)
+ paint.setXfermode(&xorMode);
+ else
+ paint.setStrokeWidth(SK_Scalar1 * 2);
+ int sc = canvas->save();
+ canvas->scale(m_inverseScale, m_inverseScale);
+ canvas->translate(m_selectX, m_selectY);
+ canvas->drawPath(path, paint);
+ if (!m_extendSelection) {
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor(SK_ColorWHITE);
+ canvas->drawPath(path, paint);
+ }
+ SkRect bounds = path.getBounds();
+ bounds.inset(-SK_Scalar1 * 2, -SK_Scalar1 * 2); // stroke width
+ addInval(inval, canvas, bounds);
+ canvas->restoreToCount(sc);
+}
+
+static void addStart(SkRegion* diff, const SkIRect& rect)
+{
+ SkIRect bounds;
+ bounds.set(rect.fLeft - CONTROL_WIDTH - STROKE_I_OUTSET,
+ rect.fBottom - STROKE_I_OUTSET, rect.fLeft + STROKE_I_OUTSET,
+ rect.fBottom + CONTROL_HEIGHT + STROKE_I_OUTSET);
+ diff->op(bounds, SkRegion::kUnion_Op);
+}
+
+static void addEnd(SkRegion* diff, const SkIRect& rect)
+{
+ SkIRect bounds;
+ bounds.set(rect.fRight - STROKE_I_OUTSET, rect.fBottom - STROKE_I_OUTSET,
+ rect.fRight + CONTROL_WIDTH + STROKE_I_OUTSET,
+ rect.fBottom + CONTROL_HEIGHT + STROKE_I_OUTSET);
+ diff->op(bounds, SkRegion::kUnion_Op);
+}
+
+void SelectText::drawSelectionRegion(SkCanvas* canvas, IntRect* inval)
+{
+ if (!m_picture)
+ return;
+ SkIRect ivisBounds = m_visibleRect;
+ ivisBounds.join(m_selStart);
+ ivisBounds.join(m_selEnd);
+ DBG_NAV_LOGD("m_selStart=(%d,%d,r=%d,b=%d) m_selEnd=(%d,%d,r=%d,b=%d)"
+ " ivisBounds=(%d,%d,r=%d,b=%d)",
+ m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom,
+ m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom,
+ ivisBounds.fLeft, ivisBounds.fTop, ivisBounds.fRight, ivisBounds.fBottom);
+ if (m_lastSelRegion != m_selRegion)
+ m_lastSelRegion.set(m_selRegion);
+ SkRegion diff(m_lastSelRegion);
+ m_selRegion.setEmpty();
+ m_flipped = buildSelection(*m_picture, ivisBounds, m_selStart, m_startBase,
+ m_selEnd, m_endBase, &m_selRegion);
+ SkPath path;
+ m_selRegion.getBoundaryPath(&path);
+ path.setFillType(SkPath::kEvenOdd_FillType);
+
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setColor(SkColorSetARGB(0x80, 0x83, 0xCC, 0x39));
+ canvas->drawPath(path, paint);
+ // experiment to draw touchable controls that resize the selection
+ canvas->save();
+ canvas->translate(m_selStart.fLeft, m_selStart.fBottom);
+ canvas->drawPicture(m_startControl);
+ canvas->restore();
+ canvas->save();
+ canvas->translate(m_selEnd.fRight, m_selEnd.fBottom);
+ canvas->drawPicture(m_endControl);
+ canvas->restore();
+ SkIRect a = diff.getBounds();
+ SkIRect b = m_selRegion.getBounds();
+ diff.op(m_selRegion, SkRegion::kXOR_Op);
+ SkIRect c = diff.getBounds();
+ DBG_NAV_LOGD("old=(%d,%d,r=%d,b=%d) new=(%d,%d,r=%d,b=%d) diff=(%d,%d,r=%d,b=%d)",
+ a.fLeft, a.fTop, a.fRight, a.fBottom, b.fLeft, b.fTop, b.fRight, b.fBottom,
+ c.fLeft, c.fTop, c.fRight, c.fBottom);
+ DBG_NAV_LOGD("lastStart=(%d,%d,r=%d,b=%d) m_lastEnd=(%d,%d,r=%d,b=%d)",
+ m_lastStart.fLeft, m_lastStart.fTop, m_lastStart.fRight, m_lastStart.fBottom,
+ m_lastEnd.fLeft, m_lastEnd.fTop, m_lastEnd.fRight, m_lastEnd.fBottom);
+ if (!m_lastDrawnStart.isEmpty())
+ addStart(&diff, m_lastDrawnStart);
+ if (m_lastStart != m_selStart) {
+ m_lastDrawnStart = m_lastStart;
+ m_lastStart = m_selStart;
+ }
+ addStart(&diff, m_selStart);
+ if (!m_lastDrawnEnd.isEmpty())
+ addEnd(&diff, m_lastDrawnEnd);
+ if (m_lastEnd != m_selEnd) {
+ m_lastDrawnEnd = m_lastEnd;
+ m_lastEnd = m_selEnd;
+ }
+ addEnd(&diff, m_selEnd);
+ SkIRect iBounds = diff.getBounds();
+ DBG_NAV_LOGD("diff=(%d,%d,r=%d,b=%d)",
+ iBounds.fLeft, iBounds.fTop, iBounds.fRight, iBounds.fBottom);
+ SkRect bounds;
+ bounds.set(iBounds);
+ addInval(inval, canvas, bounds);
+}
+
+void SelectText::extendSelection(const IntRect& vis, int x, int y)
+{
+ if (!m_picture)
+ return;
+ setVisibleRect(vis);
+ SkIRect clipRect = m_visibleRect;
+ int base;
+ DBG_NAV_LOGD("extend x/y=%d,%d m_startOffset=%d,%d", x, y,
+ m_startOffset.fX, m_startOffset.fY);
+ x -= m_startOffset.fX;
+ y -= m_startOffset.fY;
+ if (m_startSelection) {
+ if (!clipRect.contains(x, y)
+ || !clipRect.contains(m_original.fX, m_original.fY)) {
+ clipRect.set(m_original.fX, m_original.fY, x, y);
+ clipRect.sort();
+ clipRect.inset(-m_visibleRect.width(), -m_visibleRect.height());
+ }
+ FirstCheck center(m_original.fX, m_original.fY, clipRect);
+ m_selStart = m_selEnd = findClosest(center, *m_picture, &base);
+ if (m_selStart.isEmpty())
+ return;
+ DBG_NAV_LOGD("selStart clip=(%d,%d,%d,%d) m_original=%d,%d"
+ " m_selStart=(%d,%d,%d,%d)", clipRect.fLeft, clipRect.fTop,
+ clipRect.fRight, clipRect.fBottom, m_original.fX, m_original.fY,
+ m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom);
+ m_startBase = m_endBase = base;
+ m_startSelection = false;
+ m_extendSelection = true;
+ m_original.fX = m_original.fY = 0;
+ }
+ DBG_NAV_LOGD("extend x/y=%d,%d m_original=%d,%d", x, y,
+ m_original.fX, m_original.fY);
+ x -= m_original.fX;
+ y -= m_original.fY;
+ if (!clipRect.contains(x, y) || !clipRect.contains(m_selStart)) {
+ clipRect.set(m_selStart.fLeft, m_selStart.fTop, x, y);
+ clipRect.sort();
+ clipRect.inset(-m_visibleRect.width(), -m_visibleRect.height());
+ }
+ DBG_NAV_LOGD("extend clip=(%d,%d,%d,%d) x/y=%d,%d wordSel=%s outsideWord=%s",
+ clipRect.fLeft, clipRect.fTop, clipRect.fRight, clipRect.fBottom, x, y,
+ m_wordSelection ? "true" : "false", m_outsideWord ? "true" : "false");
+ FirstCheck extension(x, y, clipRect);
+ 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",
+ m_hitTopLeft ? "m_selStart" : "m_selEnd",
+ found.fLeft, found.fTop, found.fRight, found.fBottom,
+ m_extendSelection ? "true" : "false");
+ if (m_hitTopLeft) {
+ m_startBase = base;
+ m_selStart = found;
+ } else {
+ m_endBase = base;
+ m_selEnd = found;
+ }
+ 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);
+ SkIRect sloppy = closest;
+ sloppy.inset(-TOUCH_SLOP, -TOUCH_SLOP);
+ if (!sloppy.contains(x, y)) {
+ DBG_NAV_LOGD("sloppy=(%d, %d, %d, %d) area=(%d, %d, %d, %d) x/y=%d,%d",
+ sloppy.fLeft, sloppy.fTop, sloppy.fRight, sloppy.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()) {
+ if (result.isEmpty()) {
+ *base = closestBase;
+ DBG_NAV_LOGD("closest=%d,%d,%d,%d", closest.fLeft,
+ closest.fTop, closest.fRight, closest.fBottom);
+ return closest;
+ }
+ 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)
+ return String();
+ SkIRect clipRect;
+ clipRect.set(0, 0, m_picture->width(), m_picture->height());
+ String result = text(*m_picture, clipRect, m_selStart, m_startBase,
+ m_selEnd, m_endBase, m_flipped);
+ DBG_NAV_LOGD("clip=(%d,%d,%d,%d)"
+ " m_selStart=(%d, %d, %d, %d) m_selEnd=(%d, %d, %d, %d)",
+ clipRect.fLeft, clipRect.fTop, clipRect.fRight, clipRect.fBottom,
+ m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom,
+ m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom);
+ DBG_NAV_LOGD("text=%s", result.latin1().data()); // uses CString
+ return result;
+}
+
+void SelectText::getSelectionArrow(SkPath* path)
+{
+ const int arrow[] = {
+ 0, 14, 3, 11, 5, 15, 9, 15, 7, 11, 11, 11
+ };
+ for (unsigned index = 0; index < sizeof(arrow)/sizeof(arrow[0]); index += 2)
+ path->lineTo(arrow[index], arrow[index + 1]);
+ path->close();
+}
+
+void SelectText::getSelectionCaret(SkPath* path)
+{
+ SkScalar height = m_selStart.fBottom - m_selStart.fTop;
+ SkScalar dist = height / 4;
+ path->moveTo(0, -height / 2);
+ path->rLineTo(0, height);
+ path->rLineTo(-dist, dist);
+ path->rMoveTo(0, -0.5f);
+ path->rLineTo(dist * 2, 0);
+ path->rMoveTo(0, 0.5f);
+ path->rLineTo(-dist, -dist);
+}
+
+bool SelectText::hitCorner(int cx, int cy, int x, int y) const
+{
+ SkIRect test;
+ test.set(cx, cy, cx, cy);
+ test.inset(-SLOP, -SLOP);
+ return test.contains(x, y);
+}
+
+bool SelectText::hitSelection(int x, int y) const
+{
+ x -= m_startOffset.fX;
+ y -= m_startOffset.fY;
+ int left = m_selStart.fLeft - CONTROL_WIDTH / 2;
+ int top = m_selStart.fBottom + CONTROL_HEIGHT / 2;
+ if (hitCorner(left, top, x, y))
+ return true;
+ int right = m_selEnd.fRight + CONTROL_WIDTH / 2;
+ int bottom = m_selEnd.fBottom + CONTROL_HEIGHT / 2;
+ if (hitCorner(right, bottom, x, y))
+ return true;
+ return m_selRegion.contains(x, y);
+}
+
+void SelectText::moveSelection(const IntRect& vis, int x, int y)
+{
+ if (!m_picture)
+ return;
+ x -= m_startOffset.fX;
+ y -= m_startOffset.fY;
+ setVisibleRect(vis);
+ SkIRect clipRect = m_visibleRect;
+ clipRect.join(m_selStart);
+ clipRect.join(m_selEnd);
+ FirstCheck center(x, y, clipRect);
+ int base;
+ SkIRect found = findClosest(center, *m_picture, &base);
+ if (m_hitTopLeft || !m_extendSelection) {
+ m_startBase = base;
+ m_selStart = found;
+ }
+ if (!m_hitTopLeft || !m_extendSelection) {
+ m_endBase = base;
+ m_selEnd = found;
+ }
+ swapAsNeeded();
+ DBG_NAV_LOGD("x=%d y=%d extendSelection=%s m_selStart=(%d, %d, %d, %d)"
+ " m_selEnd=(%d, %d, %d, %d)", x, y, m_extendSelection ? "true" : "false",
+ m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom,
+ m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom);
+}
+
+void SelectText::reset()
+{
+ DBG_NAV_LOG("m_extendSelection=false");
+ m_selStart.setEmpty();
+ m_lastStart.setEmpty();
+ m_lastDrawnStart.setEmpty();
+ m_selEnd.setEmpty();
+ m_lastEnd.setEmpty();
+ m_lastDrawnEnd.setEmpty();
+ m_extendSelection = false;
+ m_startSelection = false;
+ SkSafeUnref(m_picture);
+ m_picture = 0;
+ m_layerId = 0;
+}
+
+IntPoint SelectText::selectableText(const CachedRoot* root)
+{
+ int x = 0;
+ int y = 0;
+ SkPicture* picture = root->pictureAt(&x, &y, &m_layerId);
+ if (!picture) {
+ DBG_NAV_LOG("picture==0");
+ return IntPoint(0, 0);
+ }
+ int width = picture->width();
+ int height = picture->height();
+ IntRect vis(0, 0, width, height);
+ FirstCheck center(width >> 1, height >> 1, vis);
+ int base;
+ const SkIRect& closest = findClosest(center, *picture, &base);
+ return IntPoint((closest.fLeft + closest.fRight) >> 1,
+ (closest.fTop + closest.fBottom) >> 1);
+}
+
+void SelectText::selectAll()
+{
+ if (!m_picture)
+ return;
+ m_selStart = findFirst(*m_picture, &m_startBase);
+ m_selEnd = findLast(*m_picture, &m_endBase);
+ m_extendSelection = true;
+}
+
+int SelectText::selectionX() const
+{
+ return (m_hitTopLeft ? m_selStart.fLeft : m_selEnd.fRight) + m_startOffset.fX;
+}
+
+int SelectText::selectionY() const
+{
+ const SkIRect& rect = m_hitTopLeft ? m_selStart : m_selEnd;
+ return ((rect.fTop + rect.fBottom) >> 1) + m_startOffset.fY;
+}
+
+void SelectText::setVisibleRect(const IntRect& vis)
+{
+ 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;
+ m_visibleRect.offset(-m_startOffset.fX, -m_startOffset.fY);
+}
+
+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);
+ SkSafeUnref(m_picture);
+ 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;
+ }
+ m_picture->ref();
+ m_startOffset.fX -= x;
+ m_startOffset.fY -= y;
+ m_original.fX = x;
+ m_original.fY = y;
+ setVisibleRect(vis);
+ if (m_selStart.isEmpty()) {
+ DBG_NAV_LOGD("empty start picture=(%d,%d) x=%d y=%d",
+ m_picture->width(), m_picture->height(), x, y);
+ m_startSelection = true;
+ return true;
+ }
+ int left = m_selStart.fLeft - CONTROL_WIDTH / 2;
+ int top = m_selStart.fBottom + CONTROL_HEIGHT / 2;
+ m_hitTopLeft = hitCorner(left, top, x, y);
+ int right = m_selEnd.fRight + CONTROL_WIDTH / 2;
+ int bottom = m_selEnd.fBottom + CONTROL_HEIGHT / 2;
+ bool hitBottomRight = hitCorner(right, bottom, x, y);
+ DBG_NAV_LOGD("picture=(%d,%d) left=%d top=%d right=%d bottom=%d x=%d y=%d",
+ m_picture->width(), m_picture->height(),left, top, right, bottom, x, y);
+ if (m_hitTopLeft && (!hitBottomRight || y - top < bottom - y)) {
+ DBG_NAV_LOG("hit top left");
+ m_original.fX -= m_selStart.fLeft;
+ m_original.fY -= (m_selStart.fTop + m_selStart.fBottom) >> 1;
+ } else if (hitBottomRight) {
+ DBG_NAV_LOG("hit bottom right");
+ m_original.fX -= m_selEnd.fRight;
+ m_original.fY -= (m_selEnd.fTop + m_selEnd.fBottom) >> 1;
+ }
+ return m_hitTopLeft || hitBottomRight;
+}
+
+/* selects the word at (x, y)
+* a word is normally delimited by spaces
+* a string of digits (even with inside spaces) is a word (for phone numbers)
+* FIXME: digit find isn't implemented yet
+* returns true if a word was selected
+*/
+bool SelectText::wordSelection(const CachedRoot* root, const IntRect& vis,
+ int x, int y)
+{
+ IntRect tapArea = IntRect(x - TOUCH_SLOP, y - TOUCH_SLOP, TOUCH_SLOP * 2,
+ TOUCH_SLOP * 2);
+ if (!startSelection(root, tapArea, x, y))
+ return false;
+ extendSelection(tapArea, x, y);
+ if (m_selStart.isEmpty())
+ return false;
+ setDrawPointer(false);
+ setVisibleRect(vis);
+ SkIRect ivisBounds = m_visibleRect;
+ ivisBounds.join(m_selStart);
+ ivisBounds.join(m_selEnd);
+ DBG_NAV_LOGD("m_selStart=(%d,%d,r=%d,b=%d) m_selEnd=(%d,%d,r=%d,b=%d)"
+ " ivisBounds=(%d,%d,r=%d,b=%d)",
+ m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom,
+ m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom,
+ ivisBounds.fLeft, ivisBounds.fTop, ivisBounds.fRight, ivisBounds.fBottom);
+ m_selRegion.setEmpty();
+ buildSelection(*m_picture, ivisBounds, m_selStart, m_startBase,
+ m_selEnd, m_endBase, &m_selRegion);
+ x = m_selStart.fLeft;
+ y = (m_selStart.fTop + m_selStart.fBottom) >> 1;
+ SkIRect clipRect = m_visibleRect;
+ clipRect.fLeft -= m_visibleRect.width() >> 1;
+ clipRect.fLeft = std::max(clipRect.fLeft, 0);
+ int base;
+ SkIRect left = findLeft(*m_picture, clipRect, x, y, &base);
+ if (!left.isEmpty()) {
+ m_startBase = base;
+ m_selStart = left;
+ }
+ x = m_selEnd.fRight;
+ y = (m_selEnd.fTop + m_selEnd.fBottom) >> 1;
+ clipRect = m_visibleRect;
+ clipRect.fRight += m_visibleRect.width() >> 1;
+ SkIRect right = findRight(*m_picture, clipRect, x, y, &base);
+ if (!right.isEmpty()) {
+ m_endBase = base;
+ m_selEnd = right;
+ }
+ DBG_NAV_LOGD("m_selStart=(%d, %d, %d, %d) m_selEnd=(%d, %d, %d, %d)",
+ 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_wordBounds = m_selStart;
+ m_wordBounds.join(m_selEnd);
+ m_extendSelection = m_wordSelection = true;
+ m_outsideWord = false;
+ return true;
+ }
+ return false;
+}
+
+void SelectText::swapAsNeeded()
+{
+ if (m_selStart.fTop >= (m_selEnd.fTop + m_selEnd.fBottom) >> 1
+ || (m_selEnd.fTop < (m_selStart.fTop + m_selStart.fBottom) >> 1
+ && m_selStart.fRight > m_selEnd.fLeft))
+ {
+ SkTSwap(m_startBase, m_endBase);
+ SkTSwap(m_selStart, m_selEnd);
+ m_hitTopLeft ^= true;
+ DBG_NAV_LOGD("m_hitTopLeft=%s", m_hitTopLeft ? "true" : "false");
+ }
+}
+
+}