Add text rendering.

Change-Id: Ibe5a9fa844d531b31b55e43de403a98d49f659b9
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 1424638..78648ff 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -410,9 +410,21 @@
     public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
         // Shaders are ignored when drawing bitmaps
         final int nativePaint = paint == null ? 0 : paint.mNativePaint;
-        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, src.left, src.top, src.right, src.bottom,
-                dst.left, dst.top, dst.right, dst.bottom, nativePaint
-        );
+
+        int left, top, right, bottom;
+        if (src == null) {
+            left = top = 0;
+            right = bitmap.getWidth();
+            bottom = bitmap.getHeight();
+        } else {
+            left = src.left;
+            right = src.right;
+            top = src.top;
+            bottom = src.bottom;
+        }
+
+        nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
+                dst.left, dst.top, dst.right, dst.bottom, nativePaint);
     }
 
     @Override
@@ -420,8 +432,7 @@
         // Shaders are ignored when drawing bitmaps
         final int nativePaint = paint == null ? 0 : paint.mNativePaint;
         nDrawBitmap(mRenderer, bitmap.mNativeBitmap, src.left, src.top, src.right, src.bottom,
-                dst.left, dst.top, dst.right, dst.bottom, nativePaint
-        );
+                dst.left, dst.top, dst.right, dst.bottom, nativePaint);
     }
 
     private native void nDrawBitmap(int renderer, int bitmap,
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index fa4d23c..2f1dcb6 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -255,7 +255,8 @@
         OpenGLRenderer* renderer, jcharArray text, int index, int count,
         jfloat x, jfloat y, int flags, SkPaint* paint) {
     jchar* textArray = env->GetCharArrayElements(text, NULL);
-    // TODO: draw from textArray + index
+    // TODO: Prepare the text for RTL
+    renderer->drawText((const char*) (textArray + index), count, x, y, paint);
     env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
 }
 
@@ -263,7 +264,8 @@
         OpenGLRenderer* renderer, jstring text, int start, int end,
         jfloat x, jfloat y, int flags, SkPaint* paint) {
     const jchar* textArray = env->GetStringChars(text, NULL);
-    // TODO: draw from textArray + start
+    // TODO: Prepare the text for RTL
+    renderer->drawText((const char*) (textArray + start), end - start, x, y, paint);
     env->ReleaseStringChars(text, textArray);
 }
 
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 3d63aa6..6349cb3 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -931,15 +931,14 @@
     }
     
     /**
-     * Temporary API to expose layer drawing. This draws a shadow layer below
-     * the main layer, with the specified offset and color, and blur radius.
-     * If radius is 0, then the shadow layer is removed.
+     * This draws a shadow layer below the main layer, with the specified
+     * offset and color, and blur radius. If radius is 0, then the shadow
+     * layer is removed.
      */
-    public native void setShadowLayer(float radius, float dx, float dy,
-                                      int color);
+    public native void setShadowLayer(float radius, float dx, float dy, int color);
 
     /**
-     * Temporary API to clear the shadow layer.
+     * Clear the shadow layer.
      */
     public void clearShadowLayer() {
         setShadowLayer(0, 0, 0, 0);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 1bdb9d3..172952a 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -2,6 +2,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
+	FontRenderer.cpp \
 	GradientCache.cpp \
 	LayerCache.cpp \
 	Matrix.cpp \
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
new file mode 100644
index 0000000..8557b87
--- /dev/null
+++ b/libs/hwui/FontRenderer.cpp
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "FontRenderer.h"
+
+#include <SkUtils.h>
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Font
+///////////////////////////////////////////////////////////////////////////////
+
+Font::Font(FontRenderer* state, uint32_t fontId, float fontSize) :
+    mState(state), mFontId(fontId), mFontSize(fontSize) {
+}
+
+
+Font::~Font() {
+    for (uint32_t ct = 0; ct < mState->mActiveFonts.size(); ct++) {
+        if (mState->mActiveFonts[ct] == this) {
+            mState->mActiveFonts.removeAt(ct);
+            break;
+        }
+    }
+
+    for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
+        CachedGlyphInfo *glyph = mCachedGlyphs.valueAt(i);
+        delete glyph;
+    }
+}
+
+void Font::invalidateTextureCache() {
+    for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) {
+        mCachedGlyphs.valueAt(i)->mIsValid = false;
+    }
+}
+
+void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y) {
+    FontRenderer *state = mState;
+
+    int nPenX = x + glyph->mBitmapLeft;
+    int nPenY = y + glyph->mBitmapTop + glyph->mBitmapHeight;
+
+    state->appendMeshQuad(nPenX, nPenY, 0, glyph->mBitmapMinU, glyph->mBitmapMaxV,
+            nPenX + (int) glyph->mBitmapWidth, nPenY, 0, glyph->mBitmapMaxU, glyph->mBitmapMaxV,
+            nPenX + (int) glyph->mBitmapWidth, nPenY - (int) glyph->mBitmapHeight,
+            0, glyph->mBitmapMaxU, glyph->mBitmapMinV, nPenX, nPenY - (int) glyph->mBitmapHeight,
+            0, glyph->mBitmapMinU, glyph->mBitmapMinV);
+}
+
+void Font::renderUTF(SkPaint* paint, const char *text, uint32_t len, uint32_t start, int numGlyphs,
+        int x, int y) {
+    if (numGlyphs == 0 || text == NULL || len == 0) {
+        return;
+    }
+
+    int penX = x, penY = y;
+    int glyphsLeft = 1;
+    if (numGlyphs > 0) {
+        glyphsLeft = numGlyphs;
+    }
+
+    //size_t index = start;
+    //size_t nextIndex = 0;
+
+    text += start;
+
+    while (glyphsLeft > 0) {
+        //int32_t utfChar = utf32_at(text, len, index, &nextIndex);
+        int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text);
+
+        // Reached the end of the string or encountered
+        if (utfChar < 0) {
+            break;
+        }
+
+        // Move to the next character in the array
+        //index = nextIndex;
+
+        CachedGlyphInfo *cachedGlyph = mCachedGlyphs.valueFor(utfChar);
+
+        if (cachedGlyph == NULL) {
+            cachedGlyph = cacheGlyph(paint, utfChar);
+        }
+        // Is the glyph still in texture cache?
+        if (!cachedGlyph->mIsValid) {
+            const SkGlyph& skiaGlyph = paint->getUnicharMetrics(utfChar);
+            updateGlyphCache(paint, skiaGlyph, cachedGlyph);
+        }
+
+        // If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
+        if (cachedGlyph->mIsValid) {
+            drawCachedGlyph(cachedGlyph, penX, penY);
+        }
+
+        // TODO: Check how to do this conversion
+        penX += SkFixedRound(cachedGlyph->mAdvanceX);
+
+        // If we were given a specific number of glyphs, decrement
+        if (numGlyphs > 0) {
+            glyphsLeft--;
+        }
+    }
+}
+
+void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo *glyph) {
+    glyph->mAdvanceX = skiaGlyph.fAdvanceX;
+    glyph->mAdvanceY = skiaGlyph.fAdvanceY;
+    glyph->mBitmapLeft = skiaGlyph.fLeft;
+    glyph->mBitmapTop = skiaGlyph.fTop;
+
+    uint32_t startX = 0;
+    uint32_t startY = 0;
+
+    // Let the font state figure out where to put the bitmap
+    FontRenderer *state = mState;
+    // Get the bitmap for the glyph
+    paint->findImage(skiaGlyph);
+    glyph->mIsValid = state->cacheBitmap(skiaGlyph, &startX, &startY);
+
+    if (!glyph->mIsValid) {
+        return;
+    }
+
+    uint32_t endX = startX + skiaGlyph.fWidth;
+    uint32_t endY = startY + skiaGlyph.fHeight;
+
+    glyph->mBitmapWidth = skiaGlyph.fWidth;
+    glyph->mBitmapHeight = skiaGlyph.fHeight;
+
+    uint32_t cacheWidth = state->getCacheWidth();
+    uint32_t cacheHeight = state->getCacheHeight();
+
+    glyph->mBitmapMinU = (float) startX / (float) cacheWidth;
+    glyph->mBitmapMinV = (float) startY / (float) cacheHeight;
+    glyph->mBitmapMaxU = (float) endX / (float) cacheWidth;
+    glyph->mBitmapMaxV = (float) endY / (float) cacheHeight;
+
+    state->mUploadTexture = true;
+}
+
+Font::CachedGlyphInfo *Font::cacheGlyph(SkPaint* paint, int32_t glyph) {
+    CachedGlyphInfo *newGlyph = new CachedGlyphInfo();
+    mCachedGlyphs.add(glyph, newGlyph);
+
+    const SkGlyph& skiaGlyph = paint->getUnicharMetrics(glyph);
+    newGlyph->mGlyphIndex = skiaGlyph.fID;
+    newGlyph->mIsValid = false;
+
+    updateGlyphCache(paint, skiaGlyph, newGlyph);
+
+    return newGlyph;
+}
+
+Font* Font::create(FontRenderer* state, uint32_t fontId, float fontSize) {
+    Vector<Font*> &activeFonts = state->mActiveFonts;
+
+    for (uint32_t i = 0; i < activeFonts.size(); i++) {
+        Font *ithFont = activeFonts[i];
+        if (ithFont->mFontId == fontId && ithFont->mFontSize == fontSize) {
+            return ithFont;
+        }
+    }
+
+    Font* newFont = new Font(state, fontId, fontSize);
+    activeFonts.push(newFont);
+    return newFont;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// FontRenderer
+///////////////////////////////////////////////////////////////////////////////
+
+FontRenderer::FontRenderer() {
+    mInitialized = false;
+    mMaxNumberOfQuads = 1024;
+    mCurrentQuadIndex = 0;
+
+    mIndexBufferID = 0;
+
+    mCacheWidth = 1024;
+    mCacheHeight = 256;
+}
+
+FontRenderer::~FontRenderer() {
+    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+        delete mCacheLines[i];
+    }
+    mCacheLines.clear();
+
+    delete mTextTexture;
+
+    Vector<Font*> fontsToDereference = mActiveFonts;
+    for (uint32_t i = 0; i < fontsToDereference.size(); i++) {
+        delete fontsToDereference[i];
+    }
+}
+
+void FontRenderer::flushAllAndInvalidate() {
+    if (mCurrentQuadIndex != 0) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+    for (uint32_t i = 0; i < mActiveFonts.size(); i++) {
+        mActiveFonts[i]->invalidateTextureCache();
+    }
+    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+        mCacheLines[i]->mCurrentCol = 0;
+    }
+}
+
+bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) {
+    // If the glyph is too tall, don't cache it
+    if (glyph.fWidth > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
+        LOGE("Font size to large to fit in cache. width, height = %i, %i",
+                (int) glyph.fWidth, (int) glyph.fHeight);
+        return false;
+    }
+
+    // Now copy the bitmap into the cache texture
+    uint32_t startX = 0;
+    uint32_t startY = 0;
+
+    bool bitmapFit = false;
+    for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+        bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
+        if (bitmapFit) {
+            break;
+        }
+    }
+
+    // If the new glyph didn't fit, flush the state so far and invalidate everything
+    if (!bitmapFit) {
+        flushAllAndInvalidate();
+
+        // Try to fit it again
+        for (uint32_t i = 0; i < mCacheLines.size(); i++) {
+            bitmapFit = mCacheLines[i]->fitBitmap(glyph, &startX, &startY);
+            if (bitmapFit) {
+                break;
+            }
+        }
+
+        // if we still don't fit, something is wrong and we shouldn't draw
+        if (!bitmapFit) {
+            LOGE("Bitmap doesn't fit in cache. width, height = %i, %i",
+                    (int) glyph.fWidth, (int) glyph.fHeight);
+            return false;
+        }
+    }
+
+    *retOriginX = startX;
+    *retOriginY = startY;
+
+    uint32_t endX = startX + glyph.fWidth;
+    uint32_t endY = startY + glyph.fHeight;
+
+    uint32_t cacheWidth = mCacheWidth;
+
+    unsigned char *cacheBuffer = mTextTexture;
+    unsigned char *bitmapBuffer = (unsigned char*) glyph.fImage;
+    unsigned int stride = glyph.rowBytes();
+
+    uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0;
+    for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) {
+        for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY++) {
+            unsigned char tempCol = bitmapBuffer[bY * stride + bX];
+            cacheBuffer[cacheY * cacheWidth + cacheX] = tempCol;
+        }
+    }
+
+    return true;
+}
+
+void FontRenderer::initTextTexture() {
+    mTextTexture = new unsigned char[mCacheWidth * mCacheHeight];
+    mUploadTexture = false;
+
+    glGenTextures(1, &mTextureId);
+    glBindTexture(GL_TEXTURE_2D, mTextureId);
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+    // Split up our cache texture into lines of certain widths
+    int nextLine = 0;
+    mCacheLines.push(new CacheTextureLine(16, mCacheWidth, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(24, mCacheWidth, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(32, mCacheWidth, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(32, mCacheWidth, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(40, mCacheWidth, nextLine, 0));
+    nextLine += mCacheLines.top()->mMaxHeight;
+    mCacheLines.push(new CacheTextureLine(mCacheHeight - nextLine, mCacheWidth, nextLine, 0));
+}
+
+// Avoid having to reallocate memory and render quad by quad
+void FontRenderer::initVertexArrayBuffers() {
+    uint32_t numIndicies = mMaxNumberOfQuads * 6;
+    uint32_t indexBufferSizeBytes = numIndicies * sizeof(uint16_t);
+    uint16_t *indexBufferData = (uint16_t*) malloc(indexBufferSizeBytes);
+
+    // Four verts, two triangles , six indices per quad
+    for (uint32_t i = 0; i < mMaxNumberOfQuads; i++) {
+        int i6 = i * 6;
+        int i4 = i * 4;
+
+        indexBufferData[i6 + 0] = i4 + 0;
+        indexBufferData[i6 + 1] = i4 + 1;
+        indexBufferData[i6 + 2] = i4 + 2;
+
+        indexBufferData[i6 + 3] = i4 + 0;
+        indexBufferData[i6 + 4] = i4 + 2;
+        indexBufferData[i6 + 5] = i4 + 3;
+    }
+
+    glGenBuffers(1, &mIndexBufferID);
+    glBindBuffer(GL_ARRAY_BUFFER, mIndexBufferID);
+    glBufferData(GL_ARRAY_BUFFER, indexBufferSizeBytes, indexBufferData, GL_DYNAMIC_DRAW);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+    free(indexBufferData);
+
+    uint32_t coordSize = 3;
+    uint32_t uvSize = 2;
+    uint32_t vertsPerQuad = 4;
+    uint32_t vertexBufferSizeBytes = mMaxNumberOfQuads * vertsPerQuad * coordSize *
+            uvSize * sizeof(float);
+    mTextMeshPtr = (float*) malloc(vertexBufferSizeBytes);
+}
+
+// We don't want to allocate anything unless we actually draw text
+void FontRenderer::checkInit() {
+    if (mInitialized) {
+        return;
+    }
+
+    initTextTexture();
+    initVertexArrayBuffers();
+
+    mInitialized = true;
+}
+
+void FontRenderer::issueDrawCommand() {
+    if (mUploadTexture) {
+        glBindTexture(GL_TEXTURE_2D, mTextureId);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0, GL_ALPHA,
+                GL_UNSIGNED_BYTE, mTextTexture);
+        mUploadTexture = false;
+    }
+
+    float *vtx = mTextMeshPtr;
+    float *tex = vtx + 3;
+
+    // position is slot 0
+    uint32_t slot = 0;
+    glVertexAttribPointer(slot, 3, GL_FLOAT, false, 20, vtx);
+
+    // texture0 is slot 1
+    slot = 1;
+    glVertexAttribPointer(slot, 2, GL_FLOAT, false, 20, tex);
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferID);
+    glDrawElements(GL_TRIANGLES, mCurrentQuadIndex * 6, GL_UNSIGNED_SHORT, NULL);
+}
+
+void FontRenderer::appendMeshQuad(float x1, float y1, float z1, float u1, float v1, float x2,
+        float y2, float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3,
+        float x4, float y4, float z4, float u4, float v4) {
+    const uint32_t vertsPerQuad = 4;
+    const uint32_t floatsPerVert = 5;
+    float *currentPos = mTextMeshPtr + mCurrentQuadIndex * vertsPerQuad * floatsPerVert;
+
+    // TODO: Cull things that are off the screen
+    //    float width = (float)mRSC->getWidth();
+    //    float height = (float)mRSC->getHeight();
+    //
+    //    if(x1 > width || y1 < 0.0f || x2 < 0 || y4 > height) {
+    //        return;
+    //    }
+
+    (*currentPos++) = x1;
+    (*currentPos++) = y1;
+    (*currentPos++) = z1;
+    (*currentPos++) = u1;
+    (*currentPos++) = v1;
+
+    (*currentPos++) = x2;
+    (*currentPos++) = y2;
+    (*currentPos++) = z2;
+    (*currentPos++) = u2;
+    (*currentPos++) = v2;
+
+    (*currentPos++) = x3;
+    (*currentPos++) = y3;
+    (*currentPos++) = z3;
+    (*currentPos++) = u3;
+    (*currentPos++) = v3;
+
+    (*currentPos++) = x4;
+    (*currentPos++) = y4;
+    (*currentPos++) = z4;
+    (*currentPos++) = u4;
+    (*currentPos++) = v4;
+
+    mCurrentQuadIndex++;
+
+    if (mCurrentQuadIndex == mMaxNumberOfQuads) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+}
+
+void FontRenderer::setFont(uint32_t fontId, float fontSize) {
+    mCurrentFont = Font::create(this, fontId, fontSize);
+}
+
+void FontRenderer::renderText(SkPaint* paint, const char *text, uint32_t len, uint32_t startIndex,
+        int numGlyphs, int x, int y) {
+    checkInit();
+
+    // Render code here
+    Font *currentFont = mCurrentFont;
+    if (!currentFont) {
+        LOGE("Unable to initialize any fonts");
+        return;
+    }
+
+    currentFont->renderUTF(paint, text, len, startIndex, numGlyphs, x, y);
+
+    if (mCurrentQuadIndex != 0) {
+        issueDrawCommand();
+        mCurrentQuadIndex = 0;
+    }
+}
+
+void FontRenderer::renderText(SkPaint* paint, const char *text, int x, int y) {
+    size_t textLen = strlen(text);
+    renderText(paint, text, textLen, 0, -1, x, y);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
new file mode 100644
index 0000000..c18327a
--- /dev/null
+++ b/libs/hwui/FontRenderer.h
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_FONT_RENDERER_H
+#define ANDROID_UI_FONT_RENDERER_H
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+
+#include <SkScalerContext.h>
+#include <SkPaint.h>
+
+#include <GLES2/gl2.h>
+
+namespace android {
+namespace uirenderer {
+
+class FontRenderer;
+
+class Font {
+public:
+    ~Font();
+
+    // Pointer to the utf data, length of data, where to start, number of glyphs ot read
+    // (each glyph may be longer than a char because we are dealing with utf data)
+    // Last two variables are the initial pen position
+    void renderUTF(SkPaint* paint, const char *text, uint32_t len, uint32_t start,
+            int numGlyphs, int x, int y);
+
+    static Font* create(FontRenderer* state, uint32_t fontId, float fontSize);
+
+protected:
+
+    friend class FontRenderer;
+
+    void invalidateTextureCache();
+    struct CachedGlyphInfo {
+        // Has the cache been invalidated?
+        bool mIsValid;
+        // Location of the cached glyph in the bitmap
+        // in case we need to resize the texture
+        uint32_t mBitmapWidth;
+        uint32_t mBitmapHeight;
+        // Also cache texture coords for the quad
+        float mBitmapMinU;
+        float mBitmapMinV;
+        float mBitmapMaxU;
+        float mBitmapMaxV;
+        // Minimize how much we call freetype
+        uint32_t mGlyphIndex;
+        uint32_t mAdvanceX;
+        uint32_t mAdvanceY;
+        // Values below contain a glyph's origin in the bitmap
+        uint32_t mBitmapLeft;
+        uint32_t mBitmapTop;
+    };
+
+    FontRenderer* mState;
+    uint32_t mFontId;
+    float mFontSize;
+
+    Font(FontRenderer* state, uint32_t fontId, float fontSize);
+
+    DefaultKeyedVector<int32_t, CachedGlyphInfo*> mCachedGlyphs;
+
+    CachedGlyphInfo *cacheGlyph(SkPaint* paint, int32_t glyph);
+    void updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyphInfo *glyph);
+    void drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y);
+};
+
+class FontRenderer {
+public:
+    FontRenderer();
+    ~FontRenderer();
+
+    void init();
+    void deinit();
+
+    void setFont(uint32_t fontId, float fontSize);
+    void renderText(SkPaint* paint, const char *text, uint32_t len, uint32_t startIndex,
+            int numGlyphs, int x, int y);
+    void renderText(SkPaint* paint, const char *text, int x, int y);
+
+    GLuint getTexture() {
+        checkInit();
+        return mTextureId;
+    }
+
+protected:
+    friend class Font;
+
+    struct CacheTextureLine {
+        uint16_t mMaxHeight;
+        uint16_t mMaxWidth;
+        uint32_t mCurrentRow;
+        uint32_t mCurrentCol;
+
+        CacheTextureLine(uint16_t maxHeight, uint16_t maxWidth, uint32_t currentRow,
+                uint32_t currentCol):
+            mMaxHeight(maxHeight), mMaxWidth(maxWidth), mCurrentRow(currentRow),
+            mCurrentCol(currentCol) {
+        }
+
+        bool fitBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY) {
+            if (glyph.fHeight > mMaxHeight) {
+                return false;
+            }
+
+            if (mCurrentCol + glyph.fWidth < mMaxWidth) {
+                *retOriginX = mCurrentCol;
+                *retOriginY = mCurrentRow;
+                mCurrentCol += glyph.fWidth;
+                return true;
+            }
+
+            return false;
+        }
+    };
+
+    uint32_t getCacheWidth() const {
+        return mCacheWidth;
+    }
+
+    uint32_t getCacheHeight() const {
+        return mCacheHeight;
+    }
+
+    void initTextTexture();
+
+    bool cacheBitmap(const SkGlyph& glyph, uint32_t *retOriginX, uint32_t *retOriginY);
+
+    void flushAllAndInvalidate();
+    void initVertexArrayBuffers();
+
+    void checkInit();
+
+    void issueDrawCommand();
+
+    void appendMeshQuad(float x1, float y1, float z1, float u1, float v1, float x2, float y2,
+            float z2, float u2, float v2, float x3, float y3, float z3, float u3, float v3,
+            float x4, float y4, float z4, float u4, float v4);
+
+    uint32_t mCacheWidth;
+    uint32_t mCacheHeight;
+
+    Font* mCurrentFont;
+
+    Vector<CacheTextureLine*> mCacheLines;
+
+    Vector<Font*> mActiveFonts;
+
+    // Texture to cache glyph bitmaps
+    unsigned char* mTextTexture;
+    GLuint mTextureId;
+    bool mUploadTexture;
+
+    // Pointer to vertex data to speed up frame to frame work
+    float *mTextMeshPtr;
+    uint32_t mCurrentQuadIndex;
+    uint32_t mMaxNumberOfQuads;
+
+    uint32_t mIndexBufferID;
+
+    bool mInitialized;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_FONT_RENDERER_H
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 1fa76d2..8f04d92 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -21,6 +21,7 @@
 #include <sys/types.h>
 
 #include <SkCanvas.h>
+#include <SkTypeface.h>
 
 #include <cutils/properties.h>
 #include <utils/Log.h>
@@ -134,6 +135,7 @@
 
     mDrawColorProgram = new DrawColorProgram;
     mDrawTextureProgram = new DrawTextureProgram;
+    mDrawTextProgram = new DrawTextProgram;
     mDrawLinearGradientProgram = new DrawLinearGradientProgram;
     mCurrentProgram = mDrawTextureProgram;
 
@@ -527,6 +529,39 @@
     drawColorRect(left, top, right, bottom, color, mode);
 }
 
+void OpenGLRenderer::drawText(const char* text, int count, float x, float y, SkPaint* paint) {
+    // TODO: Support paint's text alignments, proper clipping
+    if (quickReject(x, y, x + 1, y +1)) {
+        return;
+    }
+
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    uint32_t color = paint->getColor();
+    const GLfloat a = alpha / 255.0f;
+    const GLfloat r = a * ((color >> 16) & 0xFF) / 255.0f;
+    const GLfloat g = a * ((color >>  8) & 0xFF) / 255.0f;
+    const GLfloat b = a * ((color      ) & 0xFF) / 255.0f;
+
+    mModelView.loadIdentity();
+
+    useProgram(mDrawTextProgram);
+    mDrawTextProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform);
+
+    chooseBlending(true, mode);
+    bindTexture(mFontRenderer.getTexture(), GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
+
+    // Always premultiplied
+    glUniform4f(mDrawTextProgram->color, r, g, b, a);
+
+    mFontRenderer.setFont(SkTypeface::UniqueID(paint->getTypeface()), paint->getTextSize());
+    mFontRenderer.renderText(paint, text, count, 0, count, x, y);
+
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Shaders
 ///////////////////////////////////////////////////////////////////////////////
@@ -687,6 +722,7 @@
     float u2 = right - left;
     float v2 = bottom - top;
 
+    // TODO: If the texture is not pow, use a shader to support repeat/mirror
     if (mShaderMatrix) {
         SkMatrix inverse;
         mShaderMatrix->invert(&inverse);
@@ -742,7 +778,6 @@
     bindTexture(texture, mShaderTileX, mShaderTileY);
 
     // Always premultiplied
-    //glUniform4f(mDrawTextureProgram->color, alpha, alpha, alpha, alpha);
     glUniform4f(mDrawTextureProgram->color, alpha, alpha, alpha, alpha);
 
     glVertexAttribPointer(mDrawTextureProgram->position, 2, GL_FLOAT, GL_FALSE,
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 9dc2a43..b82366b 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -39,6 +39,7 @@
 #include "GradientCache.h"
 #include "PatchCache.h"
 #include "Vertex.h"
+#include "FontRenderer.h"
 
 namespace android {
 namespace uirenderer {
@@ -108,6 +109,8 @@
             float* positions, int count, SkShader::TileMode tileMode,
             SkMatrix* matrix, bool hasAlpha);
 
+    void drawText(const char* text, int count, float x, float y, SkPaint* paint);
+
 private:
     /**
      * Type of Skia shader in use.
@@ -329,6 +332,7 @@
     sp<Program> mCurrentProgram;
     sp<DrawColorProgram> mDrawColorProgram;
     sp<DrawTextureProgram> mDrawTextureProgram;
+    sp<DrawTextProgram> mDrawTextProgram;
     sp<DrawLinearGradientProgram> mDrawLinearGradientProgram;
 
     // Used to draw textured quads
@@ -357,6 +361,9 @@
     float* mShaderPositions;
     int mShaderCount;
 
+    // Font renderer
+    FontRenderer mFontRenderer;
+
     // Various caches
     TextureCache mTextureCache;
     LayerCache mLayerCache;
diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp
index 841b6c8..6e60808 100644
--- a/libs/hwui/Program.cpp
+++ b/libs/hwui/Program.cpp
@@ -33,6 +33,8 @@
 #include "shaders/drawTexture.vert"
 #include "shaders/drawTexture.frag"
 
+#include "shaders/drawText.frag"
+
 #include "shaders/drawLinearGradient.vert"
 #include "shaders/drawLinearGradient.frag"
 
@@ -169,6 +171,12 @@
     sampler = addUniform("sampler");
 }
 
+DrawTextureProgram::DrawTextureProgram(const char* vertex, const char* fragment):
+        DrawColorProgram(vertex, fragment) {
+    texCoords = addAttrib("texCoords");
+    sampler = addUniform("sampler");
+}
+
 void DrawTextureProgram::use() {
     DrawColorProgram::use();
     glActiveTexture(GL_TEXTURE0);
@@ -182,6 +190,14 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// Draw text
+///////////////////////////////////////////////////////////////////////////////
+
+DrawTextProgram::DrawTextProgram():
+        DrawTextureProgram(gDrawTextureVertexShader, gDrawTextFragmentShader) {
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // Draw linear gradient
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index 18a8e92..824aa05 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -167,6 +167,7 @@
 class DrawTextureProgram: public DrawColorProgram {
 public:
     DrawTextureProgram();
+    DrawTextureProgram(const char* vertex, const char* fragment);
 
     /**
      * Binds this program to the GL context.
@@ -190,6 +191,11 @@
     int texCoords;
 };
 
+class DrawTextProgram: public DrawTextureProgram {
+public:
+    DrawTextProgram();
+};
+
 /**
  * Program used to draw linear gradients. In addition to everything that the
  * DrawColorProgram supports, the following two attributes must be specified:
diff --git a/libs/hwui/shaders/drawText.frag b/libs/hwui/shaders/drawText.frag
new file mode 100644
index 0000000..49532c7
--- /dev/null
+++ b/libs/hwui/shaders/drawText.frag
@@ -0,0 +1,14 @@
+SHADER_SOURCE(gDrawTextFragmentShader,
+
+precision mediump float;
+
+varying vec2 outTexCoords;
+
+uniform vec4 color;
+uniform sampler2D sampler;
+
+void main(void) {
+    gl_FragColor = color * texture2D(sampler, outTexCoords).a;
+}
+
+);
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 098359c..8cb9e0d 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -105,6 +105,16 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        
+        <activity
+                android:name="TextActivity"
+                android:label="_Text"
+                android:theme="@android:style/Theme.NoTitleBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
                 
     </application>
 </manifest>
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
new file mode 100644
index 0000000..6665ef5
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class TextActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new CustomTextView(this));
+    }
+
+    static class CustomTextView extends View {
+        private final Paint mMediumPaint;
+        private final Paint mLargePaint;
+
+        CustomTextView(Context c) {
+            super(c);
+
+            mMediumPaint = new Paint();
+            mMediumPaint.setAntiAlias(true);
+            mMediumPaint.setColor(0xffff0000);
+            mLargePaint = new Paint();
+            mLargePaint.setAntiAlias(true);
+            mLargePaint.setTextSize(36.0f);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(255, 255, 255);
+            
+            canvas.drawText("Hello OpenGL renderer!", 100, 100, mMediumPaint);
+            canvas.drawText("Hello OpenGL renderer!", 100, 200, mLargePaint);
+        }
+    }
+}
\ No newline at end of file