diff options
| -rw-r--r-- | core/jni/Android.mk | 3 | ||||
| -rw-r--r-- | core/jni/android/graphics/Canvas.cpp | 23 | ||||
| -rw-r--r-- | core/jni/android/graphics/HarfbuzzSkia.cpp | 239 | ||||
| -rw-r--r-- | core/jni/android/graphics/HarfbuzzSkia.h | 48 | ||||
| -rw-r--r-- | core/jni/android/graphics/Paint.cpp | 31 | ||||
| -rw-r--r-- | core/jni/android/graphics/RtlProperties.h | 2 | ||||
| -rw-r--r-- | core/jni/android/graphics/TextLayoutCache.cpp | 10 | ||||
| -rw-r--r-- | core/jni/android/graphics/TextLayoutCache.h | 162 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Canvas.java | 27 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Paint.java | 41 | ||||
| -rw-r--r-- | tests/BiDiTests/Android.mk | 27 | ||||
| -rw-r--r-- | tests/BiDiTests/AndroidManifest.xml | 36 | ||||
| -rw-r--r-- | tests/BiDiTests/proguard.flags | 0 | ||||
| -rw-r--r-- | tests/BiDiTests/res/layout/biditest_main.xml | 58 | ||||
| -rw-r--r-- | tests/BiDiTests/res/values/strings.xml | 26 | ||||
| -rw-r--r-- | tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java | 43 | ||||
| -rw-r--r-- | tests/BiDiTests/src/com/android/bidi/BiDiTestView.java | 190 | 
17 files changed, 960 insertions, 6 deletions
diff --git a/core/jni/Android.mk b/core/jni/Android.mk index b4a0e4faf45d..66d8a3655987 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -93,6 +93,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 \ @@ -174,6 +175,7 @@ LOCAL_C_INCLUDES += \  	external/icu4c/i18n \  	external/icu4c/common \  	external/jpeg \ +	external/harfbuzz/src \  	frameworks/opt/emoji  LOCAL_SHARED_LIBRARIES := \ @@ -206,6 +208,7 @@ LOCAL_SHARED_LIBRARIES := \  	libjpeg \  	libnfc_ndef \  	libusbhost \ +	libharfbuzz \  ifeq ($(USE_OPENGL_RENDERER),true)  	LOCAL_SHARED_LIBRARIES += libhwui diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 0cdb3578f056..b4ad9e9c18de 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -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", diff --git a/core/jni/android/graphics/HarfbuzzSkia.cpp b/core/jni/android/graphics/HarfbuzzSkia.cpp new file mode 100644 index 000000000000..58fb32b47319 --- /dev/null +++ b/core/jni/android/graphics/HarfbuzzSkia.cpp @@ -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 diff --git a/core/jni/android/graphics/HarfbuzzSkia.h b/core/jni/android/graphics/HarfbuzzSkia.h new file mode 100644 index 000000000000..d057d76f2842 --- /dev/null +++ b/core/jni/android/graphics/HarfbuzzSkia.h @@ -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 diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index e62b034cc694..5c3497fdf4ea 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -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}, diff --git a/core/jni/android/graphics/RtlProperties.h b/core/jni/android/graphics/RtlProperties.h index 6d8ba91816e0..2c68fa397f13 100644 --- a/core/jni/android/graphics/RtlProperties.h +++ b/core/jni/android/graphics/RtlProperties.h @@ -45,5 +45,7 @@ static RtlDebugLevel readRtlDebugLevel() {      return kRtlDebugDisabled;  } +#define RTL_USE_HARFBUZZ 1 +  } // namespace android  #endif // ANDROID_RTL_PROPERTIES_H diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp index 7888769760cc..a7265beb5fc8 100644 --- a/core/jni/android/graphics/TextLayoutCache.cpp +++ b/core/jni/android/graphics/TextLayoutCache.cpp @@ -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");      } diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h index 9d55918c6a6f..e962a86f2110 100644 --- a/core/jni/android/graphics/TextLayoutCache.h +++ b/core/jni/android/graphics/TextLayoutCache.h @@ -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 diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 965abe9b923f..e493b18cc2d5 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1330,6 +1330,29 @@ public class Canvas {      }      /** +     * 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); diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 0a23bae18e87..96eb93662d06 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -1455,6 +1455,43 @@ public class Paint {      }      /** +     * 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); diff --git a/tests/BiDiTests/Android.mk b/tests/BiDiTests/Android.mk new file mode 100644 index 000000000000..ae29fc262b13 --- /dev/null +++ b/tests/BiDiTests/Android.mk @@ -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)
\ No newline at end of file diff --git a/tests/BiDiTests/AndroidManifest.xml b/tests/BiDiTests/AndroidManifest.xml new file mode 100644 index 000000000000..346ace808b4c --- /dev/null +++ b/tests/BiDiTests/AndroidManifest.xml @@ -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>
\ No newline at end of file diff --git a/tests/BiDiTests/proguard.flags b/tests/BiDiTests/proguard.flags new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/tests/BiDiTests/proguard.flags diff --git a/tests/BiDiTests/res/layout/biditest_main.xml b/tests/BiDiTests/res/layout/biditest_main.xml new file mode 100644 index 000000000000..9f77ad2e5388 --- /dev/null +++ b/tests/BiDiTests/res/layout/biditest_main.xml @@ -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>
\ No newline at end of file diff --git a/tests/BiDiTests/res/values/strings.xml b/tests/BiDiTests/res/values/strings.xml new file mode 100644 index 000000000000..ecff76e69c56 --- /dev/null +++ b/tests/BiDiTests/res/values/strings.xml @@ -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>
\ No newline at end of file diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java new file mode 100644 index 000000000000..3d7dd81dad86 --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java @@ -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"); +    } +}
\ No newline at end of file diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java new file mode 100644 index 000000000000..e9b6fa62f084 --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestView.java @@ -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]); +        } +    } +}  |