Full implementation of Canvas.drawPath()

Change-Id: I23223b89770a0cd2b4762365bead9bfddb094290
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 3df105b..f2bb6ec 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -39,6 +39,8 @@
 #define MAX_TEXT_CACHE_WIDTH 2048
 #define TEXTURE_BORDER_SIZE 2
 
+#define AUTO_KERN(prev, next) (((next) - (prev) + 32) >> 6 << 16)
+
 ///////////////////////////////////////////////////////////////////////////////
 // CacheTextureLine
 ///////////////////////////////////////////////////////////////////////////////
@@ -163,6 +165,44 @@
     }
 }
 
+void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
+        SkPathMeasure& measure, SkPoint* position, SkVector* tangent) {
+    const float halfWidth = glyph->mBitmapWidth * 0.5f;
+    const float height = glyph->mBitmapHeight;
+
+    float nPenX = glyph->mBitmapLeft;
+    vOffset += glyph->mBitmapTop + height;
+
+    const float u1 = glyph->mBitmapMinU;
+    const float u2 = glyph->mBitmapMaxU;
+    const float v1 = glyph->mBitmapMinV;
+    const float v2 = glyph->mBitmapMaxV;
+
+    SkPoint destination[4];
+    measure.getPosTan(x + hOffset + nPenX + halfWidth, position, tangent);
+
+    // Move along the tangent and offset by the normal
+    destination[0].set(-tangent->fX * halfWidth - tangent->fY * vOffset,
+            -tangent->fY * halfWidth + tangent->fX * vOffset);
+    destination[1].set(tangent->fX * halfWidth - tangent->fY * vOffset,
+            tangent->fY * halfWidth + tangent->fX * vOffset);
+    destination[2].set(destination[1].fX + tangent->fY * height,
+            destination[1].fY - tangent->fX * height);
+    destination[3].set(destination[0].fX + tangent->fY * height,
+            destination[0].fY - tangent->fX * height);
+
+    mState->appendRotatedMeshQuad(
+            position->fX + destination[0].fX,
+            position->fY + destination[0].fY, u1, v2,
+            position->fX + destination[1].fX,
+            position->fY + destination[1].fY, u2, v2,
+            position->fX + destination[2].fX,
+            position->fY + destination[2].fY, u2, v1,
+            position->fX + destination[3].fX,
+            position->fY + destination[3].fY, u1, v1,
+            glyph->mCachedTextureLine->mCacheTexture);
+}
+
 CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit) {
     CachedGlyphInfo* cachedGlyph = NULL;
     ssize_t index = mCachedGlyphs.indexOfKey(textUnit);
@@ -198,6 +238,56 @@
             0, 0, NULL, positions);
 }
 
+void Font::render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+        int numGlyphs, SkPath* path, float hOffset, float vOffset) {
+    if (numGlyphs == 0 || text == NULL || len == 0) {
+        return;
+    }
+
+    text += start;
+
+    int glyphsCount = 0;
+    SkFixed prevRsbDelta = 0;
+
+    float penX = 0.0f;
+
+    SkPoint position;
+    SkVector tangent;
+
+    SkPathMeasure measure(*path, false);
+    float pathLength = SkScalarToFloat(measure.getLength());
+
+    if (paint->getTextAlign() != SkPaint::kLeft_Align) {
+        float textWidth = SkScalarToFloat(paint->measureText(text, len));
+        float pathOffset = pathLength;
+        if (paint->getTextAlign() == SkPaint::kCenter_Align) {
+            textWidth *= 0.5f;
+            pathOffset *= 0.5f;
+        }
+        penX += pathOffset - textWidth;
+    }
+
+    while (glyphsCount < numGlyphs && penX <= pathLength) {
+        glyph_t glyph = GET_GLYPH(text);
+
+        if (IS_END_OF_STRING(glyph)) {
+            break;
+        }
+
+        CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
+        penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
+        prevRsbDelta = cachedGlyph->mRsbDelta;
+
+        if (cachedGlyph->mIsValid) {
+            drawCachedGlyph(cachedGlyph, roundf(penX), hOffset, vOffset, measure, &position, &tangent);
+        }
+
+        penX += SkFixedToFloat(cachedGlyph->mAdvanceX);
+
+        glyphsCount++;
+    }
+}
+
 void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
         int numGlyphs, Rect *bounds) {
     if (bounds == NULL) {
@@ -208,19 +298,13 @@
     render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds, NULL);
 }
 
-#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16)
-
 void Font::render(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
         int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
-        uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions) {
+        uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) {
     if (numGlyphs == 0 || text == NULL || len == 0) {
         return;
     }
 
-    int glyphsCount = 0;
-
-    text += start;
-
     static RenderGlyph gRenderGlyph[] = {
             &android::uirenderer::Font::drawCachedGlyph,
             &android::uirenderer::Font::drawCachedGlyphBitmap,
@@ -228,14 +312,15 @@
     };
     RenderGlyph render = gRenderGlyph[mode];
 
+    text += start;
+    int glyphsCount = 0;
+
     if (CC_LIKELY(positions == NULL)) {
         SkFixed prevRsbDelta = 0;
 
-        float penX = x;
+        float penX = x + 0.5f;
         int penY = y;
 
-        penX += 0.5f;
-
         while (glyphsCount < numGlyphs) {
             glyph_t glyph = GET_GLYPH(text);
 
@@ -245,7 +330,7 @@
             }
 
             CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph);
-            penX += SkFixedToFloat(SkAutoKern_AdjustF(prevRsbDelta, cachedGlyph->mLsbDelta));
+            penX += SkFixedToFloat(AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta));
             prevRsbDelta = cachedGlyph->mRsbDelta;
 
             // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
@@ -582,6 +667,7 @@
             cacheBuffer[cacheY * cacheWidth + cacheX] = mGammaTable[tempCol];
         }
     }
+
     cachedGlyph->mIsValid = true;
 }
 
@@ -758,15 +844,9 @@
     mDrawn = true;
 }
 
-void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
-        float x2, float y2, float u2, float v2,
-        float x3, float y3, float u3, float v3,
+void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
+        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
         float x4, float y4, float u4, float v4, CacheTexture* texture) {
-
-    if (mClip &&
-            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
-        return;
-    }
     if (texture != mCurrentCacheTexture) {
         if (mCurrentQuadIndex != 0) {
             // First, draw everything stored already which uses the previous texture
@@ -802,6 +882,18 @@
     (*currentPos++) = v4;
 
     mCurrentQuadIndex++;
+}
+
+void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1,
+        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
+        float x4, float y4, float u4, float v4, CacheTexture* texture) {
+
+    if (mClip &&
+            (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) {
+        return;
+    }
+
+    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
 
     if (mBounds) {
         mBounds->left = fmin(mBounds->left, x1);
@@ -816,6 +908,25 @@
     }
 }
 
+void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
+        float x2, float y2, float u2, float v2, float x3, float y3, float u3, float v3,
+        float x4, float y4, float u4, float v4, CacheTexture* texture) {
+
+    appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture);
+
+    if (mBounds) {
+        mBounds->left = fmin(mBounds->left, fmin(x1, fmin(x2, fmin(x3, x4))));
+        mBounds->top = fmin(mBounds->top, fmin(y1, fmin(y2, fmin(y3, y4))));
+        mBounds->right = fmax(mBounds->right, fmax(x1, fmax(x2, fmax(x3, x4))));
+        mBounds->bottom = fmax(mBounds->bottom, fmax(y1, fmax(y2, fmax(y3, y4))));
+    }
+
+    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+}
+
 uint32_t FontRenderer::getRemainingCacheCapacity() {
     uint32_t remainingCapacity = 0;
     float totalPixels = 0;
@@ -956,6 +1067,21 @@
     return mDrawn;
 }
 
+bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text,
+        uint32_t startIndex, uint32_t len, int numGlyphs, SkPath* path,
+        float hOffset, float vOffset, Rect* bounds) {
+    if (!mCurrentFont) {
+        ALOGE("No font set");
+        return false;
+    }
+
+    initRender(clip, bounds);
+    mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
+    finishRender();
+
+    return mDrawn;
+}
+
 void FontRenderer::computeGaussianWeights(float* weights, int32_t radius) {
     // Compute gaussian weights for the blur
     // e is the euler's number
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index b767be5..4fc5862 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -24,6 +24,8 @@
 
 #include <SkScalerContext.h>
 #include <SkPaint.h>
+#include <SkPathMeasure.h>
+#include <SkPoint.h>
 
 #include <GLES2/gl2.h>
 
@@ -155,6 +157,9 @@
     void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
             int numGlyphs, int x, int y, const float* positions);
 
+    void render(SkPaint* paint, const char *text, uint32_t start, uint32_t len,
+            int numGlyphs, SkPath* path, float hOffset, float vOffset);
+
     /**
      * Creates a new font associated with the specified font state.
      */
@@ -200,6 +205,8 @@
     void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y,
             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
             Rect* bounds, const float* pos);
+    void drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset,
+            SkPathMeasure& measure, SkPoint* position, SkVector* tangent);
 
     CachedGlyphInfo* getCachedGlyph(SkPaint* paint, glyph_t textUnit);
 
@@ -244,6 +251,9 @@
     // bounds is an out parameter
     bool renderPosText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
             uint32_t len, int numGlyphs, int x, int y, const float* positions, Rect* bounds);
+    // bounds is an out parameter
+    bool renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
+            uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds);
 
     struct DropShadow {
         DropShadow() { };
@@ -305,7 +315,7 @@
     void allocateTextureMemory(CacheTexture* cacheTexture);
     void deallocateTextureMemory(CacheTexture* cacheTexture);
     void initTextTexture();
-    CacheTexture *createCacheTexture(int width, int height, bool allocate);
+    CacheTexture* createCacheTexture(int width, int height, bool allocate);
     void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph,
             uint32_t *retOriginX, uint32_t *retOriginY);
 
@@ -320,10 +330,18 @@
     void precacheLatin(SkPaint* paint);
 
     void issueDrawCommand();
+    void appendMeshQuadNoClip(float x1, float y1, float u1, float v1,
+            float x2, float y2, float u2, float v2,
+            float x3, float y3, float u3, float v3,
+            float x4, float y4, float u4, float v4, CacheTexture* texture);
     void appendMeshQuad(float x1, float y1, float u1, float v1,
             float x2, float y2, float u2, float v2,
             float x3, float y3, float u3, float v3,
             float x4, float y4, float u4, float v4, CacheTexture* texture);
+    void appendRotatedMeshQuad(float x1, float y1, float u1, float v1,
+            float x2, float y2, float u2, float v2,
+            float x3, float y3, float u3, float v3,
+            float x4, float y4, float u4, float v4, CacheTexture* texture);
 
     uint32_t mSmallCacheWidth;
     uint32_t mSmallCacheHeight;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index ebb6d88..73625b7 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2300,15 +2300,6 @@
         return;
     }
 
-    float x = 0.0f;
-    float y = 0.0f;
-
-    const bool pureTranslate = mSnapshot->transform->isPureTranslate();
-    if (CC_LIKELY(pureTranslate)) {
-        x = (int) floorf(x + mSnapshot->transform->getTranslateX() + 0.5f);
-        y = (int) floorf(y + mSnapshot->transform->getTranslateY() + 0.5f);
-    }
-
     FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(paint);
     fontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()),
             paint->getTextSize());
@@ -2326,41 +2317,30 @@
     setupDrawShader();
     setupDrawBlending(true, mode);
     setupDrawProgram();
-    setupDrawModelView(x, y, x, y, pureTranslate, true);
+    setupDrawModelView(0.0f, 0.0f, 0.0f, 0.0f, false, true);
     setupDrawTexture(fontRenderer.getTexture(true));
     setupDrawPureColorUniforms();
     setupDrawColorFilterUniforms();
-    setupDrawShaderUniforms(pureTranslate);
+    setupDrawShaderUniforms(false);
 
-//    mat4 pathTransform;
-//    pathTransform.loadTranslate(hOffset, vOffset, 0.0f);
-//
-//    float offset = 0.0f;
-//    SkPathMeasure pathMeasure(*path, false);
-//
-//    if (paint->getTextAlign() != SkPaint::kLeft_Align) {
-//        SkScalar pathLength = pathMeasure.getLength();
-//        if (paint->getTextAlign() == SkPaint::kCenter_Align) {
-//            pathLength = SkScalarHalf(pathLength);
-//        }
-//        offset += SkScalarToFloat(pathLength);
-//    }
+    const Rect* clip = &mSnapshot->getLocalClip();
+    Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
-//        SkScalar x;
-//        SkPath      tmp;
-//        SkMatrix    m(scaledMatrix);
-//
-//        m.postTranslate(xpos + hOffset, 0);
-//        if (matrix) {
-//            m.postConcat(*matrix);
-//        }
-//        morphpath(&tmp, *iterPath, meas, m);
-//        if (fDevice) {
-//            fDevice->drawPath(*this, tmp, iter.getPaint(), NULL, true);
-//        } else {
-//            this->drawPath(tmp, iter.getPaint(), NULL, true);
-//        }
-//    }
+#if RENDER_LAYERS_AS_REGIONS
+    const bool hasActiveLayer = hasLayer();
+#else
+    const bool hasActiveLayer = false;
+#endif
+
+    if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path,
+            hOffset, vOffset, hasActiveLayer ? &bounds : NULL)) {
+#if RENDER_LAYERS_AS_REGIONS
+        if (hasActiveLayer) {
+            mSnapshot->transform->mapRect(bounds);
+            dirtyLayerUnchecked(bounds, getRegion());
+        }
+#endif
+    }
 }
 
 void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java
index b798ee7..9849e3c 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextOnPathActivity.java
@@ -21,18 +21,21 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Path;
+import android.graphics.PathMeasure;
 import android.os.Bundle;
 import android.view.View;
 
 @SuppressWarnings({"UnusedDeclaration"})
 public class TextOnPathActivity extends Activity {
     private Path mPath;
+    private Path mStraightPath;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         mPath = makePath();
+        mStraightPath = makeStraightPath();
 
         final TextOnPathView view = new TextOnPathView(this);
         setContentView(view);
@@ -51,11 +54,28 @@
         path.cubicTo(-80.0f, 200.0f, 100.0f, 200.0f, 200.0f, 0.0f);
     }
 
+    private Path makeStraightPath() {
+        Path path = new Path();
+        buildStraightPath(path);
+        return path;
+    }
+
+    private void buildStraightPath(Path path) {
+        path.moveTo(0.0f, 0.0f);
+        path.lineTo(400.0f, 0.0f);
+    }
+
     public class TextOnPathView extends View {
         private static final String TEST_STRING = "Hello OpenGL renderer, text on path! ";
 
         private final Paint mPaint;
+        private final Paint mPathPaint;
         private final String mText;
+        private final PathMeasure mMeasure;
+        private final float mLength;
+        private final float[] mLines;
+        private final float[] mPos;
+        private final float[] mTan;
 
         public TextOnPathView(Context c) {
             super(c);
@@ -64,11 +84,23 @@
             mPaint.setAntiAlias(true);
             mPaint.setColor(0xff000000);
 
+            mPathPaint = new Paint();
+            mPathPaint.setAntiAlias(true);
+            mPathPaint.setStyle(Paint.Style.STROKE);
+            mPathPaint.setColor(0xff000099);
+
             StringBuilder builder = new StringBuilder(TEST_STRING.length() * 2);
             for (int i = 0; i < 2; i++) {
                 builder.append(TEST_STRING);
             }
             mText = builder.toString();
+
+            mMeasure = new PathMeasure(mPath, false);
+            mLength = mMeasure.getLength();
+            
+            mLines = new float[100 * 4];
+            mPos = new float[2];
+            mTan = new float[2];
         }
 
         @Override
@@ -81,19 +113,40 @@
             canvas.translate(400.0f, 350.0f);
             mPaint.setTextAlign(Paint.Align.LEFT);
             canvas.drawTextOnPath(mText + mText, mPath, 0.0f, 0.0f, mPaint);
+            canvas.drawPath(mPath, mPathPaint);
+            
+            for (int i = 0; i < 100; i++) {
+                mMeasure.getPosTan(i * mLength / 100.0f, mPos, mTan);
+                mLines[i * 4    ] = mPos[0];
+                mLines[i * 4 + 1] = mPos[1];
+                mLines[i * 4 + 2] = mPos[0] + mTan[1] * 15;
+                mLines[i * 4 + 3] = mPos[1] - mTan[0] * 15;
+            }
+            canvas.drawLines(mLines, mPathPaint);
+            
+            canvas.translate(200.0f, 0.0f);
+            canvas.drawTextOnPath(mText + mText, mStraightPath, 0.0f, 0.0f, mPaint);
+            canvas.drawPath(mStraightPath, mPathPaint);
+
             canvas.restore();
 
             canvas.save();
             canvas.translate(150.0f, 60.0f);
-            canvas.drawTextOnPath(mText, mPath, 0.0f, 0.0f, mPaint);
+            canvas.drawTextOnPath(mText, mPath, 0.0f, 10.0f, mPaint);
+            mMeasure.getPosTan(5.0f, mPos, mTan);
+            canvas.drawLine(mPos[0], mPos[1], mPos[0] + mTan[1] * 10, mPos[1] - mTan[0] * 10,
+                    mPathPaint);
+            canvas.drawPath(mPath, mPathPaint);
 
             canvas.translate(250.0f, 0.0f);
             mPaint.setTextAlign(Paint.Align.CENTER);
             canvas.drawTextOnPath(mText, mPath, 0.0f, 0.0f, mPaint);
+            canvas.drawPath(mPath, mPathPaint);
 
             canvas.translate(250.0f, 0.0f);
             mPaint.setTextAlign(Paint.Align.RIGHT);
             canvas.drawTextOnPath(mText, mPath, 0.0f, 0.0f, mPaint);
+            canvas.drawPath(mPath, mPathPaint);
             canvas.restore();
         }
     }