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 <utils/JenkinsHash.h>
     20 
     21 #include "TextLayoutCache.h"
     22 #include "TextLayout.h"
     23 #include "SkFontHost.h"
     24 #include "SkTypeface_android.h"
     25 #include "HarfBuzzNGFaceSkia.h"
     26 #include <unicode/unistr.h>
     27 #include <unicode/uchar.h>
     28 #include <hb-icu.h>
     29 
     30 namespace android {
     31 
     32 //--------------------------------------------------------------------------------------------------
     33 
     34 ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine);
     35 
     36 //--------------------------------------------------------------------------------------------------
     37 
     38 TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) :
     39         mShaper(shaper),
     40         mCache(LruCache<TextLayoutCacheKey, sp<TextLayoutValue> >::kUnlimitedCapacity),
     41         mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
     42         mCacheHitCount(0), mNanosecondsSaved(0) {
     43     init();
     44 }
     45 
     46 TextLayoutCache::~TextLayoutCache() {
     47     mCache.clear();
     48 }
     49 
     50 void TextLayoutCache::init() {
     51     mCache.setOnEntryRemovedListener(this);
     52 
     53     mDebugLevel = readRtlDebugLevel();
     54     mDebugEnabled = mDebugLevel & kRtlDebugCaches;
     55     ALOGD("Using debug level = %d - Debug Enabled = %d", mDebugLevel, mDebugEnabled);
     56 
     57     mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
     58 
     59     if (mDebugEnabled) {
     60         ALOGD("Initialization is done - Start time = %lld", mCacheStartTime);
     61     }
     62 
     63     mInitialized = true;
     64 }
     65 
     66 /**
     67  *  Callbacks
     68  */
     69 void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc) {
     70     size_t totalSizeToDelete = text.getSize() + desc->getSize();
     71     mSize -= totalSizeToDelete;
     72     if (mDebugEnabled) {
     73         ALOGD("Cache value %p deleted, size = %d", desc.get(), totalSizeToDelete);
     74     }
     75 }
     76 
     77 /*
     78  * Cache clearing
     79  */
     80 void TextLayoutCache::purgeCaches() {
     81     AutoMutex _l(mLock);
     82     mCache.clear();
     83     mShaper->purgeCaches();
     84 }
     85 
     86 /*
     87  * Caching
     88  */
     89 sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
     90             const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
     91     AutoMutex _l(mLock);
     92     nsecs_t startTime = 0;
     93     if (mDebugEnabled) {
     94         startTime = systemTime(SYSTEM_TIME_MONOTONIC);
     95     }
     96 
     97     // Create the key
     98     TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
     99 
    100     // Get value from cache if possible
    101     sp<TextLayoutValue> value = mCache.get(key);
    102 
    103     // Value not found for the key, we need to add a new value in the cache
    104     if (value == NULL) {
    105         if (mDebugEnabled) {
    106             startTime = systemTime(SYSTEM_TIME_MONOTONIC);
    107         }
    108 
    109         value = new TextLayoutValue(contextCount);
    110 
    111         // Compute advances and store them
    112         mShaper->computeValues(value.get(), paint,
    113                 reinterpret_cast<const UChar*>(key.getText()), start, count,
    114                 size_t(contextCount), int(dirFlags));
    115 
    116         if (mDebugEnabled) {
    117             value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
    118         }
    119 
    120         // Don't bother to add in the cache if the entry is too big
    121         size_t size = key.getSize() + value->getSize();
    122         if (size <= mMaxSize) {
    123             // Cleanup to make some room if needed
    124             if (mSize + size > mMaxSize) {
    125                 if (mDebugEnabled) {
    126                     ALOGD("Need to clean some entries for making some room for a new entry");
    127                 }
    128                 while (mSize + size > mMaxSize) {
    129                     // This will call the callback
    130                     bool removedOne = mCache.removeOldest();
    131                     LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we "
    132                             "failed to remove the oldest entry.  "
    133                             "mSize = %u, size = %u, mMaxSize = %u, mCache.size() = %u",
    134                             mSize, size, mMaxSize, mCache.size());
    135                 }
    136             }
    137 
    138             // Update current cache size
    139             mSize += size;
    140 
    141             bool putOne = mCache.put(key, value);
    142             LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache.  "
    143                     "This indicates that the cache already has an entry with the "
    144                     "same key but it should not since we checked earlier!"
    145                     " - start = %d, count = %d, contextCount = %d - Text = '%s'",
    146                     start, count, contextCount, String8(key.getText() + start, count).string());
    147 
    148             if (mDebugEnabled) {
    149                 nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
    150                 ALOGD("CACHE MISS: Added entry %p "
    151                         "with start = %d, count = %d, contextCount = %d, "
    152                         "entry size %d bytes, remaining space %d bytes"
    153                         " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'",
    154                         value.get(), start, count, contextCount, size, mMaxSize - mSize,
    155                         value->getElapsedTime() * 0.000001f,
    156                         (totalTime - value->getElapsedTime()) * 0.000001f,
    157                         String8(key.getText() + start, count).string());
    158             }
    159         } else {
    160             if (mDebugEnabled) {
    161                 ALOGD("CACHE MISS: Calculated but not storing entry because it is too big "
    162                         "with start = %d, count = %d, contextCount = %d, "
    163                         "entry size %d bytes, remaining space %d bytes"
    164                         " - Compute time %0.6f ms - Text = '%s'",
    165                         start, count, contextCount, size, mMaxSize - mSize,
    166                         value->getElapsedTime() * 0.000001f,
    167                         String8(key.getText() + start, count).string());
    168             }
    169         }
    170     } else {
    171         // This is a cache hit, just log timestamp and user infos
    172         if (mDebugEnabled) {
    173             nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
    174             mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
    175             ++mCacheHitCount;
    176 
    177             if (value->getElapsedTime() > 0) {
    178                 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
    179                         / ((float)value->getElapsedTime()));
    180                 ALOGD("CACHE HIT #%d with start = %d, count = %d, contextCount = %d"
    181                         "- Compute time %0.6f ms - "
    182                         "Cache get time %0.6f ms - Gain in percent: %2.2f - Text = '%s'",
    183                         mCacheHitCount, start, count, contextCount,
    184                         value->getElapsedTime() * 0.000001f,
    185                         elapsedTimeThruCacheGet * 0.000001f,
    186                         deltaPercent,
    187                         String8(key.getText() + start, count).string());
    188             }
    189             if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
    190                 dumpCacheStats();
    191             }
    192         }
    193     }
    194     return value;
    195 }
    196 
    197 void TextLayoutCache::dumpCacheStats() {
    198     float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
    199     float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
    200 
    201     size_t cacheSize = mCache.size();
    202 
    203     ALOGD("------------------------------------------------");
    204     ALOGD("Cache stats");
    205     ALOGD("------------------------------------------------");
    206     ALOGD("pid       : %d", getpid());
    207     ALOGD("running   : %.0f seconds", timeRunningInSec);
    208     ALOGD("entries   : %d", cacheSize);
    209     ALOGD("max size  : %d bytes", mMaxSize);
    210     ALOGD("used      : %d bytes according to mSize", mSize);
    211     ALOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
    212     ALOGD("hits      : %d", mCacheHitCount);
    213     ALOGD("saved     : %0.6f ms", mNanosecondsSaved * 0.000001f);
    214     ALOGD("------------------------------------------------");
    215 }
    216 
    217 /**
    218  * TextLayoutCacheKey
    219  */
    220 TextLayoutCacheKey::TextLayoutCacheKey(): start(0), count(0), contextCount(0),
    221         dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
    222         hinting(SkPaint::kNo_Hinting), variant(SkPaint::kDefault_Variant), language()  {
    223 }
    224 
    225 TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
    226         size_t start, size_t count, size_t contextCount, int dirFlags) :
    227             start(start), count(count), contextCount(contextCount),
    228             dirFlags(dirFlags) {
    229     textCopy.setTo(text, contextCount);
    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     variant = paint->getFontVariant();
    237     language = paint->getLanguage();
    238 }
    239 
    240 TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
    241         textCopy(other.textCopy),
    242         start(other.start),
    243         count(other.count),
    244         contextCount(other.contextCount),
    245         dirFlags(other.dirFlags),
    246         typeface(other.typeface),
    247         textSize(other.textSize),
    248         textSkewX(other.textSkewX),
    249         textScaleX(other.textScaleX),
    250         flags(other.flags),
    251         hinting(other.hinting),
    252         variant(other.variant),
    253         language(other.language) {
    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     deltaInt = lhs.variant - rhs.variant;
    288     if (deltaInt) return (deltaInt);
    289 
    290     if (lhs.language < rhs.language) return -1;
    291     if (lhs.language > rhs.language) return +1;
    292 
    293     return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
    294 }
    295 
    296 size_t TextLayoutCacheKey::getSize() const {
    297     return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
    298 }
    299 
    300 hash_t TextLayoutCacheKey::hash() const {
    301     uint32_t hash = JenkinsHashMix(0, start);
    302     hash = JenkinsHashMix(hash, count);
    303     /* contextCount not needed because it's included in text, below */
    304     hash = JenkinsHashMix(hash, hash_type(typeface));
    305     hash = JenkinsHashMix(hash, hash_type(textSize));
    306     hash = JenkinsHashMix(hash, hash_type(textSkewX));
    307     hash = JenkinsHashMix(hash, hash_type(textScaleX));
    308     hash = JenkinsHashMix(hash, flags);
    309     hash = JenkinsHashMix(hash, hinting);
    310     hash = JenkinsHashMix(hash, variant);
    311     // Note: leaving out language is not problematic, as equality comparisons
    312     // are still valid - the only bad thing that could happen is collisions.
    313     hash = JenkinsHashMixShorts(hash, getText(), contextCount);
    314     return JenkinsHashWhiten(hash);
    315 }
    316 
    317 /**
    318  * TextLayoutCacheValue
    319  */
    320 TextLayoutValue::TextLayoutValue(size_t contextCount) :
    321         mTotalAdvance(0), mElapsedTime(0) {
    322     // Give a hint for advances and glyphs vectors size
    323     mAdvances.setCapacity(contextCount);
    324     mGlyphs.setCapacity(contextCount);
    325     mPos.setCapacity(contextCount * 2);
    326 }
    327 
    328 size_t TextLayoutValue::getSize() const {
    329     return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
    330             sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity();
    331 }
    332 
    333 void TextLayoutValue::setElapsedTime(uint32_t time) {
    334     mElapsedTime = time;
    335 }
    336 
    337 uint32_t TextLayoutValue::getElapsedTime() {
    338     return mElapsedTime;
    339 }
    340 
    341 TextLayoutShaper::TextLayoutShaper() {
    342     mBuffer = hb_buffer_create();
    343 }
    344 
    345 TextLayoutShaper::~TextLayoutShaper() {
    346     hb_buffer_destroy(mBuffer);
    347 }
    348 
    349 void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
    350         size_t start, size_t count, size_t contextCount, int dirFlags) {
    351 
    352     computeValues(paint, chars, start, count, contextCount, dirFlags,
    353             &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos);
    354 #if DEBUG_ADVANCES
    355     ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
    356             contextCount, value->mTotalAdvance);
    357 #endif
    358 }
    359 
    360 void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
    361         size_t start, size_t count, size_t contextCount, int dirFlags,
    362         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
    363         Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
    364         *outTotalAdvance = 0;
    365         if (!count) {
    366             return;
    367         }
    368 
    369         UBiDiLevel bidiReq = 0;
    370         bool forceLTR = false;
    371         bool forceRTL = false;
    372 
    373         switch (dirFlags & kBidi_Mask) {
    374             case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
    375             case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
    376             case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
    377             case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
    378             case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
    379             case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
    380         }
    381 
    382         bool useSingleRun = false;
    383         bool isRTL = forceRTL;
    384         if (forceLTR || forceRTL) {
    385             useSingleRun = true;
    386         } else {
    387             UBiDi* bidi = ubidi_open();
    388             if (bidi) {
    389                 UErrorCode status = U_ZERO_ERROR;
    390 #if DEBUG_GLYPHS
    391                 ALOGD("******** ComputeValues -- start");
    392                 ALOGD("      -- string = '%s'", String8(chars + start, count).string());
    393                 ALOGD("      -- start = %d", start);
    394                 ALOGD("      -- count = %d", count);
    395                 ALOGD("      -- contextCount = %d", contextCount);
    396                 ALOGD("      -- bidiReq = %d", bidiReq);
    397 #endif
    398                 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
    399                 if (U_SUCCESS(status)) {
    400                     int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
    401                     ssize_t rc = ubidi_countRuns(bidi, &status);
    402 #if DEBUG_GLYPHS
    403                     ALOGD("      -- dirFlags = %d", dirFlags);
    404                     ALOGD("      -- paraDir = %d", paraDir);
    405                     ALOGD("      -- run-count = %d", int(rc));
    406 #endif
    407                     if (U_SUCCESS(status) && rc == 1) {
    408                         // Normal case: one run, status is ok
    409                         isRTL = (paraDir == 1);
    410                         useSingleRun = true;
    411                     } else if (!U_SUCCESS(status) || rc < 1) {
    412                         ALOGW("Need to force to single run -- string = '%s',"
    413                                 " status = %d, rc = %d",
    414                                 String8(chars + start, count).string(), status, int(rc));
    415                         isRTL = (paraDir == 1);
    416                         useSingleRun = true;
    417                     } else {
    418                         int32_t end = start + count;
    419                         for (size_t i = 0; i < size_t(rc); ++i) {
    420                             int32_t startRun = -1;
    421                             int32_t lengthRun = -1;
    422                             UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
    423 
    424                             if (startRun == -1 || lengthRun == -1) {
    425                                 // Something went wrong when getting the visual run, need to clear
    426                                 // already computed data before doing a single run pass
    427                                 ALOGW("Visual run is not valid");
    428                                 outGlyphs->clear();
    429                                 outAdvances->clear();
    430                                 outPos->clear();
    431                                 *outTotalAdvance = 0;
    432                                 isRTL = (paraDir == 1);
    433                                 useSingleRun = true;
    434                                 break;
    435                             }
    436 
    437                             if (startRun >= end) {
    438                                 continue;
    439                             }
    440                             int32_t endRun = startRun + lengthRun;
    441                             if (endRun <= int32_t(start)) {
    442                                 continue;
    443                             }
    444                             if (startRun < int32_t(start)) {
    445                                 startRun = int32_t(start);
    446                             }
    447                             if (endRun > end) {
    448                                 endRun = end;
    449                             }
    450 
    451                             lengthRun = endRun - startRun;
    452                             isRTL = (runDir == UBIDI_RTL);
    453 #if DEBUG_GLYPHS
    454                             ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
    455                                     i, startRun, lengthRun, isRTL);
    456 #endif
    457                             computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL,
    458                                     outAdvances, outTotalAdvance, outGlyphs, outPos);
    459 
    460                         }
    461                     }
    462                 } else {
    463                     ALOGW("Cannot set Para");
    464                     useSingleRun = true;
    465                     isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
    466                 }
    467                 ubidi_close(bidi);
    468             } else {
    469                 ALOGW("Cannot ubidi_open()");
    470                 useSingleRun = true;
    471                 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
    472             }
    473         }
    474 
    475         // Default single run case
    476         if (useSingleRun){
    477 #if DEBUG_GLYPHS
    478             ALOGD("Using a SINGLE BiDi Run "
    479                     "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
    480 #endif
    481             computeRunValues(paint, chars, start, count, contextCount, isRTL,
    482                     outAdvances, outTotalAdvance, outGlyphs, outPos);
    483         }
    484 
    485 #if DEBUG_GLYPHS
    486         ALOGD("      -- Total returned glyphs-count = %d", outGlyphs->size());
    487         ALOGD("******** ComputeValues -- end");
    488 #endif
    489 }
    490 
    491 #define HB_IsHighSurrogate(ucs) \
    492     (((ucs) & 0xfc00) == 0xd800)
    493 
    494 #define HB_IsLowSurrogate(ucs) \
    495     (((ucs) & 0xfc00) == 0xdc00)
    496 
    497 #ifndef HB_SurrogateToUcs4
    498 #define HB_SurrogateToUcs4_(high, low) \
    499     (((hb_codepoint_t)(high))<<10) + (low) - 0x35fdc00;
    500 #endif
    501 
    502 #define HB_InvalidCodePoint ~0u
    503 
    504 hb_codepoint_t
    505 utf16_to_code_point(const uint16_t *chars, size_t len, ssize_t *iter) {
    506   const uint16_t v = chars[(*iter)++];
    507   if (HB_IsHighSurrogate(v)) {
    508     // surrogate pair
    509     if (size_t(*iter) >= len) {
    510       // the surrogate is incomplete.
    511       return HB_InvalidCodePoint;
    512     }
    513     const uint16_t v2 = chars[(*iter)++];
    514     if (!HB_IsLowSurrogate(v2)) {
    515       // invalidate surrogate pair.
    516       (*iter)--;
    517       return HB_InvalidCodePoint;
    518     }
    519 
    520     return HB_SurrogateToUcs4(v, v2);
    521   }
    522 
    523   if (HB_IsLowSurrogate(v)) {
    524     // this isn't a valid code point
    525     return HB_InvalidCodePoint;
    526   }
    527 
    528   return v;
    529 }
    530 
    531 hb_codepoint_t
    532 utf16_to_code_point_prev(const uint16_t *chars, size_t len, ssize_t *iter) {
    533   const uint16_t v = chars[(*iter)--];
    534   if (HB_IsLowSurrogate(v)) {
    535     // surrogate pair
    536     if (*iter < 0) {
    537       // the surrogate is incomplete.
    538       return HB_InvalidCodePoint;
    539     }
    540     const uint16_t v2 = chars[(*iter)--];
    541     if (!HB_IsHighSurrogate(v2)) {
    542       // invalidate surrogate pair.
    543       (*iter)++;
    544       return HB_InvalidCodePoint;
    545     }
    546 
    547     return HB_SurrogateToUcs4(v2, v);
    548   }
    549 
    550   if (HB_IsHighSurrogate(v)) {
    551     // this isn't a valid code point
    552     return HB_InvalidCodePoint;
    553   }
    554 
    555   return v;
    556 }
    557 
    558 struct ScriptRun {
    559     hb_script_t script;
    560     size_t pos;
    561     size_t length;
    562 };
    563 
    564 hb_script_t code_point_to_script(hb_codepoint_t codepoint) {
    565     static hb_unicode_funcs_t* u;
    566     if (!u) {
    567         u = hb_icu_get_unicode_funcs();
    568     }
    569     return hb_unicode_script(u, codepoint);
    570 }
    571 
    572 bool
    573 hb_utf16_script_run_next(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
    574   if (size_t(*iter) == len)
    575     return false;
    576 
    577   run->pos = *iter;
    578   const uint32_t init_cp = utf16_to_code_point(chars, len, iter);
    579   const hb_script_t init_script = code_point_to_script(init_cp);
    580   hb_script_t current_script = init_script;
    581   run->script = init_script;
    582 
    583   for (;;) {
    584     if (size_t(*iter) == len)
    585       break;
    586     const ssize_t prev_iter = *iter;
    587     const uint32_t cp = utf16_to_code_point(chars, len, iter);
    588     const hb_script_t script = code_point_to_script(cp);
    589 
    590     if (script != current_script) {
    591         /* BEGIN android-changed
    592            The condition was not correct by doing "a == b == constant"
    593            END android-changed */
    594       if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
    595         // If we started off as inherited, we take whatever we can find.
    596         run->script = script;
    597         current_script = script;
    598         continue;
    599       } else if (script == HB_SCRIPT_INHERITED) {
    600         continue;
    601       } else {
    602         *iter = prev_iter;
    603         break;
    604       }
    605     }
    606   }
    607 
    608   if (run->script == HB_SCRIPT_INHERITED)
    609     run->script = HB_SCRIPT_COMMON;
    610 
    611   run->length = *iter - run->pos;
    612   return true;
    613 }
    614 
    615 bool
    616 hb_utf16_script_run_prev(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
    617   if (*iter == -1)
    618     return false;
    619 
    620   const size_t ending_index = *iter;
    621   const uint32_t init_cp = utf16_to_code_point_prev(chars, len, iter);
    622   const hb_script_t init_script = code_point_to_script(init_cp);
    623   hb_script_t current_script = init_script;
    624   run->script = init_script;
    625   size_t break_iter = *iter;
    626 
    627   for (;;) {
    628     if (*iter < 0)
    629       break;
    630     const uint32_t cp = utf16_to_code_point_prev(chars, len, iter);
    631     const hb_script_t script = code_point_to_script(cp);
    632 
    633     if (script != current_script) {
    634       if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
    635         // If we started off as inherited, we take whatever we can find.
    636         run->script = script;
    637         current_script = script;
    638         // In cases of script1 + inherited + script2, always group the inherited
    639         // with script1.
    640         break_iter = *iter;
    641         continue;
    642       } else if (script == HB_SCRIPT_INHERITED) {
    643         continue;
    644       } else {
    645         *iter = break_iter;
    646         break;
    647       }
    648     } else {
    649         break_iter = *iter;
    650     }
    651   }
    652 
    653   if (run->script == HB_SCRIPT_INHERITED)
    654     run->script = HB_SCRIPT_COMMON;
    655 
    656   run->pos = *iter + 1;
    657   run->length = ending_index - *iter;
    658   return true;
    659 }
    660 
    661 
    662 static void logGlyphs(hb_buffer_t* buffer) {
    663     unsigned int numGlyphs;
    664     hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs);
    665     hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL);
    666     ALOGD("         -- glyphs count=%d", numGlyphs);
    667     for (size_t i = 0; i < numGlyphs; i++) {
    668         ALOGD("         -- glyph[%d] = %d, cluster = %u, advance = %0.2f, offset.x = %0.2f, offset.y = %0.2f", i,
    669                 info[i].codepoint,
    670                 info[i].cluster,
    671                 HBFixedToFloat(positions[i].x_advance),
    672                 HBFixedToFloat(positions[i].x_offset),
    673                 HBFixedToFloat(positions[i].y_offset));
    674     }
    675 }
    676 
    677 void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* contextChars,
    678         size_t start, size_t count, size_t contextCount, bool isRTL,
    679         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
    680         Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
    681     if (!count) {
    682         // We cannot shape an empty run.
    683         return;
    684     }
    685 
    686     // To be filled in later
    687     for (size_t i = 0; i < count; i++) {
    688         outAdvances->add(0);
    689     }
    690 
    691     // Set the string properties
    692     const UChar* chars = contextChars + start;
    693 
    694     // Define shaping paint properties
    695     mShapingPaint.setTextSize(paint->getTextSize());
    696     float skewX = paint->getTextSkewX();
    697     mShapingPaint.setTextSkewX(skewX);
    698     mShapingPaint.setTextScaleX(paint->getTextScaleX());
    699     mShapingPaint.setFlags(paint->getFlags());
    700     mShapingPaint.setHinting(paint->getHinting());
    701     mShapingPaint.setFontVariant(paint->getFontVariant());
    702     mShapingPaint.setLanguage(paint->getLanguage());
    703 
    704     // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script
    705     // into the shaperItem
    706     ssize_t indexFontRun = isRTL ? count - 1 : 0;
    707     jfloat totalAdvance = *outTotalAdvance;
    708     ScriptRun run;  // relative to chars
    709     while ((isRTL) ?
    710             hb_utf16_script_run_prev(&run, chars, count, &indexFontRun):
    711             hb_utf16_script_run_next(&run, chars, count, &indexFontRun)) {
    712 
    713 #if DEBUG_GLYPHS
    714         ALOGD("-------- Start of Script Run --------");
    715         ALOGD("Shaping Script Run with");
    716         ALOGD("         -- isRTL = %d", isRTL);
    717         ALOGD("         -- HB script = %c%c%c%c", HB_UNTAG(run.script));
    718         ALOGD("         -- run.pos = %d", int(run.pos));
    719         ALOGD("         -- run.length = %d", int(run.length));
    720         ALOGD("         -- run = '%s'", String8(chars + run.pos, run.length).string());
    721         ALOGD("         -- string = '%s'", String8(chars, count).string());
    722 #endif
    723 
    724         hb_buffer_reset(mBuffer);
    725         // Note: if we want to set unicode functions, etc., this is the place.
    726 
    727         hb_buffer_set_direction(mBuffer, isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
    728         hb_buffer_set_script(mBuffer, run.script);
    729         // Should set language here (for bug 7004056)
    730         hb_buffer_add_utf16(mBuffer, contextChars, contextCount, start + run.pos, run.length);
    731 
    732         // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
    733         // and shape the Font run
    734         size_t glyphBaseCount = shapeFontRun(paint);
    735         unsigned int numGlyphs;
    736         hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
    737         hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(mBuffer, NULL);
    738 
    739 #if DEBUG_GLYPHS
    740         ALOGD("Got from Harfbuzz");
    741         ALOGD("         -- glyphBaseCount = %d", glyphBaseCount);
    742         ALOGD("         -- num_glyph = %d", numGlyphs);
    743         ALOGD("         -- isDevKernText = %d", paint->isDevKernText());
    744         ALOGD("         -- initial totalAdvance = %f", totalAdvance);
    745 
    746         logGlyphs(mBuffer);
    747 #endif
    748 
    749         for (size_t i = 0; i < numGlyphs; i++) {
    750             size_t cluster = info[i].cluster - start;
    751             float xAdvance = HBFixedToFloat(positions[i].x_advance);
    752             outAdvances->replaceAt(outAdvances->itemAt(cluster) + xAdvance, cluster);
    753             outGlyphs->add(info[i].codepoint + glyphBaseCount);
    754             float xo = HBFixedToFloat(positions[i].x_offset);
    755             float yo = -HBFixedToFloat(positions[i].y_offset);
    756             outPos->add(totalAdvance + xo + yo * skewX);
    757             outPos->add(yo);
    758             totalAdvance += xAdvance;
    759         }
    760     }
    761 
    762     *outTotalAdvance = totalAdvance;
    763 
    764 #if DEBUG_GLYPHS
    765     ALOGD("         -- final totalAdvance = %f", totalAdvance);
    766     ALOGD("-------- End of Script Run --------");
    767 #endif
    768 }
    769 
    770 /**
    771  * Return the first typeface in the logical change, starting with this typeface,
    772  * that contains the specified unichar, or NULL if none is found.
    773  */
    774 SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface,
    775         hb_script_t script) {
    776     SkTypeface::Style currentStyle = SkTypeface::kNormal;
    777     if (typeface) {
    778         currentStyle = typeface->style();
    779     }
    780     typeface = SkCreateTypefaceForScriptNG(script, currentStyle);
    781 #if DEBUG_GLYPHS
    782     ALOGD("Using Harfbuzz Script %c%c%c%c, Style %d", HB_UNTAG(script), currentStyle);
    783 #endif
    784     return typeface;
    785 }
    786 
    787 bool TextLayoutShaper::isComplexScript(hb_script_t script) {
    788     switch (script) {
    789     case HB_SCRIPT_COMMON:
    790     case HB_SCRIPT_GREEK:
    791     case HB_SCRIPT_CYRILLIC:
    792     case HB_SCRIPT_HANGUL:
    793     case HB_SCRIPT_INHERITED:
    794     case HB_SCRIPT_HAN:
    795     case HB_SCRIPT_KATAKANA:
    796     case HB_SCRIPT_HIRAGANA:
    797         return false;
    798     default:
    799         return true;
    800     }
    801 }
    802 
    803 size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint) {
    804     // Update Harfbuzz Shaper
    805 
    806     SkTypeface* typeface = paint->getTypeface();
    807 
    808     // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz
    809     // This is needed as the Typeface used for shaping can be not the default one
    810     // when we are shaping any script that needs to use a fallback Font.
    811     // If we are a "common" script we dont need to shift
    812     size_t baseGlyphCount = 0;
    813     hb_codepoint_t firstUnichar = 0;
    814     if (isComplexScript(hb_buffer_get_script(mBuffer))) {
    815         unsigned int numGlyphs;
    816         hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
    817         for (size_t i = 0; i < numGlyphs; i++) {
    818             firstUnichar = info[i].codepoint;
    819             if (firstUnichar != ' ') {
    820                 break;
    821             }
    822         }
    823         baseGlyphCount = paint->getBaseGlyphCount(firstUnichar);
    824     }
    825 
    826     SkTypeface* scriptTypeface = NULL;
    827     if (baseGlyphCount != 0) {
    828         scriptTypeface = typefaceForScript(paint, typeface,
    829             hb_buffer_get_script(mBuffer));
    830 #if DEBUG_GLYPHS
    831         ALOGD("Using Default Typeface for script %c%c%c%c",
    832             HB_UNTAG(hb_buffer_get_script(mBuffer)));
    833 #endif
    834     }
    835     if (scriptTypeface) {
    836         typeface = scriptTypeface;
    837     } else {
    838         baseGlyphCount = 0;
    839         if (typeface) {
    840             SkSafeRef(typeface);
    841         } else {
    842             typeface = SkFontHost::CreateTypeface(NULL, NULL, SkTypeface::kNormal);
    843 #if DEBUG_GLYPHS
    844             ALOGD("Using Default Typeface (normal style)");
    845 #endif
    846         }
    847     }
    848 
    849     mShapingPaint.setTypeface(typeface);
    850     hb_face_t* face = referenceCachedHBFace(typeface);
    851 
    852     float sizeY = paint->getTextSize();
    853     float sizeX = sizeY * paint->getTextScaleX();
    854     hb_font_t* font = createFont(face, &mShapingPaint, sizeX, sizeY);
    855     hb_face_destroy(face);
    856 
    857 #if DEBUG_GLYPHS
    858     ALOGD("Run typeface = %p, uniqueID = %d, face = %p",
    859             typeface, typeface->uniqueID(), face);
    860 #endif
    861     SkSafeUnref(typeface);
    862 
    863     hb_shape(font, mBuffer, NULL, 0);
    864     hb_font_destroy(font);
    865 
    866     return baseGlyphCount;
    867 }
    868 
    869 hb_face_t* TextLayoutShaper::referenceCachedHBFace(SkTypeface* typeface) {
    870     SkFontID fontId = typeface->uniqueID();
    871     ssize_t index = mCachedHBFaces.indexOfKey(fontId);
    872     if (index >= 0) {
    873         return hb_face_reference(mCachedHBFaces.valueAt(index));
    874     }
    875     // TODO: destroy function
    876     hb_face_t* face = hb_face_create_for_tables(harfbuzzSkiaReferenceTable, typeface, NULL);
    877 #if DEBUG_GLYPHS
    878     ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface);
    879 #endif
    880     mCachedHBFaces.add(fontId, face);
    881     return hb_face_reference(face);
    882 }
    883 
    884 void TextLayoutShaper::purgeCaches() {
    885     size_t cacheSize = mCachedHBFaces.size();
    886     for (size_t i = 0; i < cacheSize; i++) {
    887         hb_face_destroy(mCachedHBFaces.valueAt(i));
    888     }
    889     mCachedHBFaces.clear();
    890 }
    891 
    892 TextLayoutEngine::TextLayoutEngine() {
    893     mShaper = new TextLayoutShaper();
    894 #if USE_TEXT_LAYOUT_CACHE
    895     mTextLayoutCache = new TextLayoutCache(mShaper);
    896 #else
    897     mTextLayoutCache = NULL;
    898 #endif
    899 }
    900 
    901 TextLayoutEngine::~TextLayoutEngine() {
    902     delete mTextLayoutCache;
    903     delete mShaper;
    904 }
    905 
    906 sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
    907         jint start, jint count, jint contextCount, jint dirFlags) {
    908     sp<TextLayoutValue> value;
    909 #if USE_TEXT_LAYOUT_CACHE
    910     value = mTextLayoutCache->getValue(paint, text, start, count,
    911             contextCount, dirFlags);
    912     if (value == NULL) {
    913         ALOGE("Cannot get TextLayoutCache value for text = '%s'",
    914                 String8(text + start, count).string());
    915     }
    916 #else
    917     value = new TextLayoutValue(count);
    918     mShaper->computeValues(value.get(), paint,
    919             reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
    920 #endif
    921     return value;
    922 }
    923 
    924 void TextLayoutEngine::purgeCaches() {
    925 #if USE_TEXT_LAYOUT_CACHE
    926     mTextLayoutCache->purgeCaches();
    927 #if DEBUG_GLYPHS
    928     ALOGD("Purged TextLayoutEngine caches");
    929 #endif
    930 #endif
    931 }
    932 
    933 
    934 } // namespace android
    935