blob: d20c44a526fc516ad3626635edaec5e74aa57fc4 [file] [log] [blame]
/*
* 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 "BidiRunList.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
#define DEBUG_TOUCH_HANDLES 0
#if DEBUG_TOUCH_HANDLES
#define DBG_HANDLE_LOG(format, ...) LOGD("%s " format, __FUNCTION__, __VA_ARGS__)
#else
#define DBG_HANDLE_LOG(...)
#endif
// 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;
BidiRunList<BidiCharacterRun>& bidiRuns = bidiResolver.runs();
bidiResolver.setStatus(BidiStatus(LeftToRight, LeftToRight, LeftToRight,
BidiContext::create(0, LeftToRight, false)));
bidiResolver.setPosition(TextRunIterator(&run, 0));
bidiResolver.createBidiRunsForLine(TextRunIterator(&run, len));
if (!bidiRuns.runCount())
return;
BidiCharacterRun* bidiRun = bidiRuns.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();
}
bidiRuns.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
// TODO: Now that java is the one actually drawing these, get the real values
// from the drawable itself
#define CONTROL_HEIGHT 47
#define CONTROL_WIDTH 26
#define CONTROL_SLOP 5
#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
SelectText::SelectText()
: m_controlWidth(CONTROL_WIDTH)
, m_controlHeight(CONTROL_HEIGHT)
, m_controlSlop(CONTROL_SLOP)
{
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::getSelectionRegion(const IntRect& vis, SkRegion *region,
LayerAndroid* root)
{
SkIRect ivisBounds = vis;
ivisBounds.join(m_selStart);
ivisBounds.join(m_selEnd);
region->setEmpty();
buildSelection(*m_picture, ivisBounds, m_selStart, m_startBase,
m_selEnd, m_endBase, region);
if (root && m_layerId) {
Layer* layer = root->findById(m_layerId);
while (layer) {
const SkPoint& pos = layer->getPosition();
region->translate(pos.fX, pos.fY);
layer = layer->getParent();
}
}
}
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
float scale = m_controlHeight / (float)CONTROL_HEIGHT;
canvas->save();
canvas->translate(m_selStart.fLeft, m_selStart.fBottom);
canvas->scale(scale, scale);
canvas->drawPicture(m_startControl);
canvas->restore();
canvas->save();
canvas->translate(m_selEnd.fRight, m_selEnd.fBottom);
canvas->scale(scale, scale);
canvas->drawPicture(m_endControl);
canvas->restore();
#if DEBUG_TOUCH_HANDLES
SkRect touchHandleRect;
paint.setColor(SkColorSetARGB(0x60, 0xFF, 0x00, 0x00));
touchHandleRect.set(0, m_selStart.fBottom, m_selStart.fLeft, 0);
touchHandleRect.fBottom = touchHandleRect.fTop + m_controlHeight;
touchHandleRect.fLeft = touchHandleRect.fRight - m_controlWidth;
canvas->drawRect(touchHandleRect, paint);
touchHandleRect.inset(-m_controlSlop, -m_controlSlop);
canvas->drawRect(touchHandleRect, paint);
touchHandleRect.set(m_selEnd.fRight, m_selEnd.fBottom, 0, 0);
touchHandleRect.fBottom = touchHandleRect.fTop + m_controlHeight;
touchHandleRect.fRight = touchHandleRect.fLeft + m_controlWidth;
canvas->drawRect(touchHandleRect, paint);
touchHandleRect.inset(-m_controlSlop, -m_controlSlop);
canvas->drawRect(touchHandleRect, paint);
#endif
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 + m_controlWidth, cy + m_controlHeight);
test.inset(-m_controlSlop, -m_controlSlop);
DBG_HANDLE_LOG("checking if %dx%d,%d-%d contains %dx%d",
cx, cy, m_controlWidth, m_controlHeight, x, y);
return test.contains(x, y);
}
bool SelectText::hitStartHandle(int x, int y) const
{
int left = m_selStart.fLeft - m_controlWidth;
return hitCorner(left, m_selStart.fBottom, x, y);
}
bool SelectText::hitEndHandle(int x, int y) const
{
int left = m_selEnd.fRight;
return hitCorner(left, m_selEnd.fBottom, x, y);
}
bool SelectText::hitSelection(int x, int y) const
{
x -= m_startOffset.fX;
y -= m_startOffset.fY;
if (hitStartHandle(x, y))
return true;
if (hitEndHandle(x, y))
return true;
return m_selRegion.contains(x, y);
}
void SelectText::getSelectionHandles(int* handles, LayerAndroid* root)
{
handles[0] = m_selStart.fLeft;
handles[1] = m_selStart.fBottom;
handles[2] = m_selEnd.fRight;
handles[3] = m_selEnd.fBottom;
if (root && m_layerId) {
Layer* layer = root->findById(m_layerId);
while (layer) {
const SkPoint& pos = layer->getPosition();
handles[0] += pos.fX;
handles[2] += pos.fX;
handles[1] += pos.fY;
handles[3] += pos.fY;
layer = layer->getParent();
}
}
}
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;
}
m_hitTopLeft = hitStartHandle(x, y);
bool hitBottomRight = hitEndHandle(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) {
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;
}
void SelectText::updateHandleScale(float handleScale)
{
m_controlHeight = CONTROL_HEIGHT * handleScale;
m_controlWidth = CONTROL_WIDTH * handleScale;
m_controlSlop = CONTROL_SLOP * handleScale;
}
/* 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");
}
}
}