summaryrefslogtreecommitdiffstats
path: root/core/jni/android/graphics/Canvas.cpp
diff options
context:
space:
mode:
authorDoug Felt <dougfelt@google.com>2010-06-09 13:53:24 -0700
committerKenny Root <kroot@google.com>2010-07-07 14:58:03 -0700
commit4beb8ff7175ebd14b96942724a658f407d0b9951 (patch)
treeb50e89ce99b9d56839e711268819cce565374767 /core/jni/android/graphics/Canvas.cpp
parent3038f47d1c716f9385cf0befc08e708c65f069bb (diff)
downloadframeworks_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/android/graphics/Canvas.cpp')
-rw-r--r--core/jni/android/graphics/Canvas.cpp325
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},