Use Harfbuzz instead of ICU4C for computing advances
- use Harfbuzz shaper for shaping and getting glyphs - add test app for showing result of drawText() and drawGlyphs() - add private API in Canvas and Paint for test app Change-Id: Ia15be216f8636d2d864066e9b7de2f53008c30f6
This commit is contained in:
@@ -92,6 +92,7 @@ LOCAL_SRC_FILES:= \
|
||||
android/graphics/DrawFilter.cpp \
|
||||
android/graphics/CreateJavaOutputStreamAdaptor.cpp \
|
||||
android/graphics/Graphics.cpp \
|
||||
android/graphics/HarfbuzzSkia.cpp \
|
||||
android/graphics/Interpolator.cpp \
|
||||
android/graphics/LayerRasterizer.cpp \
|
||||
android/graphics/MaskFilter.cpp \
|
||||
@@ -172,6 +173,7 @@ LOCAL_C_INCLUDES += \
|
||||
external/icu4c/i18n \
|
||||
external/icu4c/common \
|
||||
external/jpeg \
|
||||
external/harfbuzz/src \
|
||||
frameworks/opt/emoji
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
@@ -204,6 +206,7 @@ LOCAL_SHARED_LIBRARIES := \
|
||||
libjpeg \
|
||||
libnfc_ndef \
|
||||
libusbhost \
|
||||
libharfbuzz \
|
||||
|
||||
ifeq ($(USE_OPENGL_RENDERER),true)
|
||||
LOCAL_SHARED_LIBRARIES += libhwui
|
||||
|
||||
@@ -755,6 +755,27 @@ public:
|
||||
env->ReleaseStringChars(text, textArray);
|
||||
}
|
||||
|
||||
static void drawGlyphs___CIIFFIPaint(JNIEnv* env, jobject, SkCanvas* canvas,
|
||||
jcharArray glyphs, int index, int count,
|
||||
jfloat x, jfloat y, int flags, SkPaint* paint) {
|
||||
jchar* glyphArray = env->GetCharArrayElements(glyphs, NULL);
|
||||
|
||||
// TODO: need to suppress this code after the GL renderer is modified for not
|
||||
// copying the paint
|
||||
|
||||
// Save old text encoding
|
||||
SkPaint::TextEncoding oldEncoding = paint->getTextEncoding();
|
||||
// Define Glyph encoding
|
||||
paint->setTextEncoding(SkPaint::kGlyphID_TextEncoding);
|
||||
|
||||
TextLayout::drawText(paint, glyphArray + index, count, flags, x, y, canvas);
|
||||
|
||||
// Get back old encoding
|
||||
paint->setTextEncoding(oldEncoding);
|
||||
|
||||
env->ReleaseCharArrayElements(glyphs, glyphArray, JNI_ABORT);
|
||||
}
|
||||
|
||||
static void drawTextRun___CIIIIFFIPaint(
|
||||
JNIEnv* env, jobject, SkCanvas* canvas, jcharArray text, int index,
|
||||
int count, int contextIndex, int contextCount,
|
||||
@@ -946,6 +967,8 @@ static JNINativeMethod gCanvasMethods[] = {
|
||||
(void*) SkCanvasGlue::drawText___CIIFFIPaint},
|
||||
{"native_drawText","(ILjava/lang/String;IIFFII)V",
|
||||
(void*) SkCanvasGlue::drawText__StringIIFFIPaint},
|
||||
{"native_drawGlyphs","(I[CIIFFII)V",
|
||||
(void*) SkCanvasGlue::drawGlyphs___CIIFFIPaint},
|
||||
{"native_drawTextRun","(I[CIIIIFFII)V",
|
||||
(void*) SkCanvasGlue::drawTextRun___CIIIIFFIPaint},
|
||||
{"native_drawTextRun","(ILjava/lang/String;IIIIFFII)V",
|
||||
|
||||
239
core/jni/android/graphics/HarfbuzzSkia.cpp
Normal file
239
core/jni/android/graphics/HarfbuzzSkia.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright 2011, The Android Open Source Project
|
||||
* Copyright 2011, Google Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "HarfbuzzSkia.h"
|
||||
|
||||
#include "SkFontHost.h"
|
||||
|
||||
#include "SkPaint.h"
|
||||
#include "SkPath.h"
|
||||
#include "SkPoint.h"
|
||||
#include "SkRect.h"
|
||||
#include "SkTypeface.h"
|
||||
|
||||
extern "C" {
|
||||
#include "harfbuzz-shaper.h"
|
||||
}
|
||||
|
||||
// This file implements the callbacks which Harfbuzz requires by using Skia
|
||||
// calls. See the Harfbuzz source for references about what these callbacks do.
|
||||
|
||||
namespace android {
|
||||
|
||||
static HB_Fixed SkiaScalarToHarfbuzzFixed(SkScalar value)
|
||||
{
|
||||
// HB_Fixed is a 26.6 fixed point format.
|
||||
return value * 64;
|
||||
}
|
||||
|
||||
static void setupPaintWithFontData(SkPaint* paint, FontData* data) {
|
||||
paint->setAntiAlias(true);
|
||||
paint->setSubpixelText(true);
|
||||
paint->setHinting(SkPaint::kSlight_Hinting);
|
||||
paint->setTextSize(SkFloatToScalar(data->textSize));
|
||||
paint->setTypeface(data->typeFace);
|
||||
paint->setFakeBoldText(data->fakeBold);
|
||||
paint->setTextSkewX(data->fakeItalic ? -SK_Scalar1/4 : 0);
|
||||
}
|
||||
|
||||
static HB_Bool stringToGlyphs(HB_Font hbFont, const HB_UChar16* characters, hb_uint32 length,
|
||||
HB_Glyph* glyphs, hb_uint32* glyphsSize, HB_Bool isRTL)
|
||||
{
|
||||
FontData* data = reinterpret_cast<FontData*>(hbFont->userData);
|
||||
SkPaint paint;
|
||||
setupPaintWithFontData(&paint, data);
|
||||
|
||||
paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
|
||||
int numGlyphs = paint.textToGlyphs(characters, length * sizeof(uint16_t),
|
||||
reinterpret_cast<uint16_t*>(glyphs));
|
||||
|
||||
// HB_Glyph is 32-bit, but Skia outputs only 16-bit numbers. So our
|
||||
// |glyphs| array needs to be converted.
|
||||
for (int i = numGlyphs - 1; i >= 0; --i) {
|
||||
uint16_t value;
|
||||
// We use a memcpy to avoid breaking strict aliasing rules.
|
||||
memcpy(&value, reinterpret_cast<char*>(glyphs) + sizeof(uint16_t) * i, sizeof(value));
|
||||
glyphs[i] = value;
|
||||
}
|
||||
|
||||
*glyphsSize = numGlyphs;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void glyphsToAdvances(HB_Font hbFont, const HB_Glyph* glyphs, hb_uint32 numGlyphs,
|
||||
HB_Fixed* advances, int flags)
|
||||
{
|
||||
FontData* data = reinterpret_cast<FontData*>(hbFont->userData);
|
||||
SkPaint paint;
|
||||
setupPaintWithFontData(&paint, data);
|
||||
|
||||
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
|
||||
|
||||
uint16_t* glyphs16 = new uint16_t[numGlyphs];
|
||||
if (!glyphs16)
|
||||
return;
|
||||
for (unsigned i = 0; i < numGlyphs; ++i)
|
||||
glyphs16[i] = glyphs[i];
|
||||
paint.getTextWidths(glyphs16, numGlyphs * sizeof(uint16_t), reinterpret_cast<SkScalar*>(advances));
|
||||
|
||||
// The |advances| values which Skia outputs are SkScalars, which are floats
|
||||
// in Chromium. However, Harfbuzz wants them in 26.6 fixed point format.
|
||||
// These two formats are both 32-bits long.
|
||||
for (unsigned i = 0; i < numGlyphs; ++i) {
|
||||
float value;
|
||||
// We use a memcpy to avoid breaking strict aliasing rules.
|
||||
memcpy(&value, reinterpret_cast<char*>(advances) + sizeof(float) * i, sizeof(value));
|
||||
advances[i] = SkiaScalarToHarfbuzzFixed(value);
|
||||
}
|
||||
delete glyphs16;
|
||||
}
|
||||
|
||||
static HB_Bool canRender(HB_Font hbFont, const HB_UChar16* characters, hb_uint32 length)
|
||||
{
|
||||
FontData* data = reinterpret_cast<FontData*>(hbFont->userData);
|
||||
SkPaint paint;
|
||||
setupPaintWithFontData(&paint, data);
|
||||
|
||||
paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
|
||||
|
||||
uint16_t* glyphs16 = new uint16_t[length];
|
||||
int numGlyphs = paint.textToGlyphs(characters, length * sizeof(uint16_t), glyphs16);
|
||||
|
||||
bool result = true;
|
||||
for (int i = 0; i < numGlyphs; ++i) {
|
||||
if (!glyphs16[i]) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete glyphs16;
|
||||
return result;
|
||||
}
|
||||
|
||||
static HB_Error getOutlinePoint(HB_Font hbFont, HB_Glyph glyph, int flags, hb_uint32 point,
|
||||
HB_Fixed* xPos, HB_Fixed* yPos, hb_uint32* resultingNumPoints)
|
||||
{
|
||||
FontData* data = reinterpret_cast<FontData*>(hbFont->userData);
|
||||
SkPaint paint;
|
||||
setupPaintWithFontData(&paint, data);
|
||||
|
||||
if (flags & HB_ShaperFlag_UseDesignMetrics)
|
||||
// This is requesting pre-hinted positions. We can't support this.
|
||||
return HB_Err_Invalid_Argument;
|
||||
|
||||
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
|
||||
uint16_t glyph16 = glyph;
|
||||
SkPath path;
|
||||
paint.getTextPath(&glyph16, sizeof(glyph16), 0, 0, &path);
|
||||
uint32_t numPoints = path.getPoints(0, 0);
|
||||
if (point >= numPoints)
|
||||
return HB_Err_Invalid_SubTable;
|
||||
SkPoint* points = reinterpret_cast<SkPoint*>(malloc(sizeof(SkPoint) * (point + 1)));
|
||||
if (!points)
|
||||
return HB_Err_Invalid_SubTable;
|
||||
// Skia does let us get a single point from the path.
|
||||
path.getPoints(points, point + 1);
|
||||
*xPos = SkiaScalarToHarfbuzzFixed(points[point].fX);
|
||||
*yPos = SkiaScalarToHarfbuzzFixed(points[point].fY);
|
||||
*resultingNumPoints = numPoints;
|
||||
delete points;
|
||||
|
||||
return HB_Err_Ok;
|
||||
}
|
||||
|
||||
static void getGlyphMetrics(HB_Font hbFont, HB_Glyph glyph, HB_GlyphMetrics* metrics)
|
||||
{
|
||||
FontData* data = reinterpret_cast<FontData*>(hbFont->userData);
|
||||
SkPaint paint;
|
||||
setupPaintWithFontData(&paint, data);
|
||||
|
||||
paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
|
||||
uint16_t glyph16 = glyph;
|
||||
SkScalar width;
|
||||
SkRect bounds;
|
||||
paint.getTextWidths(&glyph16, sizeof(glyph16), &width, &bounds);
|
||||
|
||||
metrics->x = SkiaScalarToHarfbuzzFixed(bounds.fLeft);
|
||||
metrics->y = SkiaScalarToHarfbuzzFixed(bounds.fTop);
|
||||
metrics->width = SkiaScalarToHarfbuzzFixed(bounds.width());
|
||||
metrics->height = SkiaScalarToHarfbuzzFixed(bounds.height());
|
||||
|
||||
metrics->xOffset = SkiaScalarToHarfbuzzFixed(width);
|
||||
// We can't actually get the |y| correct because Skia doesn't export
|
||||
// the vertical advance. However, nor we do ever render vertical text at
|
||||
// the moment so it's unimportant.
|
||||
metrics->yOffset = 0;
|
||||
}
|
||||
|
||||
static HB_Fixed getFontMetric(HB_Font hbFont, HB_FontMetric metric)
|
||||
{
|
||||
FontData* data = reinterpret_cast<FontData*>(hbFont->userData);
|
||||
SkPaint paint;
|
||||
setupPaintWithFontData(&paint, data);
|
||||
|
||||
SkPaint::FontMetrics skiaMetrics;
|
||||
paint.getFontMetrics(&skiaMetrics);
|
||||
|
||||
switch (metric) {
|
||||
case HB_FontAscent:
|
||||
return SkiaScalarToHarfbuzzFixed(-skiaMetrics.fAscent);
|
||||
// We don't support getting the rest of the metrics and Harfbuzz doesn't seem to need them.
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const HB_FontClass harfbuzzSkiaClass = {
|
||||
stringToGlyphs,
|
||||
glyphsToAdvances,
|
||||
canRender,
|
||||
getOutlinePoint,
|
||||
getGlyphMetrics,
|
||||
getFontMetric,
|
||||
};
|
||||
|
||||
HB_Error harfbuzzSkiaGetTable(void* voidface, const HB_Tag tag, HB_Byte* buffer, HB_UInt* len)
|
||||
{
|
||||
FontData* data = reinterpret_cast<FontData*>(voidface);
|
||||
SkTypeface* typeface = data->typeFace;
|
||||
|
||||
const size_t tableSize = SkFontHost::GetTableSize(typeface->uniqueID(), tag);
|
||||
if (!tableSize)
|
||||
return HB_Err_Invalid_Argument;
|
||||
// If Harfbuzz specified a NULL buffer then it's asking for the size of the table.
|
||||
if (!buffer) {
|
||||
*len = tableSize;
|
||||
return HB_Err_Ok;
|
||||
}
|
||||
|
||||
if (*len < tableSize)
|
||||
return HB_Err_Invalid_Argument;
|
||||
SkFontHost::GetTableData(typeface->uniqueID(), tag, 0, tableSize, buffer);
|
||||
return HB_Err_Ok;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
48
core/jni/android/graphics/HarfbuzzSkia.h
Normal file
48
core/jni/android/graphics/HarfbuzzSkia.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2011, The Android Open Source Project
|
||||
* Copyright 2011, Google Inc. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef HarfbuzzSkia_h
|
||||
#define HarfbuzzSkia_h
|
||||
|
||||
#include "SkTypeface.h"
|
||||
|
||||
extern "C" {
|
||||
#include "harfbuzz-shaper.h"
|
||||
}
|
||||
|
||||
namespace android {
|
||||
typedef struct {
|
||||
SkTypeface* typeFace;
|
||||
float textSize;
|
||||
bool fakeBold;
|
||||
bool fakeItalic;
|
||||
} FontData;
|
||||
|
||||
HB_Error harfbuzzSkiaGetTable(void* voidface, const HB_Tag, HB_Byte* buffer, HB_UInt* len);
|
||||
extern const HB_FontClass harfbuzzSkiaClass;
|
||||
} // namespace android
|
||||
|
||||
#endif
|
||||
@@ -393,13 +393,40 @@ public:
|
||||
return count;
|
||||
}
|
||||
|
||||
static int getTextWidths__StringII_F(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text, int start, int end, jfloatArray widths) {
|
||||
static int getTextWidths__StringII_F(JNIEnv* env, jobject clazz, SkPaint* paint, jstring text,
|
||||
int start, int end, jfloatArray widths) {
|
||||
const jchar* textArray = env->GetStringChars(text, NULL);
|
||||
int count = dotextwidths(env, paint, textArray + start, end - start, widths);
|
||||
env->ReleaseStringChars(text, textArray);
|
||||
return count;
|
||||
}
|
||||
|
||||
static int doTextGlyphs(JNIEnv* env, SkPaint* paint, const jchar* text, jint start, jint count,
|
||||
jint contextCount, jint flags, jcharArray glyphs) {
|
||||
jchar* glyphsArray = env->GetCharArrayElements(glyphs, NULL);
|
||||
HB_ShaperItem shaperItem;
|
||||
HB_FontRec font;
|
||||
FontData fontData;
|
||||
RunAdvanceDescription::shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, text,
|
||||
start, count, contextCount, flags);
|
||||
|
||||
int glyphCount = shaperItem.num_glyphs;
|
||||
for (int i = 0; i < glyphCount; i++) {
|
||||
glyphsArray[i] = (jchar) shaperItem.glyphs[i];
|
||||
}
|
||||
return glyphCount;
|
||||
}
|
||||
|
||||
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 flags,
|
||||
jfloatArray advances, jint advancesIndex) {
|
||||
@@ -725,6 +752,8 @@ static JNINativeMethod methods[] = {
|
||||
SkPaintGlue::getTextRunAdvances___CIIIII_FI},
|
||||
{"native_getTextRunAdvances","(ILjava/lang/String;IIIII[FI)F",
|
||||
(void*) SkPaintGlue::getTextRunAdvances__StringIIIII_FI},
|
||||
{"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},
|
||||
|
||||
@@ -45,5 +45,7 @@ static RtlDebugLevel readRtlDebugLevel() {
|
||||
return kRtlDebugDisabled;
|
||||
}
|
||||
|
||||
#define RTL_USE_HARFBUZZ 1
|
||||
|
||||
} // namespace android
|
||||
#endif // ANDROID_RTL_PROPERTIES_H
|
||||
|
||||
@@ -47,8 +47,16 @@ void TextLayoutCache::init() {
|
||||
if (mDebugEnabled) {
|
||||
LOGD("TextLayoutCache start time: %lld", mCacheStartTime);
|
||||
}
|
||||
|
||||
mInitialized = true;
|
||||
|
||||
if (mDebugEnabled) {
|
||||
#if RTL_USE_HARFBUZZ
|
||||
LOGD("TextLayoutCache is using HARFBUZZ");
|
||||
#else
|
||||
LOGD("TextLayoutCache is using ICU");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (mDebugEnabled) {
|
||||
LOGD("TextLayoutCache initialization is done");
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
|
||||
#include "unicode/ubidi.h"
|
||||
#include "unicode/ushape.h"
|
||||
#include "HarfbuzzSkia.h"
|
||||
#include "harfbuzz-shaper.h"
|
||||
|
||||
#include <android_runtime/AndroidRuntime.h>
|
||||
|
||||
@@ -52,8 +54,14 @@
|
||||
// Define the interval in number of cache hits between two statistics dump
|
||||
#define DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL 100
|
||||
|
||||
// Define if we want to have Advances debug values
|
||||
#define DEBUG_ADVANCES 0
|
||||
|
||||
namespace android {
|
||||
|
||||
// Harfbuzz uses 26.6 fixed point values for pixel offsets
|
||||
#define HB_FIXED_TO_FLOAT(v) (((float) v) * (1.0 / 64))
|
||||
|
||||
/**
|
||||
* TextLayoutCacheKey is the Cache key
|
||||
*/
|
||||
@@ -149,8 +157,18 @@ public:
|
||||
advances = new float[count];
|
||||
this->count = count;
|
||||
|
||||
computeAdvances(paint, chars, start, count, contextCount, dirFlags,
|
||||
#if RTL_USE_HARFBUZZ
|
||||
computeAdvancesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
|
||||
advances, &totalAdvance);
|
||||
#else
|
||||
computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
|
||||
advances, &totalAdvance);
|
||||
#endif
|
||||
#if DEBUG_ADVANCES
|
||||
LOGD("Advances - count=%d - countextCount=%d - totalAdvance=%f - "
|
||||
"adv[0]=%f adv[1]=%f adv[2]=%f adv[3]=%f", count, contextCount, totalAdvance,
|
||||
advances[0], advances[1], advances[2], advances[3]);
|
||||
#endif
|
||||
}
|
||||
|
||||
void copyResult(jfloat* outAdvances, jfloat* outTotalAdvance) {
|
||||
@@ -165,8 +183,108 @@ public:
|
||||
return sizeof(RunAdvanceDescription) + sizeof(jfloat) * count;
|
||||
}
|
||||
|
||||
static void computeAdvances(SkPaint* paint, const UChar* chars, size_t start, size_t count,
|
||||
size_t contextCount, int dirFlags, jfloat* outAdvances, jfloat* outTotalAdvance) {
|
||||
static void setupShaperItem(HB_ShaperItem* shaperItem, HB_FontRec* font, FontData* fontData,
|
||||
SkPaint* paint, const UChar* chars, size_t start, size_t count, size_t contextCount,
|
||||
int dirFlags) {
|
||||
bool isRTL = dirFlags & 0x1;
|
||||
|
||||
font->klass = &harfbuzzSkiaClass;
|
||||
font->userData = 0;
|
||||
// The values which harfbuzzSkiaClass returns are already scaled to
|
||||
// pixel units, so we just set all these to one to disable further
|
||||
// scaling.
|
||||
font->x_ppem = 1;
|
||||
font->y_ppem = 1;
|
||||
font->x_scale = 1;
|
||||
font->y_scale = 1;
|
||||
|
||||
memset(shaperItem, 0, sizeof(*shaperItem));
|
||||
shaperItem->font = font;
|
||||
shaperItem->face = HB_NewFace(shaperItem->font, harfbuzzSkiaGetTable);
|
||||
|
||||
// We cannot know, ahead of time, how many glyphs a given script run
|
||||
// will produce. We take a guess that script runs will not produce more
|
||||
// than twice as many glyphs as there are code points plus a bit of
|
||||
// padding and fallback if we find that we are wrong.
|
||||
createGlyphArrays(shaperItem, (contextCount + 2) * 2);
|
||||
|
||||
// Free memory for clusters if needed and recreate the clusters array
|
||||
if (shaperItem->log_clusters) {
|
||||
delete shaperItem->log_clusters;
|
||||
}
|
||||
shaperItem->log_clusters = new unsigned short[contextCount];
|
||||
|
||||
shaperItem->item.pos = start;
|
||||
shaperItem->item.length = count;
|
||||
shaperItem->item.bidiLevel = isRTL;
|
||||
shaperItem->item.script = isRTL ? HB_Script_Arabic : HB_Script_Common;
|
||||
|
||||
shaperItem->string = chars;
|
||||
shaperItem->stringLength = contextCount;
|
||||
|
||||
fontData->textSize = paint->getTextSize();
|
||||
fontData->fakeBold = paint->isFakeBoldText();
|
||||
fontData->fakeItalic = (paint->getTextSkewX() > 0);
|
||||
fontData->typeFace = paint->getTypeface();
|
||||
|
||||
shaperItem->font->userData = fontData;
|
||||
}
|
||||
|
||||
static void shapeWithHarfbuzz(HB_ShaperItem* shaperItem, HB_FontRec* font, FontData* fontData,
|
||||
SkPaint* paint, const UChar* chars, size_t start, size_t count, size_t contextCount,
|
||||
int dirFlags) {
|
||||
// Setup Harfbuzz Shaper
|
||||
setupShaperItem(shaperItem, font, fontData, paint, chars, start, count,
|
||||
contextCount, dirFlags);
|
||||
|
||||
// Shape
|
||||
resetGlyphArrays(shaperItem);
|
||||
while (!HB_ShapeItem(shaperItem)) {
|
||||
// We overflowed our arrays. Resize and retry.
|
||||
// HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
|
||||
deleteGlyphArrays(shaperItem);
|
||||
createGlyphArrays(shaperItem, shaperItem->num_glyphs << 1);
|
||||
resetGlyphArrays(shaperItem);
|
||||
}
|
||||
}
|
||||
|
||||
static void computeAdvancesWithHarfbuzz(SkPaint* paint, const UChar* chars, size_t start,
|
||||
size_t count, size_t contextCount, int dirFlags,
|
||||
jfloat* outAdvances, jfloat* outTotalAdvance) {
|
||||
|
||||
bool isRTL = dirFlags & 0x1;
|
||||
|
||||
HB_ShaperItem shaperItem;
|
||||
HB_FontRec font;
|
||||
FontData fontData;
|
||||
shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, chars, start, count,
|
||||
contextCount, dirFlags);
|
||||
|
||||
#if DEBUG_ADVANCES
|
||||
LOGD("HARFBUZZ -- num_glypth=%d", shaperItem.num_glyphs);
|
||||
#endif
|
||||
|
||||
jfloat totalAdvance = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
// Be careful: we need to use roundf() for doing the same way as Skia is doing
|
||||
totalAdvance += outAdvances[i] = roundf(HB_FIXED_TO_FLOAT(shaperItem.advances[i]));
|
||||
|
||||
#if DEBUG_ADVANCES
|
||||
LOGD("hb-adv = %d - rebased = %f - total = %f", shaperItem.advances[i], outAdvances[i],
|
||||
totalAdvance);
|
||||
#endif
|
||||
}
|
||||
|
||||
deleteGlyphArrays(&shaperItem);
|
||||
HB_FreeFace(shaperItem.face);
|
||||
|
||||
*outTotalAdvance = totalAdvance;
|
||||
}
|
||||
|
||||
static void computeAdvancesWithICU(SkPaint* paint, const UChar* chars, size_t start,
|
||||
size_t count, size_t contextCount, int dirFlags,
|
||||
jfloat* outAdvances, jfloat* outTotalAdvance) {
|
||||
|
||||
SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
|
||||
jchar* buffer = tempBuffer.get();
|
||||
|
||||
@@ -199,6 +317,9 @@ public:
|
||||
|
||||
jfloat totalAdvance = 0;
|
||||
if (widths < count) {
|
||||
#if DEBUG_ADVANCES
|
||||
LOGD("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.
|
||||
|
||||
@@ -213,10 +334,19 @@ public:
|
||||
text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
|
||||
outAdvances[p++] = 0;
|
||||
}
|
||||
#if DEBUG_ADVANCES
|
||||
LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
#if DEBUG_ADVANCES
|
||||
LOGD("ICU -- count=%d", count);
|
||||
#endif
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]);
|
||||
#if DEBUG_ADVANCES
|
||||
LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
*outTotalAdvance = totalAdvance;
|
||||
@@ -228,6 +358,32 @@ private:
|
||||
size_t count;
|
||||
|
||||
uint32_t elapsedTime;
|
||||
|
||||
static void deleteGlyphArrays(HB_ShaperItem* shaperItem) {
|
||||
delete[] shaperItem->glyphs;
|
||||
delete[] shaperItem->attributes;
|
||||
delete[] shaperItem->advances;
|
||||
delete[] shaperItem->offsets;
|
||||
}
|
||||
|
||||
static void createGlyphArrays(HB_ShaperItem* shaperItem, int size) {
|
||||
shaperItem->glyphs = new HB_Glyph[size];
|
||||
shaperItem->attributes = new HB_GlyphAttributes[size];
|
||||
shaperItem->advances = new HB_Fixed[size];
|
||||
shaperItem->offsets = new HB_FixedPoint[size];
|
||||
shaperItem->num_glyphs = size;
|
||||
}
|
||||
|
||||
static void resetGlyphArrays(HB_ShaperItem* shaperItem) {
|
||||
int size = shaperItem->num_glyphs;
|
||||
// All the types here don't have pointers. It is safe to reset to
|
||||
// zero unless Harfbuzz breaks the compatibility in the future.
|
||||
memset(shaperItem->glyphs, 0, size * sizeof(shaperItem->glyphs[0]));
|
||||
memset(shaperItem->attributes, 0, size * sizeof(shaperItem->attributes[0]));
|
||||
memset(shaperItem->advances, 0, size * sizeof(shaperItem->advances[0]));
|
||||
memset(shaperItem->offsets, 0, size * sizeof(shaperItem->offsets[0]));
|
||||
}
|
||||
|
||||
}; // RunAdvanceDescription
|
||||
|
||||
|
||||
|
||||
@@ -1329,6 +1329,29 @@ public class Canvas {
|
||||
paint.mNativePaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the glyphs, with origin at (x,y), using the specified paint. The
|
||||
* origin is interpreted based on the Align setting in the paint.
|
||||
*
|
||||
* @param glyphs The glyphs to be drawn
|
||||
* @param x The x-coordinate of the origin of the text being drawn
|
||||
* @param y The y-coordinate of the origin of the text being drawn
|
||||
* @param paint The paint used for the text (e.g. color, size, style)
|
||||
*
|
||||
* @hide
|
||||
*
|
||||
* Used only for BiDi / RTL Tests
|
||||
*/
|
||||
public void drawGlyphs(char[] glyphs, int index, int count, float x, float y,
|
||||
Paint paint) {
|
||||
if ((index | count | (index + count) |
|
||||
(glyphs.length - index - count)) < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
native_drawGlyphs(mNativeCanvas, glyphs, index, count, x, y, paint.mBidiFlags,
|
||||
paint.mNativePaint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the text, with origin at (x,y), using the specified paint. The
|
||||
* origin is interpreted based on the Align setting in the paint.
|
||||
@@ -1722,7 +1745,9 @@ public class Canvas {
|
||||
private static native void native_drawText(int nativeCanvas, String text,
|
||||
int start, int end, float x,
|
||||
float y, int flags, int paint);
|
||||
|
||||
private static native void native_drawGlyphs(int nativeCanvas, char[] glyphs,
|
||||
int index, int count, float x,
|
||||
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 flags, int paint);
|
||||
|
||||
@@ -1454,6 +1454,43 @@ public class Paint {
|
||||
return getTextWidths(text, 0, text.length(), widths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the glypth 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 getTextGlypths(String text, int start, int end, int contextStart, int contextEnd,
|
||||
int flags, char[] glyphs) {
|
||||
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();
|
||||
}
|
||||
if (flags != DIRECTION_LTR && flags != DIRECTION_RTL) {
|
||||
throw new IllegalArgumentException("unknown flags value: " + flags);
|
||||
}
|
||||
return native_getTextGlyphs(mNativePaint, text, start, end, contextStart, contextEnd,
|
||||
flags, glyphs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience overload that takes a char array instead of a
|
||||
* String.
|
||||
@@ -1859,6 +1896,10 @@ 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,
|
||||
int flags, float[] advances, int advancesIndex);
|
||||
|
||||
27
tests/BiDiTests/Android.mk
Normal file
27
tests/BiDiTests/Android.mk
Normal file
@@ -0,0 +1,27 @@
|
||||
# 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.
|
||||
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
# Only compile source java files in this apk.
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_PACKAGE_NAME := BiDiTests
|
||||
|
||||
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
36
tests/BiDiTests/AndroidManifest.xml
Normal file
36
tests/BiDiTests/AndroidManifest.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<!-- Declare the contents of this Android application. The namespace
|
||||
attribute brings in the Android platform namespace, and the package
|
||||
supplies a unique name for the application. When writing your
|
||||
own application, the package name must be changed from "com.example.*"
|
||||
to come from a domain that you own or have control over. -->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.bidi"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
<application android:label="BiDiTests">
|
||||
<activity android:name="BiDiTestActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
0
tests/BiDiTests/proguard.flags
Normal file
0
tests/BiDiTests/proguard.flags
Normal file
58
tests/BiDiTests/res/layout/biditest_main.xml
Normal file
58
tests/BiDiTests/res/layout/biditest_main.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button android:id="@+id/button"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:onClick="onButtonClick"
|
||||
android:text="@string/button_text"
|
||||
android:textSize="32dip"
|
||||
/>
|
||||
|
||||
<TextView android:id="@+id/textview"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:textSize="32dip"
|
||||
android:text="@string/textview_text"
|
||||
/>
|
||||
|
||||
<EditText android:id="@+id/textview"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:textSize="32dip"
|
||||
android:text="@string/edittext_text"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<view class="com.android.bidi.BiDiTestView"
|
||||
android:id="@+id/main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#FF0000"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
26
tests/BiDiTests/res/values/strings.xml
Normal file
26
tests/BiDiTests/res/values/strings.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<!-- 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.
|
||||
-->
|
||||
<resources>
|
||||
<string name="button_text">Button</string>
|
||||
<string name="textview_text">This is a text for a TextView</string>
|
||||
<string name="edittext_text">mmmmmmmmmmmmmmmmmmmmmmmm</string>
|
||||
<string name="normal_text">Normal String</string>
|
||||
<string name="normal_long_text">mmmmmmmmmmmmmmmmmmmmmmmm</string>
|
||||
<string name="arabic_text">لا</string>
|
||||
<string name="chinese_text">利比亚局势或影响美俄关系发展</string>
|
||||
<string name="italic_text">Italic String</string>
|
||||
<string name="bold_text">Bold String</string>
|
||||
<string name="bold_italic_text">Bold Italic String</string>
|
||||
</resources>
|
||||
43
tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
Normal file
43
tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
public class BiDiTestActivity extends Activity {
|
||||
|
||||
static final String TAG = "BiDiTestActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.biditest_main);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
public void onButtonClick(View v) {
|
||||
Log.v(TAG, "onButtonClick");
|
||||
}
|
||||
}
|
||||
190
tests/BiDiTests/src/com/android/bidi/BiDiTestView.java
Normal file
190
tests/BiDiTests/src/com/android/bidi/BiDiTestView.java
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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.graphics.Typeface;
|
||||
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 = 32;
|
||||
private static final int ORIGIN = 48;
|
||||
private static final int DELTA_Y = TEXT_SIZE;
|
||||
|
||||
private static final float DEFAULT_ITALIC_SKEW_X = -0.25f;
|
||||
|
||||
private Paint paint = new Paint();
|
||||
private Rect rect = new Rect();
|
||||
|
||||
private String NORMAL_TEXT;
|
||||
private String NORMAL_LONG_TEXT;
|
||||
private String ITALIC_TEXT;
|
||||
private String BOLD_TEXT;
|
||||
private String BOLD_ITALIC_TEXT;
|
||||
private String ARABIC_TEXT;
|
||||
private String CHINESE_TEXT;
|
||||
|
||||
private Typeface typeface;
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
typeface = paint.getTypeface();
|
||||
paint.setAntiAlias(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
drawInsideRect(canvas, Color.BLACK);
|
||||
|
||||
int deltaX = testString(canvas, NORMAL_TEXT, ORIGIN, ORIGIN, paint, typeface,
|
||||
false, false, Paint.DIRECTION_LTR);
|
||||
deltaX += testString(canvas, ITALIC_TEXT, ORIGIN + deltaX, ORIGIN, paint, typeface,
|
||||
true, false, Paint.DIRECTION_LTR);
|
||||
deltaX += testString(canvas, BOLD_TEXT, ORIGIN + deltaX, ORIGIN, paint, typeface,
|
||||
false, true, Paint.DIRECTION_LTR);
|
||||
deltaX += testString(canvas, BOLD_ITALIC_TEXT, ORIGIN + deltaX, ORIGIN, paint, typeface,
|
||||
true, true, Paint.DIRECTION_LTR);
|
||||
|
||||
// Test with a long string
|
||||
deltaX = testString(canvas, NORMAL_LONG_TEXT, ORIGIN, ORIGIN + 2 * DELTA_Y, paint, typeface,
|
||||
false, false, Paint.DIRECTION_LTR);
|
||||
|
||||
// Test Arabic ligature
|
||||
deltaX = testString(canvas, ARABIC_TEXT, ORIGIN, ORIGIN + 4 * DELTA_Y, paint, typeface,
|
||||
false, false, Paint.DIRECTION_RTL);
|
||||
|
||||
// Test Chinese
|
||||
deltaX = testString(canvas, CHINESE_TEXT, ORIGIN, ORIGIN + 6 * DELTA_Y, paint, typeface,
|
||||
false, false, Paint.DIRECTION_LTR);
|
||||
}
|
||||
|
||||
private int testString(Canvas canvas, String text, int x, int y, Paint paint, Typeface typeface,
|
||||
boolean isItalic, boolean isBold, int dir) {
|
||||
paint.setTypeface(typeface);
|
||||
|
||||
// Set paint properties
|
||||
boolean oldFakeBold = paint.isFakeBoldText();
|
||||
paint.setFakeBoldText(isBold);
|
||||
|
||||
float oldTextSkewX = paint.getTextSkewX();
|
||||
if (isItalic) {
|
||||
paint.setTextSkewX(DEFAULT_ITALIC_SKEW_X);
|
||||
}
|
||||
|
||||
drawTextWithCanvasDrawText(text, canvas, x, y, TEXT_SIZE, Color.WHITE);
|
||||
|
||||
int length = text.length();
|
||||
float[] advances = new float[length];
|
||||
float textWidth = paint.getTextRunAdvances(text, 0, length, 0, length, 0, advances, 0);
|
||||
|
||||
logAdvances(text, textWidth, advances);
|
||||
drawBoxAroundText(canvas, x, y, textWidth, TEXT_SIZE, Color.RED);
|
||||
|
||||
paint.setColor(Color.WHITE);
|
||||
char[] glyphs = new char[2*length];
|
||||
int count = getGlyphs(text, glyphs, dir);
|
||||
|
||||
logGlypths(glyphs, count);
|
||||
drawTextWithDrawGlyph(canvas, glyphs, count, x, y + DELTA_Y);
|
||||
|
||||
// Restore old paint properties
|
||||
paint.setFakeBoldText(oldFakeBold);
|
||||
paint.setTextSkewX(oldTextSkewX);
|
||||
|
||||
return (int) Math.ceil(textWidth) + TEXT_PADDING;
|
||||
}
|
||||
|
||||
private void drawTextWithDrawGlyph(Canvas canvas, char[] glyphs, int count, int x, int y) {
|
||||
canvas.drawGlyphs(glyphs, 0, count, x, y, paint);
|
||||
}
|
||||
|
||||
private void logGlypths(char[] glyphs, int count) {
|
||||
Log.v(TAG, "GlyphIds - count=" + count);
|
||||
for (int n = 0; n < count; n++) {
|
||||
Log.v(TAG, "GlyphIds - Id[" + n + "]="+ (int)glyphs[n]);
|
||||
}
|
||||
}
|
||||
|
||||
private int getGlyphs(String text, char[] glyphs, int dir) {
|
||||
// int dir = 1; // Paint.DIRECTION_LTR;
|
||||
return paint.getTextGlypths(text, 0, text.length(), 0, text.length(), dir, glyphs);
|
||||
}
|
||||
|
||||
private void drawInsideRect(Canvas canvas, 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 drawTextWithCanvasDrawText(String text, Canvas canvas,
|
||||
float x, float y, float textSize, int color) {
|
||||
paint.setColor(color);
|
||||
paint.setTextSize(textSize);
|
||||
canvas.drawText(text, x, y, paint);
|
||||
}
|
||||
|
||||
private void drawBoxAroundText(Canvas canvas, int x, int y, float textWidth, int textSize,
|
||||
int color) {
|
||||
paint.setColor(color);
|
||||
canvas.drawLine(x, y - textSize, x, y + 8, paint);
|
||||
canvas.drawLine(x, y + 8, x + textWidth, y + 8, paint);
|
||||
canvas.drawLine(x + textWidth, y - textSize, x + textWidth, y + 8, paint);
|
||||
}
|
||||
|
||||
private void logAdvances(String text, float textWidth, float[] advances) {
|
||||
Log.v(TAG, "Advances for text: " + text + " total=" + textWidth);
|
||||
int length = advances.length;
|
||||
for(int n=0; n<length; n++){
|
||||
Log.v(TAG, "adv[" + n + "]=" + advances[n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user