Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #define LOG_TAG "TextLayoutCache"
     18 
     19 #include "TextLayoutCache.h"
     20 #include "TextLayout.h"
     21 #include "SkFontHost.h"
     22 #include <unicode/unistr.h>
     23 #include <unicode/normlzr.h>
     24 #include <unicode/uchar.h>
     25 
     26 extern "C" {
     27   #include "harfbuzz-unicode.h"
     28 }
     29 
     30 namespace android {
     31 
     32 //--------------------------------------------------------------------------------------------------
     33 #define TYPEFACE_ARABIC "/system/fonts/DroidNaskh-Regular-SystemUI.ttf"
     34 #define TYPE_FACE_HEBREW_REGULAR "/system/fonts/DroidSansHebrew-Regular.ttf"
     35 #define TYPE_FACE_HEBREW_BOLD "/system/fonts/DroidSansHebrew-Bold.ttf"
     36 #define TYPEFACE_BENGALI "/system/fonts/Lohit-Bengali.ttf"
     37 #define TYPEFACE_DEVANAGARI_REGULAR "/system/fonts/DroidSansDevanagari-Regular.ttf"
     38 #define TYPEFACE_TAMIL_REGULAR "/system/fonts/DroidSansTamil-Regular.ttf"
     39 #define TYPEFACE_TAMIL_BOLD "/system/fonts/DroidSansTamil-Bold.ttf"
     40 #define TYPEFACE_THAI "/system/fonts/DroidSansThai.ttf"
     41 
     42 ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine);
     43 
     44 //--------------------------------------------------------------------------------------------------
     45 
     46 TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) :
     47         mShaper(shaper),
     48         mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutValue> >::kUnlimitedCapacity),
     49         mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
     50         mCacheHitCount(0), mNanosecondsSaved(0) {
     51     init();
     52 }
     53 
     54 TextLayoutCache::~TextLayoutCache() {
     55     mCache.clear();
     56 }
     57 
     58 void TextLayoutCache::init() {
     59     mCache.setOnEntryRemovedListener(this);
     60 
     61     mDebugLevel = readRtlDebugLevel();
     62     mDebugEnabled = mDebugLevel & kRtlDebugCaches;
     63     ALOGD("Using debug level = %d - Debug Enabled = %d", mDebugLevel, mDebugEnabled);
     64 
     65     mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
     66 
     67     if (mDebugEnabled) {
     68         ALOGD("Initialization is done - Start time = %lld", mCacheStartTime);
     69     }
     70 
     71     mInitialized = true;
     72 }
     73 
     74 /**
     75  *  Callbacks
     76  */
     77 void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc) {
     78     size_t totalSizeToDelete = text.getSize() + desc->getSize();
     79     mSize -= totalSizeToDelete;
     80     if (mDebugEnabled) {
     81         ALOGD("Cache value %p deleted, size = %d", desc.get(), totalSizeToDelete);
     82     }
     83 }
     84 
     85 /*
     86  * Cache clearing
     87  */
     88 void TextLayoutCache::clear() {
     89     mCache.clear();
     90 }
     91 
     92 /*
     93  * Caching
     94  */
     95 sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
     96             const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
     97     AutoMutex _l(mLock);
     98     nsecs_t startTime = 0;
     99     if (mDebugEnabled) {
    100         startTime = systemTime(SYSTEM_TIME_MONOTONIC);
    101     }
    102 
    103     // Create the key
    104     TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
    105 
    106     // Get value from cache if possible
    107     sp<TextLayoutValue> value = mCache.get(key);
    108 
    109     // Value not found for the key, we need to add a new value in the cache
    110     if (value == NULL) {
    111         if (mDebugEnabled) {
    112             startTime = systemTime(SYSTEM_TIME_MONOTONIC);
    113         }
    114 
    115         value = new TextLayoutValue(contextCount);
    116 
    117         // Compute advances and store them
    118         mShaper->computeValues(value.get(), paint,
    119                 reinterpret_cast<const UChar*>(text), start, count,
    120                 size_t(contextCount), int(dirFlags));
    121 
    122         if (mDebugEnabled) {
    123             value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
    124         }
    125 
    126         // Don't bother to add in the cache if the entry is too big
    127         size_t size = key.getSize() + value->getSize();
    128         if (size <= mMaxSize) {
    129             // Cleanup to make some room if needed
    130             if (mSize + size > mMaxSize) {
    131                 if (mDebugEnabled) {
    132                     ALOGD("Need to clean some entries for making some room for a new entry");
    133                 }
    134                 while (mSize + size > mMaxSize) {
    135                     // This will call the callback
    136                     bool removedOne = mCache.removeOldest();
    137                     LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we "
    138                             "failed to remove the oldest entry.  "
    139                             "mSize = %u, size = %u, mMaxSize = %u, mCache.size() = %u",
    140                             mSize, size, mMaxSize, mCache.size());
    141                 }
    142             }
    143 
    144             // Update current cache size
    145             mSize += size;
    146 
    147             // Copy the text when we insert the new entry
    148             key.internalTextCopy();
    149 
    150             bool putOne = mCache.put(key, value);
    151             LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache.  "
    152                     "This indicates that the cache already has an entry with the "
    153                     "same key but it should not since we checked earlier!"
    154                     " - start = %d, count = %d, contextCount = %d - Text = '%s'",
    155                     start, count, contextCount, String8(text + start, count).string());
    156 
    157             if (mDebugEnabled) {
    158                 nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
    159                 ALOGD("CACHE MISS: Added entry %p "
    160                         "with start = %d, count = %d, contextCount = %d, "
    161                         "entry size %d bytes, remaining space %d bytes"
    162                         " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'",
    163                         value.get(), start, count, contextCount, size, mMaxSize - mSize,
    164                         value->getElapsedTime() * 0.000001f,
    165                         (totalTime - value->getElapsedTime()) * 0.000001f,
    166                         String8(text + start, count).string());
    167             }
    168         } else {
    169             if (mDebugEnabled) {
    170                 ALOGD("CACHE MISS: Calculated but not storing entry because it is too big "
    171                         "with start = %d, count = %d, contextCount = %d, "
    172                         "entry size %d bytes, remaining space %d bytes"
    173                         " - Compute time %0.6f ms - Text = '%s'",
    174                         start, count, contextCount, size, mMaxSize - mSize,
    175                         value->getElapsedTime() * 0.000001f,
    176                         String8(text + start, count).string());
    177             }
    178         }
    179     } else {
    180         // This is a cache hit, just log timestamp and user infos
    181         if (mDebugEnabled) {
    182             nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
    183             mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
    184             ++mCacheHitCount;
    185 
    186             if (value->getElapsedTime() > 0) {
    187                 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
    188                         / ((float)value->getElapsedTime()));
    189                 ALOGD("CACHE HIT #%d with start = %d, count = %d, contextCount = %d"
    190                         "- Compute time %0.6f ms - "
    191                         "Cache get time %0.6f ms - Gain in percent: %2.2f - Text = '%s'",
    192                         mCacheHitCount, start, count, contextCount,
    193                         value->getElapsedTime() * 0.000001f,
    194                         elapsedTimeThruCacheGet * 0.000001f,
    195                         deltaPercent,
    196                         String8(text + start, count).string());
    197             }
    198             if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
    199                 dumpCacheStats();
    200             }
    201         }
    202     }
    203     return value;
    204 }
    205 
    206 void TextLayoutCache::dumpCacheStats() {
    207     float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
    208     float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
    209 
    210     size_t bytes = 0;
    211     size_t cacheSize = mCache.size();
    212     for (size_t i = 0; i < cacheSize; i++) {
    213         bytes += mCache.getKeyAt(i).getSize() + mCache.getValueAt(i)->getSize();
    214     }
    215 
    216     ALOGD("------------------------------------------------");
    217     ALOGD("Cache stats");
    218     ALOGD("------------------------------------------------");
    219     ALOGD("pid       : %d", getpid());
    220     ALOGD("running   : %.0f seconds", timeRunningInSec);
    221     ALOGD("entries   : %d", cacheSize);
    222     ALOGD("max size  : %d bytes", mMaxSize);
    223     ALOGD("used      : %d bytes according to mSize, %d bytes actual", mSize, bytes);
    224     ALOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
    225     ALOGD("hits      : %d", mCacheHitCount);
    226     ALOGD("saved     : %0.6f ms", mNanosecondsSaved * 0.000001f);
    227     ALOGD("------------------------------------------------");
    228 }
    229 
    230 /**
    231  * TextLayoutCacheKey
    232  */
    233 TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0),
    234         dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
    235         hinting(SkPaint::kNo_Hinting)  {
    236 }
    237 
    238 TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
    239         size_t start, size_t count, size_t contextCount, int dirFlags) :
    240             text(text), start(start), count(count), contextCount(contextCount),
    241             dirFlags(dirFlags) {
    242     typeface = paint->getTypeface();
    243     textSize = paint->getTextSize();
    244     textSkewX = paint->getTextSkewX();
    245     textScaleX = paint->getTextScaleX();
    246     flags = paint->getFlags();
    247     hinting = paint->getHinting();
    248 }
    249 
    250 TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
    251         text(NULL),
    252         textCopy(other.textCopy),
    253         start(other.start),
    254         count(other.count),
    255         contextCount(other.contextCount),
    256         dirFlags(other.dirFlags),
    257         typeface(other.typeface),
    258         textSize(other.textSize),
    259         textSkewX(other.textSkewX),
    260         textScaleX(other.textScaleX),
    261         flags(other.flags),
    262         hinting(other.hinting) {
    263     if (other.text) {
    264         textCopy.setTo(other.text, other.contextCount);
    265     }
    266 }
    267 
    268 int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
    269     int deltaInt = lhs.start - rhs.start;
    270     if (deltaInt != 0) return (deltaInt);
    271 
    272     deltaInt = lhs.count - rhs.count;
    273     if (deltaInt != 0) return (deltaInt);
    274 
    275     deltaInt = lhs.contextCount - rhs.contextCount;
    276     if (deltaInt != 0) return (deltaInt);
    277 
    278     if (lhs.typeface < rhs.typeface) return -1;
    279     if (lhs.typeface > rhs.typeface) return +1;
    280 
    281     if (lhs.textSize < rhs.textSize) return -1;
    282     if (lhs.textSize > rhs.textSize) return +1;
    283 
    284     if (lhs.textSkewX < rhs.textSkewX) return -1;
    285     if (lhs.textSkewX > rhs.textSkewX) return +1;
    286 
    287     if (lhs.textScaleX < rhs.textScaleX) return -1;
    288     if (lhs.textScaleX > rhs.textScaleX) return +1;
    289 
    290     deltaInt = lhs.flags - rhs.flags;
    291     if (deltaInt != 0) return (deltaInt);
    292 
    293     deltaInt = lhs.hinting - rhs.hinting;
    294     if (deltaInt != 0) return (deltaInt);
    295 
    296     deltaInt = lhs.dirFlags - rhs.dirFlags;
    297     if (deltaInt) return (deltaInt);
    298 
    299     return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
    300 }
    301 
    302 void TextLayoutCacheKey::internalTextCopy() {
    303     textCopy.setTo(text, contextCount);
    304     text = NULL;
    305 }
    306 
    307 size_t TextLayoutCacheKey::getSize() const {
    308     return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
    309 }
    310 
    311 /**
    312  * TextLayoutCacheValue
    313  */
    314 TextLayoutValue::TextLayoutValue(size_t contextCount) :
    315         mTotalAdvance(0), mElapsedTime(0) {
    316     // Give a hint for advances and glyphs vectors size
    317     mAdvances.setCapacity(contextCount);
    318     mGlyphs.setCapacity(contextCount);
    319 }
    320 
    321 size_t TextLayoutValue::getSize() const {
    322     return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
    323             sizeof(jchar) * mGlyphs.capacity();
    324 }
    325 
    326 void TextLayoutValue::setElapsedTime(uint32_t time) {
    327     mElapsedTime = time;
    328 }
    329 
    330 uint32_t TextLayoutValue::getElapsedTime() {
    331     return mElapsedTime;
    332 }
    333 
    334 TextLayoutShaper::TextLayoutShaper() : mShaperItemGlyphArraySize(0) {
    335     init();
    336 
    337     mFontRec.klass = &harfbuzzSkiaClass;
    338     mFontRec.userData = 0;
    339 
    340     // The values which harfbuzzSkiaClass returns are already scaled to
    341     // pixel units, so we just set all these to one to disable further
    342     // scaling.
    343     mFontRec.x_ppem = 1;
    344     mFontRec.y_ppem = 1;
    345     mFontRec.x_scale = 1;
    346     mFontRec.y_scale = 1;
    347 
    348     memset(&mShaperItem, 0, sizeof(mShaperItem));
    349 
    350     mShaperItem.font = &mFontRec;
    351     mShaperItem.font->userData = &mShapingPaint;
    352 }
    353 
    354 void TextLayoutShaper::init() {
    355     mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, NULL, 0, SkTypeface::kNormal);
    356     mArabicTypeface = NULL;
    357     mHebrewRegularTypeface = NULL;
    358     mHebrewBoldTypeface = NULL;
    359     mBengaliTypeface = NULL;
    360     mThaiTypeface = NULL;
    361     mDevanagariRegularTypeface = NULL;
    362     mTamilRegularTypeface = NULL;
    363     mTamilBoldTypeface = NULL;
    364 }
    365 
    366 void TextLayoutShaper::unrefTypefaces() {
    367     SkSafeUnref(mDefaultTypeface);
    368     SkSafeUnref(mArabicTypeface);
    369     SkSafeUnref(mHebrewRegularTypeface);
    370     SkSafeUnref(mHebrewBoldTypeface);
    371     SkSafeUnref(mBengaliTypeface);
    372     SkSafeUnref(mThaiTypeface);
    373     SkSafeUnref(mDevanagariRegularTypeface);
    374     SkSafeUnref(mTamilRegularTypeface);
    375     SkSafeUnref(mTamilBoldTypeface);
    376 }
    377 
    378 TextLayoutShaper::~TextLayoutShaper() {
    379     unrefTypefaces();
    380     deleteShaperItemGlyphArrays();
    381 }
    382 
    383 void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
    384         size_t start, size_t count, size_t contextCount, int dirFlags) {
    385 
    386     computeValues(paint, chars, start, count, contextCount, dirFlags,
    387             &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs);
    388 #if DEBUG_ADVANCES
    389     ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
    390             contextCount, value->mTotalAdvance);
    391 #endif
    392 }
    393 
    394 void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
    395         size_t start, size_t count, size_t contextCount, int dirFlags,
    396         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
    397         Vector<jchar>* const outGlyphs) {
    398         if (!count) {
    399             *outTotalAdvance = 0;
    400             return;
    401         }
    402 
    403         UBiDiLevel bidiReq = 0;
    404         bool forceLTR = false;
    405         bool forceRTL = false;
    406 
    407         switch (dirFlags) {
    408             case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
    409             case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
    410             case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
    411             case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
    412             case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
    413             case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
    414         }
    415 
    416         bool useSingleRun = false;
    417         bool isRTL = forceRTL;
    418         if (forceLTR || forceRTL) {
    419             useSingleRun = true;
    420         } else {
    421             UBiDi* bidi = ubidi_open();
    422             if (bidi) {
    423                 UErrorCode status = U_ZERO_ERROR;
    424 #if DEBUG_GLYPHS
    425                 ALOGD("******** ComputeValues -- start");
    426                 ALOGD("      -- string = '%s'", String8(chars + start, count).string());
    427                 ALOGD("      -- start = %d", start);
    428                 ALOGD("      -- count = %d", count);
    429                 ALOGD("      -- contextCount = %d", contextCount);
    430                 ALOGD("      -- bidiReq = %d", bidiReq);
    431 #endif
    432                 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
    433                 if (U_SUCCESS(status)) {
    434                     int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
    435                     ssize_t rc = ubidi_countRuns(bidi, &status);
    436 #if DEBUG_GLYPHS
    437                     ALOGD("      -- dirFlags = %d", dirFlags);
    438                     ALOGD("      -- paraDir = %d", paraDir);
    439                     ALOGD("      -- run-count = %d", int(rc));
    440 #endif
    441                     if (U_SUCCESS(status) && rc == 1) {
    442                         // Normal case: one run, status is ok
    443                         isRTL = (paraDir == 1);
    444                         useSingleRun = true;
    445                     } else if (!U_SUCCESS(status) || rc < 1) {
    446                         ALOGW("Need to force to single run -- string = '%s',"
    447                                 " status = %d, rc = %d",
    448                                 String8(chars + start, count).string(), status, int(rc));
    449                         isRTL = (paraDir == 1);
    450                         useSingleRun = true;
    451                     } else {
    452                         int32_t end = start + count;
    453                         for (size_t i = 0; i < size_t(rc); ++i) {
    454                             int32_t startRun = -1;
    455                             int32_t lengthRun = -1;
    456                             UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
    457 
    458                             if (startRun == -1 || lengthRun == -1) {
    459                                 // Something went wrong when getting the visual run, need to clear
    460                                 // already computed data before doing a single run pass
    461                                 ALOGW("Visual run is not valid");
    462                                 outGlyphs->clear();
    463                                 outAdvances->clear();
    464                                 *outTotalAdvance = 0;
    465                                 isRTL = (paraDir == 1);
    466                                 useSingleRun = true;
    467                                 break;
    468                             }
    469 
    470                             if (startRun >= end) {
    471                                 continue;
    472                             }
    473                             int32_t endRun = startRun + lengthRun;
    474                             if (endRun <= int32_t(start)) {
    475                                 continue;
    476                             }
    477                             if (startRun < int32_t(start)) {
    478                                 startRun = int32_t(start);
    479                             }
    480                             if (endRun > end) {
    481                                 endRun = end;
    482                             }
    483 
    484                             lengthRun = endRun - startRun;
    485                             isRTL = (runDir == UBIDI_RTL);
    486                             jfloat runTotalAdvance = 0;
    487 #if DEBUG_GLYPHS
    488                             ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
    489                                     i, startRun, lengthRun, isRTL);
    490 #endif
    491                             computeRunValues(paint, chars + startRun, lengthRun, isRTL,
    492                                     outAdvances, &runTotalAdvance, outGlyphs);
    493 
    494                             *outTotalAdvance += runTotalAdvance;
    495                         }
    496                     }
    497                 } else {
    498                     ALOGW("Cannot set Para");
    499                     useSingleRun = true;
    500                     isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
    501                 }
    502                 ubidi_close(bidi);
    503             } else {
    504                 ALOGW("Cannot ubidi_open()");
    505                 useSingleRun = true;
    506                 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
    507             }
    508         }
    509 
    510         // Default single run case
    511         if (useSingleRun){
    512 #if DEBUG_GLYPHS
    513             ALOGD("Using a SINGLE BiDi Run "
    514                     "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
    515 #endif
    516             computeRunValues(paint, chars + start, count, isRTL,
    517                     outAdvances, outTotalAdvance, outGlyphs);
    518         }
    519 
    520 #if DEBUG_GLYPHS
    521         ALOGD("      -- Total returned glyphs-count = %d", outGlyphs->size());
    522         ALOGD("******** ComputeValues -- end");
    523 #endif
    524 }
    525 
    526 static void logGlyphs(HB_ShaperItem shaperItem) {
    527     ALOGD("         -- glyphs count=%d", shaperItem.num_glyphs);
    528     for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
    529         ALOGD("         -- glyph[%d] = %d, offset.x = %0.2f, offset.y = %0.2f", i,
    530                 shaperItem.glyphs[i],
    531                 HBFixedToFloat(shaperItem.offsets[i].x),
    532                 HBFixedToFloat(shaperItem.offsets[i].y));
    533     }
    534 }
    535 
    536 void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars,
    537         size_t count, bool isRTL,
    538         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
    539         Vector<jchar>* const outGlyphs) {
    540     if (!count) {
    541         // We cannot shape an empty run.
    542         *outTotalAdvance = 0;
    543         return;
    544     }
    545 
    546     // To be filled in later
    547     for (size_t i = 0; i < count; i++) {
    548         outAdvances->add(0);
    549     }
    550     UErrorCode error = U_ZERO_ERROR;
    551     bool useNormalizedString = false;
    552     for (ssize_t i = count - 1; i >= 0; --i) {
    553         UChar ch1 = chars[i];
    554         if (::ublock_getCode(ch1) == UBLOCK_COMBINING_DIACRITICAL_MARKS) {
    555             // So we have found a diacritic, let's get now the main code point which is paired
    556             // with it. As we can have several diacritics in a row, we need to iterate back again
    557 #if DEBUG_GLYPHS
    558             ALOGD("The BiDi run '%s' is containing a Diacritic at position %d",
    559                     String8(chars, count).string(), int(i));
    560 #endif
    561             ssize_t j = i - 1;
    562             for (; j >= 0;  --j) {
    563                 UChar ch2 = chars[j];
    564                 if (::ublock_getCode(ch2) != UBLOCK_COMBINING_DIACRITICAL_MARKS) {
    565                     break;
    566                 }
    567             }
    568 
    569             // We could not found the main code point, so we will just use the initial chars
    570             if (j < 0) {
    571                 break;
    572             }
    573 
    574 #if DEBUG_GLYPHS
    575             ALOGD("Found main code point at index %d", int(j));
    576 #endif
    577             // We found the main code point, so we can normalize the "chunk" and fill
    578             // the remaining with ZWSP so that the Paint.getTextWidth() APIs will still be able
    579             // to get one advance per char
    580             mBuffer.remove();
    581             Normalizer::normalize(UnicodeString(chars + j, i - j + 1),
    582                     UNORM_NFC, 0 /* no options */, mBuffer, error);
    583             if (U_SUCCESS(error)) {
    584                 if (!useNormalizedString) {
    585                     useNormalizedString = true;
    586                     mNormalizedString.setTo(false /* not terminated*/, chars, count);
    587                 }
    588                 // Set the normalized chars
    589                 for (ssize_t k = j; k < j + mBuffer.length(); ++k) {
    590                     mNormalizedString.setCharAt(k, mBuffer.charAt(k - j));
    591                 }
    592                 // Fill the remain part with ZWSP (ZWNJ and ZWJ would lead to weird results
    593                 // because some fonts are missing those glyphs)
    594                 for (ssize_t k = j + mBuffer.length(); k <= i; ++k) {
    595                     mNormalizedString.setCharAt(k, UNICODE_ZWSP);
    596                 }
    597             }
    598             i = j - 1;
    599         }
    600     }
    601 
    602     // Reverse "BiDi mirrored chars" in RTL mode only
    603     // See: http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBinaryProperties.txt
    604     // This is a workaround because Harfbuzz is not able to do mirroring in all cases and
    605     // script-run splitting with Harfbuzz is splitting on parenthesis
    606     if (isRTL) {
    607         for (ssize_t i = 0; i < ssize_t(count); i++) {
    608             UChar32 ch = chars[i];
    609             if (!u_isMirrored(ch)) continue;
    610             if (!useNormalizedString) {
    611                 useNormalizedString = true;
    612                 mNormalizedString.setTo(false /* not terminated*/, chars, count);
    613             }
    614             UChar result =  (UChar) u_charMirror(ch);
    615             mNormalizedString.setCharAt(i, result);
    616 #if DEBUG_GLYPHS
    617             ALOGD("Rewriting codepoint '%d' to '%d' at position %d",
    618                     ch, mNormalizedString[i], int(i));
    619 #endif
    620         }
    621     }
    622 
    623 #if DEBUG_GLYPHS
    624     if (useNormalizedString) {
    625         ALOGD("Will use normalized string '%s', length = %d",
    626                     String8(mNormalizedString.getTerminatedBuffer(),
    627                             mNormalizedString.length()).string(),
    628                     mNormalizedString.length());
    629     } else {
    630         ALOGD("Normalization is not needed or cannot be done, using initial string");
    631     }
    632 #endif
    633 
    634     assert(mNormalizedString.length() == count);
    635 
    636     // Set the string properties
    637     mShaperItem.string = useNormalizedString ? mNormalizedString.getTerminatedBuffer() : chars;
    638     mShaperItem.stringLength = count;
    639 
    640     // Define shaping paint properties
    641     mShapingPaint.setTextSize(paint->getTextSize());
    642     mShapingPaint.setTextSkewX(paint->getTextSkewX());
    643     mShapingPaint.setTextScaleX(paint->getTextScaleX());
    644     mShapingPaint.setFlags(paint->getFlags());
    645     mShapingPaint.setHinting(paint->getHinting());
    646 
    647     // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script
    648     // into the shaperItem
    649     ssize_t indexFontRun = isRTL ? mShaperItem.stringLength - 1 : 0;
    650     unsigned numCodePoints = 0;
    651     jfloat totalAdvance = 0;
    652     while ((isRTL) ?
    653             hb_utf16_script_run_prev(&numCodePoints, &mShaperItem.item, mShaperItem.string,
    654                     mShaperItem.stringLength, &indexFontRun):
    655             hb_utf16_script_run_next(&numCodePoints, &mShaperItem.item, mShaperItem.string,
    656                     mShaperItem.stringLength, &indexFontRun)) {
    657 
    658         ssize_t startScriptRun = mShaperItem.item.pos;
    659         size_t countScriptRun = mShaperItem.item.length;
    660         ssize_t endScriptRun = startScriptRun + countScriptRun;
    661 
    662 #if DEBUG_GLYPHS
    663         ALOGD("-------- Start of Script Run --------");
    664         ALOGD("Shaping Script Run with");
    665         ALOGD("         -- isRTL = %d", isRTL);
    666         ALOGD("         -- HB script = %d", mShaperItem.item.script);
    667         ALOGD("         -- startFontRun = %d", int(startScriptRun));
    668         ALOGD("         -- endFontRun = %d", int(endScriptRun));
    669         ALOGD("         -- countFontRun = %d", countScriptRun);
    670         ALOGD("         -- run = '%s'", String8(chars + startScriptRun, countScriptRun).string());
    671         ALOGD("         -- string = '%s'", String8(chars, count).string());
    672 #endif
    673 
    674         // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
    675         // and shape the Font run
    676         size_t glyphBaseCount = shapeFontRun(paint, isRTL);
    677 
    678 #if DEBUG_GLYPHS
    679         ALOGD("Got from Harfbuzz");
    680         ALOGD("         -- glyphBaseCount = %d", glyphBaseCount);
    681         ALOGD("         -- num_glypth = %d", mShaperItem.num_glyphs);
    682         ALOGD("         -- kerning_applied = %d", mShaperItem.kerning_applied);
    683         ALOGD("         -- isDevKernText = %d", paint->isDevKernText());
    684 
    685         logGlyphs(mShaperItem);
    686 #endif
    687 
    688         if (mShaperItem.advances == NULL || mShaperItem.num_glyphs == 0) {
    689 #if DEBUG_GLYPHS
    690             ALOGD("Advances array is empty or num_glypth = 0");
    691 #endif
    692             continue;
    693         }
    694 
    695 #if DEBUG_GLYPHS
    696         ALOGD("Returned logclusters");
    697         for (size_t i = 0; i < mShaperItem.num_glyphs; i++) {
    698             ALOGD("         -- lc[%d] = %d, hb-adv[%d] = %0.2f", i, mShaperItem.log_clusters[i],
    699                     i, HBFixedToFloat(mShaperItem.advances[i]));
    700         }
    701 #endif
    702         // Get Advances and their total
    703         jfloat currentAdvance = HBFixedToFloat(mShaperItem.advances[mShaperItem.log_clusters[0]]);
    704         jfloat totalFontRunAdvance = currentAdvance;
    705         outAdvances->replaceAt(currentAdvance, startScriptRun);
    706         for (size_t i = 1; i < countScriptRun; i++) {
    707             size_t clusterPrevious = mShaperItem.log_clusters[i - 1];
    708             size_t cluster = mShaperItem.log_clusters[i];
    709             if (cluster != clusterPrevious) {
    710                 currentAdvance = HBFixedToFloat(mShaperItem.advances[mShaperItem.log_clusters[i]]);
    711                 outAdvances->replaceAt(currentAdvance, startScriptRun + i);
    712             }
    713         }
    714         // TODO: can be removed and go back in the previous loop when Harfbuzz log clusters are fixed
    715         for (size_t i = 1; i < mShaperItem.num_glyphs; i++) {
    716             currentAdvance = HBFixedToFloat(mShaperItem.advances[i]);
    717             totalFontRunAdvance += currentAdvance;
    718         }
    719         totalAdvance += totalFontRunAdvance;
    720 
    721 #if DEBUG_ADVANCES
    722         ALOGD("Returned advances");
    723         for (size_t i = 0; i < countScriptRun; i++) {
    724             ALOGD("         -- hb-adv[%d] = %0.2f, log_clusters = %d, total = %0.2f", i,
    725                     (*outAdvances)[i], mShaperItem.log_clusters[i], totalFontRunAdvance);
    726         }
    727 #endif
    728 
    729         // Get Glyphs and reverse them in place if RTL
    730         if (outGlyphs) {
    731             size_t countGlyphs = mShaperItem.num_glyphs;
    732 #if DEBUG_GLYPHS
    733             ALOGD("Returned script run glyphs -- count = %d", countGlyphs);
    734 #endif
    735             for (size_t i = 0; i < countGlyphs; i++) {
    736                 jchar glyph = glyphBaseCount +
    737                         (jchar) mShaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i];
    738 #if DEBUG_GLYPHS
    739                 ALOGD("         -- glyph[%d] = %d", i, glyph);
    740 #endif
    741                 outGlyphs->add(glyph);
    742             }
    743         }
    744     }
    745 
    746     *outTotalAdvance = totalAdvance;
    747 
    748 #if DEBUG_GLYPHS
    749     ALOGD("-------- End of Script Run --------");
    750 #endif
    751 }
    752 
    753 /**
    754  * Return the first typeface in the logical change, starting with this typeface,
    755  * that contains the specified unichar, or NULL if none is found.
    756  *
    757  * Note that this function does _not_ increment the reference count on the typeface, as the
    758  * assumption is that its lifetime is managed elsewhere - in particular, the fallback typefaces
    759  * for the default font live in a global cache.
    760  */
    761 SkTypeface* TextLayoutShaper::typefaceForUnichar(const SkPaint* paint, SkTypeface* typeface,
    762         SkUnichar unichar, HB_Script script) {
    763     // Set the correct Typeface depending on the script
    764     switch (script) {
    765     case HB_Script_Arabic:
    766         typeface = getCachedTypeface(&mArabicTypeface, TYPEFACE_ARABIC);
    767 #if DEBUG_GLYPHS
    768         ALOGD("Using Arabic Typeface");
    769 #endif
    770         break;
    771 
    772     case HB_Script_Hebrew:
    773         if (typeface) {
    774             switch (typeface->style()) {
    775             case SkTypeface::kBold:
    776             case SkTypeface::kBoldItalic:
    777                 typeface = getCachedTypeface(&mHebrewBoldTypeface, TYPE_FACE_HEBREW_BOLD);
    778 #if DEBUG_GLYPHS
    779                 ALOGD("Using Hebrew Bold/BoldItalic Typeface");
    780 #endif
    781                 break;
    782 
    783             case SkTypeface::kNormal:
    784             case SkTypeface::kItalic:
    785             default:
    786                 typeface = getCachedTypeface(&mHebrewRegularTypeface, TYPE_FACE_HEBREW_REGULAR);
    787 #if DEBUG_GLYPHS
    788                 ALOGD("Using Hebrew Regular/Italic Typeface");
    789 #endif
    790                 break;
    791             }
    792         } else {
    793             typeface = getCachedTypeface(&mHebrewRegularTypeface, TYPE_FACE_HEBREW_REGULAR);
    794 #if DEBUG_GLYPHS
    795             ALOGD("Using Hebrew Regular Typeface");
    796 #endif
    797         }
    798         break;
    799 
    800     case HB_Script_Bengali:
    801         typeface = getCachedTypeface(&mBengaliTypeface, TYPEFACE_BENGALI);
    802 #if DEBUG_GLYPHS
    803         ALOGD("Using Bengali Typeface");
    804 #endif
    805         break;
    806 
    807     case HB_Script_Thai:
    808         typeface = getCachedTypeface(&mThaiTypeface, TYPEFACE_THAI);
    809 #if DEBUG_GLYPHS
    810         ALOGD("Using Thai Typeface");
    811 #endif
    812         break;
    813 
    814     case HB_Script_Devanagari:
    815        typeface = getCachedTypeface(&mDevanagariRegularTypeface, TYPEFACE_DEVANAGARI_REGULAR);
    816 #if DEBUG_GLYPHS
    817        ALOGD("Using Devanagari Regular Typeface");
    818 #endif
    819         break;
    820 
    821     case HB_Script_Tamil:
    822         if (typeface) {
    823             switch (typeface->style()) {
    824             case SkTypeface::kBold:
    825             case SkTypeface::kBoldItalic:
    826                 typeface = getCachedTypeface(&mTamilBoldTypeface, TYPEFACE_TAMIL_BOLD);
    827 #if DEBUG_GLYPHS
    828                 ALOGD("Using Tamil Bold Typeface");
    829 #endif
    830                 break;
    831 
    832             case SkTypeface::kNormal:
    833             case SkTypeface::kItalic:
    834             default:
    835                 typeface = getCachedTypeface(&mTamilRegularTypeface, TYPEFACE_TAMIL_REGULAR);
    836 #if DEBUG_GLYPHS
    837                 ALOGD("Using Tamil Regular Typeface");
    838 #endif
    839                 break;
    840             }
    841         } else {
    842             typeface = getCachedTypeface(&mTamilRegularTypeface, TYPEFACE_TAMIL_REGULAR);
    843 #if DEBUG_GLYPHS
    844             ALOGD("Using Tamil Regular Typeface");
    845 #endif
    846         }
    847         break;
    848 
    849     default:
    850 #if DEBUG_GLYPHS
    851         if (typeface) {
    852             ALOGD("Using Paint Typeface");
    853         }
    854 #endif
    855         break;
    856     }
    857     return typeface;
    858 }
    859 
    860 size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint, bool isRTL) {
    861     // Reset kerning
    862     mShaperItem.kerning_applied = false;
    863 
    864     // Update Harfbuzz Shaper
    865     mShaperItem.item.bidiLevel = isRTL;
    866 
    867     SkTypeface* typeface = paint->getTypeface();
    868 
    869     // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz
    870     // This is needed as the Typeface used for shaping can be not the default one
    871     // when we are shaping any script that needs to use a fallback Font.
    872     // If we are a "common" script we dont need to shift
    873     size_t baseGlyphCount = 0;
    874     SkUnichar firstUnichar = 0;
    875     switch (mShaperItem.item.script) {
    876     case HB_Script_Arabic:
    877     case HB_Script_Hebrew:
    878     case HB_Script_Bengali:
    879     case HB_Script_Devanagari:
    880     case HB_Script_Tamil:
    881     case HB_Script_Thai:{
    882         const uint16_t* text16 = (const uint16_t*)(mShaperItem.string + mShaperItem.item.pos);
    883         const uint16_t* text16End = text16 + mShaperItem.item.length;
    884         firstUnichar = SkUTF16_NextUnichar(&text16);
    885         while (firstUnichar == ' ' && text16 < text16End) {
    886             firstUnichar = SkUTF16_NextUnichar(&text16);
    887         }
    888         baseGlyphCount = paint->getBaseGlyphCount(firstUnichar);
    889         break;
    890     }
    891     default:
    892         break;
    893     }
    894 
    895     // We test the baseGlyphCount to see if the typeface supports the requested script
    896     if (baseGlyphCount != 0) {
    897         typeface = typefaceForUnichar(paint, typeface, firstUnichar, mShaperItem.item.script);
    898     }
    899 
    900     if (!typeface) {
    901         typeface = mDefaultTypeface;
    902 #if DEBUG_GLYPHS
    903         ALOGD("Using Default Typeface");
    904 #endif
    905     }
    906     mShapingPaint.setTypeface(typeface);
    907     mShaperItem.face = getCachedHBFace(typeface);
    908 
    909 #if DEBUG_GLYPHS
    910     ALOGD("Run typeface = %p, uniqueID = %d, hb_face = %p",
    911             typeface, typeface->uniqueID(), mShaperItem.face);
    912 #endif
    913 
    914     // Shape
    915     assert(mShaperItem.item.length > 0); // Harfbuzz will overwrite other memory if length is 0.
    916     ensureShaperItemGlyphArrays(mShaperItem.item.length * 3 / 2);
    917     mShaperItem.num_glyphs = mShaperItemGlyphArraySize;
    918     while (!HB_ShapeItem(&mShaperItem)) {
    919         // We overflowed our glyph arrays. Resize and retry.
    920         // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
    921         ensureShaperItemGlyphArrays(mShaperItem.num_glyphs * 2);
    922         mShaperItem.num_glyphs = mShaperItemGlyphArraySize;
    923     }
    924     return baseGlyphCount;
    925 }
    926 
    927 void TextLayoutShaper::ensureShaperItemGlyphArrays(size_t size) {
    928     if (size > mShaperItemGlyphArraySize) {
    929         deleteShaperItemGlyphArrays();
    930         createShaperItemGlyphArrays(size);
    931     }
    932 }
    933 
    934 void TextLayoutShaper::createShaperItemGlyphArrays(size_t size) {
    935 #if DEBUG_GLYPHS
    936     ALOGD("Creating Glyph Arrays with size = %d", size);
    937 #endif
    938     mShaperItemGlyphArraySize = size;
    939 
    940     // These arrays are all indexed by glyph.
    941     mShaperItem.glyphs = new HB_Glyph[size];
    942     mShaperItem.attributes = new HB_GlyphAttributes[size];
    943     mShaperItem.advances = new HB_Fixed[size];
    944     mShaperItem.offsets = new HB_FixedPoint[size];
    945 
    946     // Although the log_clusters array is indexed by character, Harfbuzz expects that
    947     // it is big enough to hold one element per glyph.  So we allocate log_clusters along
    948     // with the other glyph arrays above.
    949     mShaperItem.log_clusters = new unsigned short[size];
    950 }
    951 
    952 void TextLayoutShaper::deleteShaperItemGlyphArrays() {
    953     delete[] mShaperItem.glyphs;
    954     delete[] mShaperItem.attributes;
    955     delete[] mShaperItem.advances;
    956     delete[] mShaperItem.offsets;
    957     delete[] mShaperItem.log_clusters;
    958 }
    959 
    960 SkTypeface* TextLayoutShaper::getCachedTypeface(SkTypeface** typeface, const char path[]) {
    961     if (!*typeface) {
    962         *typeface = SkTypeface::CreateFromFile(path);
    963         // CreateFromFile(path) can return NULL if the path is non existing
    964         if (!*typeface) {
    965 #if DEBUG_GLYPHS
    966         ALOGD("Font path '%s' is not valid, will use default font", path);
    967 #endif
    968             return mDefaultTypeface;
    969         }
    970         (*typeface)->ref();
    971 #if DEBUG_GLYPHS
    972         ALOGD("Created SkTypeface from file '%s' with uniqueID = %d", path, (*typeface)->uniqueID());
    973 #endif
    974     }
    975     return *typeface;
    976 }
    977 
    978 HB_Face TextLayoutShaper::getCachedHBFace(SkTypeface* typeface) {
    979     SkFontID fontId = typeface->uniqueID();
    980     ssize_t index = mCachedHBFaces.indexOfKey(fontId);
    981     if (index >= 0) {
    982         return mCachedHBFaces.valueAt(index);
    983     }
    984     HB_Face face = HB_NewFace(typeface, harfbuzzSkiaGetTable);
    985     if (face) {
    986 #if DEBUG_GLYPHS
    987         ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface);
    988 #endif
    989         mCachedHBFaces.add(fontId, face);
    990     }
    991     return face;
    992 }
    993 
    994 void TextLayoutShaper::purgeCaches() {
    995     size_t cacheSize = mCachedHBFaces.size();
    996     for (size_t i = 0; i < cacheSize; i++) {
    997         HB_FreeFace(mCachedHBFaces.valueAt(i));
    998     }
    999     mCachedHBFaces.clear();
   1000     unrefTypefaces();
   1001     init();
   1002 }
   1003 
   1004 TextLayoutEngine::TextLayoutEngine() {
   1005     mShaper = new TextLayoutShaper();
   1006 #if USE_TEXT_LAYOUT_CACHE
   1007     mTextLayoutCache = new TextLayoutCache(mShaper);
   1008 #else
   1009     mTextLayoutCache = NULL;
   1010 #endif
   1011 }
   1012 
   1013 TextLayoutEngine::~TextLayoutEngine() {
   1014     delete mTextLayoutCache;
   1015     delete mShaper;
   1016 }
   1017 
   1018 sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
   1019         jint start, jint count, jint contextCount, jint dirFlags) {
   1020     sp<TextLayoutValue> value;
   1021 #if USE_TEXT_LAYOUT_CACHE
   1022     value = mTextLayoutCache->getValue(paint, text, start, count,
   1023             contextCount, dirFlags);
   1024     if (value == NULL) {
   1025         ALOGE("Cannot get TextLayoutCache value for text = '%s'",
   1026                 String8(text + start, count).string());
   1027     }
   1028 #else
   1029     value = new TextLayoutValue(count);
   1030     mShaper->computeValues(value.get(), paint,
   1031             reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
   1032 #endif
   1033     return value;
   1034 }
   1035 
   1036 void TextLayoutEngine::purgeCaches() {
   1037 #if USE_TEXT_LAYOUT_CACHE
   1038     mTextLayoutCache->clear();
   1039     mShaper->purgeCaches();
   1040 #if DEBUG_GLYPHS
   1041     ALOGD("Purged TextLayoutEngine caches");
   1042 #endif
   1043 #endif
   1044 }
   1045 
   1046 
   1047 } // namespace android
   1048