/* * 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 "webcoreglue" #include "CachedPrefix.h" #include "BidiResolver.h" #include "CachedRoot.h" #include "LayerAndroid.h" #include "SelectText.h" #include "SkBitmap.h" #include "SkBounder.h" #include "SkCanvas.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 #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 result; result.reserveCapacity(len); TextRun run(chars, len); BidiResolver 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 { class CommonCheck : public SkBounder { public: CommonCheck() : mMatrix(NULL), mPaint(NULL) {} virtual void setUp(const SkPaint& paint, const SkMatrix& matrix, SkScalar y, const void* text) { mMatrix = &matrix; mPaint = &paint; mText = static_cast(text); mY = y; mBase = mBottom = mTop = INT_MAX; } int base() { if (mBase == INT_MAX) { SkPoint result; mMatrix->mapXY(0, mY, &result); mBase = SkScalarFloor(result.fY); } return mBase; } 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; } 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; } protected: const SkMatrix* mMatrix; const SkPaint* mPaint; const uint16_t* mText; SkScalar mY; int mBase; int mBottom; int mTop; }; class FirstCheck : public CommonCheck { public: FirstCheck(int x, int y) : mDistance(INT_MAX), mFocusX(x), mFocusY(y) { mBestBounds.setEmpty(); } const SkIRect& bestBounds() { DBG_NAV_LOGD("mBestBounds:(%d, %d, %d, %d) mTop=%d mBottom=%d", mBestBounds.fLeft, mBestBounds.fTop, mBestBounds.fRight, mBestBounds.fBottom, mTop, mBottom); return mBestBounds; } void offsetBounds(int dx, int dy) { mBestBounds.offset(dx, dy); } virtual bool onIRect(const SkIRect& rect) { int dx = ((rect.fLeft + rect.fRight) >> 1) - mFocusX; int dy = ((top() + bottom()) >> 1) - mFocusY; int distance = dx * dx + dy * dy; #ifdef EXTRA_NOISY_LOGGING if (distance < 500 || abs(distance - mDistance) < 500) DBG_NAV_LOGD("distance=%d mDistance=%d", distance, mDistance); #endif if (mDistance > distance) { mDistance = distance; mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); #ifdef EXTRA_NOISY_LOGGING DBG_NAV_LOGD("mBestBounds={%d,%d,r=%d,b=%d}", mBestBounds.fLeft, mBestBounds.fTop, mBestBounds.fRight, mBestBounds.fBottom); #endif } return false; } protected: SkIRect mBestBounds; int mDistance; int mFocusX; int mFocusY; }; class MultilineBuilder : public CommonCheck { public: MultilineBuilder(const SkIRect& start, const SkIRect& end, int dx, int dy, SkRegion* region) : mStart(start), mEnd(end), mSelectRegion(region), mCapture(false) { mLast.setEmpty(); mLastBase = INT_MAX; mStart.offset(-dx, -dy); mEnd.offset(-dx, -dy); } virtual bool onIRect(const SkIRect& rect) { bool captureLast = false; if ((rect.fLeft == mStart.fLeft && rect.fRight == mStart.fRight && top() == mStart.fTop && bottom() == mStart.fBottom) || (rect.fLeft == mEnd.fLeft && rect.fRight == mEnd.fRight && top() == mEnd.fTop && bottom() == mEnd.fBottom)) { captureLast = mCapture; mCapture ^= true; } if (mCapture || captureLast) { SkIRect full; full.set(rect.fLeft, top(), rect.fRight, bottom()); if ((mLast.fTop < base() && mLast.fBottom >= base()) || (mLastBase <= full.fBottom && mLastBase > full.fTop)) { if (full.fLeft > mLast.fRight) full.fLeft = mLast.fRight; else if (full.fRight < mLast.fLeft) full.fRight = mLast.fLeft; } mSelectRegion->op(full, SkRegion::kUnion_Op); DBG_NAV_LOGD("MultilineBuilder full=(%d,%d,r=%d,b=%d)", full.fLeft, full.fTop, full.fRight, full.fBottom); mLast = full; mLastBase = base(); if (mStart == mEnd) mCapture = false; } return false; } protected: SkIRect mStart; SkIRect mEnd; SkIRect mLast; int mLastBase; SkRegion* mSelectRegion; bool mCapture; }; #define HYPHEN_MINUS 0x2D // ASCII hyphen #define HYPHEN 0x2010 // unicode hyphen, first in range of dashes #define HORZ_BAR 0x2015 // unicode horizontal bar, last in range of dashes class TextExtractor : public CommonCheck { public: TextExtractor(const SkRegion& region) : mSelectRegion(region), mSkipFirstSpace(true) { // don't start with a space } virtual void setUp(const SkPaint& paint, const SkMatrix& matrix, SkScalar y, const void* text) { INHERITED::setUp(paint, matrix, y, text); SkPaint charPaint = paint; charPaint.setTextEncoding(SkPaint::kUTF8_TextEncoding); mMinSpaceWidth = std::max(0, SkScalarToFixed( charPaint.measureText(" ", 1)) - SK_Fixed1); } virtual bool onIRectGlyph(const SkIRect& rect, const SkBounder::GlyphRec& rec) { SkIRect full; full.set(rect.fLeft, top(), rect.fRight, bottom()); if (mSelectRegion.contains(full)) { if (!mSkipFirstSpace && (mLastUni < HYPHEN || mLastUni > HORZ_BAR) && mLastUni != HYPHEN_MINUS && (mLastGlyph.fLSB.fY != rec.fLSB.fY // new baseline || mLastGlyph.fLSB.fX > rec.fLSB.fX // glyphs are LTR || mLastGlyph.fRSB.fX + mMinSpaceWidth < rec.fLSB.fX)) { DBG_NAV_LOGD("TextExtractor append space" " mLast=(%d,%d,r=%d,b=%d) mLastGlyph=((%g,%g),(%g,%g),%d)" " full=(%d,%d,r=%d,b=%d) rec=((%g,%g),(%g,%g),%d)" " mMinSpaceWidth=%g", mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, SkFixedToScalar(mLastGlyph.fLSB.fX), SkFixedToScalar(mLastGlyph.fLSB.fY), SkFixedToScalar(mLastGlyph.fRSB.fX), SkFixedToScalar(mLastGlyph.fRSB.fY), mLastGlyph.fGlyphID, full.fLeft, full.fTop, full.fRight, full.fBottom, SkFixedToScalar(rec.fLSB.fX), SkFixedToScalar(rec.fLSB.fY), SkFixedToScalar(rec.fRSB.fX), SkFixedToScalar(rec.fRSB.fY), rec.fGlyphID, SkFixedToScalar(mMinSpaceWidth)); *mSelectText.append() = ' '; } else mSkipFirstSpace = false; DBG_NAV_LOGD("TextExtractor [%02x] append full=(%d,%d,r=%d,b=%d)", rec.fGlyphID, full.fLeft, full.fTop, full.fRight, full.fBottom); SkPaint utfPaint = *mPaint; utfPaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); utfPaint.glyphsToUnichars(&rec.fGlyphID, 1, &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]; } mLast = full; mLastGlyph = rec; } else { mSkipFirstSpace = true; DBG_NAV_LOGD("TextExtractor [%02x] skip full=(%d,%d,r=%d,b=%d)", rec.fGlyphID, full.fLeft, full.fTop, full.fRight, full.fBottom); } return false; } WebCore::String text() { // 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 WebCore::String(mSelectText.begin(), mSelectText.count()); } protected: const SkRegion& mSelectRegion; SkTDArray mSelectText; SkIRect mLast; SkBounder::GlyphRec mLastGlyph; SkUnichar mLastUni; SkFixed mMinSpaceWidth; bool mSkipFirstSpace; private: typedef CommonCheck INHERITED; }; class TextCanvas : public SkCanvas { public: TextCanvas(CommonCheck* bounder, const SkPicture& picture, const SkIRect& area) : mBounder(*bounder) { setBounder(bounder); SkBitmap bitmap; bitmap.setConfig(SkBitmap::kARGB_8888_Config, area.width(), area.height()); setBitmapDevice(bitmap); translate(SkIntToScalar(-area.fLeft), SkIntToScalar(-area.fTop)); } virtual ~TextCanvas() { setBounder(NULL); } 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 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); SkCanvas::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); SkCanvas::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; }; void CopyPaste::buildSelection(const SkPicture& picture, const SkIRect& area, const SkIRect& selStart, const SkIRect& selEnd, 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, selEnd, area.fLeft, area.fTop, region); TextCanvas checker(&builder, picture, area); checker.drawPicture(const_cast(picture)); region->translate(area.fLeft, area.fTop); } SkIRect CopyPaste::findClosest(const SkPicture& picture, const SkIRect& area, int x, int y) { FirstCheck _check(x - area.fLeft, y - area.fTop); DBG_NAV_LOGD("area=(%d, %d, %d, %d) x=%d y=%d", area.fLeft, area.fTop, area.fRight, area.fBottom, x, y); TextCanvas checker(&_check, picture, area); checker.drawPicture(const_cast(picture)); _check.offsetBounds(area.fLeft, area.fTop); return _check.bestBounds(); } WebCore::String CopyPaste::text(const SkPicture& picture, const SkIRect& area, const SkRegion& region) { SkRegion copy = region; copy.translate(-area.fLeft, -area.fTop); const SkIRect& bounds = copy.getBounds(); DBG_NAV_LOGD("area=(%d, %d, %d, %d) region=(%d, %d, %d, %d)", area.fLeft, area.fTop, area.fRight, area.fBottom, bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom); TextExtractor extractor(copy); TextCanvas checker(&extractor, picture, area); checker.drawPicture(const_cast(picture)); return extractor.text(); } void SelectText::draw(SkCanvas* canvas, LayerAndroid* layer) { if (layer->picture() != m_picture) return; if (m_drawRegion) drawSelectionRegion(canvas); if (m_drawPointer) drawSelectionPointer(canvas); } void SelectText::drawSelectionPointer(SkCanvas* canvas) { SkPath path; if (m_extendSelection) getSelectionCaret(&path); else getSelectionArrow(&path); SkPaint paint; paint.setAntiAlias(true); paint.setStyle(SkPaint::kStroke_Style); paint.setColor(SK_ColorBLACK); SkPixelXorXfermode xorMode(SK_ColorWHITE); if (m_extendSelection) paint.setXfermode(&xorMode); else paint.setStrokeWidth(SK_Scalar1 * 2); int sc = canvas->save(); canvas->scale(m_inverseScale, m_inverseScale); canvas->translate(SkIntToScalar(m_selectX), SkIntToScalar(m_selectY)); canvas->drawPath(path, paint); if (!m_extendSelection) { paint.setStyle(SkPaint::kFill_Style); paint.setColor(SK_ColorWHITE); canvas->drawPath(path, paint); } canvas->restoreToCount(sc); } void SelectText::drawSelectionRegion(SkCanvas* canvas) { m_selRegion.setEmpty(); SkRect visBounds; if (!canvas->getClipBounds(&visBounds, SkCanvas::kAA_EdgeType)) return; SkIRect ivisBounds; visBounds.round(&ivisBounds); CopyPaste::buildSelection(*m_picture, ivisBounds, m_selStart, m_selEnd, &m_selRegion); SkPath path; m_selRegion.getBoundaryPath(&path); SkPaint paint; paint.setAntiAlias(true); paint.setColor(SkColorSetARGB(0x40, 255, 51, 204)); canvas->drawPath(path, paint); } const String SelectText::getSelection() { String result = CopyPaste::text(*m_picture, m_visibleRect, m_selRegion); 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(SkIntToScalar(arrow[index]), SkIntToScalar(arrow[index + 1])); path->close(); } void SelectText::getSelectionCaret(SkPath* path) { SkScalar height = SkIntToScalar(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, -SK_Scalar1/2); path->rLineTo(dist * 2, 0); path->rMoveTo(0, SK_Scalar1/2); path->rLineTo(-dist, -dist); } void SelectText::moveSelection(const SkPicture* picture, int x, int y, bool extendSelection) { if (!extendSelection) m_picture = picture; m_selEnd = CopyPaste::findClosest(*picture, m_visibleRect, x, y); if (!extendSelection) m_selStart = m_selEnd; DBG_NAV_LOGD("x=%d y=%d extendSelection=%s m_selStart=(%d, %d, %d, %d)" " m_selEnd=(%d, %d, %d, %d)", x, y, 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); } }