Add unit tests to text context

TBR=bsalomon@google.com
BUG=skia:

Review URL: https://codereview.chromium.org/1128153005
diff --git a/src/gpu/GrAtlasTextContext.cpp b/src/gpu/GrAtlasTextContext.cpp
index bfe9381..c908137 100644
--- a/src/gpu/GrAtlasTextContext.cpp
+++ b/src/gpu/GrAtlasTextContext.cpp
@@ -9,6 +9,7 @@
 #include "GrBatch.h"
 #include "GrBatchFontCache.h"
 #include "GrBatchTarget.h"
+#include "GrBatchTest.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawTarget.h"
 #include "GrFontScaler.h"
@@ -683,22 +684,22 @@
     return blob;
 }
 
-void GrAtlasTextContext::onDrawText(GrRenderTarget* rt, const GrClip& clip,
-                                    const GrPaint& paint, const SkPaint& skPaint,
-                                    const SkMatrix& viewMatrix,
-                                    const char text[], size_t byteLength,
-                                    SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
+inline GrAtlasTextContext::BitmapTextBlob*
+GrAtlasTextContext::createDrawTextBlob(GrRenderTarget* rt, const GrClip& clip,
+                                       const GrPaint& paint, const SkPaint& skPaint,
+                                       const SkMatrix& viewMatrix,
+                                       const char text[], size_t byteLength,
+                                       SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
     int glyphCount = skPaint.countText(text, byteLength);
     SkIRect clipRect;
     clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
 
-    // setup cache
+    BitmapTextBlob* blob;
     if (this->canDrawAsDistanceFields(skPaint, viewMatrix)) {
         SkPaint dfPaint;
         SkScalar textRatio;
         SkGlyphCache* cache;
-        SkAutoTUnref<BitmapTextBlob> blob(this->setupDFBlob(glyphCount, skPaint, viewMatrix, &cache,
-                                                            &dfPaint, &textRatio));
+        blob = this->setupDFBlob(glyphCount, skPaint, viewMatrix, &cache, &dfPaint, &textRatio);
 
         SkTDArray<char> fallbackTxt;
         SkTDArray<SkScalar> fallbackPos;
@@ -711,36 +712,36 @@
             this->fallbackDrawPosText(blob, 0, rt, clip, paint, skPaint, viewMatrix, fallbackTxt,
                                       fallbackPos, 2, offset, clipRect);
         }
-        this->flush(fContext->getTextTarget(), blob, rt, skPaint, paint, clip);
     } else {
-        SkAutoTUnref<BitmapTextBlob> blob(fCache->createBlob(glyphCount, 1, kGrayTextVASize));
+        blob = fCache->createBlob(glyphCount, 1, kGrayTextVASize);
         blob->fViewMatrix = viewMatrix;
 
         SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, &viewMatrix, false);
         this->internalDrawBMPText(blob, 0, cache, skPaint, paint.getColor(), viewMatrix, text,
                                   byteLength, x, y, clipRect);
         SkGlyphCache::AttachCache(cache);
-        this->flush(fContext->getTextTarget(), blob, rt, skPaint, paint, clip);
     }
+    return blob;
 }
 
-void GrAtlasTextContext::onDrawPosText(GrRenderTarget* rt, const GrClip& clip,
-                                       const GrPaint& paint, const SkPaint& skPaint,
-                                       const SkMatrix& viewMatrix,
-                                       const char text[], size_t byteLength,
-                                       const SkScalar pos[], int scalarsPerPosition,
-                                       const SkPoint& offset, const SkIRect& regionClipBounds) {
+inline GrAtlasTextContext::BitmapTextBlob*
+GrAtlasTextContext::createDrawPosTextBlob(GrRenderTarget* rt, const GrClip& clip,
+                                          const GrPaint& paint, const SkPaint& skPaint,
+                                          const SkMatrix& viewMatrix,
+                                          const char text[], size_t byteLength,
+                                          const SkScalar pos[], int scalarsPerPosition,
+                                          const SkPoint& offset, const SkIRect& regionClipBounds) {
     int glyphCount = skPaint.countText(text, byteLength);
 
     SkIRect clipRect;
     clip.getConservativeBounds(rt->width(), rt->height(), &clipRect);
 
+    BitmapTextBlob* blob;
     if (this->canDrawAsDistanceFields(skPaint, viewMatrix)) {
         SkPaint dfPaint;
         SkScalar textRatio;
         SkGlyphCache* cache;
-        SkAutoTUnref<BitmapTextBlob> blob(this->setupDFBlob(glyphCount, skPaint, viewMatrix, &cache,
-                                                            &dfPaint, &textRatio));
+        blob = this->setupDFBlob(glyphCount, skPaint, viewMatrix, &cache, &dfPaint, &textRatio);
 
         SkTDArray<char> fallbackTxt;
         SkTDArray<SkScalar> fallbackPos;
@@ -752,16 +753,41 @@
             this->fallbackDrawPosText(blob, 0, rt, clip, paint, skPaint, viewMatrix, fallbackTxt,
                                       fallbackPos, scalarsPerPosition, offset, clipRect);
         }
-        this->flush(fContext->getTextTarget(), blob, rt, skPaint, paint, clip);
     } else {
-        SkAutoTUnref<BitmapTextBlob> blob(fCache->createBlob(glyphCount, 1, kGrayTextVASize));
+        blob = fCache->createBlob(glyphCount, 1, kGrayTextVASize);
         blob->fViewMatrix = viewMatrix;
         SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, &viewMatrix, false);
         this->internalDrawBMPPosText(blob, 0, cache, skPaint, paint.getColor(), viewMatrix, text,
                                      byteLength, pos, scalarsPerPosition, offset, clipRect);
         SkGlyphCache::AttachCache(cache);
-        this->flush(fContext->getTextTarget(), blob, rt, skPaint, paint, clip);
     }
+    return blob;
+}
+
+void GrAtlasTextContext::onDrawText(GrRenderTarget* rt, const GrClip& clip,
+                                    const GrPaint& paint, const SkPaint& skPaint,
+                                    const SkMatrix& viewMatrix,
+                                    const char text[], size_t byteLength,
+                                    SkScalar x, SkScalar y, const SkIRect& regionClipBounds) {
+    SkAutoTUnref<BitmapTextBlob> blob(
+            this->createDrawTextBlob(rt, clip, paint, skPaint, viewMatrix,
+                                     text, byteLength, x, y, regionClipBounds));
+    this->flush(fContext->getTextTarget(), blob, rt, skPaint, paint, clip);
+}
+
+void GrAtlasTextContext::onDrawPosText(GrRenderTarget* rt, const GrClip& clip,
+                                       const GrPaint& paint, const SkPaint& skPaint,
+                                       const SkMatrix& viewMatrix,
+                                       const char text[], size_t byteLength,
+                                       const SkScalar pos[], int scalarsPerPosition,
+                                       const SkPoint& offset, const SkIRect& regionClipBounds) {
+    SkAutoTUnref<BitmapTextBlob> blob(
+            this->createDrawPosTextBlob(rt, clip, paint, skPaint, viewMatrix,
+                                        text, byteLength,
+                                        pos, scalarsPerPosition,
+                                        offset, regionClipBounds));
+
+    this->flush(fContext->getTextTarget(), blob, rt, skPaint, paint, clip);
 }
 
 void GrAtlasTextContext::internalDrawBMPText(BitmapTextBlob* blob, int runIndex,
@@ -2050,6 +2076,51 @@
     }
 }
 
+
+inline BitmapTextBatch*
+GrAtlasTextContext::createBatch(BitmapTextBlob* cacheBlob, const PerSubRunInfo& info,
+                                int glyphCount, int run, int subRun,
+                                GrColor color, SkScalar transX, SkScalar transY,
+                                const SkPaint& skPaint) {
+    GrMaskFormat format = info.fMaskFormat;
+    GrColor subRunColor;
+    if (kARGB_GrMaskFormat == format) {
+        uint8_t paintAlpha = skPaint.getAlpha();
+        subRunColor = SkColorSetARGB(paintAlpha, paintAlpha, paintAlpha, paintAlpha);
+    } else {
+        subRunColor = color;
+    }
+
+    BitmapTextBatch* batch;
+    if (info.fDrawAsDistanceFields) {
+        SkColor filteredColor;
+        SkColorFilter* colorFilter = skPaint.getColorFilter();
+        if (colorFilter) {
+            filteredColor = colorFilter->filterColor(skPaint.getColor());
+        } else {
+            filteredColor = skPaint.getColor();
+        }
+        bool useBGR = SkPixelGeometryIsBGR(fDeviceProperties.pixelGeometry());
+        float gamma = fDeviceProperties.gamma();
+        batch = BitmapTextBatch::Create(format, glyphCount, fContext->getBatchFontCache(),
+                                        fDistanceAdjustTable, filteredColor,
+                                        info.fUseLCDText, useBGR,
+                                        gamma);
+    } else {
+        batch = BitmapTextBatch::Create(format, glyphCount, fContext->getBatchFontCache());
+    }
+    BitmapTextBatch::Geometry& geometry = batch->geometry();
+    geometry.fBlob = SkRef(cacheBlob);
+    geometry.fRun = run;
+    geometry.fSubRun = subRun;
+    geometry.fColor = subRunColor;
+    geometry.fTransX = transX;
+    geometry.fTransY = transY;
+    batch->init();
+
+    return batch;
+}
+
 inline void GrAtlasTextContext::flushRun(GrDrawTarget* target, GrPipelineBuilder* pipelineBuilder,
                                          BitmapTextBlob* cacheBlob, int run, GrColor color,
                                          SkScalar transX, SkScalar transY, const SkPaint& skPaint) {
@@ -2060,42 +2131,9 @@
             continue;
         }
 
-        GrMaskFormat format = info.fMaskFormat;
-        GrColor subRunColor;
-        if (kARGB_GrMaskFormat == format) {
-            uint8_t paintAlpha = skPaint.getAlpha();
-            subRunColor = SkColorSetARGB(paintAlpha, paintAlpha, paintAlpha, paintAlpha);
-        } else {
-            subRunColor = color;
-        }
-
-        SkAutoTUnref<BitmapTextBatch> batch;
-        if (info.fDrawAsDistanceFields) {
-            SkColor filteredColor;
-            SkColorFilter* colorFilter = skPaint.getColorFilter();
-            if (colorFilter) {
-                filteredColor = colorFilter->filterColor(skPaint.getColor());
-            } else {
-                filteredColor = skPaint.getColor();
-            }
-            bool useBGR = SkPixelGeometryIsBGR(fDeviceProperties.pixelGeometry());
-            float gamma = fDeviceProperties.gamma();
-            batch.reset(BitmapTextBatch::Create(format, glyphCount, fContext->getBatchFontCache(),
-                                                fDistanceAdjustTable, filteredColor,
-                                                info.fUseLCDText, useBGR,
-                                                gamma));
-        } else {
-            batch.reset(BitmapTextBatch::Create(format, glyphCount, fContext->getBatchFontCache()));
-        }
-        BitmapTextBatch::Geometry& geometry = batch->geometry();
-        geometry.fBlob = SkRef(cacheBlob);
-        geometry.fRun = run;
-        geometry.fSubRun = subRun;
-        geometry.fColor = subRunColor;
-        geometry.fTransX = transX;
-        geometry.fTransY = transY;
-        batch->init();
-
+        SkAutoTUnref<BitmapTextBatch> batch(this->createBatch(cacheBlob, info, glyphCount, run,
+                                                              subRun, color, transX, transY,
+                                                              skPaint));
         target->drawBatch(pipelineBuilder, batch);
     }
 }
@@ -2167,3 +2205,69 @@
     // Now flush big glyphs
     this->flushBigGlyphs(cacheBlob, rt, grPaint, clip, 0, 0);
 }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef GR_TEST_UTILS
+
+BATCH_TEST_DEFINE(TextBlob) {
+    static uint32_t gContextID = SK_InvalidGenID;
+    static GrAtlasTextContext* gTextContext = NULL;
+    static SkDeviceProperties gDeviceProperties(SkDeviceProperties::kLegacyLCD_InitType);
+
+    if (context->uniqueID() != gContextID) {
+        gContextID = context->uniqueID();
+        SkDELETE(gTextContext);
+        // We don't yet test the fall back to paths in the GrTextContext base class.  This is mostly
+        // because we don't really want to have a gpu device here.
+        // We enable distance fields by twiddling a knob on the paint
+        gTextContext = GrAtlasTextContext::Create(context, NULL, gDeviceProperties, false);
+    }
+
+    // create dummy render target
+    GrSurfaceDesc desc;
+    desc.fFlags = kRenderTarget_GrSurfaceFlag;
+    desc.fWidth = 1024;
+    desc.fHeight = 1024;
+    desc.fConfig = kRGBA_8888_GrPixelConfig;
+    desc.fSampleCnt = 1;
+    SkAutoTUnref<GrTexture> texture(context->textureProvider()->createTexture(desc, true, NULL, 0));
+    SkASSERT(texture);
+    SkASSERT(NULL != texture->asRenderTarget());
+    GrRenderTarget* rt = texture->asRenderTarget();
+
+    // Setup dummy SkPaint / GrPaint
+    GrColor color = GrRandomColor(random);
+    SkMatrix viewMatrix = GrTest::TestMatrix(random);
+    SkPaint skPaint;
+    skPaint.setDistanceFieldTextTEMP(random->nextBool());
+    skPaint.setColor(color);
+    skPaint.setLCDRenderText(random->nextBool());
+    skPaint.setAntiAlias(skPaint.isLCDRenderText() ? true : random->nextBool());
+    skPaint.setSubpixelText(random->nextBool());
+
+    GrPaint grPaint;
+    if (!SkPaint2GrPaint(context, rt, skPaint, viewMatrix, true, &grPaint)) {
+        SkFAIL("couldn't convert paint\n");
+    }
+
+    const char* text = "The quick brown fox jumps over the lazy dog.";
+    int textLen = (int)strlen(text);
+
+    // Setup clip
+    GrClip clip;
+    SkIRect noClip = SkIRect::MakeLargest();
+
+    // right now we don't handle textblobs, nor do we handle drawPosText.  Since we only
+    // intend to test the batch with this unit test, that is okay.
+    SkAutoTUnref<GrAtlasTextContext::BitmapTextBlob> blob(
+            gTextContext->createDrawTextBlob(rt, clip, grPaint, skPaint, viewMatrix, text,
+                                             static_cast<size_t>(textLen), 0, 0, noClip));
+
+    SkScalar transX = static_cast<SkScalar>(random->nextU());
+    SkScalar transY = static_cast<SkScalar>(random->nextU());
+    const GrAtlasTextContext::BitmapTextBlob::Run::SubRunInfo& info = blob->fRuns[0].fSubRunInfo[0];
+    return gTextContext->createBatch(blob, info, textLen, 0, 0, color, transX, transY, skPaint);
+}
+
+#endif
diff --git a/src/gpu/GrAtlasTextContext.h b/src/gpu/GrAtlasTextContext.h
index c6e1d2f..55597cf 100644
--- a/src/gpu/GrAtlasTextContext.h
+++ b/src/gpu/GrAtlasTextContext.h
@@ -19,6 +19,11 @@
 #include "SkTextBlob.h"
 #include "SkTInternalLList.h"
 
+#ifdef GR_TEST_UTILS
+#include "GrBatchTest.h"
+#endif
+
+class BitmapTextBatch;
 class GrPipelineBuilder;
 class GrTextBlobCache;
 
@@ -265,6 +270,10 @@
     inline void flushRunAsPaths(const SkTextBlob::RunIterator&, const SkPaint&, SkDrawFilter*,
                                 const SkMatrix& viewMatrix, const SkIRect& clipBounds, SkScalar x,
                                 SkScalar y);
+    inline BitmapTextBatch* createBatch(BitmapTextBlob*, const PerSubRunInfo&,
+                                        int glyphCount, int run, int subRun,
+                                        GrColor, SkScalar transX, SkScalar transY,
+                                        const SkPaint&);
     inline void flushRun(GrDrawTarget*, GrPipelineBuilder*, BitmapTextBlob*, int run, GrColor,
                          SkScalar transX, SkScalar transY, const SkPaint&);
     inline void flushBigGlyphs(BitmapTextBlob* cacheBlob, GrRenderTarget* rt,
@@ -332,6 +341,20 @@
     inline void initDistanceFieldPaint(BitmapTextBlob*, SkPaint*, SkScalar* textRatio,
                                        const SkMatrix&);
 
+    // Test methods
+    // TODO this is really ugly.  It'd be much nicer if positioning could be moved to batch
+    inline BitmapTextBlob* createDrawTextBlob(GrRenderTarget*, const GrClip&, const GrPaint&,
+                                              const SkPaint&, const SkMatrix& viewMatrix,
+                                              const char text[], size_t byteLength,
+                                              SkScalar x, SkScalar y,
+                                              const SkIRect& regionClipBounds);
+    inline BitmapTextBlob* createDrawPosTextBlob(GrRenderTarget*, const GrClip&, const GrPaint&,
+                                                 const SkPaint&, const SkMatrix& viewMatrix,
+                                                 const char text[], size_t byteLength,
+                                                 const SkScalar pos[], int scalarsPerPosition,
+                                                 const SkPoint& offset,
+                                                 const SkIRect& regionClipBounds);
+
     // Distance field text needs this table to compute a value for use in the fragment shader.
     // Because the GrAtlasTextContext can go out of scope before the final flush, this needs to be
     // refcnted and malloced
@@ -360,6 +383,10 @@
     friend class GrTextBlobCache;
     friend class BitmapTextBatch;
 
+#ifdef GR_TEST_UTILS
+    BATCH_TEST_FRIEND(TextBlob);
+#endif
+
     typedef GrTextContext INHERITED;
 };
 
diff --git a/src/gpu/GrBatchTest.h b/src/gpu/GrBatchTest.h
index 3199354..9daa960 100644
--- a/src/gpu/GrBatchTest.h
+++ b/src/gpu/GrBatchTest.h
@@ -29,7 +29,9 @@
 #define BATCH_TEST_EXTERN(Batch) \
     extern GrBatch* Batch##__Test(SkRandom*, GrContext* context);
 #define BATCH_TEST_ENTRY(Batch) \
-        Batch##__Test
+    Batch##__Test
+#define BATCH_TEST_FRIEND(Batch) \
+    friend GrBatch* Batch##__Test(SkRandom* random, GrContext* context);
 
 GrBatch* GrRandomBatch(SkRandom*, GrContext*);
 
diff --git a/src/gpu/GrTextContext.cpp b/src/gpu/GrTextContext.cpp
index 6153f3b..4c6dabc 100644
--- a/src/gpu/GrTextContext.cpp
+++ b/src/gpu/GrTextContext.cpp
@@ -66,6 +66,7 @@
     } while (textContext);
 
     // fall back to drawing as a path
+    SkASSERT(fGpuDevice);
     this->drawTextAsPath(skPaint, viewMatrix, text, byteLength, x, y, clipBounds);
 }
 
@@ -89,6 +90,7 @@
     } while (textContext);
 
     // fall back to drawing as a path
+    SkASSERT(fGpuDevice);
     this->drawPosTextAsPath(skPaint, viewMatrix, text, byteLength, pos, scalarsPerPosition, offset,
                             clipBounds);
 }
diff --git a/src/gpu/GrTextContext.h b/src/gpu/GrTextContext.h
index 60714ca..8529528 100644
--- a/src/gpu/GrTextContext.h
+++ b/src/gpu/GrTextContext.h
@@ -12,7 +12,6 @@
 #include "GrGlyph.h"
 #include "GrPaint.h"
 #include "SkDeviceProperties.h"
-
 #include "SkPostConfig.h"
 
 class GrClip;