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