summaryrefslogtreecommitdiffstats
path: root/core/jni
diff options
context:
space:
mode:
authorDoug Felt <dougfelt@google.com>2010-07-01 16:20:43 -0700
committerDoug Felt <dougfelt@google.com>2010-07-12 13:50:08 -0700
commitf7cb1f75fdaedf996cab7c4690b080adc7bc5b97 (patch)
tree6f2fb5f1d5bdea5c385e1902a1f981b34323b326 /core/jni
parentc801768e4d29667a2608695449ebc2833ba0f200 (diff)
downloadframeworks_base-f7cb1f75fdaedf996cab7c4690b080adc7bc5b97.zip
frameworks_base-f7cb1f75fdaedf996cab7c4690b080adc7bc5b97.tar.gz
frameworks_base-f7cb1f75fdaedf996cab7c4690b080adc7bc5b97.tar.bz2
Support bidi/shaping for getTextPath
Move layout-related code into separate class since it's needed by both canvas and paint. Change-Id: Iba89a1d94d7cca650255ffa3cbc952b988a51b54
Diffstat (limited to 'core/jni')
-rw-r--r--core/jni/Android.mk1
-rw-r--r--core/jni/android/graphics/Canvas.cpp260
-rw-r--r--core/jni/android/graphics/Paint.cpp79
-rw-r--r--core/jni/android/graphics/TextLayout.cpp324
-rw-r--r--core/jni/android/graphics/TextLayout.h77
5 files changed, 436 insertions, 305 deletions
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 @@ LOCAL_SRC_FILES:= \
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 @@ 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();
}
@@ -767,192 +751,12 @@ public:
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 @@ 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);
+ 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 @@ public:
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 @@ 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, 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 @@ public:
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 @@ public:
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 @@ public:
return result;
}
- static void getTextPath___CIIFFPath(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text, int index, int count, jfloat x, jfloat y, SkPath* path) {
+ 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___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);
- paint->getTextPath(textArray + index, count << 1, SkFloatToScalar(x), SkFloatToScalar(y), path);
- env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray),
- JNI_ABORT);
+ getTextPath(env, paint, textArray + index, count, bidiFlags, x, y, path);
+ env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
}
-
- 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__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 @@ static JNINativeMethod methods[] = {
{"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);
+};
+
+}