From f47d7405bbcb25d7cdf89ebb059f41520fe9ab87 Mon Sep 17 00:00:00 2001 From: Doug Felt Date: Wed, 21 Apr 2010 16:01:52 -0700 Subject: [PATCH] Modify Canvas drawText to run bidi and shape. Adds drawTextRun as internal API on Canvas and GraphicsOperations. Adds implementation to implementors of GraphicsOperations. Adds state and API on Paint to control the bidi algorithm when used by Canvas. This API is currently hidden. The drawText changes are incomplete since shaping is not yet available in the native code. Change-Id: I4368048aef9545df0953a349381771603e04b619 --- .../java/android/text/GraphicsOperations.java | 6 + .../android/text/SpannableStringBuilder.java | 38 ++- core/java/android/text/TextLine.java | 43 +-- core/java/android/widget/TextView.java | 5 + core/jni/android/graphics/Canvas.cpp | 257 +++++++++++++++--- graphics/java/android/graphics/Canvas.java | 139 ++++++++-- graphics/java/android/graphics/Paint.java | 95 ++++++- 7 files changed, 481 insertions(+), 102 deletions(-) diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java index c3bd0aeb8c975..d51bf7c88d3e9 100644 --- a/core/java/android/text/GraphicsOperations.java +++ b/core/java/android/text/GraphicsOperations.java @@ -34,6 +34,12 @@ extends CharSequence 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}. */ float measureText(int start, int end, Paint p); diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index caaafa1473976..756317932d067 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -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); @@ -1055,6 +1056,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} */ diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index 4aeabf3530826..fae3fc3d0736f 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -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); } } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 64c9c9964d167..968636b22eaf9 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -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); } diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index e1e953634e4a4..4c7e76237db81 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 "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(); } @@ -743,45 +763,206 @@ public: canvas->drawVertices(mode, ptCount, verts, texs, colors, NULL, 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___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__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__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_); - } - - 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; + + 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); + } + } + + 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); + } + } + + if (shaped) { + free(shaped); } - 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_); } - + + 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, jcharArray text, int index, int count, jfloatArray pos, SkPaint* paint) { @@ -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", diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 345f810ecf2a9..064fab692a837 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -16,11 +16,10 @@ package android.graphics; -import android.text.TextUtils; -import android.text.SpannedString; -import android.text.SpannableString; import android.text.GraphicsOperations; -import android.util.DisplayMetrics; +import android.text.SpannableString; +import android.text.SpannedString; +import android.text.TextUtils; import javax.microedition.khronos.opengles.GL; @@ -58,6 +57,18 @@ public class Canvas { @SuppressWarnings({"UnusedDeclaration"}) private int mSurfaceFormat; + /** + * Flag for drawTextRun indicating left-to-right run direction. + * @hide + */ + public static final int DIRECTION_LTR = 0; + + /** + * Flag for drawTextRun indicating right-to-left run direction. + * @hide + */ + public static final int DIRECTION_RTL = 1; + /** * Construct an empty raster canvas. Use setBitmap() to specify a bitmap to * draw into. The initial target density is {@link Bitmap#DENSITY_NONE}; @@ -1246,8 +1257,8 @@ public class Canvas { (text.length - index - count)) < 0) { throw new IndexOutOfBoundsException(); } - native_drawText(mNativeCanvas, text, index, count, x, y, - paint.mNativePaint); + native_drawText(mNativeCanvas, text, index, count, x, y, paint.mBidiFlags, + paint.mNativePaint); } /** @@ -1259,7 +1270,10 @@ public class Canvas { * @param y The y-coordinate of the origin of the text being drawn * @param paint The paint used for the text (e.g. color, size, style) */ - public native void drawText(String text, float x, float y, Paint paint); + public void drawText(String text, float x, float y, Paint paint) { + native_drawText(mNativeCanvas, text, 0, text.length(), x, y, paint.mBidiFlags, + paint.mNativePaint); + } /** * Draw the text, with origin at (x,y), using the specified paint. @@ -1277,8 +1291,8 @@ public class Canvas { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } - native_drawText(mNativeCanvas, text, start, end, x, y, - paint.mNativePaint); + native_drawText(mNativeCanvas, text, start, end, x, y, paint.mBidiFlags, + paint.mNativePaint); } /** @@ -1299,16 +1313,100 @@ public class Canvas { if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { native_drawText(mNativeCanvas, text.toString(), start, end, x, y, - paint.mNativePaint); - } - else if (text instanceof GraphicsOperations) { + paint.mBidiFlags, paint.mNativePaint); + } else if (text instanceof GraphicsOperations) { ((GraphicsOperations) text).drawText(this, start, end, x, y, paint); - } - else { + } else { char[] buf = TemporaryBuffer.obtain(end - start); TextUtils.getChars(text, start, end, buf, 0); - drawText(buf, 0, end - start, x, y, paint); + native_drawText(mNativeCanvas, buf, 0, end - start, x, y, + paint.mBidiFlags, paint.mNativePaint); + TemporaryBuffer.recycle(buf); + } + } + + /** + * Render a run of all LTR or all RTL text, with shaping. This does not run + * bidi on the provided text, but renders it as a uniform right-to-left or + * left-to-right run, as indicated by dir. Alignment of the text is as + * determined by the Paint's TextAlign value. + * + * @param text the text to render + * @param index the start of the text to render. Data before this position + * can be used for shaping context. + * @param length the length of the text to render. Data at or after this + * position (start + length) can be used for shaping context. + * @param x the x position at which to draw the text + * @param y the y position at which to draw the text + * @param dir the run direction, either {@link DIRECTION_LTR} or + * {@link DIRECTION_RTL}. + * @param paint the paint + * @hide + */ + public void drawTextRun(char[] text, int index, int length, float x, float y, int dir, + Paint paint) { + + if (text == null) { + throw new NullPointerException("text is null"); + } + if (paint == null) { + throw new NullPointerException("paint is null"); + } + if ((index | length | text.length - index - length) < 0) { + throw new IndexOutOfBoundsException(); + } + if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) { + throw new IllegalArgumentException("unknown dir: " + dir); + } + + native_drawTextRun(mNativeCanvas, text, index, length, x, y, dir, paint.mNativePaint); + } + + /** + * Render a run of all LTR or all RTL text, with shaping. This does not run + * bidi on the provided text, but renders it as a uniform right-to-left or + * left-to-right run, as indicated by dir. Alignment of the text is as + * determined by the Paint's TextAlign value. + * + * @param text the text to render + * @param start the start of the text to render. Data before this position + * can be used for shaping context. + * @param end the end of the text to render. Data at or after this + * position can be used for shaping context. + * @param x the x position at which to draw the text + * @param y the y position at which to draw the text + * @param dir the run direction, either 0 for LTR or 1 for RTL. + * @param paint the paint + * @hide + */ + public void drawTextRun(CharSequence text, int start, int end, float x, + float y, int dir, Paint paint) { + + if (text == null) { + throw new NullPointerException("text is null"); + } + if (paint == null) { + throw new NullPointerException("paint is null"); + } + if ((start | end | end - start | text.length() - end) < 0) { + throw new IndexOutOfBoundsException(); + } + + int flags = dir == 0 ? 0 : 1; + + if (text instanceof String || text instanceof SpannedString || + text instanceof SpannableString) { + native_drawTextRun(mNativeCanvas, text.toString(), start, end, x, y, + flags, paint.mNativePaint); + } else if (text instanceof GraphicsOperations) { + ((GraphicsOperations) text).drawTextRun(this, start, end, x, y, flags, + paint); + } else { + char[] buf = TemporaryBuffer.obtain(end - start); + TextUtils.getChars(text, start, end, buf, 0); + native_drawTextRun(mNativeCanvas, buf, 0, end - start, x, y, + flags, paint.mNativePaint); TemporaryBuffer.recycle(buf); } } @@ -1555,10 +1653,17 @@ public class Canvas { private static native void native_drawText(int nativeCanvas, char[] text, int index, int count, float x, - float y, int paint); + float y, int flags, int paint); private static native void native_drawText(int nativeCanvas, String text, int start, int end, float x, - float y, int paint); + float y, int flags, int paint); + + private static native void native_drawTextRun(int nativeCanvas, String + text, int start, int end, float x, float y, int flags, int paint); + + private static native void native_drawTextRun(int nativeCanvas, char[] + text, int start, int len, float x, float y, int flags, int paint); + private static native void native_drawPosText(int nativeCanvas, char[] text, int index, int count, float[] pos, diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 183c8961d9b67..b5649293e3078 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -16,10 +16,10 @@ package android.graphics; -import android.text.TextUtils; +import android.text.GraphicsOperations; import android.text.SpannableString; import android.text.SpannedString; -import android.text.GraphicsOperations; +import android.text.TextUtils; /** * The Paint class holds the style and color information about how to draw @@ -39,6 +39,7 @@ public class Paint { private boolean mHasCompatScaling; private float mCompatScaling; private float mInvCompatScaling; + /* package */ int mBidiFlags = BIDI_DEFAULT_LTR; private static final Style[] sStyleArray = { Style.FILL, Style.STROKE, Style.FILL_AND_STROKE @@ -76,8 +77,64 @@ public class Paint { private static final int DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG; /** - * The Style specifies if the primitive being drawn is filled, - * stroked, or both (in the same color). The default is FILL. + * Bidi flag to set LTR paragraph direction. + * + * @hide + */ + public static final int BIDI_LTR = 0x0; + + /** + * Bidi flag to set RTL paragraph direction. + * + * @hide + */ + public static final int BIDI_RTL = 0x1; + + /** + * Bidi flag to detect paragraph direction via heuristics, defaulting to + * LTR. + * + * @hide + */ + public static final int BIDI_DEFAULT_LTR = 0x2; + + /** + * Bidi flag to detect paragraph direction via heuristics, defaulting to + * RTL. + * + * @hide + */ + public static final int BIDI_DEFAULT_RTL = 0x3; + + /** + * Bidi flag to override direction to all LTR (ignore bidi). + * + * @hide + */ + public static final int BIDI_FORCE_LTR = 0x4; + + /** + * Bidi flag to override direction to all RTL (ignore bidi). + * + * @hide + */ + public static final int BIDI_FORCE_RTL = 0x5; + + /** + * Maximum Bidi flag value. + * @hide + */ + private static final int BIDI_MAX_FLAG_VALUE = BIDI_FORCE_RTL; + + /** + * Mask for bidi flags. + * @hide + */ + private static final int BIDI_FLAG_MASK = 0x7; + + /** + * The Style specifies if the primitive being drawn is filled, stroked, or + * both (in the same color). The default is FILL. */ public enum Style { /** @@ -210,6 +267,7 @@ public class Paint { mHasCompatScaling = paint.mHasCompatScaling; mCompatScaling = paint.mCompatScaling; mInvCompatScaling = paint.mInvCompatScaling; + mBidiFlags = paint.mBidiFlags; } /** Restores the paint to its default settings. */ @@ -218,6 +276,7 @@ public class Paint { setFlags(DEFAULT_PAINT_FLAGS); mHasCompatScaling = false; mCompatScaling = mInvCompatScaling = 1; + mBidiFlags = BIDI_DEFAULT_LTR; } /** @@ -240,6 +299,7 @@ public class Paint { mHasCompatScaling = src.mHasCompatScaling; mCompatScaling = src.mCompatScaling; mInvCompatScaling = src.mInvCompatScaling; + mBidiFlags = src.mBidiFlags; } } @@ -254,10 +314,33 @@ public class Paint { mInvCompatScaling = 1.0f/factor; } } - + + /** + * Return the bidi flags on the paint. + * + * @return the bidi flags on the paint + * @hide + */ + public int getBidiFlags() { + return mBidiFlags; + } + + /** + * Set the bidi flags on the paint. + * @hide + */ + public void setBidiFlags(int flags) { + // only flag value is the 3-bit BIDI control setting + flags &= BIDI_FLAG_MASK; + if (flags > BIDI_MAX_FLAG_VALUE) { + throw new IllegalArgumentException("unknown bidi flag: " + flags); + } + mBidiFlags = flags; + } + /** * Return the paint's flags. Use the Flag enum to test flag values. - * + * * @return the paint's flags (see enums ending in _Flag for bit masks) */ public native int getFlags();