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:
Fabrice Di Meglio
2011-03-08 12:02:59 -08:00
parent d54952b451
commit 9f82b580d7
17 changed files with 960 additions and 6 deletions

View File

@@ -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

View File

@@ -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",

View 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

View 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

View File

@@ -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},

View File

@@ -45,5 +45,7 @@ static RtlDebugLevel readRtlDebugLevel() {
return kRtlDebugDisabled;
}
#define RTL_USE_HARFBUZZ 1
} // namespace android
#endif // ANDROID_RTL_PROPERTIES_H

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View 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)

View 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>

View File

View 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>

View 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">&#x0644;&#x0627;</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>

View 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");
}
}

View 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]);
}
}
}