Support bidi/shaping for getTextPath

Move layout-related code into separate class since it's needed by both
canvas and paint.

Change-Id: Iba89a1d94d7cca650255ffa3cbc952b988a51b54
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 722dacb..d19cae4 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -105,6 +105,7 @@
 	android/graphics/Rasterizer.cpp \
 	android/graphics/Region.cpp \
 	android/graphics/Shader.cpp \
+	android/graphics/TextLayout.cpp \
 	android/graphics/Typeface.cpp \
 	android/graphics/Xfermode.cpp \
 	android/graphics/YuvToJpegEncoder.cpp \
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 2e49c64..558f5ff 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -30,6 +30,8 @@
 #include "SkBoundaryPatch.h"
 #include "SkMeshUtils.h"
 
+#include "TextLayout.h"
+
 #include "unicode/ubidi.h"
 #include "unicode/ushape.h"
 
@@ -57,24 +59,6 @@
 class SkCanvasGlue {
 public:
 
-    enum {
-      kDirection_LTR = 0,
-      kDirection_RTL = 1
-    };
-
-    enum {
-      kDirection_Mask = 0x1
-    };
-
-    enum {
-      kBidi_LTR = 0,
-      kBidi_RTL = 1,
-      kBidi_Default_LTR = 2,
-      kBidi_Default_RTL = 3,
-      kBidi_Force_LTR = 4,
-      kBidi_Force_RTL = 5
-    };
-
     static void finalizer(JNIEnv* env, jobject clazz, SkCanvas* canvas) {
         canvas->unref();
     }
@@ -767,192 +751,12 @@
                              indices, indexCount, *paint);
     }
 
-    /**
-     * Character-based Arabic shaping.
-     *
-     * We'll use harfbuzz and glyph-based shaping instead once we're set up for it.
-     *
-     * @context the text context
-     * @start the start of the text to render
-     * @count the length of the text to render, start + count  must be <= contextCount
-     * @contextCount the length of the context
-     * @shaped where to put the shaped text, must have capacity for count uchars
-     * @return the length of the shaped text, or -1 if error
-     */
-    static int shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
-                            jchar* shaped, UErrorCode &status) {
-        jchar buffer[contextCount];
-
-        // Use fixed length since we need to keep start and count valid
-        u_shapeArabic(context, contextCount, buffer, contextCount,
-                       U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
-                       U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
-                       U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
-
-        if (U_SUCCESS(status)) {
-            // trim out 0xffff following ligatures, if any
-            int end = 0;
-            for (int i = start, e = start + count; i < e; ++i) {
-                if (buffer[i] != 0xffff) {
-                    buffer[end++] = buffer[i];
-                }
-            }
-            count = end;
-            // LOG(LOG_INFO, "CSRTL", "start %d count %d ccount %d\n", start, count, contextCount);
-            ubidi_writeReverse(buffer, count, shaped, count, UBIDI_DO_MIRRORING | UBIDI_OUTPUT_REVERSE
-                               | UBIDI_KEEP_BASE_COMBINING, &status);
-            if (U_SUCCESS(status)) {
-                return count;
-            }
-        }
-
-        return -1;
-    }
-
-    /**
-     * Basic character-based layout supporting rtl and arabic shaping.
-     * Runs bidi on the text and generates a reordered, shaped line in buffer, returning
-     * the length.
-     * @text the text
-     * @len the length of the text in uchars
-     * @dir receives the resolved paragraph direction
-     * @buffer the buffer to receive the reordered, shaped line.  Must have capacity of
-     * at least len jchars.
-     * @flags line bidi flags
-     * @return the length of the reordered, shaped line, or -1 if error
-     */
-    static jint layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
-            UErrorCode &status) {
-        static int RTL_OPTS = UBIDI_DO_MIRRORING | UBIDI_KEEP_BASE_COMBINING |
-                UBIDI_REMOVE_BIDI_CONTROLS | UBIDI_OUTPUT_REVERSE;
-
-        UBiDiLevel bidiReq = 0;
-        switch (flags) {
-          case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
-          case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
-          case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
-          case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
-          case kBidi_Force_LTR: memcpy(buffer, text, len * sizeof(jchar)); return len;
-          case kBidi_Force_RTL: return shapeRtlText(text, 0, len, len, buffer, status);
-        }
-
-        int32_t result = -1;
-
-        UBiDi* bidi = ubidi_open();
-        if (bidi) {
-            ubidi_setPara(bidi, text, len, bidiReq, NULL, &status);
-            if (U_SUCCESS(status)) {
-                dir = ubidi_getParaLevel(bidi) & 0x1; // 0 if ltr, 1 if rtl
-
-                int rc = ubidi_countRuns(bidi, &status);
-                if (U_SUCCESS(status)) {
-                    // LOG(LOG_INFO, "LAYOUT", "para bidiReq=%d dir=%d rc=%d\n", bidiReq, dir, rc);
-
-                    int32_t slen = 0;
-                    for (int i = 0; i < rc; ++i) {
-                        int32_t start;
-                        int32_t length;
-                        UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &start, &length);
-                        // LOG(LOG_INFO, "LAYOUT", "  [%2d] runDir=%d start=%3d len=%3d\n", i, runDir, start, length);
-                        if (runDir == UBIDI_RTL) {
-                            slen += shapeRtlText(text + start, 0, length, length, buffer + slen, status);
-                        } else {
-                            memcpy(buffer + slen, text + start, length * sizeof(jchar));
-                            slen += length;
-                        }
-                    }
-                    if (U_SUCCESS(status)) {
-                        result = slen;
-                    }
-                }
-            }
-            ubidi_close(bidi);
-        }
-
-        return result;
-    }
-
-    // Returns true if we might need layout.  If bidiFlags force LTR, assume no layout, if
-    // bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
-    // looking for a character >= the first RTL character in unicode and assume we do if
-    // we find one.
-    static bool needsLayout(const jchar* text, jint len, jint bidiFlags) {
-        if (bidiFlags == kBidi_Force_LTR) {
-            return false;
-        }
-        if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) ||
-                bidiFlags == kBidi_Force_RTL) {
-            return true;
-        }
-        for (int i = 0; i < len; ++i) {
-            if (text[i] >= 0x0590) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // Draws a paragraph of text on a single line, running bidi and shaping
-    static void drawText(JNIEnv* env, SkCanvas* canvas, const jchar* text, jsize len,
-                           jfloat x, jfloat y, int bidiFlags, SkPaint* paint) {
-
-        SkScalar x_ = SkFloatToScalar(x);
-        SkScalar y_ = SkFloatToScalar(y);
-
-        SkPaint::Align horiz = paint->getTextAlign();
-
-        const jchar *workText = text;
-        jchar *buffer = NULL;
-        int dir = kDirection_LTR;
-        if (needsLayout(text, len, bidiFlags)) {
-            buffer =(jchar *) malloc(len * sizeof(jchar));
-            if (!buffer) {
-                return;
-            }
-            UErrorCode status = U_ZERO_ERROR;
-            len = layoutLine(text, len, bidiFlags, dir, buffer, status); // might change len, dir
-            if (!U_SUCCESS(status)) {
-                LOG(LOG_WARN, "LAYOUT", "drawText error %d\n", status);
-                free(buffer);
-                return; // can't render
-            }
-
-            workText = buffer; // use the shaped text
-        }
-
-        bool trimLeft = false;
-        bool trimRight = false;
-
-        switch (horiz) {
-        case SkPaint::kLeft_Align: trimLeft = dir & kDirection_Mask; break;
-        case SkPaint::kCenter_Align: trimLeft = trimRight = true; break;
-        case SkPaint::kRight_Align: trimRight = !(dir & kDirection_Mask);
-        default: break;
-        }
-        const jchar* workLimit = workText + len;
-
-        if (trimLeft) {
-            while (workText < workLimit && *workText == ' ') {
-                ++workText;
-            }
-        }
-        if (trimRight) {
-            while (workLimit > workText && *(workLimit - 1) == ' ') {
-                --workLimit;
-            }
-        }
-        int32_t workBytes = (workLimit - workText) << 1;
-
-        canvas->drawText(workText, workBytes, x_, y_, *paint);
-
-        free(buffer);
-    }
 
     static void drawText___CIIFFIPaint(JNIEnv* env, jobject, SkCanvas* canvas,
                                       jcharArray text, int index, int count,
                                       jfloat x, jfloat y, int flags, SkPaint* paint) {
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        drawText(env, canvas, textArray + index, count, x, y, flags, paint);
+        TextLayout::drawText(paint, textArray + index, count, flags, x, y, canvas);
         env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
     }
 
@@ -961,41 +765,18 @@
                                           int start, int end,
                                           jfloat x, jfloat y, int flags, SkPaint* paint) {
         const jchar* textArray = env->GetStringChars(text, NULL);
-        drawText(env, canvas, textArray + start, end - start, x, y, flags, paint);
+        TextLayout::drawText(paint, textArray + start, end - start, flags, x, y, canvas);
         env->ReleaseStringChars(text, textArray);
     }
 
-    // Draws a unidirectional run of text.
-    static void drawTextRun(JNIEnv* env, SkCanvas* canvas, const jchar* chars,
-                              jint start, jint count, jint contextCount,
-                              jfloat x, jfloat y, int dirFlags, SkPaint* paint) {
-
-        SkScalar x_ = SkFloatToScalar(x);
-        SkScalar y_ = SkFloatToScalar(y);
-
-        uint8_t rtl = dirFlags & 0x1;
-        if (rtl) {
-            SkAutoSTMalloc<80, jchar> buffer(contextCount);
-            UErrorCode status = U_ZERO_ERROR;
-            count = shapeRtlText(chars, start, count, contextCount, buffer.get(), status);
-            if (U_SUCCESS(status)) {
-                canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
-            } else {
-                LOG(LOG_WARN, "LAYOUT", "drawTextRun error %d\n", status);
-            }
-        } else {
-            canvas->drawText(chars + start, count << 1, x_, y_, *paint);
-        }
-    }
-
     static void drawTextRun___CIIIIFFIPaint(
         JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index,
         int count, int contextIndex, int contextCount,
         jfloat x, jfloat y, int dirFlags, SkPaint* paint) {
 
         jchar* chars = env->GetCharArrayElements(text, NULL);
-        drawTextRun(env, canvas, chars + contextIndex, index - contextIndex,
-                      count, contextCount, x, y, dirFlags, paint);
+        TextLayout::drawTextRun(paint, chars + contextIndex, index - contextIndex,
+                                count, contextCount, dirFlags, x, y, canvas);
         env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
     }
 
@@ -1007,8 +788,8 @@
         jint count = end - start;
         jint contextCount = contextEnd - contextStart;
         const jchar* chars = env->GetStringChars(text, NULL);
-        drawTextRun(env, canvas, chars + contextStart, start - contextStart,
-                      count, contextCount, x, y, dirFlags, paint);
+        TextLayout::drawTextRun(paint, chars + contextStart, start - contextStart,
+                                count, contextCount, dirFlags, x, y, canvas);
         env->ReleaseStringChars(text, chars);
     }
 
@@ -1059,31 +840,13 @@
         delete[] posPtr;
     }
 
-    static void drawTextOnPath(JNIEnv *env, SkCanvas* canvas, const jchar* text, int count,
-            int bidiFlags, SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
-
-        if (!needsLayout(text, count, bidiFlags)) {
-            canvas->drawTextOnPathHV(text, count << 1, *path,
-                SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
-            return;
-        }
-
-        SkAutoSTMalloc<80, jchar> buffer(count);
-        int dir = kDirection_LTR;
-        UErrorCode status = U_ZERO_ERROR;
-        count = layoutLine(text, count, bidiFlags, dir, buffer.get(), status);
-        if (U_SUCCESS(status)) {
-            canvas->drawTextOnPathHV(buffer.get(), count << 1, *path,
-                SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
-        }
-    }
-
     static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject,
             SkCanvas* canvas, jcharArray text, int index, int count,
             SkPath* path, jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {
 
         jchar* textArray = env->GetCharArrayElements(text, NULL);
-        drawTextOnPath(env, canvas, textArray, count, bidiFlags, path, hOffset, vOffset, paint);
+        TextLayout::drawTextOnPath(paint, textArray, count, bidiFlags, hOffset, vOffset,
+                                   path, canvas);
         env->ReleaseCharArrayElements(text, textArray, 0);
     }
 
@@ -1092,7 +855,8 @@
             jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {
         const jchar* text_ = env->GetStringChars(text, NULL);
         int count = env->GetStringLength(text);
-        drawTextOnPath(env, canvas, text_, count, bidiFlags, path, hOffset, vOffset, paint);
+        TextLayout::drawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset,
+                                   path, canvas);
         env->ReleaseStringChars(text, text_);
     }
 
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index ca9c9de..e4d4850 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -32,6 +32,7 @@
 #include "SkTypeface.h"
 #include "SkXfermode.h"
 #include "unicode/ushape.h"
+#include "TextLayout.h"
 
 // temporary for debugging
 #include <utils/Log.h>
@@ -403,56 +404,14 @@
         return count;
     }
 
-    static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, const jchar *text, jint start, jint count, jint contextCount, jint flags,
+    static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, const jchar *text,
+                                    jint start, jint count, jint contextCount, jint flags,
                                     jfloatArray advances, jint advancesIndex) {
         jfloat advancesArray[count];
-        jchar buffer[contextCount];
+        jfloat totalAdvance;
 
-        SkScalar* scalarArray = (SkScalar *)advancesArray;
-        jfloat totalAdvance = 0;
-
-        // this is where we'd call harfbuzz
-        // for now we just use ushape.c
-
-        int widths;
-        if (flags & 0x1) { // rtl, call arabic shaping in case
-            UErrorCode status = U_ZERO_ERROR;
-            // Use fixed length since we need to keep start and count valid
-            u_shapeArabic(text, contextCount, buffer, contextCount,
-                          U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
-                          U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
-                          U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
-            // we shouldn't fail unless there's an out of memory condition,
-            // in which case we're hosed anyway
-            for (int i = start, e = i + count; i < e; ++i) {
-              if (buffer[i] == 0xffff) {
-                buffer[i] = 0x200b; // zero-width-space for skia
-              }
-            }
-            widths = paint->getTextWidths(buffer + start, count << 1, scalarArray);
-        } else {
-            widths = paint->getTextWidths(text + start, count << 1, scalarArray);
-        }
-
-        if (widths < count) {
-            // Skia operates on code points, not code units, so surrogate pairs return only
-            // one value. Expand the result so we have one value per UTF-16 code unit.
-
-            // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
-            // leaving the remaining widths zero.  Not nice.
-            const jchar *chars = text + start;
-            for (int i = 0, p = 0; i < widths; ++i) {
-                totalAdvance += advancesArray[p++] = SkScalarToFloat(scalarArray[i]);
-                if (p < count && chars[p] >= 0xdc00 && chars[p] < 0xe000 &&
-                        chars[p-1] >= 0xd800 && chars[p-1] < 0xdc00) {
-                    advancesArray[p++] = 0;
-                }
-            }
-        } else {
-            for (int i = 0; i < count; i++) {
-                totalAdvance += advancesArray[i] = SkScalarToFloat(scalarArray[i]);
-            }
-        }
+        TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, flags,
+                                       advancesArray, totalAdvance);
 
         if (advances != NULL) {
             env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray);
@@ -580,19 +539,25 @@
         return result;
     }
 
-    static void getTextPath___CIIFFPath(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text, int index, int count, jfloat x, jfloat y, SkPath* path) {
-        const jchar* textArray = env->GetCharArrayElements(text, NULL);
-        paint->getTextPath(textArray + index, count << 1, SkFloatToScalar(x), SkFloatToScalar(y), path);
-        env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray),
-                                      JNI_ABORT);
+    static void getTextPath(JNIEnv* env, SkPaint* paint, const jchar* text, jint count,
+                            jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
+        TextLayout::getTextPath(paint, text, count, bidiFlags, x, y, path);
     }
- 
-    static void getTextPath__StringIIFFPath(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text, int start, int end, jfloat x, jfloat y, SkPath* path) {
+
+    static void getTextPath___C(JNIEnv* env, jobject clazz, SkPaint* paint, jint bidiFlags,
+            jcharArray text, int index, int count, jfloat x, jfloat y, SkPath* path) {
+        const jchar* textArray = env->GetCharArrayElements(text, NULL);
+        getTextPath(env, paint, textArray + index, count, bidiFlags, x, y, path);
+        env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
+    }
+
+    static void getTextPath__String(JNIEnv* env, jobject clazz, SkPaint* paint, jint bidiFlags,
+            jstring text, int start, int end, jfloat x, jfloat y, SkPath* path) {
         const jchar* textArray = env->GetStringChars(text, NULL);
-        paint->getTextPath(textArray + start, (end - start) << 1, SkFloatToScalar(x), SkFloatToScalar(y), path);
+        getTextPath(env, paint, textArray + start, end - start, bidiFlags, x, y, path);
         env->ReleaseStringChars(text, textArray);
     }
- 
+
     static void setShadowLayer(JNIEnv* env, jobject jpaint, jfloat radius,
                                jfloat dx, jfloat dy, int color) {
         NPE_CHECK_RETURN_VOID(env, jpaint);
@@ -767,8 +732,8 @@
     {"native_getTextRunCursor", "(I[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
     {"native_getTextRunCursor", "(ILjava/lang/String;IIIII)I",
         (void*) SkPaintGlue::getTextRunCursor__String},
-    {"native_getTextPath","(I[CIIFFI)V", (void*) SkPaintGlue::getTextPath___CIIFFPath},
-    {"native_getTextPath","(ILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__StringIIFFPath},
+    {"native_getTextPath","(II[CIIFFI)V", (void*) SkPaintGlue::getTextPath___C},
+    {"native_getTextPath","(IILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__String},
     {"nativeGetStringBounds", "(ILjava/lang/String;IILandroid/graphics/Rect;)V",
                                         (void*) SkPaintGlue::getStringBounds },
     {"nativeGetCharArrayBounds", "(I[CIILandroid/graphics/Rect;)V",
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
new file mode 100644
index 0000000..e2536ee
--- /dev/null
+++ b/core/jni/android/graphics/TextLayout.cpp
@@ -0,0 +1,324 @@
+/*
+ * 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.
+ */
+
+#include "TextLayout.h"
+
+#include <android_runtime/AndroidRuntime.h>
+
+#include "SkTemplates.h"
+#include "unicode/ubidi.h"
+#include "unicode/ushape.h"
+#include <utils/Log.h>
+
+
+namespace android {
+// Returns true if we might need layout.  If bidiFlags force LTR, assume no layout, if
+// bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
+// looking for a character >= the first RTL character in unicode and assume we do if
+// we find one.
+bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) {
+    if (bidiFlags == kBidi_Force_LTR) {
+        return false;
+    }
+    if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) ||
+            bidiFlags == kBidi_Force_RTL) {
+        return true;
+    }
+    for (int i = 0; i < len; ++i) {
+        if (text[i] >= 0x0590) {
+            return true;
+        }
+    }
+    return false;
+}
+
+/**
+ * Character-based Arabic shaping.
+ *
+ * We'll use harfbuzz and glyph-based shaping instead once we're set up for it.
+ *
+ * @context the text context
+ * @start the start of the text to render
+ * @count the length of the text to render, start + count  must be <= contextCount
+ * @contextCount the length of the context
+ * @shaped where to put the shaped text, must have capacity for count uchars
+ * @return the length of the shaped text, or -1 if error
+ */
+int TextLayout::shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
+                        jchar* shaped, UErrorCode &status) {
+    jchar buffer[contextCount];
+
+    // Use fixed length since we need to keep start and count valid
+    u_shapeArabic(context, contextCount, buffer, contextCount,
+                   U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
+                   U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
+                   U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+
+    if (U_SUCCESS(status)) {
+        // trim out 0xffff following ligatures, if any
+        int end = 0;
+        for (int i = start, e = start + count; i < e; ++i) {
+            if (buffer[i] != 0xffff) {
+                buffer[end++] = buffer[i];
+            }
+        }
+        count = end;
+        // LOG(LOG_INFO, "CSRTL", "start %d count %d ccount %d\n", start, count, contextCount);
+        ubidi_writeReverse(buffer, count, shaped, count, UBIDI_DO_MIRRORING | UBIDI_OUTPUT_REVERSE
+                           | UBIDI_KEEP_BASE_COMBINING, &status);
+        if (U_SUCCESS(status)) {
+            return count;
+        }
+    }
+
+    return -1;
+}
+
+/**
+ * Basic character-based layout supporting rtl and arabic shaping.
+ * Runs bidi on the text and generates a reordered, shaped line in buffer, returning
+ * the length.
+ * @text the text
+ * @len the length of the text in uchars
+ * @dir receives the resolved paragraph direction
+ * @buffer the buffer to receive the reordered, shaped line.  Must have capacity of
+ * at least len jchars.
+ * @flags line bidi flags
+ * @return the length of the reordered, shaped line, or -1 if error
+ */
+jint TextLayout::layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
+        UErrorCode &status) {
+    static const int RTL_OPTS = UBIDI_DO_MIRRORING | UBIDI_KEEP_BASE_COMBINING |
+            UBIDI_REMOVE_BIDI_CONTROLS | UBIDI_OUTPUT_REVERSE;
+
+    UBiDiLevel bidiReq = 0;
+    switch (flags) {
+    case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
+    case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
+    case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
+    case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
+    case kBidi_Force_LTR: memcpy(buffer, text, len * sizeof(jchar)); return len;
+    case kBidi_Force_RTL: return shapeRtlText(text, 0, len, len, buffer, status);
+    }
+
+    int32_t result = -1;
+
+    UBiDi* bidi = ubidi_open();
+    if (bidi) {
+        ubidi_setPara(bidi, text, len, bidiReq, NULL, &status);
+        if (U_SUCCESS(status)) {
+            dir = ubidi_getParaLevel(bidi) & 0x1; // 0 if ltr, 1 if rtl
+
+            int rc = ubidi_countRuns(bidi, &status);
+            if (U_SUCCESS(status)) {
+                // LOG(LOG_INFO, "LAYOUT", "para bidiReq=%d dir=%d rc=%d\n", bidiReq, dir, rc);
+
+                int32_t slen = 0;
+                for (int i = 0; i < rc; ++i) {
+                    int32_t start;
+                    int32_t length;
+                    UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &start, &length);
+
+                    if (runDir == UBIDI_RTL) {
+                        slen += shapeRtlText(text + start, 0, length, length, buffer + slen, status);
+                    } else {
+                        memcpy(buffer + slen, text + start, length * sizeof(jchar));
+                        slen += length;
+                    }
+                }
+                if (U_SUCCESS(status)) {
+                    result = slen;
+                }
+            }
+        }
+        ubidi_close(bidi);
+    }
+
+    return result;
+}
+
+// Draws or gets the path of a paragraph of text on a single line, running bidi and shaping.
+// This will draw if canvas is not null, otherwise path must be non-null and it will create
+// a path representing the text that would have been drawn.
+void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len,
+                            jint bidiFlags, jfloat x, jfloat y,SkCanvas *canvas, SkPath *path) {
+
+    const jchar *workText = text;
+    jchar *buffer = NULL;
+    int dir = kDirection_LTR;
+    if (needsLayout(text, len, bidiFlags)) {
+        buffer =(jchar *) malloc(len * sizeof(jchar));
+        if (!buffer) {
+            return;
+        }
+        UErrorCode status = U_ZERO_ERROR;
+        len = layoutLine(text, len, bidiFlags, dir, buffer, status); // might change len, dir
+        if (!U_SUCCESS(status)) {
+            LOG(LOG_WARN, "LAYOUT", "drawText error %d\n", status);
+            free(buffer);
+            return; // can't render
+        }
+
+        workText = buffer; // use the shaped text
+    }
+
+    bool trimLeft = false;
+    bool trimRight = false;
+
+    SkPaint::Align horiz = paint->getTextAlign();
+    switch (horiz) {
+    case SkPaint::kLeft_Align: trimLeft = dir & kDirection_Mask; break;
+    case SkPaint::kCenter_Align: trimLeft = trimRight = true; break;
+    case SkPaint::kRight_Align: trimRight = !(dir & kDirection_Mask);
+    default: break;
+    }
+    const jchar* workLimit = workText + len;
+
+    if (trimLeft) {
+        while (workText < workLimit && *workText == ' ') {
+            ++workText;
+        }
+    }
+    if (trimRight) {
+        while (workLimit > workText && *(workLimit - 1) == ' ') {
+            --workLimit;
+        }
+    }
+
+    int32_t workBytes = (workLimit - workText) << 1;
+    SkScalar x_ = SkFloatToScalar(x);
+    SkScalar y_ = SkFloatToScalar(y);
+    if (canvas) {
+        canvas->drawText(workText, workBytes, x_, y_, *paint);
+    } else {
+        paint->getTextPath(workText, workBytes, x_, y_, path);
+    }
+
+    free(buffer);
+}
+
+void TextLayout::drawTextRun(SkPaint* paint, const jchar* chars,
+                             jint start, jint count, jint contextCount,
+                             int dirFlags, jfloat x, jfloat y, SkCanvas* canvas) {
+
+     SkScalar x_ = SkFloatToScalar(x);
+     SkScalar y_ = SkFloatToScalar(y);
+
+     uint8_t rtl = dirFlags & 0x1;
+     if (rtl) {
+         SkAutoSTMalloc<80, jchar> buffer(contextCount);
+         UErrorCode status = U_ZERO_ERROR;
+         count = shapeRtlText(chars, start, count, contextCount, buffer.get(), status);
+         if (U_SUCCESS(status)) {
+             canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
+         } else {
+             LOG(LOG_WARN, "LAYOUT", "drawTextRun error %d\n", status);
+         }
+     } else {
+         canvas->drawText(chars + start, count << 1, x_, y_, *paint);
+     }
+ }
+
+void TextLayout::getTextRunAdvances(SkPaint *paint, const jchar *chars, jint start,
+                                    jint count, jint contextCount, jint dirFlags,
+                                    jfloat *resultAdvances, jfloat &resultTotalAdvance) {
+    jchar buffer[contextCount];
+
+    SkScalar* scalarArray = (SkScalar *)resultAdvances;
+    resultTotalAdvance = 0;
+
+    // this is where we'd call harfbuzz
+    // for now we just use ushape.c
+
+    int widths;
+    const jchar* text;
+    if (dirFlags & 0x1) { // rtl, call arabic shaping in case
+        UErrorCode status = U_ZERO_ERROR;
+        // Use fixed length since we need to keep start and count valid
+        u_shapeArabic(chars, contextCount, buffer, contextCount,
+                      U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
+                      U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
+                      U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
+        // we shouldn't fail unless there's an out of memory condition,
+        // in which case we're hosed anyway
+        for (int i = start, e = i + count; i < e; ++i) {
+          if (buffer[i] == 0xffff) {
+            buffer[i] = 0x200b; // zero-width-space for skia
+          }
+        }
+        text = buffer + start;
+        widths = paint->getTextWidths(text, count << 1, scalarArray);
+    } else {
+        text = chars + start;
+        widths = paint->getTextWidths(text, count << 1, scalarArray);
+    }
+
+    if (widths < count) {
+        // Skia operates on code points, not code units, so surrogate pairs return only
+        // one value. Expand the result so we have one value per UTF-16 code unit.
+
+        // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
+        // leaving the remaining widths zero.  Not nice.
+        for (int i = 0, p = 0; i < widths; ++i) {
+            resultTotalAdvance += resultAdvances[p++] = SkScalarToFloat(scalarArray[i]);
+            if (p < count && text[p] >= 0xdc00 && text[p] < 0xe000 &&
+                    text[p-1] >= 0xd800 && text[p-1] < 0xdc00) {
+                resultAdvances[p++] = 0;
+            }
+        }
+    } else {
+        for (int i = 0; i < count; i++) {
+            resultTotalAdvance += resultAdvances[i] = SkScalarToFloat(scalarArray[i]);
+        }
+    }
+}
+
+
+// Draws a paragraph of text on a single line, running bidi and shaping
+void TextLayout::drawText(SkPaint* paint, const jchar* text, jsize len,
+                          int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas) {
+
+    handleText(paint, text, len, bidiFlags, x, y, canvas, NULL);
+}
+
+void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len,
+                             jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
+    handleText(paint, text, len, bidiFlags, x, y, NULL, path);
+}
+
+
+void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
+                                int bidiFlags, jfloat hOffset, jfloat vOffset,
+                                SkPath* path, SkCanvas* canvas) {
+
+    SkScalar h_ = SkFloatToScalar(hOffset);
+    SkScalar v_ = SkFloatToScalar(vOffset);
+
+    if (!needsLayout(text, count, bidiFlags)) {
+        canvas->drawTextOnPathHV(text, count << 1, *path, h_, v_, *paint);
+        return;
+    }
+
+    SkAutoSTMalloc<80, jchar> buffer(count);
+    int dir = kDirection_LTR;
+    UErrorCode status = U_ZERO_ERROR;
+    count = layoutLine(text, count, bidiFlags, dir, buffer.get(), status);
+    if (U_SUCCESS(status)) {
+        canvas->drawTextOnPathHV(buffer.get(), count << 1, *path, h_, v_, *paint);
+    }
+}
+
+}
diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h
new file mode 100644
index 0000000..c0d9f75
--- /dev/null
+++ b/core/jni/android/graphics/TextLayout.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "unicode/utypes.h"
+
+namespace android {
+
+class TextLayout {
+public:
+
+    enum {
+        kDirection_LTR = 0,
+        kDirection_RTL = 1,
+
+        kDirection_Mask = 0x1
+    };
+
+    enum {
+        kBidi_LTR = 0,
+        kBidi_RTL = 1,
+        kBidi_Default_LTR = 2,
+        kBidi_Default_RTL = 3,
+        kBidi_Force_LTR = 4,
+        kBidi_Force_RTL = 5,
+
+        kBidi_Mask = 0x7
+    };
+
+    /*
+     * Draws a unidirectional run of text.
+     */
+    static void drawTextRun(SkPaint* paint, const jchar* chars,
+                            jint start, jint count, jint contextCount,
+                            int dirFlags, jfloat x, jfloat y, SkCanvas* canvas);
+
+    static void getTextRunAdvances(SkPaint *paint, const jchar *chars, jint start,
+                                   jint count, jint contextCount, jint dirFlags,
+                                   jfloat *resultAdvances, jfloat &resultTotalAdvance);
+
+    static void drawText(SkPaint* paint, const jchar* text, jsize len,
+                         jint bidiFlags, jfloat x, jfloat y, SkCanvas* canvas);
+
+    static void getTextPath(SkPaint *paint, const jchar *text, jsize len,
+                            jint bidiFlags, jfloat x, jfloat y, SkPath *path);
+
+    static void drawTextOnPath(SkPaint* paint, const jchar* text, jsize len,
+                               int bidiFlags, jfloat hOffset, jfloat vOffset,
+                               SkPath* path, SkCanvas* canvas);
+
+private:
+    static bool needsLayout(const jchar* text, jint len, jint bidiFlags);
+    static int shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
+                            jchar* shaped, UErrorCode &status);
+    static jint layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
+                           UErrorCode &status);
+    static void handleText(SkPaint *paint, const jchar* text, jsize len,
+                           int bidiFlags, jfloat x, jfloat y,SkCanvas *canvas, SkPath *path);
+};
+
+}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 88ff0e6..9b4d3a8 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -1718,7 +1718,8 @@
         if ((index | count) < 0 || index + count > text.length) {
             throw new ArrayIndexOutOfBoundsException();
         }
-        native_getTextPath(mNativePaint, text, index, count, x, y, path.ni());
+        native_getTextPath(mNativePaint, mBidiFlags, text, index, count, x, y, 
+                path.ni());
     }
 
     /**
@@ -1739,7 +1740,8 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        native_getTextPath(mNativePaint, text, start, end, x, y, path.ni());
+        native_getTextPath(mNativePaint, mBidiFlags, text, start, end, x, y, 
+                path.ni());
     }
     
     /**
@@ -1836,9 +1838,9 @@
     private native int native_getTextRunCursor(int native_object, String text,
             int contextStart, int contextEnd, int flags, int offset, int cursorOpt);
 
-    private static native void native_getTextPath(int native_object,
+    private static native void native_getTextPath(int native_object, int bidiFlags,
                 char[] text, int index, int count, float x, float y, int path);
-    private static native void native_getTextPath(int native_object,
+    private static native void native_getTextPath(int native_object, int bidiFlags,
                 String text, int start, int end, float x, float y, int path);
     private static native void nativeGetStringBounds(int nativePaint,
                                 String text, int start, int end, Rect bounds);