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 
     22 extern "C" {
     23   #include "harfbuzz-unicode.h"
     24 }
     25 
     26 namespace android {
     27 
     28 //--------------------------------------------------------------------------------------------------
     29 #if USE_TEXT_LAYOUT_CACHE
     30     ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutCache);
     31 #endif
     32 //--------------------------------------------------------------------------------------------------
     33 
     34 TextLayoutCache::TextLayoutCache() :
     35         mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
     36         mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
     37         mCacheHitCount(0), mNanosecondsSaved(0) {
     38     init();
     39 }
     40 
     41 TextLayoutCache::~TextLayoutCache() {
     42     mCache.clear();
     43 }
     44 
     45 void TextLayoutCache::init() {
     46     mCache.setOnEntryRemovedListener(this);
     47 
     48     mDebugLevel = readRtlDebugLevel();
     49     mDebugEnabled = mDebugLevel & kRtlDebugCaches;
     50     LOGD("Using debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
     51 
     52     mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
     53 
     54     if (mDebugEnabled) {
     55         LOGD("Initialization is done - Start time: %lld", mCacheStartTime);
     56     }
     57 
     58     mInitialized = true;
     59 }
     60 
     61 /*
     62  * Size management
     63  */
     64 
     65 uint32_t TextLayoutCache::getSize() {
     66     return mSize;
     67 }
     68 
     69 uint32_t TextLayoutCache::getMaxSize() {
     70     return mMaxSize;
     71 }
     72 
     73 void TextLayoutCache::setMaxSize(uint32_t maxSize) {
     74     mMaxSize = maxSize;
     75     removeOldests();
     76 }
     77 
     78 void TextLayoutCache::removeOldests() {
     79     while (mSize > mMaxSize) {
     80         mCache.removeOldest();
     81     }
     82 }
     83 
     84 /**
     85  *  Callbacks
     86  */
     87 void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) {
     88     if (desc != NULL) {
     89         size_t totalSizeToDelete = text.getSize() + desc->getSize();
     90         mSize -= totalSizeToDelete;
     91         if (mDebugEnabled) {
     92             LOGD("Cache value deleted, size = %d", totalSizeToDelete);
     93         }
     94         desc.clear();
     95     }
     96 }
     97 
     98 /*
     99  * Cache clearing
    100  */
    101 void TextLayoutCache::clear() {
    102     mCache.clear();
    103 }
    104 
    105 /*
    106  * Caching
    107  */
    108 sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint,
    109             const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
    110     AutoMutex _l(mLock);
    111     nsecs_t startTime = 0;
    112     if (mDebugEnabled) {
    113         startTime = systemTime(SYSTEM_TIME_MONOTONIC);
    114     }
    115 
    116     // Create the key
    117     TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
    118 
    119     // Get value from cache if possible
    120     sp<TextLayoutCacheValue> value = mCache.get(key);
    121 
    122     // Value not found for the key, we need to add a new value in the cache
    123     if (value == NULL) {
    124         if (mDebugEnabled) {
    125             startTime = systemTime(SYSTEM_TIME_MONOTONIC);
    126         }
    127 
    128         value = new TextLayoutCacheValue();
    129 
    130         // Compute advances and store them
    131         value->computeValues(paint, text, start, count, contextCount, dirFlags);
    132 
    133         nsecs_t endTime = systemTime(SYSTEM_TIME_MONOTONIC);
    134 
    135         // Don't bother to add in the cache if the entry is too big
    136         size_t size = key.getSize() + value->getSize();
    137         if (size <= mMaxSize) {
    138             // Cleanup to make some room if needed
    139             if (mSize + size > mMaxSize) {
    140                 if (mDebugEnabled) {
    141                     LOGD("Need to clean some entries for making some room for a new entry");
    142                 }
    143                 while (mSize + size > mMaxSize) {
    144                     // This will call the callback
    145                     mCache.removeOldest();
    146                 }
    147             }
    148 
    149             // Update current cache size
    150             mSize += size;
    151 
    152             // Copy the text when we insert the new entry
    153             key.internalTextCopy();
    154             mCache.put(key, value);
    155 
    156             if (mDebugEnabled) {
    157                 // Update timing information for statistics
    158                 value->setElapsedTime(endTime - startTime);
    159 
    160                 LOGD("CACHE MISS: Added entry with "
    161                         "count=%d, entry size %d bytes, remaining space %d bytes"
    162                         " - Compute time in nanos: %d - Text='%s' ",
    163                         count, size, mMaxSize - mSize, value->getElapsedTime(),
    164                         String8(text, count).string());
    165             }
    166         } else {
    167             if (mDebugEnabled) {
    168                 LOGD("CACHE MISS: Calculated but not storing entry because it is too big "
    169                         "with start=%d count=%d contextCount=%d, "
    170                         "entry size %d bytes, remaining space %d bytes"
    171                         " - Compute time in nanos: %lld - Text='%s'",
    172                         start, count, contextCount, size, mMaxSize - mSize, endTime,
    173                         String8(text, count).string());
    174             }
    175             value.clear();
    176         }
    177     } else {
    178         // This is a cache hit, just log timestamp and user infos
    179         if (mDebugEnabled) {
    180             nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
    181             mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
    182             ++mCacheHitCount;
    183 
    184             if (value->getElapsedTime() > 0) {
    185                 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
    186                         / ((float)value->getElapsedTime()));
    187                 LOGD("CACHE HIT #%d with start=%d count=%d contextCount=%d"
    188                         "- Compute time in nanos: %d - "
    189                         "Cache get time in nanos: %lld - Gain in percent: %2.2f - Text='%s' ",
    190                         mCacheHitCount, start, count, contextCount,
    191                         value->getElapsedTime(), elapsedTimeThruCacheGet, deltaPercent,
    192                         String8(text, count).string());
    193             }
    194             if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
    195                 dumpCacheStats();
    196             }
    197         }
    198     }
    199     return value;
    200 }
    201 
    202 void TextLayoutCache::dumpCacheStats() {
    203     float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
    204     float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
    205     LOGD("------------------------------------------------");
    206     LOGD("Cache stats");
    207     LOGD("------------------------------------------------");
    208     LOGD("pid       : %d", getpid());
    209     LOGD("running   : %.0f seconds", timeRunningInSec);
    210     LOGD("entries   : %d", mCache.size());
    211     LOGD("size      : %d bytes", mMaxSize);
    212     LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
    213     LOGD("hits      : %d", mCacheHitCount);
    214     LOGD("saved     : %lld milliseconds", mNanosecondsSaved / 1000000);
    215     LOGD("------------------------------------------------");
    216 }
    217 
    218 /**
    219  * TextLayoutCacheKey
    220  */
    221 TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0),
    222         dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
    223         hinting(SkPaint::kNo_Hinting)  {
    224 }
    225 
    226 TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
    227         size_t start, size_t count, size_t contextCount, int dirFlags) :
    228             text(text), start(start), count(count), contextCount(contextCount),
    229             dirFlags(dirFlags) {
    230     typeface = paint->getTypeface();
    231     textSize = paint->getTextSize();
    232     textSkewX = paint->getTextSkewX();
    233     textScaleX = paint->getTextScaleX();
    234     flags = paint->getFlags();
    235     hinting = paint->getHinting();
    236 }
    237 
    238 TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
    239         text(NULL),
    240         textCopy(other.textCopy),
    241         start(other.start),
    242         count(other.count),
    243         contextCount(other.contextCount),
    244         dirFlags(other.dirFlags),
    245         typeface(other.typeface),
    246         textSize(other.textSize),
    247         textSkewX(other.textSkewX),
    248         textScaleX(other.textScaleX),
    249         flags(other.flags),
    250         hinting(other.hinting) {
    251     if (other.text) {
    252         textCopy.setTo(other.text, other.contextCount);
    253     }
    254 }
    255 
    256 int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
    257     int deltaInt = lhs.start - rhs.start;
    258     if (deltaInt != 0) return (deltaInt);
    259 
    260     deltaInt = lhs.count - rhs.count;
    261     if (deltaInt != 0) return (deltaInt);
    262 
    263     deltaInt = lhs.contextCount - rhs.contextCount;
    264     if (deltaInt != 0) return (deltaInt);
    265 
    266     if (lhs.typeface < rhs.typeface) return -1;
    267     if (lhs.typeface > rhs.typeface) return +1;
    268 
    269     if (lhs.textSize < rhs.textSize) return -1;
    270     if (lhs.textSize > rhs.textSize) return +1;
    271 
    272     if (lhs.textSkewX < rhs.textSkewX) return -1;
    273     if (lhs.textSkewX > rhs.textSkewX) return +1;
    274 
    275     if (lhs.textScaleX < rhs.textScaleX) return -1;
    276     if (lhs.textScaleX > rhs.textScaleX) return +1;
    277 
    278     deltaInt = lhs.flags - rhs.flags;
    279     if (deltaInt != 0) return (deltaInt);
    280 
    281     deltaInt = lhs.hinting - rhs.hinting;
    282     if (deltaInt != 0) return (deltaInt);
    283 
    284     deltaInt = lhs.dirFlags - rhs.dirFlags;
    285     if (deltaInt) return (deltaInt);
    286 
    287     return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
    288 }
    289 
    290 void TextLayoutCacheKey::internalTextCopy() {
    291     textCopy.setTo(text, contextCount);
    292     text = NULL;
    293 }
    294 
    295 size_t TextLayoutCacheKey::getSize() {
    296     return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
    297 }
    298 
    299 /**
    300  * TextLayoutCacheValue
    301  */
    302 TextLayoutCacheValue::TextLayoutCacheValue() :
    303         mTotalAdvance(0), mElapsedTime(0) {
    304 }
    305 
    306 void TextLayoutCacheValue::setElapsedTime(uint32_t time) {
    307     mElapsedTime = time;
    308 }
    309 
    310 uint32_t TextLayoutCacheValue::getElapsedTime() {
    311     return mElapsedTime;
    312 }
    313 
    314 void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars,
    315         size_t start, size_t count, size_t contextCount, int dirFlags) {
    316     // Give a hint for advances, glyphs and log clusters vectors size
    317     mAdvances.setCapacity(contextCount);
    318     mGlyphs.setCapacity(contextCount);
    319 
    320     computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
    321             &mAdvances, &mTotalAdvance, &mGlyphs);
    322 #if DEBUG_ADVANCES
    323     LOGD("Advances - start=%d, count=%d, countextCount=%d, totalAdvance=%f", start, count,
    324             contextCount, mTotalAdvance);
    325 #endif
    326 }
    327 
    328 size_t TextLayoutCacheValue::getSize() {
    329     return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvances.capacity() +
    330             sizeof(jchar) * mGlyphs.capacity();
    331 }
    332 
    333 void TextLayoutCacheValue::initShaperItem(HB_ShaperItem& shaperItem, HB_FontRec* font,
    334         FontData* fontData, SkPaint* paint, const UChar* chars, size_t contextCount) {
    335     // Zero the Shaper struct
    336     memset(&shaperItem, 0, sizeof(shaperItem));
    337 
    338     font->klass = &harfbuzzSkiaClass;
    339     font->userData = 0;
    340 
    341     // The values which harfbuzzSkiaClass returns are already scaled to
    342     // pixel units, so we just set all these to one to disable further
    343     // scaling.
    344     font->x_ppem = 1;
    345     font->y_ppem = 1;
    346     font->x_scale = 1;
    347     font->y_scale = 1;
    348 
    349     shaperItem.font = font;
    350     shaperItem.face = HB_NewFace(shaperItem.font, harfbuzzSkiaGetTable);
    351 
    352     // Reset kerning
    353     shaperItem.kerning_applied = false;
    354 
    355     // Define font data
    356     fontData->typeFace = paint->getTypeface();
    357     fontData->textSize = paint->getTextSize();
    358     fontData->textSkewX = paint->getTextSkewX();
    359     fontData->textScaleX = paint->getTextScaleX();
    360     fontData->flags = paint->getFlags();
    361     fontData->hinting = paint->getHinting();
    362 
    363     shaperItem.font->userData = fontData;
    364 
    365     // We cannot know, ahead of time, how many glyphs a given script run
    366     // will produce. We take a guess that script runs will not produce more
    367     // than twice as many glyphs as there are code points plus a bit of
    368     // padding and fallback if we find that we are wrong.
    369     createGlyphArrays(shaperItem, (contextCount + 2) * 2);
    370 
    371     // Create log clusters array
    372     shaperItem.log_clusters = new unsigned short[contextCount];
    373 
    374     // Set the string properties
    375     shaperItem.string = chars;
    376     shaperItem.stringLength = contextCount;
    377 }
    378 
    379 void TextLayoutCacheValue::freeShaperItem(HB_ShaperItem& shaperItem) {
    380     deleteGlyphArrays(shaperItem);
    381     delete[] shaperItem.log_clusters;
    382     HB_FreeFace(shaperItem.face);
    383 }
    384 
    385 void TextLayoutCacheValue::shapeRun(HB_ShaperItem& shaperItem, size_t start, size_t count,
    386         bool isRTL) {
    387     // Update Harfbuzz Shaper
    388     shaperItem.item.pos = start;
    389     shaperItem.item.length = count;
    390     shaperItem.item.bidiLevel = isRTL;
    391 
    392     shaperItem.item.script = isRTL ? HB_Script_Arabic : HB_Script_Common;
    393 
    394     // Shape
    395     while (!HB_ShapeItem(&shaperItem)) {
    396         // We overflowed our arrays. Resize and retry.
    397         // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
    398         deleteGlyphArrays(shaperItem);
    399         createGlyphArrays(shaperItem, shaperItem.num_glyphs << 1);
    400     }
    401 }
    402 
    403 void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
    404         size_t start, size_t count, size_t contextCount, int dirFlags,
    405         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
    406         Vector<jchar>* const outGlyphs) {
    407 
    408         UBiDiLevel bidiReq = 0;
    409         bool forceLTR = false;
    410         bool forceRTL = false;
    411 
    412         switch (dirFlags) {
    413             case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
    414             case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
    415             case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
    416             case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
    417             case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
    418             case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
    419         }
    420 
    421         HB_ShaperItem shaperItem;
    422         HB_FontRec font;
    423         FontData fontData;
    424 
    425         // Initialize Harfbuzz Shaper
    426         initShaperItem(shaperItem, &font, &fontData, paint, chars, contextCount);
    427 
    428         bool useSingleRun = false;
    429         bool isRTL = forceRTL;
    430         if (forceLTR || forceRTL) {
    431             useSingleRun = true;
    432         } else {
    433             UBiDi* bidi = ubidi_open();
    434             if (bidi) {
    435                 UErrorCode status = U_ZERO_ERROR;
    436 #if DEBUG_GLYPHS
    437                 LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq);
    438 #endif
    439                 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
    440                 if (U_SUCCESS(status)) {
    441                     int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
    442                     ssize_t rc = ubidi_countRuns(bidi, &status);
    443 #if DEBUG_GLYPHS
    444                     LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d",
    445                             dirFlags, rc, paraDir);
    446 #endif
    447                     if (U_SUCCESS(status) && rc == 1) {
    448                         // Normal case: one run, status is ok
    449                         isRTL = (paraDir == 1);
    450                         useSingleRun = true;
    451                     } else if (!U_SUCCESS(status) || rc < 1) {
    452                         LOGW("computeValuesWithHarfbuzz -- need to force to single run");
    453                         isRTL = (paraDir == 1);
    454                         useSingleRun = true;
    455                     } else {
    456                         int32_t end = start + count;
    457                         for (size_t i = 0; i < size_t(rc); ++i) {
    458                             int32_t startRun = -1;
    459                             int32_t lengthRun = -1;
    460                             UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
    461 
    462                             if (startRun == -1 || lengthRun == -1) {
    463                                 // Something went wrong when getting the visual run, need to clear
    464                                 // already computed data before doing a single run pass
    465                                 LOGW("computeValuesWithHarfbuzz -- visual run is not valid");
    466                                 outGlyphs->clear();
    467                                 outAdvances->clear();
    468                                 *outTotalAdvance = 0;
    469                                 isRTL = (paraDir == 1);
    470                                 useSingleRun = true;
    471                                 break;
    472                             }
    473 
    474                             if (startRun >= end) {
    475                                 continue;
    476                             }
    477                             int32_t endRun = startRun + lengthRun;
    478                             if (endRun <= int32_t(start)) {
    479                                 continue;
    480                             }
    481                             if (startRun < int32_t(start)) {
    482                                 startRun = int32_t(start);
    483                             }
    484                             if (endRun > end) {
    485                                 endRun = end;
    486                             }
    487 
    488                             lengthRun = endRun - startRun;
    489                             isRTL = (runDir == UBIDI_RTL);
    490                             jfloat runTotalAdvance = 0;
    491 #if DEBUG_GLYPHS
    492                             LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d isRTL=%d",
    493                                     startRun, lengthRun, isRTL);
    494 #endif
    495                             computeRunValuesWithHarfbuzz(shaperItem, paint,
    496                                     startRun, lengthRun, isRTL,
    497                                     outAdvances, &runTotalAdvance, outGlyphs);
    498 
    499                             *outTotalAdvance += runTotalAdvance;
    500                         }
    501                     }
    502                 } else {
    503                     LOGW("computeValuesWithHarfbuzz -- cannot set Para");
    504                     useSingleRun = true;
    505                     isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
    506                 }
    507                 ubidi_close(bidi);
    508             } else {
    509                 LOGW("computeValuesWithHarfbuzz -- cannot ubidi_open()");
    510                 useSingleRun = true;
    511                 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
    512             }
    513         }
    514 
    515         // Default single run case
    516         if (useSingleRun){
    517 #if DEBUG_GLYPHS
    518             LOGD("computeValuesWithHarfbuzz -- Using a SINGLE Run "
    519                     "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL);
    520 #endif
    521             computeRunValuesWithHarfbuzz(shaperItem, paint,
    522                     start, count, isRTL,
    523                     outAdvances, outTotalAdvance, outGlyphs);
    524         }
    525 
    526         // Cleaning
    527         freeShaperItem(shaperItem);
    528 
    529 #if DEBUG_GLYPHS
    530         LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", outGlyphs->size());
    531 #endif
    532 }
    533 
    534 static void logGlyphs(HB_ShaperItem shaperItem) {
    535     LOGD("Got glyphs - count=%d", shaperItem.num_glyphs);
    536     for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
    537         LOGD("      glyph[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i],
    538                 HBFixedToFloat(shaperItem.offsets[i].x),
    539                 HBFixedToFloat(shaperItem.offsets[i].y));
    540     }
    541 }
    542 
    543 void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(HB_ShaperItem& shaperItem, SkPaint* paint,
    544         size_t start, size_t count, bool isRTL,
    545         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
    546         Vector<jchar>* const outGlyphs) {
    547 
    548     shapeRun(shaperItem, start, count, isRTL);
    549 
    550 #if DEBUG_GLYPHS
    551     LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs,
    552             shaperItem.kerning_applied);
    553     LOGD("         -- string= '%s'", String8(chars + start, count).string());
    554     LOGD("         -- isDevKernText=%d", paint->isDevKernText());
    555 
    556     logGlyphs(shaperItem);
    557 #endif
    558 
    559     if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) {
    560 #if DEBUG_GLYPHS
    561     LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0");
    562 #endif
    563         outAdvances->insertAt(0, outAdvances->size(), count);
    564         *outTotalAdvance = 0;
    565         return;
    566     }
    567 
    568     // Get Advances and their total
    569     jfloat currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]);
    570     jfloat totalAdvance = currentAdvance;
    571     outAdvances->add(currentAdvance);
    572     for (size_t i = 1; i < count; i++) {
    573         size_t clusterPrevious = shaperItem.log_clusters[i - 1];
    574         size_t cluster = shaperItem.log_clusters[i];
    575         if (cluster == clusterPrevious) {
    576             outAdvances->add(0);
    577         } else {
    578             currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]);
    579             totalAdvance += currentAdvance;
    580             outAdvances->add(currentAdvance);
    581         }
    582     }
    583     *outTotalAdvance = totalAdvance;
    584 
    585 #if DEBUG_ADVANCES
    586     for (size_t i = 0; i < count; i++) {
    587         LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i,
    588                 (*outAdvances)[i], shaperItem.log_clusters[i], totalAdvance);
    589     }
    590 #endif
    591 
    592     // Get Glyphs and reverse them in place if RTL
    593     if (outGlyphs) {
    594         size_t countGlyphs = shaperItem.num_glyphs;
    595         for (size_t i = 0; i < countGlyphs; i++) {
    596             jchar glyph = (jchar) shaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i];
    597 #if DEBUG_GLYPHS
    598             LOGD("HARFBUZZ  -- glyph[%d]=%d", i, glyph);
    599 #endif
    600             outGlyphs->add(glyph);
    601         }
    602     }
    603 }
    604 
    605 void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem& shaperItem) {
    606     delete[] shaperItem.glyphs;
    607     delete[] shaperItem.attributes;
    608     delete[] shaperItem.advances;
    609     delete[] shaperItem.offsets;
    610 }
    611 
    612 void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem& shaperItem, int size) {
    613     shaperItem.glyphs = new HB_Glyph[size];
    614     shaperItem.attributes = new HB_GlyphAttributes[size];
    615     shaperItem.advances = new HB_Fixed[size];
    616     shaperItem.offsets = new HB_FixedPoint[size];
    617     shaperItem.num_glyphs = size;
    618 }
    619 
    620 } // namespace android
    621