Support bidi/shaping for getTextPath
Move layout-related code into separate class since it's needed by both canvas and paint. Change-Id: Iba89a1d94d7cca650255ffa3cbc952b988a51b54
This commit is contained in:
@@ -105,6 +105,7 @@ LOCAL_SRC_FILES:= \
|
||||
android/graphics/Rasterizer.cpp \
|
||||
android/graphics/Region.cpp \
|
||||
android/graphics/Shader.cpp \
|
||||
android/graphics/TextLayout.cpp \
|
||||
android/graphics/Typeface.cpp \
|
||||
android/graphics/Xfermode.cpp \
|
||||
android/graphics/YuvToJpegEncoder.cpp \
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
#include "SkBoundaryPatch.h"
|
||||
#include "SkMeshUtils.h"
|
||||
|
||||
#include "TextLayout.h"
|
||||
|
||||
#include "unicode/ubidi.h"
|
||||
#include "unicode/ushape.h"
|
||||
|
||||
@@ -57,24 +59,6 @@ namespace android {
|
||||
class SkCanvasGlue {
|
||||
public:
|
||||
|
||||
enum {
|
||||
kDirection_LTR = 0,
|
||||
kDirection_RTL = 1
|
||||
};
|
||||
|
||||
enum {
|
||||
kDirection_Mask = 0x1
|
||||
};
|
||||
|
||||
enum {
|
||||
kBidi_LTR = 0,
|
||||
kBidi_RTL = 1,
|
||||
kBidi_Default_LTR = 2,
|
||||
kBidi_Default_RTL = 3,
|
||||
kBidi_Force_LTR = 4,
|
||||
kBidi_Force_RTL = 5
|
||||
};
|
||||
|
||||
static void finalizer(JNIEnv* env, jobject clazz, SkCanvas* canvas) {
|
||||
canvas->unref();
|
||||
}
|
||||
@@ -767,192 +751,12 @@ public:
|
||||
indices, indexCount, *paint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Character-based Arabic shaping.
|
||||
*
|
||||
* We'll use harfbuzz and glyph-based shaping instead once we're set up for it.
|
||||
*
|
||||
* @context the text context
|
||||
* @start the start of the text to render
|
||||
* @count the length of the text to render, start + count must be <= contextCount
|
||||
* @contextCount the length of the context
|
||||
* @shaped where to put the shaped text, must have capacity for count uchars
|
||||
* @return the length of the shaped text, or -1 if error
|
||||
*/
|
||||
static int shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
|
||||
jchar* shaped, UErrorCode &status) {
|
||||
jchar buffer[contextCount];
|
||||
|
||||
// Use fixed length since we need to keep start and count valid
|
||||
u_shapeArabic(context, contextCount, buffer, contextCount,
|
||||
U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
|
||||
U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
|
||||
U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
|
||||
|
||||
if (U_SUCCESS(status)) {
|
||||
// trim out 0xffff following ligatures, if any
|
||||
int end = 0;
|
||||
for (int i = start, e = start + count; i < e; ++i) {
|
||||
if (buffer[i] != 0xffff) {
|
||||
buffer[end++] = buffer[i];
|
||||
}
|
||||
}
|
||||
count = end;
|
||||
// LOG(LOG_INFO, "CSRTL", "start %d count %d ccount %d\n", start, count, contextCount);
|
||||
ubidi_writeReverse(buffer, count, shaped, count, UBIDI_DO_MIRRORING | UBIDI_OUTPUT_REVERSE
|
||||
| UBIDI_KEEP_BASE_COMBINING, &status);
|
||||
if (U_SUCCESS(status)) {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic character-based layout supporting rtl and arabic shaping.
|
||||
* Runs bidi on the text and generates a reordered, shaped line in buffer, returning
|
||||
* the length.
|
||||
* @text the text
|
||||
* @len the length of the text in uchars
|
||||
* @dir receives the resolved paragraph direction
|
||||
* @buffer the buffer to receive the reordered, shaped line. Must have capacity of
|
||||
* at least len jchars.
|
||||
* @flags line bidi flags
|
||||
* @return the length of the reordered, shaped line, or -1 if error
|
||||
*/
|
||||
static jint layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
|
||||
UErrorCode &status) {
|
||||
static int RTL_OPTS = UBIDI_DO_MIRRORING | UBIDI_KEEP_BASE_COMBINING |
|
||||
UBIDI_REMOVE_BIDI_CONTROLS | UBIDI_OUTPUT_REVERSE;
|
||||
|
||||
UBiDiLevel bidiReq = 0;
|
||||
switch (flags) {
|
||||
case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
|
||||
case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
|
||||
case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
|
||||
case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
|
||||
case kBidi_Force_LTR: memcpy(buffer, text, len * sizeof(jchar)); return len;
|
||||
case kBidi_Force_RTL: return shapeRtlText(text, 0, len, len, buffer, status);
|
||||
}
|
||||
|
||||
int32_t result = -1;
|
||||
|
||||
UBiDi* bidi = ubidi_open();
|
||||
if (bidi) {
|
||||
ubidi_setPara(bidi, text, len, bidiReq, NULL, &status);
|
||||
if (U_SUCCESS(status)) {
|
||||
dir = ubidi_getParaLevel(bidi) & 0x1; // 0 if ltr, 1 if rtl
|
||||
|
||||
int rc = ubidi_countRuns(bidi, &status);
|
||||
if (U_SUCCESS(status)) {
|
||||
// LOG(LOG_INFO, "LAYOUT", "para bidiReq=%d dir=%d rc=%d\n", bidiReq, dir, rc);
|
||||
|
||||
int32_t slen = 0;
|
||||
for (int i = 0; i < rc; ++i) {
|
||||
int32_t start;
|
||||
int32_t length;
|
||||
UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &start, &length);
|
||||
// LOG(LOG_INFO, "LAYOUT", " [%2d] runDir=%d start=%3d len=%3d\n", i, runDir, start, length);
|
||||
if (runDir == UBIDI_RTL) {
|
||||
slen += shapeRtlText(text + start, 0, length, length, buffer + slen, status);
|
||||
} else {
|
||||
memcpy(buffer + slen, text + start, length * sizeof(jchar));
|
||||
slen += length;
|
||||
}
|
||||
}
|
||||
if (U_SUCCESS(status)) {
|
||||
result = slen;
|
||||
}
|
||||
}
|
||||
}
|
||||
ubidi_close(bidi);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Returns true if we might need layout. If bidiFlags force LTR, assume no layout, if
|
||||
// bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
|
||||
// looking for a character >= the first RTL character in unicode and assume we do if
|
||||
// we find one.
|
||||
static bool needsLayout(const jchar* text, jint len, jint bidiFlags) {
|
||||
if (bidiFlags == kBidi_Force_LTR) {
|
||||
return false;
|
||||
}
|
||||
if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) ||
|
||||
bidiFlags == kBidi_Force_RTL) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < len; ++i) {
|
||||
if (text[i] >= 0x0590) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Draws a paragraph of text on a single line, running bidi and shaping
|
||||
static void drawText(JNIEnv* env, SkCanvas* canvas, const jchar* text, jsize len,
|
||||
jfloat x, jfloat y, int bidiFlags, SkPaint* paint) {
|
||||
|
||||
SkScalar x_ = SkFloatToScalar(x);
|
||||
SkScalar y_ = SkFloatToScalar(y);
|
||||
|
||||
SkPaint::Align horiz = paint->getTextAlign();
|
||||
|
||||
const jchar *workText = text;
|
||||
jchar *buffer = NULL;
|
||||
int dir = kDirection_LTR;
|
||||
if (needsLayout(text, len, bidiFlags)) {
|
||||
buffer =(jchar *) malloc(len * sizeof(jchar));
|
||||
if (!buffer) {
|
||||
return;
|
||||
}
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
len = layoutLine(text, len, bidiFlags, dir, buffer, status); // might change len, dir
|
||||
if (!U_SUCCESS(status)) {
|
||||
LOG(LOG_WARN, "LAYOUT", "drawText error %d\n", status);
|
||||
free(buffer);
|
||||
return; // can't render
|
||||
}
|
||||
|
||||
workText = buffer; // use the shaped text
|
||||
}
|
||||
|
||||
bool trimLeft = false;
|
||||
bool trimRight = false;
|
||||
|
||||
switch (horiz) {
|
||||
case SkPaint::kLeft_Align: trimLeft = dir & kDirection_Mask; break;
|
||||
case SkPaint::kCenter_Align: trimLeft = trimRight = true; break;
|
||||
case SkPaint::kRight_Align: trimRight = !(dir & kDirection_Mask);
|
||||
default: break;
|
||||
}
|
||||
const jchar* workLimit = workText + len;
|
||||
|
||||
if (trimLeft) {
|
||||
while (workText < workLimit && *workText == ' ') {
|
||||
++workText;
|
||||
}
|
||||
}
|
||||
if (trimRight) {
|
||||
while (workLimit > workText && *(workLimit - 1) == ' ') {
|
||||
--workLimit;
|
||||
}
|
||||
}
|
||||
int32_t workBytes = (workLimit - workText) << 1;
|
||||
|
||||
canvas->drawText(workText, workBytes, x_, y_, *paint);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
static void drawText___CIIFFIPaint(JNIEnv* env, jobject, SkCanvas* canvas,
|
||||
jcharArray text, int index, int count,
|
||||
jfloat x, jfloat y, int flags, SkPaint* paint) {
|
||||
jchar* textArray = env->GetCharArrayElements(text, NULL);
|
||||
drawText(env, canvas, textArray + index, count, x, y, flags, paint);
|
||||
TextLayout::drawText(paint, textArray + index, count, flags, x, y, canvas);
|
||||
env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
|
||||
}
|
||||
|
||||
@@ -961,41 +765,18 @@ public:
|
||||
int start, int end,
|
||||
jfloat x, jfloat y, int flags, SkPaint* paint) {
|
||||
const jchar* textArray = env->GetStringChars(text, NULL);
|
||||
drawText(env, canvas, textArray + start, end - start, x, y, flags, paint);
|
||||
TextLayout::drawText(paint, textArray + start, end - start, flags, x, y, canvas);
|
||||
env->ReleaseStringChars(text, textArray);
|
||||
}
|
||||
|
||||
// Draws a unidirectional run of text.
|
||||
static void drawTextRun(JNIEnv* env, SkCanvas* canvas, const jchar* chars,
|
||||
jint start, jint count, jint contextCount,
|
||||
jfloat x, jfloat y, int dirFlags, SkPaint* paint) {
|
||||
|
||||
SkScalar x_ = SkFloatToScalar(x);
|
||||
SkScalar y_ = SkFloatToScalar(y);
|
||||
|
||||
uint8_t rtl = dirFlags & 0x1;
|
||||
if (rtl) {
|
||||
SkAutoSTMalloc<80, jchar> buffer(contextCount);
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
count = shapeRtlText(chars, start, count, contextCount, buffer.get(), status);
|
||||
if (U_SUCCESS(status)) {
|
||||
canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
|
||||
} else {
|
||||
LOG(LOG_WARN, "LAYOUT", "drawTextRun error %d\n", status);
|
||||
}
|
||||
} else {
|
||||
canvas->drawText(chars + start, count << 1, x_, y_, *paint);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawTextRun___CIIIIFFIPaint(
|
||||
JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index,
|
||||
int count, int contextIndex, int contextCount,
|
||||
jfloat x, jfloat y, int dirFlags, SkPaint* paint) {
|
||||
|
||||
jchar* chars = env->GetCharArrayElements(text, NULL);
|
||||
drawTextRun(env, canvas, chars + contextIndex, index - contextIndex,
|
||||
count, contextCount, x, y, dirFlags, paint);
|
||||
TextLayout::drawTextRun(paint, chars + contextIndex, index - contextIndex,
|
||||
count, contextCount, dirFlags, x, y, canvas);
|
||||
env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
|
||||
}
|
||||
|
||||
@@ -1007,8 +788,8 @@ public:
|
||||
jint count = end - start;
|
||||
jint contextCount = contextEnd - contextStart;
|
||||
const jchar* chars = env->GetStringChars(text, NULL);
|
||||
drawTextRun(env, canvas, chars + contextStart, start - contextStart,
|
||||
count, contextCount, x, y, dirFlags, paint);
|
||||
TextLayout::drawTextRun(paint, chars + contextStart, start - contextStart,
|
||||
count, contextCount, dirFlags, x, y, canvas);
|
||||
env->ReleaseStringChars(text, chars);
|
||||
}
|
||||
|
||||
@@ -1059,31 +840,13 @@ public:
|
||||
delete[] posPtr;
|
||||
}
|
||||
|
||||
static void drawTextOnPath(JNIEnv *env, SkCanvas* canvas, const jchar* text, int count,
|
||||
int bidiFlags, SkPath* path, jfloat hOffset, jfloat vOffset, SkPaint* paint) {
|
||||
|
||||
if (!needsLayout(text, count, bidiFlags)) {
|
||||
canvas->drawTextOnPathHV(text, count << 1, *path,
|
||||
SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
|
||||
return;
|
||||
}
|
||||
|
||||
SkAutoSTMalloc<80, jchar> buffer(count);
|
||||
int dir = kDirection_LTR;
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
count = layoutLine(text, count, bidiFlags, dir, buffer.get(), status);
|
||||
if (U_SUCCESS(status)) {
|
||||
canvas->drawTextOnPathHV(buffer.get(), count << 1, *path,
|
||||
SkFloatToScalar(hOffset), SkFloatToScalar(vOffset), *paint);
|
||||
}
|
||||
}
|
||||
|
||||
static void drawTextOnPath___CIIPathFFPaint(JNIEnv* env, jobject,
|
||||
SkCanvas* canvas, jcharArray text, int index, int count,
|
||||
SkPath* path, jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {
|
||||
|
||||
jchar* textArray = env->GetCharArrayElements(text, NULL);
|
||||
drawTextOnPath(env, canvas, textArray, count, bidiFlags, path, hOffset, vOffset, paint);
|
||||
TextLayout::drawTextOnPath(paint, textArray, count, bidiFlags, hOffset, vOffset,
|
||||
path, canvas);
|
||||
env->ReleaseCharArrayElements(text, textArray, 0);
|
||||
}
|
||||
|
||||
@@ -1092,7 +855,8 @@ public:
|
||||
jfloat hOffset, jfloat vOffset, jint bidiFlags, SkPaint* paint) {
|
||||
const jchar* text_ = env->GetStringChars(text, NULL);
|
||||
int count = env->GetStringLength(text);
|
||||
drawTextOnPath(env, canvas, text_, count, bidiFlags, path, hOffset, vOffset, paint);
|
||||
TextLayout::drawTextOnPath(paint, text_, count, bidiFlags, hOffset, vOffset,
|
||||
path, canvas);
|
||||
env->ReleaseStringChars(text, text_);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "SkTypeface.h"
|
||||
#include "SkXfermode.h"
|
||||
#include "unicode/ushape.h"
|
||||
#include "TextLayout.h"
|
||||
|
||||
// temporary for debugging
|
||||
#include <utils/Log.h>
|
||||
@@ -403,56 +404,14 @@ public:
|
||||
return count;
|
||||
}
|
||||
|
||||
static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, const jchar *text, jint start, jint count, jint contextCount, jint flags,
|
||||
static jfloat doTextRunAdvances(JNIEnv *env, SkPaint *paint, const jchar *text,
|
||||
jint start, jint count, jint contextCount, jint flags,
|
||||
jfloatArray advances, jint advancesIndex) {
|
||||
jfloat advancesArray[count];
|
||||
jchar buffer[contextCount];
|
||||
jfloat totalAdvance;
|
||||
|
||||
SkScalar* scalarArray = (SkScalar *)advancesArray;
|
||||
jfloat totalAdvance = 0;
|
||||
|
||||
// this is where we'd call harfbuzz
|
||||
// for now we just use ushape.c
|
||||
|
||||
int widths;
|
||||
if (flags & 0x1) { // rtl, call arabic shaping in case
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
// Use fixed length since we need to keep start and count valid
|
||||
u_shapeArabic(text, contextCount, buffer, contextCount,
|
||||
U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
|
||||
U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
|
||||
U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
|
||||
// we shouldn't fail unless there's an out of memory condition,
|
||||
// in which case we're hosed anyway
|
||||
for (int i = start, e = i + count; i < e; ++i) {
|
||||
if (buffer[i] == 0xffff) {
|
||||
buffer[i] = 0x200b; // zero-width-space for skia
|
||||
}
|
||||
}
|
||||
widths = paint->getTextWidths(buffer + start, count << 1, scalarArray);
|
||||
} else {
|
||||
widths = paint->getTextWidths(text + start, count << 1, scalarArray);
|
||||
}
|
||||
|
||||
if (widths < count) {
|
||||
// Skia operates on code points, not code units, so surrogate pairs return only
|
||||
// one value. Expand the result so we have one value per UTF-16 code unit.
|
||||
|
||||
// Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
|
||||
// leaving the remaining widths zero. Not nice.
|
||||
const jchar *chars = text + start;
|
||||
for (int i = 0, p = 0; i < widths; ++i) {
|
||||
totalAdvance += advancesArray[p++] = SkScalarToFloat(scalarArray[i]);
|
||||
if (p < count && chars[p] >= 0xdc00 && chars[p] < 0xe000 &&
|
||||
chars[p-1] >= 0xd800 && chars[p-1] < 0xdc00) {
|
||||
advancesArray[p++] = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < count; i++) {
|
||||
totalAdvance += advancesArray[i] = SkScalarToFloat(scalarArray[i]);
|
||||
}
|
||||
}
|
||||
TextLayout::getTextRunAdvances(paint, text, start, count, contextCount, flags,
|
||||
advancesArray, totalAdvance);
|
||||
|
||||
if (advances != NULL) {
|
||||
env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray);
|
||||
@@ -580,19 +539,25 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
static void getTextPath___CIIFFPath(JNIEnv* env, jobject clazz, SkPaint* paint, jcharArray text, int index, int count, jfloat x, jfloat y, SkPath* path) {
|
||||
const jchar* textArray = env->GetCharArrayElements(text, NULL);
|
||||
paint->getTextPath(textArray + index, count << 1, SkFloatToScalar(x), SkFloatToScalar(y), path);
|
||||
env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray),
|
||||
JNI_ABORT);
|
||||
static void getTextPath(JNIEnv* env, SkPaint* paint, const jchar* text, jint count,
|
||||
jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
|
||||
TextLayout::getTextPath(paint, text, count, bidiFlags, x, y, path);
|
||||
}
|
||||
|
||||
static void getTextPath__StringIIFFPath(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text, int start, int end, jfloat x, jfloat y, SkPath* path) {
|
||||
|
||||
static void getTextPath___C(JNIEnv* env, jobject clazz, SkPaint* paint, jint bidiFlags,
|
||||
jcharArray text, int index, int count, jfloat x, jfloat y, SkPath* path) {
|
||||
const jchar* textArray = env->GetCharArrayElements(text, NULL);
|
||||
getTextPath(env, paint, textArray + index, count, bidiFlags, x, y, path);
|
||||
env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
|
||||
}
|
||||
|
||||
static void getTextPath__String(JNIEnv* env, jobject clazz, SkPaint* paint, jint bidiFlags,
|
||||
jstring text, int start, int end, jfloat x, jfloat y, SkPath* path) {
|
||||
const jchar* textArray = env->GetStringChars(text, NULL);
|
||||
paint->getTextPath(textArray + start, (end - start) << 1, SkFloatToScalar(x), SkFloatToScalar(y), path);
|
||||
getTextPath(env, paint, textArray + start, end - start, bidiFlags, x, y, path);
|
||||
env->ReleaseStringChars(text, textArray);
|
||||
}
|
||||
|
||||
|
||||
static void setShadowLayer(JNIEnv* env, jobject jpaint, jfloat radius,
|
||||
jfloat dx, jfloat dy, int color) {
|
||||
NPE_CHECK_RETURN_VOID(env, jpaint);
|
||||
@@ -767,8 +732,8 @@ static JNINativeMethod methods[] = {
|
||||
{"native_getTextRunCursor", "(I[CIIIII)I", (void*) SkPaintGlue::getTextRunCursor___C},
|
||||
{"native_getTextRunCursor", "(ILjava/lang/String;IIIII)I",
|
||||
(void*) SkPaintGlue::getTextRunCursor__String},
|
||||
{"native_getTextPath","(I[CIIFFI)V", (void*) SkPaintGlue::getTextPath___CIIFFPath},
|
||||
{"native_getTextPath","(ILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__StringIIFFPath},
|
||||
{"native_getTextPath","(II[CIIFFI)V", (void*) SkPaintGlue::getTextPath___C},
|
||||
{"native_getTextPath","(IILjava/lang/String;IIFFI)V", (void*) SkPaintGlue::getTextPath__String},
|
||||
{"nativeGetStringBounds", "(ILjava/lang/String;IILandroid/graphics/Rect;)V",
|
||||
(void*) SkPaintGlue::getStringBounds },
|
||||
{"nativeGetCharArrayBounds", "(I[CIILandroid/graphics/Rect;)V",
|
||||
|
||||
324
core/jni/android/graphics/TextLayout.cpp
Normal file
324
core/jni/android/graphics/TextLayout.cpp
Normal file
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "TextLayout.h"
|
||||
|
||||
#include <android_runtime/AndroidRuntime.h>
|
||||
|
||||
#include "SkTemplates.h"
|
||||
#include "unicode/ubidi.h"
|
||||
#include "unicode/ushape.h"
|
||||
#include <utils/Log.h>
|
||||
|
||||
|
||||
namespace android {
|
||||
// Returns true if we might need layout. If bidiFlags force LTR, assume no layout, if
|
||||
// bidiFlags indicate there probably is RTL, assume we do, otherwise scan the text
|
||||
// looking for a character >= the first RTL character in unicode and assume we do if
|
||||
// we find one.
|
||||
bool TextLayout::needsLayout(const jchar* text, jint len, jint bidiFlags) {
|
||||
if (bidiFlags == kBidi_Force_LTR) {
|
||||
return false;
|
||||
}
|
||||
if ((bidiFlags == kBidi_RTL) || (bidiFlags == kBidi_Default_RTL) ||
|
||||
bidiFlags == kBidi_Force_RTL) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < len; ++i) {
|
||||
if (text[i] >= 0x0590) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Character-based Arabic shaping.
|
||||
*
|
||||
* We'll use harfbuzz and glyph-based shaping instead once we're set up for it.
|
||||
*
|
||||
* @context the text context
|
||||
* @start the start of the text to render
|
||||
* @count the length of the text to render, start + count must be <= contextCount
|
||||
* @contextCount the length of the context
|
||||
* @shaped where to put the shaped text, must have capacity for count uchars
|
||||
* @return the length of the shaped text, or -1 if error
|
||||
*/
|
||||
int TextLayout::shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
|
||||
jchar* shaped, UErrorCode &status) {
|
||||
jchar buffer[contextCount];
|
||||
|
||||
// Use fixed length since we need to keep start and count valid
|
||||
u_shapeArabic(context, contextCount, buffer, contextCount,
|
||||
U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
|
||||
U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
|
||||
U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
|
||||
|
||||
if (U_SUCCESS(status)) {
|
||||
// trim out 0xffff following ligatures, if any
|
||||
int end = 0;
|
||||
for (int i = start, e = start + count; i < e; ++i) {
|
||||
if (buffer[i] != 0xffff) {
|
||||
buffer[end++] = buffer[i];
|
||||
}
|
||||
}
|
||||
count = end;
|
||||
// LOG(LOG_INFO, "CSRTL", "start %d count %d ccount %d\n", start, count, contextCount);
|
||||
ubidi_writeReverse(buffer, count, shaped, count, UBIDI_DO_MIRRORING | UBIDI_OUTPUT_REVERSE
|
||||
| UBIDI_KEEP_BASE_COMBINING, &status);
|
||||
if (U_SUCCESS(status)) {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic character-based layout supporting rtl and arabic shaping.
|
||||
* Runs bidi on the text and generates a reordered, shaped line in buffer, returning
|
||||
* the length.
|
||||
* @text the text
|
||||
* @len the length of the text in uchars
|
||||
* @dir receives the resolved paragraph direction
|
||||
* @buffer the buffer to receive the reordered, shaped line. Must have capacity of
|
||||
* at least len jchars.
|
||||
* @flags line bidi flags
|
||||
* @return the length of the reordered, shaped line, or -1 if error
|
||||
*/
|
||||
jint TextLayout::layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
|
||||
UErrorCode &status) {
|
||||
static const int RTL_OPTS = UBIDI_DO_MIRRORING | UBIDI_KEEP_BASE_COMBINING |
|
||||
UBIDI_REMOVE_BIDI_CONTROLS | UBIDI_OUTPUT_REVERSE;
|
||||
|
||||
UBiDiLevel bidiReq = 0;
|
||||
switch (flags) {
|
||||
case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
|
||||
case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
|
||||
case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
|
||||
case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
|
||||
case kBidi_Force_LTR: memcpy(buffer, text, len * sizeof(jchar)); return len;
|
||||
case kBidi_Force_RTL: return shapeRtlText(text, 0, len, len, buffer, status);
|
||||
}
|
||||
|
||||
int32_t result = -1;
|
||||
|
||||
UBiDi* bidi = ubidi_open();
|
||||
if (bidi) {
|
||||
ubidi_setPara(bidi, text, len, bidiReq, NULL, &status);
|
||||
if (U_SUCCESS(status)) {
|
||||
dir = ubidi_getParaLevel(bidi) & 0x1; // 0 if ltr, 1 if rtl
|
||||
|
||||
int rc = ubidi_countRuns(bidi, &status);
|
||||
if (U_SUCCESS(status)) {
|
||||
// LOG(LOG_INFO, "LAYOUT", "para bidiReq=%d dir=%d rc=%d\n", bidiReq, dir, rc);
|
||||
|
||||
int32_t slen = 0;
|
||||
for (int i = 0; i < rc; ++i) {
|
||||
int32_t start;
|
||||
int32_t length;
|
||||
UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &start, &length);
|
||||
|
||||
if (runDir == UBIDI_RTL) {
|
||||
slen += shapeRtlText(text + start, 0, length, length, buffer + slen, status);
|
||||
} else {
|
||||
memcpy(buffer + slen, text + start, length * sizeof(jchar));
|
||||
slen += length;
|
||||
}
|
||||
}
|
||||
if (U_SUCCESS(status)) {
|
||||
result = slen;
|
||||
}
|
||||
}
|
||||
}
|
||||
ubidi_close(bidi);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Draws or gets the path of a paragraph of text on a single line, running bidi and shaping.
|
||||
// This will draw if canvas is not null, otherwise path must be non-null and it will create
|
||||
// a path representing the text that would have been drawn.
|
||||
void TextLayout::handleText(SkPaint *paint, const jchar* text, jsize len,
|
||||
jint bidiFlags, jfloat x, jfloat y,SkCanvas *canvas, SkPath *path) {
|
||||
|
||||
const jchar *workText = text;
|
||||
jchar *buffer = NULL;
|
||||
int dir = kDirection_LTR;
|
||||
if (needsLayout(text, len, bidiFlags)) {
|
||||
buffer =(jchar *) malloc(len * sizeof(jchar));
|
||||
if (!buffer) {
|
||||
return;
|
||||
}
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
len = layoutLine(text, len, bidiFlags, dir, buffer, status); // might change len, dir
|
||||
if (!U_SUCCESS(status)) {
|
||||
LOG(LOG_WARN, "LAYOUT", "drawText error %d\n", status);
|
||||
free(buffer);
|
||||
return; // can't render
|
||||
}
|
||||
|
||||
workText = buffer; // use the shaped text
|
||||
}
|
||||
|
||||
bool trimLeft = false;
|
||||
bool trimRight = false;
|
||||
|
||||
SkPaint::Align horiz = paint->getTextAlign();
|
||||
switch (horiz) {
|
||||
case SkPaint::kLeft_Align: trimLeft = dir & kDirection_Mask; break;
|
||||
case SkPaint::kCenter_Align: trimLeft = trimRight = true; break;
|
||||
case SkPaint::kRight_Align: trimRight = !(dir & kDirection_Mask);
|
||||
default: break;
|
||||
}
|
||||
const jchar* workLimit = workText + len;
|
||||
|
||||
if (trimLeft) {
|
||||
while (workText < workLimit && *workText == ' ') {
|
||||
++workText;
|
||||
}
|
||||
}
|
||||
if (trimRight) {
|
||||
while (workLimit > workText && *(workLimit - 1) == ' ') {
|
||||
--workLimit;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t workBytes = (workLimit - workText) << 1;
|
||||
SkScalar x_ = SkFloatToScalar(x);
|
||||
SkScalar y_ = SkFloatToScalar(y);
|
||||
if (canvas) {
|
||||
canvas->drawText(workText, workBytes, x_, y_, *paint);
|
||||
} else {
|
||||
paint->getTextPath(workText, workBytes, x_, y_, path);
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
void TextLayout::drawTextRun(SkPaint* paint, const jchar* chars,
|
||||
jint start, jint count, jint contextCount,
|
||||
int dirFlags, jfloat x, jfloat y, SkCanvas* canvas) {
|
||||
|
||||
SkScalar x_ = SkFloatToScalar(x);
|
||||
SkScalar y_ = SkFloatToScalar(y);
|
||||
|
||||
uint8_t rtl = dirFlags & 0x1;
|
||||
if (rtl) {
|
||||
SkAutoSTMalloc<80, jchar> buffer(contextCount);
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
count = shapeRtlText(chars, start, count, contextCount, buffer.get(), status);
|
||||
if (U_SUCCESS(status)) {
|
||||
canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
|
||||
} else {
|
||||
LOG(LOG_WARN, "LAYOUT", "drawTextRun error %d\n", status);
|
||||
}
|
||||
} else {
|
||||
canvas->drawText(chars + start, count << 1, x_, y_, *paint);
|
||||
}
|
||||
}
|
||||
|
||||
void TextLayout::getTextRunAdvances(SkPaint *paint, const jchar *chars, jint start,
|
||||
jint count, jint contextCount, jint dirFlags,
|
||||
jfloat *resultAdvances, jfloat &resultTotalAdvance) {
|
||||
jchar buffer[contextCount];
|
||||
|
||||
SkScalar* scalarArray = (SkScalar *)resultAdvances;
|
||||
resultTotalAdvance = 0;
|
||||
|
||||
// this is where we'd call harfbuzz
|
||||
// for now we just use ushape.c
|
||||
|
||||
int widths;
|
||||
const jchar* text;
|
||||
if (dirFlags & 0x1) { // rtl, call arabic shaping in case
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
// Use fixed length since we need to keep start and count valid
|
||||
u_shapeArabic(chars, contextCount, buffer, contextCount,
|
||||
U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
|
||||
U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
|
||||
U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
|
||||
// we shouldn't fail unless there's an out of memory condition,
|
||||
// in which case we're hosed anyway
|
||||
for (int i = start, e = i + count; i < e; ++i) {
|
||||
if (buffer[i] == 0xffff) {
|
||||
buffer[i] = 0x200b; // zero-width-space for skia
|
||||
}
|
||||
}
|
||||
text = buffer + start;
|
||||
widths = paint->getTextWidths(text, count << 1, scalarArray);
|
||||
} else {
|
||||
text = chars + start;
|
||||
widths = paint->getTextWidths(text, count << 1, scalarArray);
|
||||
}
|
||||
|
||||
if (widths < count) {
|
||||
// Skia operates on code points, not code units, so surrogate pairs return only
|
||||
// one value. Expand the result so we have one value per UTF-16 code unit.
|
||||
|
||||
// Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
|
||||
// leaving the remaining widths zero. Not nice.
|
||||
for (int i = 0, p = 0; i < widths; ++i) {
|
||||
resultTotalAdvance += resultAdvances[p++] = SkScalarToFloat(scalarArray[i]);
|
||||
if (p < count && text[p] >= 0xdc00 && text[p] < 0xe000 &&
|
||||
text[p-1] >= 0xd800 && text[p-1] < 0xdc00) {
|
||||
resultAdvances[p++] = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < count; i++) {
|
||||
resultTotalAdvance += resultAdvances[i] = SkScalarToFloat(scalarArray[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draws a paragraph of text on a single line, running bidi and shaping
|
||||
void TextLayout::drawText(SkPaint* paint, const jchar* text, jsize len,
|
||||
int bidiFlags, jfloat x, jfloat y, SkCanvas* canvas) {
|
||||
|
||||
handleText(paint, text, len, bidiFlags, x, y, canvas, NULL);
|
||||
}
|
||||
|
||||
void TextLayout::getTextPath(SkPaint *paint, const jchar *text, jsize len,
|
||||
jint bidiFlags, jfloat x, jfloat y, SkPath *path) {
|
||||
handleText(paint, text, len, bidiFlags, x, y, NULL, path);
|
||||
}
|
||||
|
||||
|
||||
void TextLayout::drawTextOnPath(SkPaint* paint, const jchar* text, int count,
|
||||
int bidiFlags, jfloat hOffset, jfloat vOffset,
|
||||
SkPath* path, SkCanvas* canvas) {
|
||||
|
||||
SkScalar h_ = SkFloatToScalar(hOffset);
|
||||
SkScalar v_ = SkFloatToScalar(vOffset);
|
||||
|
||||
if (!needsLayout(text, count, bidiFlags)) {
|
||||
canvas->drawTextOnPathHV(text, count << 1, *path, h_, v_, *paint);
|
||||
return;
|
||||
}
|
||||
|
||||
SkAutoSTMalloc<80, jchar> buffer(count);
|
||||
int dir = kDirection_LTR;
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
count = layoutLine(text, count, bidiFlags, dir, buffer.get(), status);
|
||||
if (U_SUCCESS(status)) {
|
||||
canvas->drawTextOnPathHV(buffer.get(), count << 1, *path, h_, v_, *paint);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
77
core/jni/android/graphics/TextLayout.h
Normal file
77
core/jni/android/graphics/TextLayout.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "jni.h"
|
||||
|
||||
#include "SkCanvas.h"
|
||||
#include "SkPaint.h"
|
||||
#include "unicode/utypes.h"
|
||||
|
||||
namespace android {
|
||||
|
||||
class TextLayout {
|
||||
public:
|
||||
|
||||
enum {
|
||||
kDirection_LTR = 0,
|
||||
kDirection_RTL = 1,
|
||||
|
||||
kDirection_Mask = 0x1
|
||||
};
|
||||
|
||||
enum {
|
||||
kBidi_LTR = 0,
|
||||
kBidi_RTL = 1,
|
||||
kBidi_Default_LTR = 2,
|
||||
kBidi_Default_RTL = 3,
|
||||
kBidi_Force_LTR = 4,
|
||||
kBidi_Force_RTL = 5,
|
||||
|
||||
kBidi_Mask = 0x7
|
||||
};
|
||||
|
||||
/*
|
||||
* Draws a unidirectional run of text.
|
||||
*/
|
||||
static void drawTextRun(SkPaint* paint, const jchar* chars,
|
||||
jint start, jint count, jint contextCount,
|
||||
int dirFlags, jfloat x, jfloat y, SkCanvas* canvas);
|
||||
|
||||
static void getTextRunAdvances(SkPaint *paint, const jchar *chars, jint start,
|
||||
jint count, jint contextCount, jint dirFlags,
|
||||
jfloat *resultAdvances, jfloat &resultTotalAdvance);
|
||||
|
||||
static void drawText(SkPaint* paint, const jchar* text, jsize len,
|
||||
jint bidiFlags, jfloat x, jfloat y, SkCanvas* canvas);
|
||||
|
||||
static void getTextPath(SkPaint *paint, const jchar *text, jsize len,
|
||||
jint bidiFlags, jfloat x, jfloat y, SkPath *path);
|
||||
|
||||
static void drawTextOnPath(SkPaint* paint, const jchar* text, jsize len,
|
||||
int bidiFlags, jfloat hOffset, jfloat vOffset,
|
||||
SkPath* path, SkCanvas* canvas);
|
||||
|
||||
private:
|
||||
static bool needsLayout(const jchar* text, jint len, jint bidiFlags);
|
||||
static int shapeRtlText(const jchar* context, jsize start, jsize count, jsize contextCount,
|
||||
jchar* shaped, UErrorCode &status);
|
||||
static jint layoutLine(const jchar* text, jint len, jint flags, int &dir, jchar* buffer,
|
||||
UErrorCode &status);
|
||||
static void handleText(SkPaint *paint, const jchar* text, jsize len,
|
||||
int bidiFlags, jfloat x, jfloat y,SkCanvas *canvas, SkPath *path);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1718,7 +1718,8 @@ public class Paint {
|
||||
if ((index | count) < 0 || index + count > text.length) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
native_getTextPath(mNativePaint, text, index, count, x, y, path.ni());
|
||||
native_getTextPath(mNativePaint, mBidiFlags, text, index, count, x, y,
|
||||
path.ni());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1739,7 +1740,8 @@ public class Paint {
|
||||
if ((start | end | (end - start) | (text.length() - end)) < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
native_getTextPath(mNativePaint, text, start, end, x, y, path.ni());
|
||||
native_getTextPath(mNativePaint, mBidiFlags, text, start, end, x, y,
|
||||
path.ni());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1836,9 +1838,9 @@ public class Paint {
|
||||
private native int native_getTextRunCursor(int native_object, String text,
|
||||
int contextStart, int contextEnd, int flags, int offset, int cursorOpt);
|
||||
|
||||
private static native void native_getTextPath(int native_object,
|
||||
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,
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user