Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6cfbb379 authored by Kenny Root's avatar Kenny Root Committed by Android (Google) Code Review
Browse files

Merge "Support bidi layout for drawTextOnPath."

parents 7fbee2f7 4beb8ff7
Loading
Loading
Loading
Loading
+188 −137
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@
#include "unicode/ubidi.h"
#include "unicode/ushape.h"

// temporary for debugging
#include <utils/Log.h>

#define TIME_DRAWx
@@ -769,120 +768,158 @@ public:
    }

    /**
     * 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 <= len
     * @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,
    static int shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
                            jchar* shaped, UErrorCode &status) {
        jchar buffer[contextCount];

        // We'd rather use harfbuzz here.  Use character based shaping for now.

        // 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)) {
            return 0;
        }

        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) {
              continue;
            }
                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;
            }

    static void drawText__(JNIEnv* env, SkCanvas* canvas, const jchar* text, jsize len,
                           jfloat x, jfloat y, int flags, SkPaint* paint) {
        SkScalar x_ = SkFloatToScalar(x);
        SkScalar y_ = SkFloatToScalar(y);

        SkPaint::Align horiz = paint->getTextAlign();

        bool needBidi = (flags == kBidi_RTL) || (flags == kBidi_Default_RTL);
        if (!needBidi && flags < kBidi_Force_LTR) {
            for (int i = 0; i < len; ++i) {
                if (text[i] >= 0x0590) {
                    needBidi = TRUE;
                    break;
                }
        }

        return -1;
    }

        int dir = (flags == kBidi_Force_RTL) ? kDirection_RTL : kDirection_LTR; // will be reset if we run bidi
        UErrorCode status = U_ZERO_ERROR;
        jchar *shaped = NULL;
        int32_t slen = 0;
        if (needBidi || (flags == kBidi_Force_RTL)) {
            shaped = (jchar *)malloc(len * sizeof(jchar));
            if (!shaped) {
                status = U_MEMORY_ALLOCATION_ERROR;
            } else {
                if (needBidi) {
    /**
     * 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;
                    jint lineDir = 0;

        UBiDiLevel bidiReq = 0;
        switch (flags) {
                    case kBidi_LTR: lineDir = 0; break; // no ICU constant, canonical LTR level
                    case kBidi_RTL: lineDir = 1; break; // no ICU constant, canonical RTL level
                    case kBidi_Default_LTR: lineDir = UBIDI_DEFAULT_LTR; break;
                    case kBidi_Default_RTL: lineDir = UBIDI_DEFAULT_RTL; break;
          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();
                    ubidi_setPara(bidi, text, len, lineDir, NULL, &status);
        if (bidi) {
            ubidi_setPara(bidi, text, len, bidiReq, NULL, &status);
            if (U_SUCCESS(status)) {
                        dir = ubidi_getParaLevel(bidi) & 0x1;
                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 dir;
                            jchar *buffer = NULL;
                            for (int i = 0; i < rc; ++i) {
                                dir = ubidi_getVisualRun(bidi, i, &start, &length);
                                // fake shaping, except it doesn't shape, just mirrors and reverses
                                // use harfbuzz when available
                                if (dir == UBIDI_RTL) {
                                    slen += ubidi_writeReverse(text + start, length, shaped + slen,
                                                               length, RTL_OPTS, &status);
                        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 {
                                    for (int i = 0; i < length; ++i) {
                                        shaped[slen + i] = text[start + i];
                                    }
                            memcpy(buffer + slen, text + start, length * sizeof(jchar));
                            slen += length;
                        }
                    }
                    if (U_SUCCESS(status)) {
                        result = slen;
                    }
                }
            }
            ubidi_close(bidi);
        }
                } else {
                  len = shapeRtlText__(text, 0, len, len, shaped, status);

        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)) {
            char buffer[35];
            sprintf(buffer, "DrawText bidi error %d", status);
            doThrowIAE(env, buffer);
        } else {
                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;

@@ -892,7 +929,6 @@ public:
        case SkPaint::kRight_Align: trimRight = !(dir & kDirection_Mask);
        default: break;
        }
            const jchar* workText = shaped ? shaped : text;
        const jchar* workLimit = workText + len;

        if (trimLeft) {
@@ -908,18 +944,15 @@ public:
        int32_t workBytes = (workLimit - workText) << 1;

        canvas->drawText(workText, workBytes, x_, y_, *paint);
        }

        if (shaped) {
            free(shaped);
        }
        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);
        drawText(env, canvas, textArray + index, count, x, y, flags, paint);
        env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
    }

@@ -928,27 +961,27 @@ public:
                                          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);
        drawText(env, canvas, textArray + start, end - start, x, y, flags, paint);
        env->ReleaseStringChars(text, textArray);
    }

    // Draws a unidirectional run of text.
    static void drawTextRun__(JNIEnv* env, SkCanvas* canvas, const jchar* chars,
    static void drawTextRun(JNIEnv* env, SkCanvas* canvas, const jchar* chars,
                              jint start, jint count, jint contextCount,
                              jfloat x, jfloat y, int flags, SkPaint* paint) {
                              jfloat x, jfloat y, int dirFlags, SkPaint* paint) {

        SkScalar x_ = SkFloatToScalar(x);
        SkScalar y_ = SkFloatToScalar(y);

        uint8_t rtl = flags & 0x1;
        uint8_t rtl = dirFlags & 0x1;
        if (rtl) {
            jchar context[contextCount];
            SkAutoSTMalloc<80, jchar> buffer(contextCount);
            UErrorCode status = U_ZERO_ERROR;
            count = shapeRtlText__(chars, start, count, contextCount, context, status);
            count = shapeRtlText(chars, start, count, contextCount, buffer.get(), status);
            if (U_SUCCESS(status)) {
                canvas->drawText(context, count << 1, x_, y_, *paint);
                canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
            } else {
                doThrowIAE(env, "shaping error");
                LOG(LOG_WARN, "LAYOUT", "drawTextRun error %d\n", status);
            }
        } else {
            canvas->drawText(chars + start, count << 1, x_, y_, *paint);
@@ -958,24 +991,24 @@ public:
    static void drawTextRun___CIIIIFFIPaint(
        JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index,
        int count, int contextIndex, int contextCount,
        jfloat x, jfloat y, int flags, SkPaint* paint) {
        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, flags, paint);
        drawTextRun(env, canvas, chars + contextIndex, index - contextIndex,
                      count, contextCount, x, y, dirFlags, paint);
        env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
    }

    static void drawTextRun__StringIIIIFFIPaint(
        JNIEnv* env, jobject obj, SkCanvas* canvas, jstring text, jint start,
        jint end, jint contextStart, jint contextEnd,
        jfloat x, jfloat y, jint flags, SkPaint* paint) {
        jfloat x, jfloat y, jint dirFlags, SkPaint* paint) {

        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, flags, paint);
        drawTextRun(env, canvas, chars + contextStart, start - contextStart,
                      count, contextCount, x, y, dirFlags, paint);
        env->ReleaseStringChars(text, chars);
    }

@@ -1004,7 +1037,8 @@ public:

    static void drawPosText__String_FPaint(JNIEnv* env, jobject,
                                           SkCanvas* canvas, jstring text,
                                           jfloatArray pos, SkPaint* paint) {
                                           jfloatArray pos,
                                           SkPaint* paint) {
        const void* text_ = text ? env->GetStringChars(text, NULL) : NULL;
        int byteLength = text ? env->GetStringLength(text) : 0;
        float* posArray = pos ? env->GetFloatArrayElements(pos, NULL) : NULL;
@@ -1025,23 +1059,40 @@ public:
        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, SkPaint* paint) {
            SkPath* path, jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {

        jchar* textArray = env->GetCharArrayElements(text, NULL);
        canvas->drawTextOnPathHV(textArray + index, count << 1, *path,
                    SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
        drawTextOnPath(env, canvas, textArray, count, bidiFlags, path, hOffset, vOffset, paint);
        env->ReleaseCharArrayElements(text, textArray, 0);
    }

    static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject,
            SkCanvas* canvas, jstring text, SkPath* path,
                            jfloat hOffset, jfloat vOffset, SkPaint* paint) {
            jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {
        const jchar* text_ = env->GetStringChars(text, NULL);
        int byteLength = env->GetStringLength(text) << 1;
        canvas->drawTextOnPathHV(text_, byteLength, *path,
                    SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
        int count = env->GetStringLength(text);
        drawTextOnPath(env, canvas, text_, count, bidiFlags, path, hOffset, vOffset, paint);
        env->ReleaseStringChars(text, text_);
    }

@@ -1155,9 +1206,9 @@ static JNINativeMethod gCanvasMethods[] = {
        (void*) SkCanvasGlue::drawPosText___CII_FPaint},
    {"native_drawPosText","(ILjava/lang/String;[FI)V",
        (void*) SkCanvasGlue::drawPosText__String_FPaint},
    {"native_drawTextOnPath","(I[CIIIFFI)V",
    {"native_drawTextOnPath","(I[CIIIFFII)V",
        (void*) SkCanvasGlue::drawTextOnPath___CIIPathFFPaint},
    {"native_drawTextOnPath","(ILjava/lang/String;IFFI)V",
    {"native_drawTextOnPath","(ILjava/lang/String;IFFII)V",
        (void*) SkCanvasGlue::drawTextOnPath__StringPathFFPaint},
    {"native_drawPicture", "(II)V", (void*) SkCanvasGlue::drawPicture},

+8 −5
Original line number Diff line number Diff line
@@ -1505,7 +1505,7 @@ public class Canvas {
        }
        native_drawTextOnPath(mNativeCanvas, text, index, count,
                              path.ni(), hOffset, vOffset,
                              paint.mNativePaint);
                              paint.mBidiFlags, paint.mNativePaint);
    }

    /**
@@ -1525,7 +1525,8 @@ public class Canvas {
                               float vOffset, Paint paint) {
        if (text.length() > 0) {
            native_drawTextOnPath(mNativeCanvas, text, path.ni(),
                                  hOffset, vOffset, paint.mNativePaint);
                                  hOffset, vOffset, paint.mBidiFlags,
                                  paint.mNativePaint);
        }
    }

@@ -1716,11 +1717,13 @@ public class Canvas {
                                                     char[] text, int index,
                                                     int count, int path,
                                                     float hOffset,
                                                     float vOffset, int paint);
                                                     float vOffset, int bidiFlags,
                                                     int paint);
    private static native void native_drawTextOnPath(int nativeCanvas,
                                                     String text, int path,
                                                     float hOffset, 
                                                     float vOffset, int paint);
                                                     float vOffset, 
                                                     int flags, int paint);
    private static native void native_drawPicture(int nativeCanvas,
                                                  int nativePicture);
    private static native void finalizer(int nativeCanvas);