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

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

Merge "Modify Canvas drawText to run bidi and shape."

parents 2b6b8351 f47d7405
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -33,6 +33,12 @@ extends CharSequence
    void drawText(Canvas c, int start, int end,
                         float x, float y, Paint p);

    /**
     * Just like {@link Canvas#drawTextRun}.
     */
    void drawTextRun(Canvas c, int start, int end,
                         float x, float y, int flags, Paint p);

   /**
     * Just like {@link Paint#measureText}.
     */
+36 −2
Original line number Diff line number Diff line
@@ -17,8 +17,9 @@
package android.text;

import com.android.internal.util.ArrayUtils;
import android.graphics.Paint;

import android.graphics.Canvas;
import android.graphics.Paint;

import java.lang.reflect.Array;

@@ -780,7 +781,7 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
        }

        if (count == 0) {
            return (T[]) ArrayUtils.emptyArray(kind);
            return ArrayUtils.emptyArray(kind);
        }
        if (count == 1) {
            ret = (Object[]) Array.newInstance(kind, 1);
@@ -1054,6 +1055,39 @@ implements CharSequence, GetChars, Spannable, Editable, Appendable,
        }
    }

    /**
     * Don't call this yourself -- exists for Canvas to use internally.
     * {@hide}
     */
    public void drawTextRun(Canvas c, int start, int end,
                         float x, float y, int flags, Paint p) {
        checkRange("drawTextRun", start, end);

        // Assume context requires no more than 8 chars on either side.
        // This is ample, only decomposed U+FDFA falls into this
        // category, and no one should put a style break within it 
        // anyway.
        int cstart = start - 8;
        if (cstart < 0) {
            cstart = 0;
        }
        int cend = end + 8;
        int max = length();
        if (cend > max) {
            cend = max;
        }
        if (cend <= mGapStart) {
            c.drawTextRun(mText, start, end - start, x, y, flags, p);
        } else if (cstart >= mGapStart) {
            c.drawTextRun(mText, start + mGapLength, end - start, x, y, flags, p);
        } else {
            char[] buf = TextUtils.obtain(cend - cstart);
            getChars(cstart, cend, buf, 0);
            c.drawTextRun(buf, start - cstart, end - start, x, y, flags, p);
            TextUtils.recycle(buf);
        }
    }

   /**
     * Don't call this yourself -- exists for Paint to use internally.
     * {@hide}
+3 −40
Original line number Diff line number Diff line
@@ -951,48 +951,11 @@ class TextLine {
    private void drawTextRun(Canvas c, TextPaint wp, int start, int limit,
            boolean runIsRtl, float x, int y) {

        // Since currently skia only renders text left-to-right, we need to
        // put the shaped characters into visual order before rendering.
        // Since we might want to re-render the line again, we swap them
        // back when we're done.  If we left them swapped, measurement
        // would be broken since it expects the characters in logical order.
        if (runIsRtl) {
            swapRun(start, limit);
        }
        int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR;
        if (mCharsValid) {
            c.drawText(mChars, start, limit - start, x, y, wp);
            c.drawTextRun(mChars, start, limit - start, x, y, flags, wp);
        } else {
            c.drawText(mText, mStart + start, mStart + limit, x, y, wp);
        }
        if (runIsRtl) {
            swapRun(start, limit);
        }
    }

    /**
     * Reverses the order of characters in the chars array between start and
     * limit, used by drawTextRun.
     * @param start the start of the run to reverse
     * @param limit the limit of the run to reverse
     */
    private void swapRun(int start, int limit) {
        // First we swap all the characters one for one, then we
        // do another pass looking for surrogate pairs and swapping them
        // back into their logical order.
        char[] chars = mChars;
        for (int s = start, e = limit - 1; s < e; ++s, --e) {
            char ch = chars[s]; chars[s] = chars[e]; chars[e] = ch;
        }

        for (int s = start, e = limit - 1; s < e; ++s) {
            char c1 = chars[s];
            if (c1 >= 0xdc00 && c1 < 0xe000) {
                char c2 = chars[s+1];
                if (c2 >= 0xd800 && c2 < 0xdc00) {
                    chars[s++] = c2;
                    chars[s] = c1;
                }
            }
            c.drawTextRun(mText, mStart + start, mStart + limit, x, y, flags, wp);
        }
    }

+5 −0
Original line number Diff line number Diff line
@@ -2781,6 +2781,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            c.drawText(mChars, start + mStart, end - start, x, y, p);
        }

        public void drawTextRun(Canvas c, int start, int end,
                float x, float y, int flags, Paint p) {
            c.drawTextRun(mChars, start + mStart, end - start, x, y, flags, p);
        }

        public float measureText(int start, int end, Paint p) {
            return p.measureText(mChars, start + mStart, end - start);
        }
+219 −36
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@
#include "SkBoundaryPatch.h"
#include "SkMeshUtils.h"

#include "unicode/ubidi.h"

#define TIME_DRAWx

static uint32_t get_thread_msec() {
@@ -52,6 +54,24 @@ namespace android {
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();
    }
@@ -744,42 +764,203 @@ public:
                             indices, indexCount, *paint);
    }

    static void drawText___CIIFFPaint(JNIEnv* env, jobject, SkCanvas* canvas,
                                      jcharArray text, int index, int count,
                                      jfloat x, jfloat y, SkPaint* paint) {
        jchar* textArray = env->GetCharArrayElements(text, NULL);
        jsize textCount = env->GetArrayLength(text);
    static void shapeRtlText__(const jchar* text, jsize len, jsize start, jsize count, jchar* shaped) {
        // fake shaping, just reverse the text
        for (int i = 0; i < count; ++i) {
            shaped[i] = text[start + count - 1 - i];
        }
        // fix surrogate pairs, if any
        for (int i = 1; i < count; ++i) {
            if (shaped[i] >= 0xd800 && shaped[i] < 0xdc00 && 
                    shaped[i-1] >= 0xdc00 && shaped[i-1] < 0xe000) {
                jchar c = shaped[i]; shaped[i] = shaped[i-1]; shaped[i-1] = c;
                i += 1;
            }
        }
    }

    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);
        canvas->drawText(textArray + index, count << 1, x_, y_, *paint);
        env->ReleaseCharArrayElements(text, textArray, 0);

	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;
                }
            }
        }

        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) {
                    static int RTL_OPTS = UBIDI_DO_MIRRORING | UBIDI_KEEP_BASE_COMBINING |
                            UBIDI_REMOVE_BIDI_CONTROLS | UBIDI_OUTPUT_REVERSE;
                    jint lineDir = 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;
                    }
                      
                    UBiDi* bidi = ubidi_open();
                    ubidi_setPara(bidi, text, len, lineDir, NULL, &status);
                    if (U_SUCCESS(status)) {
                        dir = ubidi_getParaLevel(bidi) & 0x1;

                        int rc = ubidi_countRuns(bidi, &status);
                        if (U_SUCCESS(status)) {
                            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);
                                } else {
                                    for (int i = 0; i < length; ++i) {
                                        shaped[slen + i] = text[start + i];
                                    }
                                    slen += length;
                                }
                            }
                        }
                        ubidi_close(bidi);
                    } 
                } else {
                    shapeRtlText__(text, len, 0, len, shaped);
                }
            }
        }

        if (!U_SUCCESS(status)) {
            char buffer[35];
            sprintf(buffer, "DrawText bidi error %d", status);
            doThrowIAE(env, buffer);
        } else {
            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* workText = shaped ? shaped : text;
            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);
        }

        if (shaped) {
            free(shaped);
        }
    }
    
    static void drawText__StringIIFFPaint(JNIEnv* env, jobject,
                            SkCanvas* canvas, jstring text, int start, int end,
                                          jfloat x, jfloat y, SkPaint* paint) {
        const void* text_ = env->GetStringChars(text, NULL);
    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);
        env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
    }
 
    static void drawText__StringIIFFIPaint(JNIEnv* env, jobject,
                                          SkCanvas* canvas, jstring text, 
                                          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);
        env->ReleaseStringChars(text, textArray);
    }
    
    // Draws a unidirectional run of text.  Does not run bidi, but does reorder the
    // text and run shaping (or will, when we have harfbuzz support).
    static void drawTextRun__(JNIEnv* env, SkCanvas* canvas, const jchar* chars, int len, 
                              int start, int count,
                              jfloat x, jfloat y, int flags, SkPaint* paint) {

        SkScalar x_ = SkFloatToScalar(x);
        SkScalar y_ = SkFloatToScalar(y);
        canvas->drawText((const uint16_t*)text_ + start, (end - start) << 1,
                         x_, y_, *paint);
        env->ReleaseStringChars(text, (const jchar*) text_);

        uint8_t rtl = flags & 0x1;

        UErrorCode status = U_ZERO_ERROR;
        jchar *shaped = NULL;
        if (rtl) {
            shaped = (jchar *)malloc(count * sizeof(jchar));
            if (!shaped) {
                status = U_MEMORY_ALLOCATION_ERROR;
            } else {
                shapeRtlText__(chars, len, start, count, shaped);
            }
        }

    static void drawString(JNIEnv* env, jobject canvas, jstring text,
                           jfloat x, jfloat y, jobject paint) {
        NPE_CHECK_RETURN_VOID(env, canvas);
        NPE_CHECK_RETURN_VOID(env, paint);
        NPE_CHECK_RETURN_VOID(env, text);
        size_t count = env->GetStringLength(text);
        if (0 == count) {
            return;
        if (!U_SUCCESS(status)) {
            char buffer[30];
            sprintf(buffer, "DrawTextRun error %d", status);
            doThrowIAE(env, buffer);
        } else {
            if (shaped) {
                canvas->drawText(shaped, count << 1, x_, y_, *paint);
            } else {
                canvas->drawText(chars + start, count << 1, x_, y_, *paint);
            }
        const jchar* text_ = env->GetStringChars(text, NULL);
        SkCanvas* c = GraphicsJNI::getNativeCanvas(env, canvas);
        c->drawText(text_, count << 1, SkFloatToScalar(x), SkFloatToScalar(y),
                    *GraphicsJNI::getNativePaint(env, paint));
        env->ReleaseStringChars(text, text_);
        }

        if (shaped) {
            free(shaped);
        }
    }

    static void drawTextRun___CIIFFIPaint(
        JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index,
        int count, jfloat x, jfloat y, int flags, SkPaint* paint) {

        jint len = env->GetArrayLength(text);
        jchar* chars = env->GetCharArrayElements(text, NULL);
        drawTextRun__(env, canvas, chars, len, index, count, x, y, flags, paint);
        env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
    }

    static void drawTextRun__StringIIFFIPaint(
        JNIEnv* env, jobject obj, SkCanvas* canvas, jstring text, int start,
        int end, jfloat x, jfloat y, int flags, SkPaint* paint) {

        jint len = env->GetStringLength(text);
        const jchar* chars = env->GetStringChars(text, NULL);
        drawTextRun__(env, canvas, chars, len, start, end - start, x, y, flags, paint);
        env->ReleaseStringChars(text, chars);
    }

    static void drawPosText___CII_FPaint(JNIEnv* env, jobject, SkCanvas* canvas,
@@ -947,12 +1128,14 @@ static JNINativeMethod gCanvasMethods[] = {
        (void*)SkCanvasGlue::drawBitmapMesh},
    {"nativeDrawVertices", "(III[FI[FI[II[SIII)V",
        (void*)SkCanvasGlue::drawVertices},
    {"native_drawText","(I[CIIFFI)V",
        (void*) SkCanvasGlue::drawText___CIIFFPaint},
    {"native_drawText","(ILjava/lang/String;IIFFI)V",
        (void*) SkCanvasGlue::drawText__StringIIFFPaint},
    {"drawText","(Ljava/lang/String;FFLandroid/graphics/Paint;)V",
        (void*) SkCanvasGlue::drawString},
    {"native_drawText","(I[CIIFFII)V",
        (void*) SkCanvasGlue::drawText___CIIFFIPaint},
    {"native_drawText","(ILjava/lang/String;IIFFII)V",
        (void*) SkCanvasGlue::drawText__StringIIFFIPaint},
    {"native_drawTextRun","(I[CIIFFII)V",
        (void*) SkCanvasGlue::drawTextRun___CIIFFIPaint},
    {"native_drawTextRun","(ILjava/lang/String;IIFFII)V",
        (void*) SkCanvasGlue::drawTextRun__StringIIFFIPaint},
    {"native_drawPosText","(I[CII[FI)V",
        (void*) SkCanvasGlue::drawPosText___CII_FPaint},
    {"native_drawPosText","(ILjava/lang/String;[FI)V",
Loading