Fix rendering bug in pages with shadowed text.

Shadowed text currently does not get subjected to culling until
immediately prior to rendering each glyph. This is problematic
for any page with an axis greater than 32k as we can't covert
the glyph coordinates to fixed point. Additionally, this is a
large perf hit as we look at every shadowed glyph on the page
for every draw call regardless of the canvas' clip.

This fix enables shadowed text to be quickly rejected based on
the canvas' clip when the draw text command is executed.

Finally, a mirror image of this CL is currently under review for
inclusion in the open-source Skia project.

bug: 5571685
Change-Id: I5df94eccecbd7d77a08004b5cbcca02120e390f7
diff --git a/include/core/SkDrawLooper.h b/include/core/SkDrawLooper.h
index 270abc2..3830b4a 100644
--- a/include/core/SkDrawLooper.h
+++ b/include/core/SkDrawLooper.h
@@ -52,6 +52,20 @@
      */
     virtual bool next(SkCanvas*, SkPaint* paint) = 0;
     
+    /**
+     * The fast bounds functions are used to enable the paint to be culled early
+     * in the drawing pipeline. If a subclass can support this feature it must
+     * return true for the canComputeFastBounds() function.  If that function
+     * returns false then computeFastBounds behavior is undefined otherwise it
+     * is expected to have the following behavior. Given the parent paint and
+     * the parent's bounding rect the subclass must fill in and return the
+     * storage rect, where the storage rect is with the union of the src rect
+     * and the looper's bounding rect.
+     */
+    virtual bool canComputeFastBounds(const SkPaint& paint);
+    virtual void computeFastBounds(const SkPaint& paint,
+                                   const SkRect& src, SkRect* dst);
+
 protected:
     SkDrawLooper() {}
     SkDrawLooper(SkFlattenableReadBuffer& buffer) : INHERITED(buffer) {}
diff --git a/include/core/SkMaskFilter.h b/include/core/SkMaskFilter.h
index 641ad83..60dade9 100644
--- a/include/core/SkMaskFilter.h
+++ b/include/core/SkMaskFilter.h
@@ -72,6 +72,19 @@
 
     virtual void flatten(SkFlattenableWriteBuffer& ) {}
 
+    /**
+     * The fast bounds function is used to enable the paint to be culled early
+     * in the drawing pipeline. This function accepts the current bounds of the
+     * paint as its src param and the filter adjust those bounds using its
+     * current mask and returns the result using the dest param. Callers are
+     * allowed to provide the same struct for both src and dest so each
+     * implementation must accomodate that behavior.
+     *
+     *  The default impl calls filterMask with the src mask having no image,
+     *  but subclasses may override this if they can compute the rect faster.
+     */
+    virtual void computeFastBounds(const SkRect& src, SkRect* dest);
+
 protected:
     // empty for now, but lets get our subclass to remember to init us for the future
     SkMaskFilter(SkFlattenableReadBuffer&) {}
diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h
index f336f5b..9b547a4 100644
--- a/include/core/SkPaint.h
+++ b/include/core/SkPaint.h
@@ -18,7 +18,7 @@
 #define SkPaint_DEFINED
 
 #include "SkColor.h"
-#include "SkMath.h"
+#include "SkDrawLooper.h"
 #include "SkXfermode.h"
 
 class SkAutoGlyphCache;
@@ -35,7 +35,6 @@
 class SkPathEffect;
 class SkRasterizer;
 class SkShader;
-class SkDrawLooper;
 class SkTypeface;
 
 typedef const SkGlyph& (*SkDrawCacheProc)(SkGlyphCache*, const char**,
@@ -425,10 +424,11 @@
         the bounds computation expensive.
     */
     bool canComputeFastBounds() const {
+        if (this->getLooper()) {
+            return this->getLooper()->canComputeFastBounds(*this);
+        }
         // use bit-or since no need for early exit
-        return (reinterpret_cast<uintptr_t>(this->getMaskFilter()) |
-                reinterpret_cast<uintptr_t>(this->getLooper()) |
-                reinterpret_cast<uintptr_t>(this->getRasterizer()) |
+        return (reinterpret_cast<uintptr_t>(this->getRasterizer()) |
                 reinterpret_cast<uintptr_t>(this->getPathEffect())) == 0;
     }
 
@@ -454,8 +454,12 @@
         }
     */
     const SkRect& computeFastBounds(const SkRect& orig, SkRect* storage) const {
-        return this->getStyle() == kFill_Style ? orig :
-                    this->computeStrokeFastBounds(orig, storage);
+        if (this->getStyle() == kFill_Style &&
+                !this->getLooper() && !this->getMaskFilter()) {
+            return orig;
+        }
+
+        return this->doComputeFastBounds(orig, storage);
     }
 
     /** Get the paint's shader object.
@@ -864,8 +868,7 @@
                         void (*proc)(const SkDescriptor*, void*),
                         void* context, bool ignoreGamma = false) const;
 
-    const SkRect& computeStrokeFastBounds(const SkRect& orig,
-                                          SkRect* storage) const;
+    const SkRect& doComputeFastBounds(const SkRect& orig, SkRect* storage) const;
 
     enum {
         kCanonicalTextSizeForPaths = 64
diff --git a/src/core/SkMaskFilter.cpp b/src/core/SkMaskFilter.cpp
index a278261..2008c93 100644
--- a/src/core/SkMaskFilter.cpp
+++ b/src/core/SkMaskFilter.cpp
@@ -57,4 +57,19 @@
     return true;
 }
 
+void SkMaskFilter::computeFastBounds(const SkRect& src, SkRect* dst) {
+    SkMask  srcM, dstM;
+
+    srcM.fImage = NULL;
+    src.roundOut(&srcM.fBounds);
+    srcM.fRowBytes = 0;
+    srcM.fFormat = SkMask::kA8_Format;
+
+    SkIPoint margin;    // ignored
+    if (this->filterMask(&dstM, srcM, SkMatrix::I(), &margin)) {
+        dst->set(dstM.fBounds);
+    } else {
+        dst->set(srcM.fBounds);
+    }
+}
 
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index c29c4f0..21bd078 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -17,7 +17,6 @@
 
 #include "SkPaint.h"
 #include "SkColorFilter.h"
-#include "SkDrawLooper.h"
 #include "SkFontHost.h"
 #include "SkMaskFilter.h"
 #include "SkPathEffect.h"
@@ -1703,23 +1702,38 @@
     return width != 0;  // return true if we're filled, or false if we're hairline (width == 0)
 }
 
-const SkRect& SkPaint::computeStrokeFastBounds(const SkRect& src,
-                                               SkRect* storage) const {
+const SkRect& SkPaint::doComputeFastBounds(const SkRect& src,
+                                                 SkRect* storage) const {
     SkASSERT(storage);
-    SkASSERT(this->getStyle() != SkPaint::kFill_Style);
 
-    // since we're stroked, outset the rect by the radius (and join type)
-    SkScalar radius = SkScalarHalf(this->getStrokeWidth());
-    if (0 == radius) {  // hairline
-        radius = SK_Scalar1;
-    } else if (this->getStrokeJoin() == SkPaint::kMiter_Join) {
-        SkScalar scale = this->getStrokeMiter();
-        if (scale > SK_Scalar1) {
-            radius = SkScalarMul(radius, scale);
-        }
+    if (this->getLooper()) {
+        SkASSERT(this->getLooper()->canComputeFastBounds(*this));
+        this->getLooper()->computeFastBounds(*this, src, storage);
+        return *storage;
     }
-    storage->set(src.fLeft - radius, src.fTop - radius,
-                 src.fRight + radius, src.fBottom + radius);
+
+    if (this->getStyle() != SkPaint::kFill_Style) {
+        // since we're stroked, outset the rect by the radius (and join type)
+        SkScalar radius = SkScalarHalf(this->getStrokeWidth());
+        if (0 == radius) {  // hairline
+            radius = SK_Scalar1;
+        } else if (this->getStrokeJoin() == SkPaint::kMiter_Join) {
+            SkScalar scale = this->getStrokeMiter();
+            if (scale > SK_Scalar1) {
+                radius = SkScalarMul(radius, scale);
+            }
+        }
+        storage->set(src.fLeft - radius, src.fTop - radius,
+                     src.fRight + radius, src.fBottom + radius);
+    } else {
+        *storage = src;
+    }
+
+    // check the mask filter
+    if (this->getMaskFilter()) {
+        this->getMaskFilter()->computeFastBounds(*storage, storage);
+    }
+
     return *storage;
 }
 
@@ -1811,3 +1825,48 @@
     }
     return NULL;
 }
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool SkDrawLooper::canComputeFastBounds(const SkPaint& paint) {
+    SkCanvas canvas;
+
+    this->init(&canvas);
+    for (;;) {
+        SkPaint p(paint);
+        if (this->next(&canvas, &p)) {
+            p.setLooper(NULL);
+            if (!p.canComputeFastBounds()) {
+                return false;
+            }
+        } else {
+            break;
+        }
+    }
+    return true;
+}
+
+void SkDrawLooper::computeFastBounds(const SkPaint& paint, const SkRect& src,
+                                     SkRect* dst) {
+    SkCanvas canvas;
+
+    this->init(&canvas);
+    for (bool firstTime = true;; firstTime = false) {
+        SkPaint p(paint);
+        if (this->next(&canvas, &p)) {
+            SkRect r(src);
+
+            p.setLooper(NULL);
+            p.computeFastBounds(r, &r);
+            canvas.getTotalMatrix().mapRect(&r);
+
+            if (firstTime) {
+                *dst = r;
+            } else {
+                dst->join(r);
+            }
+        } else {
+            break;
+        }
+    }
+}
diff --git a/src/core/SkPictureFlat.h b/src/core/SkPictureFlat.h
index 697b399..4db61f7 100644
--- a/src/core/SkPictureFlat.h
+++ b/src/core/SkPictureFlat.h
@@ -25,6 +25,7 @@
     DRAW_PICTURE,
     DRAW_POINTS,
     DRAW_POS_TEXT,
+    DRAW_POS_TEXT_TOP_BOTTOM, // fast variant of DRAW_POS_TEXT
     DRAW_POS_TEXT_H,
     DRAW_POS_TEXT_H_TOP_BOTTOM, // fast variant of DRAW_POS_TEXT_H
     DRAW_RECT,
diff --git a/src/core/SkPicturePlayback.cpp b/src/core/SkPicturePlayback.cpp
index 80189ab..5a856ef 100644
--- a/src/core/SkPicturePlayback.cpp
+++ b/src/core/SkPicturePlayback.cpp
@@ -625,6 +625,17 @@
                 const SkPoint* pos = (const SkPoint*)fReader.skip(points * sizeof(SkPoint));
                 canvas.drawPosText(text.text(), text.length(), pos, paint);
             } break;
+            case DRAW_POS_TEXT_TOP_BOTTOM: {
+                const SkPaint& paint = *getPaint();
+                getText(&text);
+                size_t points = getInt();
+                const SkPoint* pos = (const SkPoint*)fReader.skip(points * sizeof(SkPoint));
+                const SkScalar top = fReader.readScalar();
+                const SkScalar bottom = fReader.readScalar();
+                if (!canvas.quickRejectY(top, bottom, SkCanvas::kAA_EdgeType)) {
+                    canvas.drawPosText(text.text(), text.length(), pos, paint);
+                }
+            } break;
             case DRAW_POS_TEXT_H: {
                 const SkPaint& paint = *getPaint();
                 getText(&text);
diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp
index 6b2b330..5810051 100644
--- a/src/core/SkPictureRecord.cpp
+++ b/src/core/SkPictureRecord.cpp
@@ -242,14 +242,14 @@
 }
 
 void SkPictureRecord::addFontMetricsTopBottom(const SkPaint& paint,
-                                              SkScalar baselineY) {
+                                              SkScalar minY, SkScalar maxY) {
     SkPaint::FontMetrics metrics;
     paint.getFontMetrics(&metrics);
     SkRect bounds;
     // construct a rect so we can see any adjustments from the paint.
     // we use 0,1 for left,right, just so the rect isn't empty
-    bounds.set(0, metrics.fTop + baselineY,
-               SK_Scalar1, metrics.fBottom + baselineY);
+    bounds.set(0, metrics.fTop + minY,
+               SK_Scalar1, metrics.fBottom + maxY);
     (void)paint.computeFastBounds(bounds, &bounds);
     // now record the top and bottom
     addScalar(bounds.fTop);
@@ -266,7 +266,7 @@
     addScalar(x);
     addScalar(y);
     if (fast) {
-        addFontMetricsTopBottom(paint, y);
+        addFontMetricsTopBottom(paint, y, y);
     }
     validate();
 }
@@ -278,23 +278,34 @@
         return;
 
     bool canUseDrawH = true;
+    SkScalar minY = pos[0].fY;
+    SkScalar maxY = pos[0].fY;
     // check if the caller really should have used drawPosTextH()
     {
         const SkScalar firstY = pos[0].fY;
         for (size_t index = 1; index < points; index++) {
             if (pos[index].fY != firstY) {
                 canUseDrawH = false;
-                break;
+                if (pos[index].fY < minY) {
+                    minY = pos[index].fY;
+                } else if (pos[index].fY > maxY) {
+                    maxY = pos[index].fY;
+                }
             }
         }
     }
 
-    bool fast = canUseDrawH && paint.canComputeFastBounds();
+    bool fastBounds = paint.canComputeFastBounds();
+    bool fast = canUseDrawH && fastBounds;
 
     if (fast) {
         addDraw(DRAW_POS_TEXT_H_TOP_BOTTOM);
+    } else if (canUseDrawH) {
+        addDraw(DRAW_POS_TEXT_H);
+    } else if (fastBounds) {
+        addDraw(DRAW_POS_TEXT_TOP_BOTTOM);
     } else {
-        addDraw(canUseDrawH ? DRAW_POS_TEXT_H : DRAW_POS_TEXT);
+        addDraw(DRAW_POS_TEXT);
     }
     addPaint(paint);
     addText(text, byteLength);
@@ -305,7 +316,7 @@
 #endif
     if (canUseDrawH) {
         if (fast) {
-            addFontMetricsTopBottom(paint, pos[0].fY);
+            addFontMetricsTopBottom(paint, pos[0].fY, pos[0].fY);
         }
         addScalar(pos[0].fY);
         SkScalar* xptr = (SkScalar*)fWriter.reserve(points * sizeof(SkScalar));
@@ -314,6 +325,9 @@
     }
     else {
         fWriter.writeMul4(pos, points * sizeof(SkPoint));
+        if (fastBounds) {
+            addFontMetricsTopBottom(paint, minY, maxY);
+        }
     }
 #ifdef SK_DEBUG_SIZE
     fPointBytes += fWriter.size() - start;
@@ -340,7 +354,7 @@
     size_t start = fWriter.size();
 #endif
     if (fast) {
-        addFontMetricsTopBottom(paint, constY);
+        addFontMetricsTopBottom(paint, constY, constY);
     }
     addScalar(constY);
     fWriter.writeMul4(xpos, points * sizeof(SkScalar));
diff --git a/src/core/SkPictureRecord.h b/src/core/SkPictureRecord.h
index e0d6a50..5a400cb 100644
--- a/src/core/SkPictureRecord.h
+++ b/src/core/SkPictureRecord.h
@@ -59,8 +59,8 @@
                               const SkPaint&);
     virtual void drawData(const void*, size_t);
 
-    void addFontMetricsTopBottom(const SkPaint& paint, SkScalar baselineY);
-    
+    void addFontMetricsTopBottom(const SkPaint& paint, SkScalar minY, SkScalar maxY);
+
     const SkTDArray<const SkFlatBitmap* >& getBitmaps() const {
         return fBitmaps;
     }
diff --git a/src/effects/SkBlurMaskFilter.cpp b/src/effects/SkBlurMaskFilter.cpp
index 41e04b8..ad00d17 100644
--- a/src/effects/SkBlurMaskFilter.cpp
+++ b/src/effects/SkBlurMaskFilter.cpp
@@ -27,6 +27,7 @@
     // overrides from SkMaskFilter
     virtual SkMask::Format getFormat();
     virtual bool filterMask(SkMask* dst, const SkMask& src, const SkMatrix& matrix, SkIPoint* margin);
+    virtual void computeFastBounds(const SkRect& src, SkRect* dst);
 
     // overrides from SkFlattenable
     // This method is not exported to java.
@@ -111,6 +112,11 @@
     return false;
 }
 
+void SkBlurMaskFilterImpl::computeFastBounds(const SkRect& src, SkRect* dst) {
+    dst->set(src.fLeft - fRadius, src.fTop - fRadius,
+             src.fRight + fRadius, src.fBottom + fRadius);
+}
+
 SkFlattenable* SkBlurMaskFilterImpl::CreateProc(SkFlattenableReadBuffer& buffer)
 {
     return SkNEW_ARGS(SkBlurMaskFilterImpl, (buffer));
diff --git a/src/effects/SkLayerDrawLooper.cpp b/src/effects/SkLayerDrawLooper.cpp
index 5d0fdcf..74cc624 100644
--- a/src/effects/SkLayerDrawLooper.cpp
+++ b/src/effects/SkLayerDrawLooper.cpp
@@ -64,15 +64,18 @@
     }
 }
 
+// Even with kEntirePaint_Bits, we always ensure that the master paint's
+// text-encoding is respected, since that controls how we interpret the
+// text/length parameters of a draw[Pos]Text call.
 void SkLayerDrawLooper::ApplyInfo(SkPaint* dst, const SkPaint& src,
                                   const LayerInfo& info) {
 
     uint32_t mask = info.fFlagsMask;
     dst->setFlags((dst->getFlags() & ~mask) | (src.getFlags() & mask));
-
     dst->setColor(xferColor(src.getColor(), dst->getColor(), info.fColorMode));
 
     BitFlags bits = info.fPaintBits;
+    SkPaint::TextEncoding encoding = dst->getTextEncoding();
 
     if (0 == bits) {
         return;
@@ -84,6 +87,7 @@
         *dst = src;
         dst->setFlags(f);
         dst->setColor(c);
+        dst->setTextEncoding(encoding);
         return;
     }