diff options
author | Doug Felt <dougfelt@google.com> | 2010-06-09 13:53:24 -0700 |
---|---|---|
committer | Kenny Root <kroot@google.com> | 2010-07-07 14:58:03 -0700 |
commit | 4beb8ff7175ebd14b96942724a658f407d0b9951 (patch) | |
tree | b50e89ce99b9d56839e711268819cce565374767 /core/jni | |
parent | 3038f47d1c716f9385cf0befc08e708c65f069bb (diff) | |
download | frameworks_base-4beb8ff7175ebd14b96942724a658f407d0b9951.zip frameworks_base-4beb8ff7175ebd14b96942724a658f407d0b9951.tar.gz frameworks_base-4beb8ff7175ebd14b96942724a658f407d0b9951.tar.bz2 |
Support bidi layout for drawTextOnPath.
Change-Id: Ie5867fdb66fe15336774e20d65fa63e0d05bf6fe
Diffstat (limited to 'core/jni')
-rw-r--r-- | core/jni/android/graphics/Canvas.cpp | 325 |
1 files changed, 188 insertions, 137 deletions
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index b044271..2e49c64 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -33,7 +33,6 @@ #include "unicode/ubidi.h" #include "unicode/ushape.h" -// temporary for debugging #include <utils/Log.h> #define TIME_DRAWx @@ -769,157 +768,191 @@ 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, - jchar* shaped, UErrorCode &status) { + 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; - } - - // 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 (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; } - 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); - 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(); + return -1; + } - 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; - } - } + /** + * 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); } - 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; - } + int32_t result = -1; - 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* 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; } - ubidi_close(bidi); } - } else { - len = shapeRtlText__(text, 0, len, len, shaped, status); + if (U_SUCCESS(status)) { + result = slen; + } } } + ubidi_close(bidi); } - 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; + 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; } - const jchar* workText = shaped ? shaped : text; - const jchar* workLimit = workText + len; + } + return false; + } - if (trimLeft) { - while (workText < workLimit && *workText == ' ') { - ++workText; - } + // 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; } - if (trimRight) { - while (workLimit > workText && *(workLimit - 1) == ' ') { - --workLimit; - } + 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 } - int32_t workBytes = (workLimit - workText) << 1; - canvas->drawText(workText, workBytes, x_, y_, *paint); + 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 (shaped) { - free(shaped); + 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); + 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) { + SkCanvas* canvas, jcharArray text, int index, int count, + 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) { + SkCanvas* canvas, jstring text, SkPath* path, + 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}, |