diff --git a/core/java/android/text/GraphicsOperations.java b/core/java/android/text/GraphicsOperations.java index 60545e5cbdf2d..831ccc556bf92 100644 --- a/core/java/android/text/GraphicsOperations.java +++ b/core/java/android/text/GraphicsOperations.java @@ -38,7 +38,7 @@ extends CharSequence * {@hide} */ void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd, - float x, float y, Paint p); + float x, float y, int flags, Paint p); /** * Just like {@link Paint#measureText}. @@ -55,12 +55,19 @@ extends CharSequence * @hide */ float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, - float[] advances, int advancesIndex, Paint paint); + int flags, float[] advances, int advancesIndex, Paint paint); + + /** + * Just like {@link Paint#getTextRunAdvances}. + * @hide + */ + float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, + int flags, float[] advances, int advancesIndex, Paint paint, int reserved); /** * Just like {@link Paint#getTextRunCursor}. * @hide */ - int getTextRunCursor(int contextStart, int contextEnd, int offset, + int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset, int cursorOpt, Paint p); } diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java index 0c881a4da6b36..bd9310c1b1b22 100644 --- a/core/java/android/text/MeasuredText.java +++ b/core/java/android/text/MeasuredText.java @@ -159,15 +159,18 @@ class MeasuredText { mPos = p + len; if (mEasy) { - return paint.getTextRunAdvances(mChars, p, len, p, len, mWidths, p); + int flags = mDir == Layout.DIR_LEFT_TO_RIGHT + ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL; + return paint.getTextRunAdvances(mChars, p, len, p, len, flags, mWidths, p); } float totalAdvance = 0; int level = mLevels[p]; for (int q = p, i = p + 1, e = p + len;; ++i) { if (i == e || mLevels[i] != level) { + int flags = (level & 0x1) == 0 ? Canvas.DIRECTION_LTR : Canvas.DIRECTION_RTL; totalAdvance += - paint.getTextRunAdvances(mChars, q, i - q, q, i - q, mWidths, q); + paint.getTextRunAdvances(mChars, q, i - q, q, i - q, flags, mWidths, q); if (i == e) { break; } diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index 9e4367117a150..0f30d25f7d154 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -1130,20 +1130,20 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable * {@hide} */ public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd, - float x, float y, Paint p) { + float x, float y, int flags, Paint p) { checkRange("drawTextRun", start, end); int contextLen = contextEnd - contextStart; int len = end - start; if (contextEnd <= mGapStart) { - c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, p); + c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p); } else if (contextStart >= mGapStart) { c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength, - contextLen, x, y, p); + contextLen, x, y, flags, p); } else { char[] buf = TextUtils.obtain(contextLen); getChars(contextStart, contextEnd, buf, 0); - c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, p); + c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p); TextUtils.recycle(buf); } } @@ -1200,7 +1200,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable * Don't call this yourself -- exists for Paint to use internally. * {@hide} */ - public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, + public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags, float[] advances, int advancesPos, Paint p) { float ret; @@ -1210,15 +1210,44 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (end <= mGapStart) { ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen, - advances, advancesPos); + flags, advances, advancesPos); } else if (start >= mGapStart) { ret = p.getTextRunAdvances(mText, start + mGapLength, len, - contextStart + mGapLength, contextLen, advances, advancesPos); + contextStart + mGapLength, contextLen, flags, advances, advancesPos); } else { char[] buf = TextUtils.obtain(contextLen); getChars(contextStart, contextEnd, buf, 0); ret = p.getTextRunAdvances(buf, start - contextStart, len, - 0, contextLen, advances, advancesPos); + 0, contextLen, flags, advances, advancesPos); + TextUtils.recycle(buf); + } + + return ret; + } + + /** + * Don't call this yourself -- exists for Paint to use internally. + * {@hide} + */ + public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags, + float[] advances, int advancesPos, Paint p, int reserved) { + + float ret; + + int contextLen = contextEnd - contextStart; + int len = end - start; + + if (end <= mGapStart) { + ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen, + flags, advances, advancesPos, reserved); + } else if (start >= mGapStart) { + ret = p.getTextRunAdvances(mText, start + mGapLength, len, + contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved); + } else { + char[] buf = TextUtils.obtain(contextLen); + getChars(contextStart, contextEnd, buf, 0); + ret = p.getTextRunAdvances(buf, start - contextStart, len, + 0, contextLen, flags, advances, advancesPos, reserved); TextUtils.recycle(buf); } @@ -1241,7 +1270,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable * * @param contextStart the start index of the context * @param contextEnd the (non-inclusive) end index of the context - * @param flags reserved + * @param flags either DIRECTION_RTL or DIRECTION_LTR * @param offset the cursor position to move from * @param cursorOpt how to move the cursor, one of CURSOR_AFTER, * CURSOR_AT_OR_AFTER, CURSOR_BEFORE, @@ -1252,30 +1281,22 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable */ @Deprecated public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset, - int cursorOpt, Paint p) { - return getTextRunCursor(contextStart, contextEnd, offset, cursorOpt, p); - } - - /** - * @hide - */ - public int getTextRunCursor(int contextStart, int contextEnd, int offset, - int cursorOpt, Paint p) { + int cursorOpt, Paint p) { int ret; int contextLen = contextEnd - contextStart; if (contextEnd <= mGapStart) { ret = p.getTextRunCursor(mText, contextStart, contextLen, - offset, cursorOpt); + flags, offset, cursorOpt); } else if (contextStart >= mGapStart) { ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen, - offset + mGapLength, cursorOpt) - mGapLength; + flags, offset + mGapLength, cursorOpt) - mGapLength; } else { char[] buf = TextUtils.obtain(contextLen); getChars(contextStart, contextEnd, buf, 0); ret = p.getTextRunCursor(buf, 0, contextLen, - offset - contextStart, cursorOpt) + contextStart; + flags, offset - contextStart, cursorOpt) + contextStart; TextUtils.recycle(buf); } diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java index e34a0efccf21e..1fecf81d5a4fe 100644 --- a/core/java/android/text/TextLine.java +++ b/core/java/android/text/TextLine.java @@ -664,13 +664,14 @@ class TextLine { } } + int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR; int cursorOpt = after ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE; if (mCharsValid) { return wp.getTextRunCursor(mChars, spanStart, spanLimit - spanStart, - offset, cursorOpt); + flags, offset, cursorOpt); } else { return wp.getTextRunCursor(mText, mStart + spanStart, - mStart + spanLimit, mStart + offset, cursorOpt) - mStart; + mStart + spanLimit, flags, mStart + offset, cursorOpt) - mStart; } } @@ -737,13 +738,15 @@ class TextLine { int contextLen = contextEnd - contextStart; if (needWidth || (c != null && (wp.bgColor != 0 || wp.underlineColor != 0 || runIsRtl))) { + int flags = runIsRtl ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR; if (mCharsValid) { ret = wp.getTextRunAdvances(mChars, start, runLen, - contextStart, contextLen, null, 0); + contextStart, contextLen, flags, null, 0); } else { int delta = mStart; - ret = wp.getTextRunAdvances(mText, delta + start, delta + end, - delta + contextStart, delta + contextEnd, null, 0); + ret = wp.getTextRunAdvances(mText, delta + start, + delta + end, delta + contextStart, delta + contextEnd, + flags, null, 0); } } @@ -783,7 +786,8 @@ class TextLine { wp.setAntiAlias(previousAntiAlias); } - drawTextRun(c, wp, start, end, contextStart, contextEnd, x, y + wp.baselineShift); + drawTextRun(c, wp, start, end, contextStart, contextEnd, runIsRtl, + x, y + wp.baselineShift); } return runIsRtl ? -ret : ret; @@ -966,21 +970,23 @@ class TextLine { * @param end the end of the run * @param contextStart the start of context for the run * @param contextEnd the end of the context for the run + * @param runIsRtl true if the run is right-to-left * @param x the x position of the left edge of the run * @param y the baseline of the run */ private void drawTextRun(Canvas c, TextPaint wp, int start, int end, - int contextStart, int contextEnd, float x, int y) { + int contextStart, int contextEnd, boolean runIsRtl, float x, int y) { + int flags = runIsRtl ? Canvas.DIRECTION_RTL : Canvas.DIRECTION_LTR; if (mCharsValid) { int count = end - start; int contextCount = contextEnd - contextStart; c.drawTextRun(mChars, start, count, contextStart, contextCount, - x, y, wp); + x, y, flags, wp); } else { int delta = mStart; c.drawTextRun(mText, delta + start, delta + end, - delta + contextStart, delta + contextEnd, x, y, wp); + delta + contextStart, delta + contextEnd, x, y, flags, wp); } } diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 6c48e434d9277..c369ebe8c1b81 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -1162,14 +1162,14 @@ class GLES20Canvas extends HardwareCanvas { int modifiers = setupModifiers(paint); try { - nDrawText(mRenderer, text, index, count, x, y, paint.mNativePaint); + nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } private static native void nDrawText(int renderer, char[] text, int index, int count, - float x, float y, int paint); + float x, float y, int bidiFlags, int paint); @Override public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { @@ -1177,14 +1177,16 @@ class GLES20Canvas extends HardwareCanvas { try { if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { - nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mNativePaint); + nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags, + paint.mNativePaint); } else if (text instanceof GraphicsOperations) { ((GraphicsOperations) text).drawText(this, start, end, x, y, paint); } else { char[] buf = TemporaryBuffer.obtain(end - start); TextUtils.getChars(text, start, end, buf, 0); - nDrawText(mRenderer, buf, 0, end - start, x, y, paint.mNativePaint); + nDrawText(mRenderer, buf, 0, end - start, x, y, + paint.mBidiFlags, paint.mNativePaint); TemporaryBuffer.recycle(buf); } } finally { @@ -1200,20 +1202,21 @@ class GLES20Canvas extends HardwareCanvas { int modifiers = setupModifiers(paint); try { - nDrawText(mRenderer, text, start, end, x, y, paint.mNativePaint); + nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } private static native void nDrawText(int renderer, String text, int start, int end, - float x, float y, int paint); + float x, float y, int bidiFlags, int paint); @Override public void drawText(String text, float x, float y, Paint paint) { int modifiers = setupModifiers(paint); try { - nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mNativePaint); + nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags, + paint.mNativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } @@ -1229,14 +1232,14 @@ class GLES20Canvas extends HardwareCanvas { int modifiers = setupModifiers(paint); try { nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset, - paint.mNativePaint); + paint.mBidiFlags, paint.mNativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } private static native void nDrawTextOnPath(int renderer, char[] text, int index, int count, - int path, float hOffset, float vOffset, int nativePaint); + int path, float hOffset, float vOffset, int bidiFlags, int nativePaint); @Override public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) { @@ -1245,25 +1248,28 @@ class GLES20Canvas extends HardwareCanvas { int modifiers = setupModifiers(paint); try { nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset, - paint.mNativePaint); + paint.mBidiFlags, paint.mNativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } private static native void nDrawTextOnPath(int renderer, String text, int start, int end, - int path, float hOffset, float vOffset, int nativePaint); + int path, float hOffset, float vOffset, int bidiFlags, int nativePaint); @Override public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, - float x, float y, Paint paint) { + float x, float y, int dir, Paint paint) { if ((index | count | text.length - index - count) < 0) { throw new IndexOutOfBoundsException(); } + if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) { + throw new IllegalArgumentException("Unknown direction: " + dir); + } int modifiers = setupModifiers(paint); try { - nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, + nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir, paint.mNativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); @@ -1271,31 +1277,32 @@ class GLES20Canvas extends HardwareCanvas { } private static native void nDrawTextRun(int renderer, char[] text, int index, int count, - int contextIndex, int contextCount, float x, float y, int nativePaint); + int contextIndex, int contextCount, float x, float y, int dir, int nativePaint); @Override public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, - float x, float y, Paint paint) { + float x, float y, int dir, Paint paint) { if ((start | end | end - start | text.length() - end) < 0) { throw new IndexOutOfBoundsException(); } int modifiers = setupModifiers(paint); try { + int flags = dir == 0 ? 0 : 1; if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { nDrawTextRun(mRenderer, text.toString(), start, end, contextStart, - contextEnd, x, y, paint.mNativePaint); + contextEnd, x, y, flags, paint.mNativePaint); } else if (text instanceof GraphicsOperations) { ((GraphicsOperations) text).drawTextRun(this, start, end, - contextStart, contextEnd, x, y, paint); + contextStart, contextEnd, x, y, flags, paint); } else { int contextLen = contextEnd - contextStart; int len = end - start; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen, - x, y, paint.mNativePaint); + x, y, flags, paint.mNativePaint); TemporaryBuffer.recycle(buf); } } finally { @@ -1304,7 +1311,7 @@ class GLES20Canvas extends HardwareCanvas { } private static native void nDrawTextRun(int renderer, String text, int start, int end, - int contextStart, int contextEnd, float x, float y, int nativePaint); + int contextStart, int contextEnd, float x, float y, int flags, int nativePaint); @Override public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset, diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index 947cf44131785..7da2451579490 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -267,15 +267,15 @@ class GLES20RecordingCanvas extends GLES20Canvas { @Override public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount, - float x, float y, Paint paint) { - super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, paint); + float x, float y, int dir, Paint paint) { + super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, dir, paint); recordShaderBitmap(paint); } @Override public void drawTextRun(CharSequence text, int start, int end, int contextStart, - int contextEnd, float x, float y, Paint paint) { - super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, paint); + int contextEnd, float x, float y, int dir, Paint paint) { + super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, dir, paint); recordShaderBitmap(paint); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index adacef27515de..b084ac437f778 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8813,11 +8813,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } public void drawTextRun(Canvas c, int start, int end, - int contextStart, int contextEnd, float x, float y, Paint p) { + int contextStart, int contextEnd, float x, float y, int flags, Paint p) { int count = end - start; int contextCount = contextEnd - contextStart; c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, - contextCount, x, y, p); + contextCount, x, y, flags, p); } public float measureText(int start, int end, Paint p) { @@ -8829,20 +8829,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } public float getTextRunAdvances(int start, int end, int contextStart, - int contextEnd, float[] advances, int advancesIndex, + int contextEnd, int flags, float[] advances, int advancesIndex, Paint p) { int count = end - start; int contextCount = contextEnd - contextStart; return p.getTextRunAdvances(mChars, start + mStart, count, - contextStart + mStart, contextCount, advances, + contextStart + mStart, contextCount, flags, advances, advancesIndex); } - public int getTextRunCursor(int contextStart, int contextEnd, + public float getTextRunAdvances(int start, int end, int contextStart, + int contextEnd, int flags, float[] advances, int advancesIndex, + Paint p, int reserved) { + int count = end - start; + int contextCount = contextEnd - contextStart; + return p.getTextRunAdvances(mChars, start + mStart, count, + contextStart + mStart, contextCount, flags, advances, + advancesIndex, reserved); + } + + public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset, int cursorOpt, Paint p) { int contextCount = contextEnd - contextStart; return p.getTextRunCursor(mChars, contextStart + mStart, - contextCount, offset + mStart, cursorOpt); + contextCount, flags, offset + mStart, cursorOpt); } } diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 8be86b87ef447..9a9f6c8a151a1 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -754,35 +754,35 @@ public: static void drawText___CIIFFIPaint(JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index, int count, - jfloat x, jfloat y, SkPaint* paint) { + jfloat x, jfloat y, int flags, SkPaint* paint) { jchar* textArray = env->GetCharArrayElements(text, NULL); - drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, paint); + drawTextWithGlyphs(canvas, textArray + index, 0, 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, SkPaint* paint) { + jfloat x, jfloat y, int flags, SkPaint* paint) { const jchar* textArray = env->GetStringChars(text, NULL); - drawTextWithGlyphs(canvas, textArray, start, end, x, y, paint); + drawTextWithGlyphs(canvas, textArray, start, end, x, y, flags, paint); env->ReleaseStringChars(text, textArray); } static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray, int start, int end, - jfloat x, jfloat y, SkPaint* paint) { + jfloat x, jfloat y, int flags, SkPaint* paint) { jint count = end - start; - drawTextWithGlyphs(canvas, textArray + start, 0, count, count, x, y, paint); + drawTextWithGlyphs(canvas, textArray + start, 0, count, count, x, y, flags, paint); } static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray, int start, int count, int contextCount, - jfloat x, jfloat y, SkPaint* paint) { + jfloat x, jfloat y, int flags, SkPaint* paint) { sp value = TextLayoutEngine::getInstance().getValue(paint, - textArray, start, count, contextCount); + textArray, start, count, contextCount, flags); if (value == NULL) { return; } @@ -793,7 +793,7 @@ public: x -= value->getTotalAdvance(); } paint->setTextAlign(SkPaint::kLeft_Align); - doDrawGlyphsPos(canvas, value->getGlyphs(), value->getPos(), 0, value->getGlyphsCount(), x, y, paint); + doDrawGlyphsPos(canvas, value->getGlyphs(), value->getPos(), 0, value->getGlyphsCount(), x, y, flags, paint); doDrawTextDecorations(canvas, x, y, value->getTotalAdvance(), paint); paint->setTextAlign(align); } @@ -835,8 +835,14 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l } } + static void doDrawGlyphs(SkCanvas* canvas, const jchar* glyphArray, int index, int count, + jfloat x, jfloat y, int flags, SkPaint* paint) { + // Beware: this needs Glyph encoding (already done on the Paint constructor) + canvas->drawText(glyphArray + index * 2, count * 2, x, y, *paint); + } + static void doDrawGlyphsPos(SkCanvas* canvas, const jchar* glyphArray, const jfloat* posArray, - int index, int count, jfloat x, jfloat y, SkPaint* paint) { + int index, int count, jfloat x, jfloat y, int flags, SkPaint* paint) { SkPoint* posPtr = new SkPoint[count]; for (int indx = 0; indx < count; indx++) { posPtr[indx].fX = SkFloatToScalar(x + posArray[indx * 2]); @@ -853,7 +859,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l jchar* chars = env->GetCharArrayElements(text, NULL); drawTextWithGlyphs(canvas, chars + contextIndex, index - contextIndex, - count, contextCount, x, y, paint); + count, contextCount, x, y, dirFlags, paint); env->ReleaseCharArrayElements(text, chars, JNI_ABORT); } @@ -866,7 +872,7 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l jint contextCount = contextEnd - contextStart; const jchar* chars = env->GetStringChars(text, NULL); drawTextWithGlyphs(canvas, chars + contextStart, start - contextStart, - count, contextCount, x, y, paint); + count, contextCount, x, y, dirFlags, paint); env->ReleaseStringChars(text, chars); } @@ -929,19 +935,21 @@ static void doDrawTextDecorations(SkCanvas* canvas, jfloat x, jfloat y, jfloat l static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index, int count, - SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) { + SkPath* path, jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) { jchar* textArray = env->GetCharArrayElements(text, NULL); - TextLayout::drawTextOnPath(paint, textArray + index, count, hOffset, vOffset, path, canvas); + TextLayout::drawTextOnPath(paint, textArray + index, count, bidiFlags, hOffset, vOffset, + path, canvas); env->ReleaseCharArrayElements(text, textArray, 0); } static void drawTextOnPath__StringPathFFPaint(JNIEnv* env, jobject, SkCanvas* canvas, jstring text, SkPath* path, - jfloat hOffset, jfloat vOffset, SkPaint* paint) { + jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) { const jchar* text_ = env->GetStringChars(text, NULL); int count = env->GetStringLength(text); - TextLayout::drawTextOnPath(paint, text_, count, hOffset, vOffset, path, canvas); + TextLayout::drawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset, + path, canvas); env->ReleaseStringChars(text, text_); } @@ -1051,21 +1059,21 @@ static JNINativeMethod gCanvasMethods[] = { (void*)SkCanvasGlue::drawBitmapMesh}, {"nativeDrawVertices", "(III[FI[FI[II[SIII)V", (void*)SkCanvasGlue::drawVertices}, - {"native_drawText","(I[CIIFFI)V", + {"native_drawText","(I[CIIFFII)V", (void*) SkCanvasGlue::drawText___CIIFFIPaint}, - {"native_drawText","(ILjava/lang/String;IIFFI)V", + {"native_drawText","(ILjava/lang/String;IIFFII)V", (void*) SkCanvasGlue::drawText__StringIIFFIPaint}, - {"native_drawTextRun","(I[CIIIIFFI)V", + {"native_drawTextRun","(I[CIIIIFFII)V", (void*) SkCanvasGlue::drawTextRun___CIIIIFFIPaint}, - {"native_drawTextRun","(ILjava/lang/String;IIIIFFI)V", + {"native_drawTextRun","(ILjava/lang/String;IIIIFFII)V", (void*) SkCanvasGlue::drawTextRun__StringIIIIFFIPaint}, {"native_drawPosText","(I[CII[FI)V", (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}, diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index e8304923230fe..29a36de502885 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -401,7 +401,7 @@ public: jfloat result = 0; TextLayout::getTextRunAdvances(paint, textArray, index, count, textLength, - NULL /* dont need all advances */, &result); + paint->getFlags(), NULL /* dont need all advances */, &result); env->ReleaseCharArrayElements(text, const_cast(textArray), JNI_ABORT); return result; @@ -426,7 +426,7 @@ public: jfloat width = 0; TextLayout::getTextRunAdvances(paint, textArray, start, count, textLength, - NULL /* dont need all advances */, &width); + paint->getFlags(), NULL /* dont need all advances */, &width); env->ReleaseStringChars(text, textArray); return width; @@ -446,7 +446,7 @@ public: jfloat width = 0; TextLayout::getTextRunAdvances(paint, textArray, 0, textLength, textLength, - NULL /* dont need all advances */, &width); + paint->getFlags(), NULL /* dont need all advances */, &width); env->ReleaseStringChars(text, textArray); return width; @@ -473,7 +473,7 @@ public: jfloat* widthsArray = autoWidths.ptr(); TextLayout::getTextRunAdvances(paint, text, 0, count, count, - widthsArray, NULL /* dont need totalAdvance */); + paint->getFlags(), widthsArray, NULL /* dont need totalAdvance */); return count; } @@ -494,8 +494,48 @@ public: return count; } + static int doTextGlyphs(JNIEnv* env, SkPaint* paint, const jchar* text, jint start, jint count, + jint contextCount, jint flags, jcharArray glyphs) { + NPE_CHECK_RETURN_ZERO(env, paint); + NPE_CHECK_RETURN_ZERO(env, text); + + if ((start | count | contextCount) < 0 || contextCount < count || !glyphs) { + doThrowAIOOBE(env); + return 0; + } + if (count == 0) { + return 0; + } + size_t glypthsLength = env->GetArrayLength(glyphs); + if ((size_t)count > glypthsLength) { + doThrowAIOOBE(env); + return 0; + } + + jchar* glyphsArray = env->GetCharArrayElements(glyphs, NULL); + + sp value = TextLayoutEngine::getInstance().getValue(paint, + text, start, count, contextCount, flags); + const jchar* shapedGlyphs = value->getGlyphs(); + size_t glyphsCount = value->getGlyphsCount(); + memcpy(glyphsArray, shapedGlyphs, sizeof(jchar) * glyphsCount); + + env->ReleaseCharArrayElements(glyphs, glyphsArray, JNI_ABORT); + return glyphsCount; + } + + static int getTextGlyphs__StringIIIII_C(JNIEnv* env, jobject clazz, SkPaint* paint, + jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags, + jcharArray glyphs) { + const jchar* textArray = env->GetStringChars(text, NULL); + int count = doTextGlyphs(env, paint, textArray + contextStart, start - contextStart, + end - start, contextEnd - contextStart, flags, glyphs); + env->ReleaseStringChars(text, textArray); + return count; + } + static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, const jchar *text, - jint start, jint count, jint contextCount, + jint start, jint count, jint contextCount, jint flags, jfloatArray advances, jint advancesIndex) { NPE_CHECK_RETURN_ZERO(env, paint); NPE_CHECK_RETURN_ZERO(env, text); @@ -517,7 +557,7 @@ public: jfloat advancesArray[count]; jfloat totalAdvance = 0; - TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, + TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, flags, advancesArray, &totalAdvance); if (advances != NULL) { @@ -526,23 +566,61 @@ public: return totalAdvance; } - static float getTextRunAdvances___CIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint, + static jfloat doTextRunAdvancesICU(JNIEnv *env, SkPaint *paint, const jchar *text, + jint start, jint count, jint contextCount, jint flags, + jfloatArray advances, jint advancesIndex) { + NPE_CHECK_RETURN_ZERO(env, paint); + NPE_CHECK_RETURN_ZERO(env, text); + + if ((start | count | contextCount | advancesIndex) < 0 || contextCount < count) { + doThrowAIOOBE(env); + return 0; + } + if (count == 0) { + return 0; + } + if (advances) { + size_t advancesLength = env->GetArrayLength(advances); + if ((size_t)count > advancesLength) { + doThrowAIOOBE(env); + return 0; + } + } + + jfloat advancesArray[count]; + jfloat totalAdvance = 0; + + TextLayout::getTextRunAdvancesICU(paint, text, start, count, contextCount, flags, + advancesArray, totalAdvance); + + if (advances != NULL) { + env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray); + } + return totalAdvance; + } + + static float getTextRunAdvances___CIIIII_FII(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text, jint index, jint count, jint contextIndex, jint contextCount, - jfloatArray advances, jint advancesIndex) { + jint flags, jfloatArray advances, jint advancesIndex, jint reserved) { jchar* textArray = env->GetCharArrayElements(text, NULL); - jfloat result = doTextRunAdvances(env, paint, textArray + contextIndex, - index - contextIndex, count, contextCount, advances, advancesIndex); + jfloat result = (reserved == 0) ? + doTextRunAdvances(env, paint, textArray + contextIndex, index - contextIndex, + count, contextCount, flags, advances, advancesIndex) : + doTextRunAdvancesICU(env, paint, textArray + contextIndex, index - contextIndex, + count, contextCount, flags, advances, advancesIndex); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); return result; } - static float getTextRunAdvances__StringIIII_FI(JNIEnv* env, jobject clazz, SkPaint* paint, - jstring text, jint start, jint end, jint contextStart, jint contextEnd, - jfloatArray advances, jint advancesIndex) { + static float getTextRunAdvances__StringIIIII_FII(JNIEnv* env, jobject clazz, SkPaint* paint, + jstring text, jint start, jint end, jint contextStart, jint contextEnd, jint flags, + jfloatArray advances, jint advancesIndex, jint reserved) { const jchar* textArray = env->GetStringChars(text, NULL); - jfloat result = doTextRunAdvances(env, paint, textArray + contextStart, - start - contextStart, end - start, contextEnd - contextStart, - advances, advancesIndex); + jfloat result = (reserved == 0) ? + doTextRunAdvances(env, paint, textArray + contextStart, start - contextStart, + end - start, contextEnd - contextStart, flags, advances, advancesIndex) : + doTextRunAdvancesICU(env, paint, textArray + contextStart, start - contextStart, + end - start, contextEnd - contextStart, flags, advances, advancesIndex); env->ReleaseStringChars(text, textArray); return result; } @@ -551,7 +629,7 @@ public: jint count, jint flags, jint offset, jint opt) { jfloat scalarArray[count]; - TextLayout::getTextRunAdvances(paint, text, start, count, start + count, + TextLayout::getTextRunAdvances(paint, text, start, count, start + count, flags, scalarArray, NULL /* dont need totalAdvance */); jint pos = offset - start; @@ -610,21 +688,21 @@ public: } static void getTextPath(JNIEnv* env, SkPaint* paint, const jchar* text, jint count, - jfloat x, jfloat y, SkPath *path) { - TextLayout::getTextPath(paint, text, count, x, y, path); + 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, + 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); - getTextPath(env, paint, textArray + index, count, x, y, path); + getTextPath(env, paint, textArray + index, count, bidiFlags, x, y, path); env->ReleaseCharArrayElements(text, const_cast(textArray), JNI_ABORT); } - static void getTextPath__String(JNIEnv* env, jobject clazz, SkPaint* paint, + 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); - getTextPath(env, paint, textArray + start, end - start, x, y, path); + getTextPath(env, paint, textArray + start, end - start, bidiFlags, x, y, path); env->ReleaseStringChars(text, textArray); } @@ -648,7 +726,7 @@ public: int count, float maxWidth, jfloatArray jmeasured, SkPaint::TextBufferDirection tbd) { sp value = TextLayoutEngine::getInstance().getValue(&paint, - text, 0, count, count); + text, 0, count, count, paint.getFlags()); if (value == NULL) { return 0; } @@ -720,7 +798,7 @@ public: SkIRect ir; sp value = TextLayoutEngine::getInstance().getValue(&paint, - text, 0, count, count); + text, 0, count, count, paint.getFlags()); if (value == NULL) { return; } @@ -808,15 +886,19 @@ static JNINativeMethod methods[] = { {"native_breakText","(Ljava/lang/String;ZF[F)I", (void*) SkPaintGlue::breakTextS}, {"native_getTextWidths","(I[CII[F)I", (void*) SkPaintGlue::getTextWidths___CII_F}, {"native_getTextWidths","(ILjava/lang/String;II[F)I", (void*) SkPaintGlue::getTextWidths__StringII_F}, - {"native_getTextRunAdvances","(I[CIIII[FI)F", - (void*) SkPaintGlue::getTextRunAdvances___CIIII_FI}, - {"native_getTextRunAdvances","(ILjava/lang/String;IIII[FI)F", - (void*) SkPaintGlue::getTextRunAdvances__StringIIII_FI}, - {"native_getTextRunCursor", "(I[CIIII)I", (void*) SkPaintGlue::getTextRunCursor___C}, - {"native_getTextRunCursor", "(ILjava/lang/String;IIII)I", + {"native_getTextRunAdvances","(I[CIIIII[FII)F", + (void*) SkPaintGlue::getTextRunAdvances___CIIIII_FII}, + {"native_getTextRunAdvances","(ILjava/lang/String;IIIII[FII)F", + (void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FII}, + + + {"native_getTextGlyphs","(ILjava/lang/String;IIIII[C)I", + (void*) SkPaintGlue::getTextGlyphs__StringIIIII_C}, + {"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___C}, - {"native_getTextPath","(ILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__String}, + {"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 index b77236c572625..2beedada409f3 100644 --- a/core/jni/android/graphics/TextLayout.cpp +++ b/core/jni/android/graphics/TextLayout.cpp @@ -28,13 +28,33 @@ 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] >= UNICODE_FIRST_RTL_CHAR) { + return true; + } + } + return false; +} + // 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, - jfloat x, jfloat y, SkPath *path) { + jint bidiFlags, jfloat x, jfloat y, SkPath *path) { sp value = TextLayoutEngine::getInstance().getValue(paint, - text, 0, len, len); + text, 0, len, len, bidiFlags); if (value == NULL) { return ; } @@ -45,10 +65,10 @@ void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len, } void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start, - jint count, jint contextCount, + jint count, jint contextCount, jint dirFlags, jfloat* resultAdvances, jfloat* resultTotalAdvance) { sp value = TextLayoutEngine::getInstance().getValue(paint, - chars, start, count, contextCount); + chars, start, count, contextCount, dirFlags); if (value == NULL) { return ; } @@ -60,20 +80,29 @@ void TextLayout::getTextRunAdvances(SkPaint* paint, const jchar* chars, jint sta } } -void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len, - jfloat x, jfloat y, SkPath *path) { - handleText(paint, text, len, x, y, path); +void TextLayout::getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start, + jint count, jint contextCount, jint dirFlags, + jfloat* resultAdvances, jfloat& resultTotalAdvance) { + // Compute advances and return them + computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags, + resultAdvances, &resultTotalAdvance); } +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, path); +} + + void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count, - jfloat hOffset, jfloat vOffset, + int bidiFlags, jfloat hOffset, jfloat vOffset, SkPath* path, SkCanvas* canvas) { SkScalar h_ = SkFloatToScalar(hOffset); SkScalar v_ = SkFloatToScalar(vOffset); sp value = TextLayoutEngine::getInstance().getValue(paint, - text, 0, count, count); + text, 0, count, count, bidiFlags); if (value == NULL) { return; } @@ -82,4 +111,73 @@ void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count, canvas->drawTextOnPathHV(value->getGlyphs(), value->getGlyphsCount() * 2, *path, h_, v_, *paint); } +void TextLayout::computeAdvancesWithICU(SkPaint* paint, const UChar* chars, + size_t start, size_t count, size_t contextCount, int dirFlags, + jfloat* outAdvances, jfloat* outTotalAdvance) { + SkAutoSTMalloc tempBuffer(contextCount); + jchar* buffer = tempBuffer.get(); + SkScalar* scalarArray = (SkScalar*)outAdvances; + + // this is where we'd call harfbuzz + // for now we just use ushape.c + size_t 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] == UNICODE_NOT_A_CHAR) { + buffer[i] = UNICODE_ZWSP; // 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); + } + + jfloat totalAdvance = 0; + if (widths < count) { +#if DEBUG_ADVANCES + ALOGD("ICU -- count=%d", widths); +#endif + // 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 (size_t i = 0, p = 0; i < widths; ++i) { + totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]); + if (p < count && + text[p] >= UNICODE_FIRST_LOW_SURROGATE && + text[p] < UNICODE_FIRST_PRIVATE_USE && + text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE && + text[p-1] < UNICODE_FIRST_LOW_SURROGATE) { + outAdvances[p++] = 0; + } +#if DEBUG_ADVANCES + ALOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); +#endif + } + } else { +#if DEBUG_ADVANCES + ALOGD("ICU -- count=%d", count); +#endif + for (size_t i = 0; i < count; i++) { + totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]); +#if DEBUG_ADVANCES + ALOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); +#endif + } + } + *outTotalAdvance = totalAdvance; +} + } diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h index fa388c8ed74f6..a0f94024fcf8e 100644 --- a/core/jni/android/graphics/TextLayout.h +++ b/core/jni/android/graphics/TextLayout.h @@ -41,6 +41,17 @@ namespace android { */ #define USE_TEXT_LAYOUT_CACHE 1 +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 +}; + enum { kDirection_LTR = 0, kDirection_RTL = 1, @@ -52,18 +63,28 @@ class TextLayout { public: static void getTextRunAdvances(SkPaint* paint, const jchar* chars, jint start, - jint count, jint contextCount, + jint count, jint contextCount, jint dirFlags, jfloat* resultAdvances, jfloat* resultTotalAdvance); + static void getTextRunAdvancesICU(SkPaint* paint, const jchar* chars, jint start, + jint count, jint contextCount, jint dirFlags, + jfloat* resultAdvances, jfloat& resultTotalAdvance); + static void getTextPath(SkPaint* paint, const jchar* text, jsize len, - jfloat x, jfloat y, SkPath* path); + jint bidiFlags, jfloat x, jfloat y, SkPath* path); static void drawTextOnPath(SkPaint* paint, const jchar* text, jsize len, - jfloat hOffset, jfloat vOffset, + int bidiFlags, jfloat hOffset, jfloat vOffset, SkPath* path, SkCanvas* canvas); private: + static bool needsLayout(const jchar* text, jint len, jint bidiFlags); + static void handleText(SkPaint* paint, const jchar* text, jsize len, - jfloat x, jfloat y, SkPath* path); + int bidiFlags, jfloat x, jfloat y, SkPath* path); + + static void computeAdvancesWithICU(SkPaint* paint, const UChar* chars, + size_t start, size_t count, size_t contextCount, int dirFlags, + jfloat* outAdvances, jfloat* outTotalAdvance); }; } // namespace android diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp index 1a8612e6aa117..1ace23e22e417 100644 --- a/core/jni/android/graphics/TextLayoutCache.cpp +++ b/core/jni/android/graphics/TextLayoutCache.cpp @@ -87,7 +87,7 @@ void TextLayoutCache::purgeCaches() { * Caching */ sp TextLayoutCache::getValue(const SkPaint* paint, - const jchar* text, jint start, jint count, jint contextCount) { + const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) { AutoMutex _l(mLock); nsecs_t startTime = 0; if (mDebugEnabled) { @@ -95,7 +95,7 @@ sp TextLayoutCache::getValue(const SkPaint* paint, } // Create the key - TextLayoutCacheKey key(paint, text, start, count, contextCount); + TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags); // Get value from cache if possible sp value = mCache.get(key); @@ -111,7 +111,7 @@ sp TextLayoutCache::getValue(const SkPaint* paint, // Compute advances and store them mShaper->computeValues(value.get(), paint, reinterpret_cast(key.getText()), start, count, - size_t(contextCount)); + size_t(contextCount), int(dirFlags)); if (mDebugEnabled) { value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime); @@ -218,13 +218,14 @@ void TextLayoutCache::dumpCacheStats() { * TextLayoutCacheKey */ TextLayoutCacheKey::TextLayoutCacheKey(): start(0), count(0), contextCount(0), - typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0), + dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0), hinting(SkPaint::kNo_Hinting), variant(SkPaint::kDefault_Variant), language() { } TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text, - size_t start, size_t count, size_t contextCount) : - start(start), count(count), contextCount(contextCount) { + size_t start, size_t count, size_t contextCount, int dirFlags) : + start(start), count(count), contextCount(contextCount), + dirFlags(dirFlags) { textCopy.setTo(text, contextCount); typeface = paint->getTypeface(); textSize = paint->getTextSize(); @@ -241,6 +242,7 @@ TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) : start(other.start), count(other.count), contextCount(other.contextCount), + dirFlags(other.dirFlags), typeface(other.typeface), textSize(other.textSize), textSkewX(other.textSkewX), @@ -279,6 +281,9 @@ int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutC deltaInt = lhs.hinting - rhs.hinting; if (deltaInt != 0) return (deltaInt); + deltaInt = lhs.dirFlags - rhs.dirFlags; + if (deltaInt) return (deltaInt); + deltaInt = lhs.variant - rhs.variant; if (deltaInt) return (deltaInt); @@ -354,9 +359,9 @@ TextLayoutShaper::~TextLayoutShaper() { } void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars, - size_t start, size_t count, size_t contextCount) { + size_t start, size_t count, size_t contextCount, int dirFlags) { - computeValues(paint, chars, start, count, contextCount, + computeValues(paint, chars, start, count, contextCount, dirFlags, &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos); #if DEBUG_ADVANCES ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count, @@ -365,7 +370,7 @@ void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* pain } void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars, - size_t start, size_t count, size_t contextCount, + size_t start, size_t count, size_t contextCount, int dirFlags, Vector* const outAdvances, jfloat* outTotalAdvance, Vector* const outGlyphs, Vector* const outPos) { *outTotalAdvance = 0; @@ -373,94 +378,110 @@ void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars, return; } - UBiDiLevel bidiReq = UBIDI_DEFAULT_LTR; + UBiDiLevel bidiReq = 0; + bool forceLTR = false; + bool forceRTL = false; + + switch (dirFlags & kBidi_Mask) { + 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: forceLTR = true; break; // every char is LTR + case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL + } + bool useSingleRun = false; - bool isRTL = false; - - UBiDi* bidi = ubidi_open(); - if (bidi) { - UErrorCode status = U_ZERO_ERROR; + bool isRTL = forceRTL; + if (forceLTR || forceRTL) { + useSingleRun = true; + } else { + UBiDi* bidi = ubidi_open(); + if (bidi) { + UErrorCode status = U_ZERO_ERROR; #if DEBUG_GLYPHS - ALOGD("******** ComputeValues -- start"); - ALOGD(" -- string = '%s'", String8(chars + start, count).string()); - ALOGD(" -- start = %d", start); - ALOGD(" -- count = %d", count); - ALOGD(" -- contextCount = %d", contextCount); - ALOGD(" -- bidiReq = %d", bidiReq); + ALOGD("******** ComputeValues -- start"); + ALOGD(" -- string = '%s'", String8(chars + start, count).string()); + ALOGD(" -- start = %d", start); + ALOGD(" -- count = %d", count); + ALOGD(" -- contextCount = %d", contextCount); + ALOGD(" -- bidiReq = %d", bidiReq); #endif - ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); - if (U_SUCCESS(status)) { - int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl - ssize_t rc = ubidi_countRuns(bidi, &status); + ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); + if (U_SUCCESS(status)) { + int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl + ssize_t rc = ubidi_countRuns(bidi, &status); #if DEBUG_GLYPHS - ALOGD(" -- paraDir = %d", paraDir); - ALOGD(" -- run-count = %d", int(rc)); + ALOGD(" -- dirFlags = %d", dirFlags); + ALOGD(" -- paraDir = %d", paraDir); + ALOGD(" -- run-count = %d", int(rc)); #endif - if (U_SUCCESS(status) && rc == 1) { - // Normal case: one run, status is ok - isRTL = (paraDir == 1); - useSingleRun = true; - } else if (!U_SUCCESS(status) || rc < 1) { - ALOGW("Need to force to single run -- string = '%s'," - " status = %d, rc = %d", - String8(chars + start, count).string(), status, int(rc)); - isRTL = (paraDir == 1); - useSingleRun = true; - } else { - int32_t end = start + count; - for (size_t i = 0; i < size_t(rc); ++i) { - int32_t startRun = -1; - int32_t lengthRun = -1; - UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); + if (U_SUCCESS(status) && rc == 1) { + // Normal case: one run, status is ok + isRTL = (paraDir == 1); + useSingleRun = true; + } else if (!U_SUCCESS(status) || rc < 1) { + ALOGW("Need to force to single run -- string = '%s'," + " status = %d, rc = %d", + String8(chars + start, count).string(), status, int(rc)); + isRTL = (paraDir == 1); + useSingleRun = true; + } else { + int32_t end = start + count; + for (size_t i = 0; i < size_t(rc); ++i) { + int32_t startRun = -1; + int32_t lengthRun = -1; + UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); - if (startRun == -1 || lengthRun == -1) { - // Something went wrong when getting the visual run, need to clear - // already computed data before doing a single run pass - ALOGW("Visual run is not valid"); - outGlyphs->clear(); - outAdvances->clear(); - outPos->clear(); - *outTotalAdvance = 0; - isRTL = (paraDir == 1); - useSingleRun = true; - break; - } + if (startRun == -1 || lengthRun == -1) { + // Something went wrong when getting the visual run, need to clear + // already computed data before doing a single run pass + ALOGW("Visual run is not valid"); + outGlyphs->clear(); + outAdvances->clear(); + outPos->clear(); + *outTotalAdvance = 0; + isRTL = (paraDir == 1); + useSingleRun = true; + break; + } - if (startRun >= end) { - continue; - } - int32_t endRun = startRun + lengthRun; - if (endRun <= int32_t(start)) { - continue; - } - if (startRun < int32_t(start)) { - startRun = int32_t(start); - } - if (endRun > end) { - endRun = end; - } + if (startRun >= end) { + continue; + } + int32_t endRun = startRun + lengthRun; + if (endRun <= int32_t(start)) { + continue; + } + if (startRun < int32_t(start)) { + startRun = int32_t(start); + } + if (endRun > end) { + endRun = end; + } - lengthRun = endRun - startRun; - isRTL = (runDir == UBIDI_RTL); + lengthRun = endRun - startRun; + isRTL = (runDir == UBIDI_RTL); #if DEBUG_GLYPHS - ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d", - i, startRun, lengthRun, isRTL); + ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d", + i, startRun, lengthRun, isRTL); #endif - computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL, - outAdvances, outTotalAdvance, outGlyphs, outPos); + computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL, + outAdvances, outTotalAdvance, outGlyphs, outPos); + } } + } else { + ALOGW("Cannot set Para"); + useSingleRun = true; + isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); } + ubidi_close(bidi); } else { - ALOGW("Cannot set Para"); + ALOGW("Cannot ubidi_open()"); useSingleRun = true; isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); } - ubidi_close(bidi); - } else { - ALOGW("Cannot ubidi_open()"); - useSingleRun = true; - isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); } // Default single run case @@ -897,11 +918,11 @@ TextLayoutEngine::~TextLayoutEngine() { } sp TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text, - jint start, jint count, jint contextCount) { + jint start, jint count, jint contextCount, jint dirFlags) { sp value; #if USE_TEXT_LAYOUT_CACHE value = mTextLayoutCache->getValue(paint, text, start, count, - contextCount); + contextCount, dirFlags); if (value == NULL) { ALOGE("Cannot get TextLayoutCache value for text = '%s'", String8(text + start, count).string()); @@ -909,7 +930,7 @@ sp TextLayoutEngine::getValue(const SkPaint* paint, const jchar #else value = new TextLayoutValue(count); mShaper->computeValues(value.get(), paint, - reinterpret_cast(text), start, count, contextCount); + reinterpret_cast(text), start, count, contextCount, dirFlags); #endif return value; } diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h index 6858c0e94a6ce..f9b9900169db5 100644 --- a/core/jni/android/graphics/TextLayoutCache.h +++ b/core/jni/android/graphics/TextLayoutCache.h @@ -70,7 +70,7 @@ public: TextLayoutCacheKey(); TextLayoutCacheKey(const SkPaint* paint, const UChar* text, size_t start, size_t count, - size_t contextCount); + size_t contextCount, int dirFlags); TextLayoutCacheKey(const TextLayoutCacheKey& other); @@ -97,6 +97,7 @@ private: size_t start; size_t count; size_t contextCount; + int dirFlags; SkTypeface* typeface; SkScalar textSize; SkScalar textSkewX; @@ -180,7 +181,7 @@ public: virtual ~TextLayoutShaper(); void computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars, - size_t start, size_t count, size_t contextCount); + size_t start, size_t count, size_t contextCount, int dirFlags); void purgeCaches(); @@ -214,7 +215,7 @@ private: size_t shapeFontRun(const SkPaint* paint); void computeValues(const SkPaint* paint, const UChar* chars, - size_t start, size_t count, size_t contextCount, + size_t start, size_t count, size_t contextCount, int dirFlags, Vector* const outAdvances, jfloat* outTotalAdvance, Vector* const outGlyphs, Vector* const outPos); @@ -251,7 +252,7 @@ public: void operator()(TextLayoutCacheKey& text, sp& desc); sp getValue(const SkPaint* paint, const jchar* text, jint start, - jint count, jint contextCount); + jint count, jint contextCount, jint dirFlags); /** * Clear the cache @@ -303,7 +304,7 @@ public: * the call. Be careful of this when doing optimization. **/ sp getValue(const SkPaint* paint, const jchar* text, jint start, - jint count, jint contextCount); + jint count, jint contextCount, jint dirFlags); void purgeCaches(); diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index ba62f6d57bd73..10c92ba151689 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -568,9 +568,9 @@ static void android_view_GLES20Canvas_resetPaintFilter(JNIEnv* env, jobject claz // ---------------------------------------------------------------------------- static void renderText(OpenGLRenderer* renderer, const jchar* text, int count, - jfloat x, jfloat y, SkPaint* paint) { + jfloat x, jfloat y, int flags, SkPaint* paint) { sp value = TextLayoutEngine::getInstance().getValue(paint, - text, 0, count, count); + text, 0, count, count, flags); if (value == NULL) { return; } @@ -584,9 +584,9 @@ static void renderText(OpenGLRenderer* renderer, const jchar* text, int count, } static void renderTextOnPath(OpenGLRenderer* renderer, const jchar* text, int count, - SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) { + SkPath* path, jfloat hOffset, jfloat vOffset, int flags, SkPaint* paint) { sp value = TextLayoutEngine::getInstance().getValue(paint, - text, 0, count, count); + text, 0, count, count, flags); if (value == NULL) { return; } @@ -599,9 +599,9 @@ static void renderTextOnPath(OpenGLRenderer* renderer, const jchar* text, int co static void renderTextRun(OpenGLRenderer* renderer, const jchar* text, jint start, jint count, jint contextCount, jfloat x, jfloat y, - SkPaint* paint) { + int flags, SkPaint* paint) { sp value = TextLayoutEngine::getInstance().getValue(paint, - text, start, count, contextCount); + text, start, count, contextCount, flags); if (value == NULL) { return; } @@ -616,62 +616,64 @@ static void renderTextRun(OpenGLRenderer* renderer, const jchar* text, static void android_view_GLES20Canvas_drawTextArray(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer, jcharArray text, jint index, jint count, - jfloat x, jfloat y, SkPaint* paint) { + jfloat x, jfloat y, jint flags, SkPaint* paint) { jchar* textArray = env->GetCharArrayElements(text, NULL); - renderText(renderer, textArray + index, count, x, y, paint); + renderText(renderer, textArray + index, count, x, y, flags, paint); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); } static void android_view_GLES20Canvas_drawText(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer, jstring text, jint start, jint end, - jfloat x, jfloat y, SkPaint* paint) { + jfloat x, jfloat y, jint flags, SkPaint* paint) { const jchar* textArray = env->GetStringChars(text, NULL); - renderText(renderer, textArray + start, end - start, x, y, paint); + renderText(renderer, textArray + start, end - start, x, y, flags, paint); env->ReleaseStringChars(text, textArray); } static void android_view_GLES20Canvas_drawTextArrayOnPath(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer, jcharArray text, jint index, jint count, - SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) { + SkPath* path, jfloat hOffset, jfloat vOffset, jint flags, SkPaint* paint) { jchar* textArray = env->GetCharArrayElements(text, NULL); renderTextOnPath(renderer, textArray + index, count, path, - hOffset, vOffset, paint); + hOffset, vOffset, flags, paint); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); } static void android_view_GLES20Canvas_drawTextOnPath(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer, jstring text, jint start, jint end, - SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) { + SkPath* path, jfloat hOffset, jfloat vOffset, jint flags, SkPaint* paint) { const jchar* textArray = env->GetStringChars(text, NULL); renderTextOnPath(renderer, textArray + start, end - start, path, - hOffset, vOffset, paint); + hOffset, vOffset, flags, paint); env->ReleaseStringChars(text, textArray); } static void android_view_GLES20Canvas_drawTextRunArray(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer, jcharArray text, jint index, jint count, - jint contextIndex, jint contextCount, jfloat x, jfloat y, SkPaint* paint) { + jint contextIndex, jint contextCount, jfloat x, jfloat y, jint dirFlags, + SkPaint* paint) { jchar* textArray = env->GetCharArrayElements(text, NULL); renderTextRun(renderer, textArray + contextIndex, index - contextIndex, - count, contextCount, x, y, paint); + count, contextCount, x, y, dirFlags, paint); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); } static void android_view_GLES20Canvas_drawTextRun(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer, jstring text, jint start, jint end, - jint contextStart, int contextEnd, jfloat x, jfloat y, SkPaint* paint) { + jint contextStart, int contextEnd, jfloat x, jfloat y, jint dirFlags, + SkPaint* paint) { const jchar* textArray = env->GetStringChars(text, NULL); jint count = end - start; jint contextCount = contextEnd - contextStart; renderTextRun(renderer, textArray + contextStart, start - contextStart, - count, contextCount, x, y, paint); + count, contextCount, x, y, dirFlags, paint); env->ReleaseStringChars(text, textArray); } static void renderPosText(OpenGLRenderer* renderer, const jchar* text, int count, - const jfloat* positions, SkPaint* paint) { + const jfloat* positions, jint dirFlags, SkPaint* paint) { sp value = TextLayoutEngine::getInstance().getValue(paint, - text, 0, count, count); + text, 0, count, count, dirFlags); if (value == NULL) { return; } @@ -689,7 +691,7 @@ static void android_view_GLES20Canvas_drawPosTextArray(JNIEnv* env, jobject claz jchar* textArray = env->GetCharArrayElements(text, NULL); jfloat* positions = env->GetFloatArrayElements(pos, NULL); - renderPosText(renderer, textArray + index, count, positions, paint); + renderPosText(renderer, textArray + index, count, positions, kBidi_LTR, paint); env->ReleaseFloatArrayElements(pos, positions, JNI_ABORT); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); @@ -701,7 +703,7 @@ static void android_view_GLES20Canvas_drawPosText(JNIEnv* env, jobject clazz, const jchar* textArray = env->GetStringChars(text, NULL); jfloat* positions = env->GetFloatArrayElements(pos, NULL); - renderPosText(renderer, textArray + start, end - start, positions, paint); + renderPosText(renderer, textArray + start, end - start, positions, kBidi_LTR, paint); env->ReleaseFloatArrayElements(pos, positions, JNI_ABORT); env->ReleaseStringChars(text, textArray); @@ -1005,16 +1007,16 @@ static JNINativeMethod gMethods[] = { { "nSetupPaintFilter", "(III)V", (void*) android_view_GLES20Canvas_setupPaintFilter }, { "nResetPaintFilter", "(I)V", (void*) android_view_GLES20Canvas_resetPaintFilter }, - { "nDrawText", "(I[CIIFFI)V", (void*) android_view_GLES20Canvas_drawTextArray }, - { "nDrawText", "(ILjava/lang/String;IIFFI)V", + { "nDrawText", "(I[CIIFFII)V", (void*) android_view_GLES20Canvas_drawTextArray }, + { "nDrawText", "(ILjava/lang/String;IIFFII)V", (void*) android_view_GLES20Canvas_drawText }, - { "nDrawTextOnPath", "(I[CIIIFFI)V", (void*) android_view_GLES20Canvas_drawTextArrayOnPath }, - { "nDrawTextOnPath", "(ILjava/lang/String;IIIFFI)V", + { "nDrawTextOnPath", "(I[CIIIFFII)V", (void*) android_view_GLES20Canvas_drawTextArrayOnPath }, + { "nDrawTextOnPath", "(ILjava/lang/String;IIIFFII)V", (void*) android_view_GLES20Canvas_drawTextOnPath }, - { "nDrawTextRun", "(I[CIIIIFFI)V", (void*) android_view_GLES20Canvas_drawTextRunArray }, - { "nDrawTextRun", "(ILjava/lang/String;IIIIFFI)V", + { "nDrawTextRun", "(I[CIIIIFFII)V", (void*) android_view_GLES20Canvas_drawTextRunArray }, + { "nDrawTextRun", "(ILjava/lang/String;IIIIFFII)V", (void*) android_view_GLES20Canvas_drawTextRun }, { "nDrawPosText", "(I[CII[FI)V", (void*) android_view_GLES20Canvas_drawPosTextArray }, diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 9bb90f17fb139..4df5c24d41b90 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -62,6 +62,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; + // Maximum bitmap size as defined in Skia's native code // (see SkCanvas.cpp, SkDraw.cpp) private static final int MAXMIMUM_BITMAP_SIZE = 32766; @@ -1333,7 +1345,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); } /** @@ -1346,7 +1359,8 @@ public class Canvas { * @param paint The paint used for the text (e.g. color, size, style) */ public void drawText(String text, float x, float y, Paint paint) { - native_drawText(mNativeCanvas, text, 0, text.length(), x, y, paint.mNativePaint); + native_drawText(mNativeCanvas, text, 0, text.length(), x, y, paint.mBidiFlags, + paint.mNativePaint); } /** @@ -1364,7 +1378,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); } /** @@ -1383,14 +1398,16 @@ public class Canvas { public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) { if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { - native_drawText(mNativeCanvas, text.toString(), start, end, x, y, paint.mNativePaint); + native_drawText(mNativeCanvas, text.toString(), start, end, x, y, + paint.mBidiFlags, paint.mNativePaint); } else if (text instanceof GraphicsOperations) { ((GraphicsOperations) text).drawText(this, start, end, x, y, paint); } else { char[] buf = TemporaryBuffer.obtain(end - start); TextUtils.getChars(text, start, end, buf, 0); - native_drawText(mNativeCanvas, buf, 0, end - start, x, y, paint.mNativePaint); + native_drawText(mNativeCanvas, buf, 0, end - start, x, y, + paint.mBidiFlags, paint.mNativePaint); TemporaryBuffer.recycle(buf); } } @@ -1411,11 +1428,13 @@ public class Canvas { * + count. * @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 count, int contextIndex, int contextCount, - float x, float y, Paint paint) { + float x, float y, int dir, Paint paint) { if (text == null) { throw new NullPointerException("text is null"); @@ -1426,9 +1445,12 @@ public class Canvas { if ((index | count | text.length - index - count) < 0) { throw new IndexOutOfBoundsException(); } + if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) { + throw new IllegalArgumentException("unknown dir: " + dir); + } native_drawTextRun(mNativeCanvas, text, index, count, - contextIndex, contextCount, x, y, paint.mNativePaint); + contextIndex, contextCount, x, y, dir, paint.mNativePaint); } /** @@ -1444,11 +1466,12 @@ public class Canvas { * 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, int contextStart, int contextEnd, - float x, float y, Paint paint) { + float x, float y, int dir, Paint paint) { if (text == null) { throw new NullPointerException("text is null"); @@ -1460,20 +1483,22 @@ public class Canvas { 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, - contextStart, contextEnd, x, y, paint.mNativePaint); + contextStart, contextEnd, x, y, flags, paint.mNativePaint); } else if (text instanceof GraphicsOperations) { ((GraphicsOperations) text).drawTextRun(this, start, end, - contextStart, contextEnd, x, y, paint); + contextStart, contextEnd, x, y, flags, paint); } else { int contextLen = contextEnd - contextStart; int len = end - start; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); native_drawTextRun(mNativeCanvas, buf, start - contextStart, len, - 0, contextLen, x, y, paint.mNativePaint); + 0, contextLen, x, y, flags, paint.mNativePaint); TemporaryBuffer.recycle(buf); } } @@ -1539,7 +1564,8 @@ public class Canvas { throw new ArrayIndexOutOfBoundsException(); } native_drawTextOnPath(mNativeCanvas, text, index, count, - path.ni(), hOffset, vOffset, paint.mNativePaint); + path.ni(), hOffset, vOffset, + paint.mBidiFlags, paint.mNativePaint); } /** @@ -1558,7 +1584,7 @@ public class Canvas { public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) { if (text.length() > 0) { native_drawTextOnPath(mNativeCanvas, text, path.ni(), hOffset, vOffset, - paint.mNativePaint); + paint.mBidiFlags, paint.mNativePaint); } } @@ -1720,18 +1746,18 @@ 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, int contextStart, int contextEnd, - float x, float y, int paint); + float x, float y, int flags, int paint); private static native void native_drawTextRun(int nativeCanvas, char[] text, int start, int count, int contextStart, int contextCount, - float x, float y, int paint); + float x, float y, int flags, int paint); private static native void native_drawPosText(int nativeCanvas, char[] text, int index, @@ -1744,13 +1770,13 @@ public class Canvas { char[] text, int index, int count, int path, float hOffset, - float vOffset, + float vOffset, int bidiFlags, int paint); private static native void native_drawTextOnPath(int nativeCanvas, String text, int path, float hOffset, float vOffset, - int paint); + int flags, int paint); private static native void native_drawPicture(int nativeCanvas, int nativePicture); private static native void finalizer(int nativeCanvas); diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index fd454093d436f..216dc6caafb4a 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -69,6 +69,11 @@ public class Paint { */ public int shadowColor; + /** + * @hide + */ + public int mBidiFlags = BIDI_DEFAULT_LTR; + static final Style[] sStyleArray = { Style.FILL, Style.STROKE, Style.FILL_AND_STROKE }; @@ -114,6 +119,74 @@ public class Paint { */ public static final int HINTING_ON = 0x1; + /** + * 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; + + /** + * Flag for getTextRunAdvances indicating left-to-right run direction. + * @hide + */ + public static final int DIRECTION_LTR = 0; + + /** + * Flag for getTextRunAdvances indicating right-to-left run direction. + * @hide + */ + public static final int DIRECTION_RTL = 1; + /** * Option for getTextRunCursor to compute the valid cursor after * offset or the limit of the context, whichever is less. @@ -322,6 +395,7 @@ public class Paint { shadowRadius = 0; shadowColor = 0; + mBidiFlags = BIDI_DEFAULT_LTR; setTextLocale(Locale.getDefault()); } @@ -361,6 +435,7 @@ public class Paint { shadowRadius = paint.shadowRadius; shadowColor = paint.shadowColor; + mBidiFlags = paint.mBidiFlags; mLocale = paint.mLocale; } @@ -376,6 +451,29 @@ public class Paint { } } + /** + * 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. * @@ -1569,20 +1667,77 @@ public class Paint { return getTextWidths(text, 0, text.length(), widths); } + /** + * Return the glyph Ids for the characters in the string. + * + * @param text The text to measure + * @param start The index of the first char to to measure + * @param end The end of the text slice to measure + * @param contextStart the index of the first character to use for shaping context, + * must be <= start + * @param contextEnd the index past the last character to use for shaping context, + * must be >= end + * @param flags the flags to control the advances, either {@link #DIRECTION_LTR} + * or {@link #DIRECTION_RTL} + * @param glyphs array to receive the glyph Ids of the characters. + * Must be at least a large as the text. + * @return the number of glyphs in the returned array + * + * @hide + * + * Used only for BiDi / RTL Tests + */ + public int getTextGlyphs(String text, int start, int end, int contextStart, int contextEnd, + int flags, char[] glyphs) { + if (text == null) { + throw new IllegalArgumentException("text cannot be null"); + } + if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) { + throw new IllegalArgumentException("unknown flags value: " + flags); + } + if ((start | end | contextStart | contextEnd | (end - start) + | (start - contextStart) | (contextEnd - end) | (text.length() - end) + | (text.length() - contextEnd)) < 0) { + throw new IndexOutOfBoundsException(); + } + if (end - start > glyphs.length) { + throw new ArrayIndexOutOfBoundsException(); + } + return native_getTextGlyphs(mNativePaint, text, start, end, contextStart, contextEnd, + flags, glyphs); + } + /** * Convenience overload that takes a char array instead of a * String. * - * @see #getTextRunAdvances(String, int, int, int, int, float[], int) + * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int) * @hide */ public float getTextRunAdvances(char[] chars, int index, int count, - int contextIndex, int contextCount, float[] advances, + int contextIndex, int contextCount, int flags, float[] advances, int advancesIndex) { + return getTextRunAdvances(chars, index, count, contextIndex, contextCount, flags, + advances, advancesIndex, 0 /* use Harfbuzz*/); + } + + /** + * Convenience overload that takes a char array instead of a + * String. + * + * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int, int) + * @hide + */ + public float getTextRunAdvances(char[] chars, int index, int count, + int contextIndex, int contextCount, int flags, float[] advances, + int advancesIndex, int reserved) { if (chars == null) { throw new IllegalArgumentException("text cannot be null"); } + if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) { + throw new IllegalArgumentException("unknown flags value: " + flags); + } if ((index | count | contextIndex | contextCount | advancesIndex | (index - contextIndex) | (contextCount - count) | ((contextIndex + contextCount) - (index + count)) @@ -1597,13 +1752,13 @@ public class Paint { } if (!mHasCompatScaling) { return native_getTextRunAdvances(mNativePaint, chars, index, count, - contextIndex, contextCount, advances, advancesIndex); + contextIndex, contextCount, flags, advances, advancesIndex, reserved); } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); float res = native_getTextRunAdvances(mNativePaint, chars, index, count, - contextIndex, contextCount, advances, advancesIndex); + contextIndex, contextCount, flags, advances, advancesIndex, reserved); setTextSize(oldSize); if (advances != null) { @@ -1618,12 +1773,26 @@ public class Paint { * Convenience overload that takes a CharSequence instead of a * String. * - * @see #getTextRunAdvances(String, int, int, int, int, float[], int) + * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int) * @hide */ public float getTextRunAdvances(CharSequence text, int start, int end, - int contextStart, int contextEnd, float[] advances, + int contextStart, int contextEnd, int flags, float[] advances, int advancesIndex) { + return getTextRunAdvances(text, start, end, contextStart, contextEnd, flags, + advances, advancesIndex, 0 /* use Harfbuzz */); + } + + /** + * Convenience overload that takes a CharSequence instead of a + * String. + * + * @see #getTextRunAdvances(String, int, int, int, int, int, float[], int) + * @hide + */ + public float getTextRunAdvances(CharSequence text, int start, int end, + int contextStart, int contextEnd, int flags, float[] advances, + int advancesIndex, int reserved) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); @@ -1638,16 +1807,16 @@ public class Paint { if (text instanceof String) { return getTextRunAdvances((String) text, start, end, - contextStart, contextEnd, advances, advancesIndex); + contextStart, contextEnd, flags, advances, advancesIndex, reserved); } if (text instanceof SpannedString || text instanceof SpannableString) { return getTextRunAdvances(text.toString(), start, end, - contextStart, contextEnd, advances, advancesIndex); + contextStart, contextEnd, flags, advances, advancesIndex, reserved); } if (text instanceof GraphicsOperations) { return ((GraphicsOperations) text).getTextRunAdvances(start, end, - contextStart, contextEnd, advances, advancesIndex, this); + contextStart, contextEnd, flags, advances, advancesIndex, this); } if (text.length() == 0 || end == start) { return 0f; @@ -1658,7 +1827,7 @@ public class Paint { char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); float result = getTextRunAdvances(buf, start - contextStart, len, - 0, contextLen, advances, advancesIndex); + 0, contextLen, flags, advances, advancesIndex, reserved); TemporaryBuffer.recycle(buf); return result; } @@ -1695,6 +1864,8 @@ public class Paint { * must be <= start * @param contextEnd the index past the last character to use for shaping context, * must be >= end + * @param flags the flags to control the advances, either {@link #DIRECTION_LTR} + * or {@link #DIRECTION_RTL} * @param advances array to receive the advances, must have room for all advances, * can be null if only total advance is needed * @param advancesIndex the position in advances at which to put the @@ -1704,11 +1875,63 @@ public class Paint { * @hide */ public float getTextRunAdvances(String text, int start, int end, int contextStart, - int contextEnd, float[] advances, int advancesIndex) { + int contextEnd, int flags, float[] advances, int advancesIndex) { + return getTextRunAdvances(text, start, end, contextStart, contextEnd, flags, + advances, advancesIndex, 0 /* use Harfbuzz*/); + } + + /** + * Returns the total advance width for the characters in the run + * between start and end, and if advances is not null, the advance + * assigned to each of these characters (java chars). + * + *

The trailing surrogate in a valid surrogate pair is assigned + * an advance of 0. Thus the number of returned advances is + * always equal to count, not to the number of unicode codepoints + * represented by the run. + * + *

In the case of conjuncts or combining marks, the total + * advance is assigned to the first logical character, and the + * following characters are assigned an advance of 0. + * + *

This generates the sum of the advances of glyphs for + * characters in a reordered cluster as the width of the first + * logical character in the cluster, and 0 for the widths of all + * other characters in the cluster. In effect, such clusters are + * treated like conjuncts. + * + *

The shaping bounds limit the amount of context available + * outside start and end that can be used for shaping analysis. + * These bounds typically reflect changes in bidi level or font + * metrics across which shaping does not occur. + * + * @param text the text to measure. Cannot be null. + * @param start the index of the first character to measure + * @param end the index past the last character to measure + * @param contextStart the index of the first character to use for shaping context, + * must be <= start + * @param contextEnd the index past the last character to use for shaping context, + * must be >= end + * @param flags the flags to control the advances, either {@link #DIRECTION_LTR} + * or {@link #DIRECTION_RTL} + * @param advances array to receive the advances, must have room for all advances, + * can be null if only total advance is needed + * @param advancesIndex the position in advances at which to put the + * advance corresponding to the character at start + * @param reserved int reserved value + * @return the total advance + * + * @hide + */ + public float getTextRunAdvances(String text, int start, int end, int contextStart, + int contextEnd, int flags, float[] advances, int advancesIndex, int reserved) { if (text == null) { throw new IllegalArgumentException("text cannot be null"); } + if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) { + throw new IllegalArgumentException("unknown flags value: " + flags); + } if ((start | end | contextStart | contextEnd | advancesIndex | (end - start) | (start - contextStart) | (contextEnd - end) | (text.length() - contextEnd) @@ -1723,13 +1946,13 @@ public class Paint { if (!mHasCompatScaling) { return native_getTextRunAdvances(mNativePaint, text, start, end, - contextStart, contextEnd, advances, advancesIndex); + contextStart, contextEnd, flags, advances, advancesIndex, reserved); } final float oldSize = getTextSize(); setTextSize(oldSize * mCompatScaling); float totalAdvance = native_getTextRunAdvances(mNativePaint, text, start, end, - contextStart, contextEnd, advances, advancesIndex); + contextStart, contextEnd, flags, advances, advancesIndex, reserved); setTextSize(oldSize); if (advances != null) { @@ -1758,6 +1981,7 @@ public class Paint { * @param text the text * @param contextStart the start of the context * @param contextLength the length of the context + * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} * @param offset the cursor position to move from * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER}, * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE}, @@ -1766,7 +1990,7 @@ public class Paint { * @hide */ public int getTextRunCursor(char[] text, int contextStart, int contextLength, - int offset, int cursorOpt) { + int flags, int offset, int cursorOpt) { int contextEnd = contextStart + contextLength; if (((contextStart | contextEnd | offset | (contextEnd - contextStart) | (offset - contextStart) | (contextEnd - offset) @@ -1776,7 +2000,7 @@ public class Paint { } return native_getTextRunCursor(mNativePaint, text, - contextStart, contextLength, offset, cursorOpt); + contextStart, contextLength, flags, offset, cursorOpt); } /** @@ -1797,6 +2021,7 @@ public class Paint { * @param text the text * @param contextStart the start of the context * @param contextEnd the end of the context + * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} * @param offset the cursor position to move from * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER}, * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE}, @@ -1805,22 +2030,22 @@ public class Paint { * @hide */ public int getTextRunCursor(CharSequence text, int contextStart, - int contextEnd, int offset, int cursorOpt) { + int contextEnd, int flags, int offset, int cursorOpt) { if (text instanceof String || text instanceof SpannedString || text instanceof SpannableString) { return getTextRunCursor(text.toString(), contextStart, contextEnd, - offset, cursorOpt); + flags, offset, cursorOpt); } if (text instanceof GraphicsOperations) { return ((GraphicsOperations) text).getTextRunCursor( - contextStart, contextEnd, offset, cursorOpt, this); + contextStart, contextEnd, flags, offset, cursorOpt, this); } int contextLen = contextEnd - contextStart; char[] buf = TemporaryBuffer.obtain(contextLen); TextUtils.getChars(text, contextStart, contextEnd, buf, 0); - int result = getTextRunCursor(buf, 0, contextLen, offset - contextStart, cursorOpt); + int result = getTextRunCursor(buf, 0, contextLen, flags, offset - contextStart, cursorOpt); TemporaryBuffer.recycle(buf); return result; } @@ -1843,6 +2068,7 @@ public class Paint { * @param text the text * @param contextStart the start of the context * @param contextEnd the end of the context + * @param flags either {@link #DIRECTION_RTL} or {@link #DIRECTION_LTR} * @param offset the cursor position to move from * @param cursorOpt how to move the cursor, one of {@link #CURSOR_AFTER}, * {@link #CURSOR_AT_OR_AFTER}, {@link #CURSOR_BEFORE}, @@ -1860,7 +2086,7 @@ public class Paint { } return native_getTextRunCursor(mNativePaint, text, - contextStart, contextEnd, offset, cursorOpt); + contextStart, contextEnd, flags, offset, cursorOpt); } /** @@ -1881,7 +2107,7 @@ public class Paint { if ((index | count) < 0 || index + count > text.length) { throw new ArrayIndexOutOfBoundsException(); } - native_getTextPath(mNativePaint, text, index, count, x, y, + native_getTextPath(mNativePaint, mBidiFlags, text, index, count, x, y, path.ni()); } @@ -1903,7 +2129,7 @@ public class Paint { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } - native_getTextPath(mNativePaint, text, start, end, x, y, + native_getTextPath(mNativePaint, mBidiFlags, text, start, end, x, y, path.ni()); } @@ -1995,22 +2221,26 @@ public class Paint { private static native int native_getTextWidths(int native_object, String text, int start, int end, float[] widths); + private static native int native_getTextGlyphs(int native_object, + String text, int start, int end, int contextStart, int contextEnd, + int flags, char[] glyphs); + private static native float native_getTextRunAdvances(int native_object, char[] text, int index, int count, int contextIndex, int contextCount, - float[] advances, int advancesIndex); + int flags, float[] advances, int advancesIndex, int reserved); private static native float native_getTextRunAdvances(int native_object, String text, int start, int end, int contextStart, int contextEnd, - float[] advances, int advancesIndex); + int flags, float[] advances, int advancesIndex, int reserved); private native int native_getTextRunCursor(int native_object, char[] text, - int contextStart, int contextLength, int offset, int cursorOpt); + int contextStart, int contextLength, int flags, int offset, int cursorOpt); private native int native_getTextRunCursor(int native_object, String text, - int contextStart, int contextEnd, int offset, int cursorOpt); + int contextStart, int contextEnd, int flags, int offset, int cursorOpt); - private static native void native_getTextPath(int native_object, char[] text, - int index, int count, float x, float y, int path); - private static native void native_getTextPath(int native_object, String text, - int start, int end, float x, float y, int path); + private static native void native_getTextPath(int native_object, int bidiFlags, + char[] text, int index, int count, float x, float y, int path); + private static native void native_getTextPath(int native_object, int bidiFlags, + String text, int start, int end, float x, float y, int path); private static native void nativeGetStringBounds(int nativePaint, String text, int start, int end, Rect bounds); private static native void nativeGetCharArrayBounds(int nativePaint, diff --git a/tests/BiDiTests/res/layout/canvas.xml b/tests/BiDiTests/res/layout/canvas.xml new file mode 100644 index 0000000000000..0319a83b03825 --- /dev/null +++ b/tests/BiDiTests/res/layout/canvas.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java index b88a885efa437..209597e27de8a 100644 --- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java @@ -101,6 +101,7 @@ public class BiDiTestActivity extends Activity { addItem(result, "Basic", BiDiTestBasic.class, R.id.basic); + addItem(result, "Canvas", BiDiTestCanvas.class, R.id.canvas); addItem(result, "Canvas2", BiDiTestCanvas2.class, R.id.canvas2); addItem(result, "TextView LTR", BiDiTestTextViewLtr.class, R.id.textview_ltr); diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestCanvas.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestCanvas.java new file mode 100644 index 0000000000000..33ed731749e29 --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestCanvas.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011 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. + */ + +package com.android.bidi; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.SeekBar; + +import static com.android.bidi.BiDiTestConstants.FONT_MAX_SIZE; +import static com.android.bidi.BiDiTestConstants.FONT_MIN_SIZE; + +public class BiDiTestCanvas extends Fragment { + + static final int INIT_TEXT_SIZE = (FONT_MAX_SIZE - FONT_MIN_SIZE) / 2; + + private BiDiTestView testView; + private SeekBar textSizeSeekBar; + private View currentView; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + currentView = inflater.inflate(R.layout.canvas, container, false); + return currentView; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + testView = (BiDiTestView) currentView.findViewById(R.id.testview); + testView.setCurrentTextSize(INIT_TEXT_SIZE); + + textSizeSeekBar = (SeekBar) currentView.findViewById(R.id.seekbar); + textSizeSeekBar.setProgress(INIT_TEXT_SIZE); + textSizeSeekBar.setMax(FONT_MAX_SIZE - FONT_MIN_SIZE); + + textSizeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + testView.setCurrentTextSize(FONT_MIN_SIZE + progress); + } + + public void onStartTrackingTouch(SeekBar seekBar) { + } + + public void onStopTrackingTouch(SeekBar seekBar) { + } + }); + } +} diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java new file mode 100644 index 0000000000000..0b1974a5b11dc --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2011 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. + */ + +package com.android.bidi; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +public class BiDiTestView extends View { + + private static final String TAG = "BiDiTestView"; + + private static final int BORDER_PADDING = 4; + private static final int TEXT_PADDING = 16; + private static final int TEXT_SIZE = 16; + private static final int ORIGIN = 80; + + private static final float DEFAULT_ITALIC_SKEW_X = -0.25f; + + private Rect rect = new Rect(); + + private String NORMAL_TEXT; + private String NORMAL_LONG_TEXT; + private String NORMAL_LONG_TEXT_2; + private String NORMAL_LONG_TEXT_3; + private String ITALIC_TEXT; + private String BOLD_TEXT; + private String BOLD_ITALIC_TEXT; + private String ARABIC_TEXT; + private String CHINESE_TEXT; + private String MIXED_TEXT_1; + private String HEBREW_TEXT; + private String RTL_TEXT; + private String THAI_TEXT; + + private int currentTextSize; + + public BiDiTestView(Context context) { + super(context); + init(context); + } + + public BiDiTestView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public BiDiTestView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context); + } + + private void init(Context context) { + NORMAL_TEXT = context.getString(R.string.normal_text); + NORMAL_LONG_TEXT = context.getString(R.string.normal_long_text); + NORMAL_LONG_TEXT_2 = context.getString(R.string.normal_long_text_2); + NORMAL_LONG_TEXT_3 = context.getString(R.string.normal_long_text_3); + ITALIC_TEXT = context.getString(R.string.italic_text); + BOLD_TEXT = context.getString(R.string.bold_text); + BOLD_ITALIC_TEXT = context.getString(R.string.bold_italic_text); + ARABIC_TEXT = context.getString(R.string.arabic_text); + CHINESE_TEXT = context.getString(R.string.chinese_text); + MIXED_TEXT_1 = context.getString(R.string.mixed_text_1); + HEBREW_TEXT = context.getString(R.string.hebrew_text); + RTL_TEXT = context.getString(R.string.rtl); + THAI_TEXT = context.getString(R.string.pointer_location); + } + + public void setCurrentTextSize(int size) { + currentTextSize = size; + invalidate(); + } + + @Override + public void onDraw(Canvas canvas) { + drawInsideRect(canvas, new Paint(), Color.BLACK); + + int deltaX = 0; + + deltaX = testString(canvas, NORMAL_TEXT, ORIGIN, ORIGIN, + false, false, Paint.DIRECTION_LTR, currentTextSize); + + deltaX += testString(canvas, ITALIC_TEXT, ORIGIN + deltaX, ORIGIN, + true, false, Paint.DIRECTION_LTR, currentTextSize); + + deltaX += testString(canvas, BOLD_TEXT, ORIGIN + deltaX, ORIGIN, + false, true, Paint.DIRECTION_LTR, currentTextSize); + + deltaX += testString(canvas, BOLD_ITALIC_TEXT, ORIGIN + deltaX, ORIGIN, + true, true, Paint.DIRECTION_LTR, currentTextSize); + + // Test with a long string + deltaX = testString(canvas, NORMAL_LONG_TEXT, ORIGIN, ORIGIN + 2 * currentTextSize, + false, false, Paint.DIRECTION_LTR, currentTextSize); + + // Test with a long string + deltaX = testString(canvas, NORMAL_LONG_TEXT_2, ORIGIN, ORIGIN + 4 * currentTextSize, + false, false, Paint.DIRECTION_LTR, currentTextSize); + + // Test with a long string + deltaX = testString(canvas, NORMAL_LONG_TEXT_3, ORIGIN, ORIGIN + 6 * currentTextSize, + false, false, Paint.DIRECTION_LTR, currentTextSize); + + // Test Arabic ligature + deltaX = testString(canvas, ARABIC_TEXT, ORIGIN, ORIGIN + 8 * currentTextSize, + false, false, Paint.DIRECTION_RTL, currentTextSize); + + // Test Chinese + deltaX = testString(canvas, CHINESE_TEXT, ORIGIN, ORIGIN + 10 * currentTextSize, + false, false, Paint.DIRECTION_LTR, currentTextSize); + + // Test Mixed (English and Arabic) + deltaX = testString(canvas, MIXED_TEXT_1, ORIGIN, ORIGIN + 12 * currentTextSize, + false, false, Paint.DIRECTION_LTR, currentTextSize); + + // Test Hebrew + deltaX = testString(canvas, RTL_TEXT, ORIGIN, ORIGIN + 14 * currentTextSize, + false, false, Paint.DIRECTION_RTL, currentTextSize); + + // Test Thai + deltaX = testString(canvas, THAI_TEXT, ORIGIN, ORIGIN + 16 * currentTextSize, + false, false, Paint.DIRECTION_LTR, currentTextSize); + } + + private int testString(Canvas canvas, String text, int x, int y, + boolean isItalic, boolean isBold, int dir, int textSize) { + + TextPaint paint = new TextPaint(); + paint.setAntiAlias(true); + + // Set paint properties + boolean oldFakeBold = paint.isFakeBoldText(); + paint.setFakeBoldText(isBold); + + float oldTextSkewX = paint.getTextSkewX(); + if (isItalic) { + paint.setTextSkewX(DEFAULT_ITALIC_SKEW_X); + } + + paint.setTextSize(textSize); + paint.setColor(Color.WHITE); + canvas.drawText(text, x, y, paint); + + int length = text.length(); + float[] advances = new float[length]; + float textWidthHB = paint.getTextRunAdvances(text, 0, length, 0, length, dir, advances, 0); + setPaintDir(paint, dir); + float textWidthICU = paint.getTextRunAdvances(text, 0, length, 0, length, dir, advances, 0, + 1 /* use ICU */); + + logAdvances(text, textWidthHB, textWidthICU, advances); + drawMetricsAroundText(canvas, x, y, textWidthHB, textWidthICU, textSize, Color.RED, Color.GREEN); + + // Restore old paint properties + paint.setFakeBoldText(oldFakeBold); + paint.setTextSkewX(oldTextSkewX); + + return (int) Math.ceil(textWidthHB) + TEXT_PADDING; + } + + private void setPaintDir(Paint paint, int dir) { + Log.v(TAG, "Setting Paint dir=" + dir); + paint.setBidiFlags(dir); + } + + private void drawInsideRect(Canvas canvas, Paint paint, int color) { + paint.setColor(color); + int width = getWidth(); + int height = getHeight(); + rect.set(BORDER_PADDING, BORDER_PADDING, width - BORDER_PADDING, height - BORDER_PADDING); + canvas.drawRect(rect, paint); + } + + private void drawMetricsAroundText(Canvas canvas, int x, int y, float textWidthHB, + float textWidthICU, int textSize, int color, int colorICU) { + Paint paint = new Paint(); + paint.setColor(color); + canvas.drawLine(x, y - textSize, x, y + 8, paint); + canvas.drawLine(x, y + 8, x + textWidthHB, y + 8, paint); + canvas.drawLine(x + textWidthHB, y - textSize, x + textWidthHB, y + 8, paint); + paint.setColor(colorICU); + canvas.drawLine(x + textWidthICU, y - textSize, x + textWidthICU, y + 8, paint); + } + + private void logAdvances(String text, float textWidth, float textWidthICU, float[] advances) { + Log.v(TAG, "Advances for text: " + text + " total= " + textWidth + " - totalICU= " + textWidthICU); +// int length = advances.length; +// for(int n=0; n