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 "SkGlyphCache.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) {
    223     paintOpts.setUseFontFallbacks(true);
    224 }
    225 
    226 TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
    227         size_t start, size_t count, size_t contextCount, int dirFlags) :
    228             start(start), count(count), contextCount(contextCount),
    229             dirFlags(dirFlags) {
    230     textCopy.setTo(text, contextCount);
    231     typeface = paint->getTypeface();
    232     textSize = paint->getTextSize();
    233     textSkewX = paint->getTextSkewX();
    234     textScaleX = paint->getTextScaleX();
    235     flags = paint->getFlags();
    236     hinting = paint->getHinting();
    237     paintOpts = paint->getPaintOptionsAndroid();
    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         paintOpts(other.paintOpts) {
    253 }
    254 
    255 int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
    256     int deltaInt = lhs.start - rhs.start;
    257     if (deltaInt != 0) return (deltaInt);
    258 
    259     deltaInt = lhs.count - rhs.count;
    260     if (deltaInt != 0) return (deltaInt);
    261 
    262     deltaInt = lhs.contextCount - rhs.contextCount;
    263     if (deltaInt != 0) return (deltaInt);
    264 
    265     if (lhs.typeface < rhs.typeface) return -1;
    266     if (lhs.typeface > rhs.typeface) return +1;
    267 
    268     if (lhs.textSize < rhs.textSize) return -1;
    269     if (lhs.textSize > rhs.textSize) return +1;
    270 
    271     if (lhs.textSkewX < rhs.textSkewX) return -1;
    272     if (lhs.textSkewX > rhs.textSkewX) return +1;
    273 
    274     if (lhs.textScaleX < rhs.textScaleX) return -1;
    275     if (lhs.textScaleX > rhs.textScaleX) return +1;
    276 
    277     deltaInt = lhs.flags - rhs.flags;
    278     if (deltaInt != 0) return (deltaInt);
    279 
    280     deltaInt = lhs.hinting - rhs.hinting;
    281     if (deltaInt != 0) return (deltaInt);
    282 
    283     deltaInt = lhs.dirFlags - rhs.dirFlags;
    284     if (deltaInt) return (deltaInt);
    285 
    286     if (lhs.paintOpts != rhs.paintOpts)
    287         return memcmp(&lhs.paintOpts, &rhs.paintOpts, sizeof(SkPaintOptionsAndroid));
    288 
    289     return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
    290 }
    291 
    292 size_t TextLayoutCacheKey::getSize() const {
    293     return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
    294 }
    295 
    296 hash_t TextLayoutCacheKey::hash() const {
    297     uint32_t hash = JenkinsHashMix(0, start);
    298     hash = JenkinsHashMix(hash, count);
    299     /* contextCount not needed because it's included in text, below */
    300     hash = JenkinsHashMix(hash, hash_type(typeface));
    301     hash = JenkinsHashMix(hash, hash_type(textSize));
    302     hash = JenkinsHashMix(hash, hash_type(textSkewX));
    303     hash = JenkinsHashMix(hash, hash_type(textScaleX));
    304     hash = JenkinsHashMix(hash, flags);
    305     hash = JenkinsHashMix(hash, hinting);
    306     hash = JenkinsHashMix(hash, paintOpts.getFontVariant());
    307     // Note: leaving out language is not problematic, as equality comparisons
    308     // are still valid - the only bad thing that could happen is collisions.
    309     hash = JenkinsHashMixShorts(hash, getText(), contextCount);
    310     return JenkinsHashWhiten(hash);
    311 }
    312 
    313 /**
    314  * TextLayoutCacheValue
    315  */
    316 TextLayoutValue::TextLayoutValue(size_t contextCount) :
    317         mTotalAdvance(0), mElapsedTime(0) {
    318     mBounds.setEmpty();
    319     // Give a hint for advances and glyphs vectors size
    320     mAdvances.setCapacity(contextCount);
    321     mGlyphs.setCapacity(contextCount);
    322     mPos.setCapacity(contextCount * 2);
    323 }
    324 
    325 size_t TextLayoutValue::getSize() const {
    326     return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
    327             sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity();
    328 }
    329 
    330 void TextLayoutValue::setElapsedTime(uint32_t time) {
    331     mElapsedTime = time;
    332 }
    333 
    334 uint32_t TextLayoutValue::getElapsedTime() {
    335     return mElapsedTime;
    336 }
    337 
    338 TextLayoutShaper::TextLayoutShaper() {
    339     mBuffer = hb_buffer_create();
    340 }
    341 
    342 TextLayoutShaper::~TextLayoutShaper() {
    343     hb_buffer_destroy(mBuffer);
    344 }
    345 
    346 void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint,
    347         const UChar* chars, size_t start, size_t count, size_t contextCount, int dirFlags) {
    348     computeValues(paint, chars, start, count, contextCount, dirFlags,
    349             &value->mAdvances, &value->mTotalAdvance, &value->mBounds,
    350             &value->mGlyphs, &value->mPos);
    351 #if DEBUG_ADVANCES
    352     ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
    353             contextCount, value->mTotalAdvance);
    354 #endif
    355 }
    356 
    357 void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
    358         size_t start, size_t count, size_t contextCount, int dirFlags,
    359         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds,
    360         Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
    361         *outTotalAdvance = 0;
    362         if (!count) {
    363             return;
    364         }
    365 
    366         UBiDiLevel bidiReq = 0;
    367         bool forceLTR = false;
    368         bool forceRTL = false;
    369 
    370         switch (dirFlags & kBidi_Mask) {
    371             case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
    372             case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
    373             case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
    374             case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
    375             case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
    376             case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
    377         }
    378 
    379         bool useSingleRun = false;
    380         bool isRTL = forceRTL;
    381         if (forceLTR || forceRTL) {
    382             useSingleRun = true;
    383         } else {
    384             UBiDi* bidi = ubidi_open();
    385             if (bidi) {
    386                 UErrorCode status = U_ZERO_ERROR;
    387 #if DEBUG_GLYPHS
    388                 ALOGD("******** ComputeValues -- start");
    389                 ALOGD("      -- string = '%s'", String8(chars + start, count).string());
    390                 ALOGD("      -- start = %d", start);
    391                 ALOGD("      -- count = %d", count);
    392                 ALOGD("      -- contextCount = %d", contextCount);
    393                 ALOGD("      -- bidiReq = %d", bidiReq);
    394 #endif
    395                 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
    396                 if (U_SUCCESS(status)) {
    397                     int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
    398                     ssize_t rc = ubidi_countRuns(bidi, &status);
    399 #if DEBUG_GLYPHS
    400                     ALOGD("      -- dirFlags = %d", dirFlags);
    401                     ALOGD("      -- paraDir = %d", paraDir);
    402                     ALOGD("      -- run-count = %d", int(rc));
    403 #endif
    404                     if (U_SUCCESS(status) && rc == 1) {
    405                         // Normal case: one run, status is ok
    406                         isRTL = (paraDir == 1);
    407                         useSingleRun = true;
    408                     } else if (!U_SUCCESS(status) || rc < 1) {
    409                         ALOGW("Need to force to single run -- string = '%s',"
    410                                 " status = %d, rc = %d",
    411                                 String8(chars + start, count).string(), status, int(rc));
    412                         isRTL = (paraDir == 1);
    413                         useSingleRun = true;
    414                     } else {
    415                         int32_t end = start + count;
    416                         for (size_t i = 0; i < size_t(rc); ++i) {
    417                             int32_t startRun = -1;
    418                             int32_t lengthRun = -1;
    419                             UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
    420 
    421                             if (startRun == -1 || lengthRun == -1) {
    422                                 // Something went wrong when getting the visual run, need to clear
    423                                 // already computed data before doing a single run pass
    424                                 ALOGW("Visual run is not valid");
    425                                 outGlyphs->clear();
    426                                 outAdvances->clear();
    427                                 outPos->clear();
    428                                 *outTotalAdvance = 0;
    429                                 isRTL = (paraDir == 1);
    430                                 useSingleRun = true;
    431                                 break;
    432                             }
    433 
    434                             if (startRun >= end) {
    435                                 continue;
    436                             }
    437                             int32_t endRun = startRun + lengthRun;
    438                             if (endRun <= int32_t(start)) {
    439                                 continue;
    440                             }
    441                             if (startRun < int32_t(start)) {
    442                                 startRun = int32_t(start);
    443                             }
    444                             if (endRun > end) {
    445                                 endRun = end;
    446                             }
    447 
    448                             lengthRun = endRun - startRun;
    449                             isRTL = (runDir == UBIDI_RTL);
    450 #if DEBUG_GLYPHS
    451                             ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
    452                                     i, startRun, lengthRun, isRTL);
    453 #endif
    454                             computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL,
    455                                     outAdvances, outTotalAdvance, outBounds, outGlyphs, outPos);
    456 
    457                         }
    458                     }
    459                 } else {
    460                     ALOGW("Cannot set Para");
    461                     useSingleRun = true;
    462                     isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
    463                 }
    464                 ubidi_close(bidi);
    465             } else {
    466                 ALOGW("Cannot ubidi_open()");
    467                 useSingleRun = true;
    468                 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
    469             }
    470         }
    471 
    472         // Default single run case
    473         if (useSingleRun){
    474 #if DEBUG_GLYPHS
    475             ALOGD("Using a SINGLE BiDi Run "
    476                     "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
    477 #endif
    478             computeRunValues(paint, chars, start, count, contextCount, isRTL,
    479                     outAdvances, outTotalAdvance, outBounds, outGlyphs, outPos);
    480         }
    481 
    482 #if DEBUG_GLYPHS
    483         ALOGD("      -- Total returned glyphs-count = %d", outGlyphs->size());
    484         ALOGD("******** ComputeValues -- end");
    485 #endif
    486 }
    487 
    488 #define HB_IsHighSurrogate(ucs) \
    489     (((ucs) & 0xfc00) == 0xd800)
    490 
    491 #define HB_IsLowSurrogate(ucs) \
    492     (((ucs) & 0xfc00) == 0xdc00)
    493 
    494 #ifndef HB_SurrogateToUcs4
    495 #define HB_SurrogateToUcs4_(high, low) \
    496     (((hb_codepoint_t)(high))<<10) + (low) - 0x35fdc00;
    497 #endif
    498 
    499 #define HB_InvalidCodePoint ~0u
    500 
    501 hb_codepoint_t
    502 utf16_to_code_point(const uint16_t *chars, size_t len, ssize_t *iter) {
    503   const uint16_t v = chars[(*iter)++];
    504   if (HB_IsHighSurrogate(v)) {
    505     // surrogate pair
    506     if (size_t(*iter) >= len) {
    507       // the surrogate is incomplete.
    508       return HB_InvalidCodePoint;
    509     }
    510     const uint16_t v2 = chars[(*iter)++];
    511     if (!HB_IsLowSurrogate(v2)) {
    512       // invalidate surrogate pair.
    513       (*iter)--;
    514       return HB_InvalidCodePoint;
    515     }
    516 
    517     return HB_SurrogateToUcs4(v, v2);
    518   }
    519 
    520   if (HB_IsLowSurrogate(v)) {
    521     // this isn't a valid code point
    522     return HB_InvalidCodePoint;
    523   }
    524 
    525   return v;
    526 }
    527 
    528 hb_codepoint_t
    529 utf16_to_code_point_prev(const uint16_t *chars, size_t len, ssize_t *iter) {
    530   const uint16_t v = chars[(*iter)--];
    531   if (HB_IsLowSurrogate(v)) {
    532     // surrogate pair
    533     if (*iter < 0) {
    534       // the surrogate is incomplete.
    535       return HB_InvalidCodePoint;
    536     }
    537     const uint16_t v2 = chars[(*iter)--];
    538     if (!HB_IsHighSurrogate(v2)) {
    539       // invalidate surrogate pair.
    540       (*iter)++;
    541       return HB_InvalidCodePoint;
    542     }
    543 
    544     return HB_SurrogateToUcs4(v2, v);
    545   }
    546 
    547   if (HB_IsHighSurrogate(v)) {
    548     // this isn't a valid code point
    549     return HB_InvalidCodePoint;
    550   }
    551 
    552   return v;
    553 }
    554 
    555 struct ScriptRun {
    556     hb_script_t script;
    557     size_t pos;
    558     size_t length;
    559 };
    560 
    561 hb_script_t code_point_to_script(hb_codepoint_t codepoint) {
    562     static hb_unicode_funcs_t* u;
    563     if (!u) {
    564         u = hb_icu_get_unicode_funcs();
    565     }
    566     return hb_unicode_script(u, codepoint);
    567 }
    568 
    569 bool
    570 hb_utf16_script_run_next(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
    571   if (size_t(*iter) == len)
    572     return false;
    573 
    574   run->pos = *iter;
    575   const uint32_t init_cp = utf16_to_code_point(chars, len, iter);
    576   const hb_script_t init_script = code_point_to_script(init_cp);
    577   hb_script_t current_script = init_script;
    578   run->script = init_script;
    579 
    580   for (;;) {
    581     if (size_t(*iter) == len)
    582       break;
    583     const ssize_t prev_iter = *iter;
    584     const uint32_t cp = utf16_to_code_point(chars, len, iter);
    585     const hb_script_t script = code_point_to_script(cp);
    586 
    587     if (script != current_script) {
    588         /* BEGIN android-changed
    589            The condition was not correct by doing "a == b == constant"
    590            END android-changed */
    591       if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
    592         // If we started off as inherited, we take whatever we can find.
    593         run->script = script;
    594         current_script = script;
    595         continue;
    596       } else if (script == HB_SCRIPT_INHERITED) {
    597         continue;
    598       } else {
    599         *iter = prev_iter;
    600         break;
    601       }
    602     }
    603   }
    604 
    605   if (run->script == HB_SCRIPT_INHERITED)
    606     run->script = HB_SCRIPT_COMMON;
    607 
    608   run->length = *iter - run->pos;
    609   return true;
    610 }
    611 
    612 bool
    613 hb_utf16_script_run_prev(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) {
    614   if (*iter == -1)
    615     return false;
    616 
    617   const size_t ending_index = *iter;
    618   const uint32_t init_cp = utf16_to_code_point_prev(chars, len, iter);
    619   const hb_script_t init_script = code_point_to_script(init_cp);
    620   hb_script_t current_script = init_script;
    621   run->script = init_script;
    622   size_t break_iter = *iter;
    623 
    624   for (;;) {
    625     if (*iter < 0)
    626       break;
    627     const uint32_t cp = utf16_to_code_point_prev(chars, len, iter);
    628     const hb_script_t script = code_point_to_script(cp);
    629 
    630     if (script != current_script) {
    631       if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) {
    632         // If we started off as inherited, we take whatever we can find.
    633         run->script = script;
    634         current_script = script;
    635         // In cases of script1 + inherited + script2, always group the inherited
    636         // with script1.
    637         break_iter = *iter;
    638         continue;
    639       } else if (script == HB_SCRIPT_INHERITED) {
    640         continue;
    641       } else {
    642         *iter = break_iter;
    643         break;
    644       }
    645     } else {
    646         break_iter = *iter;
    647     }
    648   }
    649 
    650   if (run->script == HB_SCRIPT_INHERITED)
    651     run->script = HB_SCRIPT_COMMON;
    652 
    653   run->pos = *iter + 1;
    654   run->length = ending_index - *iter;
    655   return true;
    656 }
    657 
    658 
    659 static void logGlyphs(hb_buffer_t* buffer) {
    660     unsigned int numGlyphs;
    661     hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs);
    662     hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL);
    663     ALOGD("         -- glyphs count=%d", numGlyphs);
    664     for (size_t i = 0; i < numGlyphs; i++) {
    665         ALOGD("         -- glyph[%d] = %d, cluster = %u, advance = %0.2f, offset.x = %0.2f, offset.y = %0.2f", i,
    666                 info[i].codepoint,
    667                 info[i].cluster,
    668                 HBFixedToFloat(positions[i].x_advance),
    669                 HBFixedToFloat(positions[i].x_offset),
    670                 HBFixedToFloat(positions[i].y_offset));
    671     }
    672 }
    673 
    674 void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* contextChars,
    675         size_t start, size_t count, size_t contextCount, bool isRTL,
    676         Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds,
    677         Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
    678     if (!count) {
    679         // We cannot shape an empty run.
    680         return;
    681     }
    682 
    683     // To be filled in later
    684     for (size_t i = 0; i < count; i++) {
    685         outAdvances->add(0);
    686     }
    687 
    688     // Set the string properties
    689     const UChar* chars = contextChars + start;
    690 
    691     // Define shaping paint properties
    692     mShapingPaint.setTextSize(paint->getTextSize());
    693     float skewX = paint->getTextSkewX();
    694     mShapingPaint.setTextSkewX(skewX);
    695     mShapingPaint.setTextScaleX(paint->getTextScaleX());
    696     mShapingPaint.setFlags(paint->getFlags());
    697     mShapingPaint.setHinting(paint->getHinting());
    698     mShapingPaint.setPaintOptionsAndroid(paint->getPaintOptionsAndroid());
    699 
    700     // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script
    701     // into the shaperItem
    702     ssize_t indexFontRun = isRTL ? count - 1 : 0;
    703     jfloat totalAdvance = *outTotalAdvance;
    704     ScriptRun run;  // relative to chars
    705     while ((isRTL) ?
    706             hb_utf16_script_run_prev(&run, chars, count, &indexFontRun):
    707             hb_utf16_script_run_next(&run, chars, count, &indexFontRun)) {
    708 
    709 #if DEBUG_GLYPHS
    710         ALOGD("-------- Start of Script Run --------");
    711         ALOGD("Shaping Script Run with");
    712         ALOGD("         -- isRTL = %d", isRTL);
    713         ALOGD("         -- HB script = %c%c%c%c", HB_UNTAG(run.script));
    714         ALOGD("         -- run.pos = %d", int(run.pos));
    715         ALOGD("         -- run.length = %d", int(run.length));
    716         ALOGD("         -- run = '%s'", String8(chars + run.pos, run.length).string());
    717         ALOGD("         -- string = '%s'", String8(chars, count).string());
    718 #endif
    719 
    720         hb_buffer_reset(mBuffer);
    721         // Note: if we want to set unicode functions, etc., this is the place.
    722 
    723         hb_buffer_set_direction(mBuffer, isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
    724         hb_buffer_set_script(mBuffer, run.script);
    725         SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag();
    726         hb_buffer_set_language(mBuffer, hb_language_from_string(langString.c_str(), -1));
    727         hb_buffer_add_utf16(mBuffer, contextChars, contextCount, start + run.pos, run.length);
    728 
    729         // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
    730         // and shape the Font run
    731         size_t glyphBaseCount = shapeFontRun(paint);
    732         unsigned int numGlyphs;
    733         hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
    734         hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(mBuffer, NULL);
    735 
    736 #if DEBUG_GLYPHS
    737         ALOGD("Got from Harfbuzz");
    738         ALOGD("         -- glyphBaseCount = %d", glyphBaseCount);
    739         ALOGD("         -- num_glyph = %d", numGlyphs);
    740         ALOGD("         -- isDevKernText = %d", paint->isDevKernText());
    741         ALOGD("         -- initial totalAdvance = %f", totalAdvance);
    742 
    743         logGlyphs(mBuffer);
    744 #endif
    745 
    746         for (size_t i = 0; i < numGlyphs; i++) {
    747             size_t cluster = info[i].cluster - start;
    748             float xAdvance = HBFixedToFloat(positions[i].x_advance);
    749             outAdvances->replaceAt(outAdvances->itemAt(cluster) + xAdvance, cluster);
    750             jchar glyphId = info[i].codepoint + glyphBaseCount;
    751             outGlyphs->add(glyphId);
    752             float xo = HBFixedToFloat(positions[i].x_offset);
    753             float yo = -HBFixedToFloat(positions[i].y_offset);
    754 
    755             float xpos = totalAdvance + xo + yo * skewX;
    756             float ypos = yo;
    757             outPos->add(xpos);
    758             outPos->add(ypos);
    759             totalAdvance += xAdvance;
    760 
    761             SkAutoGlyphCache autoCache(mShapingPaint, NULL, NULL);
    762             const SkGlyph& metrics = autoCache.getCache()->getGlyphIDMetrics(glyphId);
    763             outBounds->join(xpos + metrics.fLeft, ypos + metrics.fTop,
    764                     xpos + metrics.fLeft + metrics.fWidth, ypos + metrics.fTop + metrics.fHeight);
    765 
    766         }
    767     }
    768 
    769     *outTotalAdvance = totalAdvance;
    770 
    771 #if DEBUG_GLYPHS
    772     ALOGD("         -- final totalAdvance = %f", totalAdvance);
    773     ALOGD("-------- End of Script Run --------");
    774 #endif
    775 }
    776 
    777 /**
    778  * Return the first typeface in the logical change, starting with this typeface,
    779  * that contains the specified unichar, or NULL if none is found.
    780  */
    781 SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface,
    782         hb_script_t script) {
    783     SkTypeface::Style currentStyle = SkTypeface::kNormal;
    784     if (typeface) {
    785         currentStyle = typeface->style();
    786     }
    787     typeface = SkCreateTypefaceForScriptNG(script, currentStyle);
    788 #if DEBUG_GLYPHS
    789     ALOGD("Using Harfbuzz Script %c%c%c%c, Style %d", HB_UNTAG(script), currentStyle);
    790 #endif
    791     return typeface;
    792 }
    793 
    794 bool TextLayoutShaper::isComplexScript(hb_script_t script) {
    795     switch (script) {
    796     case HB_SCRIPT_COMMON:
    797     case HB_SCRIPT_GREEK:
    798     case HB_SCRIPT_CYRILLIC:
    799     case HB_SCRIPT_HANGUL:
    800     case HB_SCRIPT_INHERITED:
    801     case HB_SCRIPT_HAN:
    802     case HB_SCRIPT_KATAKANA:
    803     case HB_SCRIPT_HIRAGANA:
    804         return false;
    805     default:
    806         return true;
    807     }
    808 }
    809 
    810 size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint) {
    811     // Update Harfbuzz Shaper
    812 
    813     SkTypeface* typeface = paint->getTypeface();
    814 
    815     // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz
    816     // This is needed as the Typeface used for shaping can be not the default one
    817     // when we are shaping any script that needs to use a fallback Font.
    818     // If we are a "common" script we dont need to shift
    819     size_t baseGlyphCount = 0;
    820     hb_codepoint_t firstUnichar = 0;
    821     if (isComplexScript(hb_buffer_get_script(mBuffer))) {
    822         unsigned int numGlyphs;
    823         hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs);
    824         for (size_t i = 0; i < numGlyphs; i++) {
    825             firstUnichar = info[i].codepoint;
    826             if (firstUnichar != ' ') {
    827                 break;
    828             }
    829         }
    830         baseGlyphCount = paint->getBaseGlyphCount(firstUnichar);
    831     }
    832 
    833     SkTypeface* scriptTypeface = NULL;
    834     if (baseGlyphCount != 0) {
    835         scriptTypeface = typefaceForScript(paint, typeface,
    836             hb_buffer_get_script(mBuffer));
    837 #if DEBUG_GLYPHS
    838         ALOGD("Using Default Typeface for script %c%c%c%c",
    839             HB_UNTAG(hb_buffer_get_script(mBuffer)));
    840 #endif
    841     }
    842     if (scriptTypeface) {
    843         typeface = scriptTypeface;
    844     } else {
    845         baseGlyphCount = 0;
    846         if (typeface) {
    847             SkSafeRef(typeface);
    848         } else {
    849             typeface = SkTypeface::CreateFromName(NULL, SkTypeface::kNormal);
    850 #if DEBUG_GLYPHS
    851             ALOGD("Using Default Typeface (normal style)");
    852 #endif
    853         }
    854     }
    855 
    856     mShapingPaint.setTypeface(typeface);
    857     hb_face_t* face = referenceCachedHBFace(typeface);
    858 
    859     float sizeY = paint->getTextSize();
    860     float sizeX = sizeY * paint->getTextScaleX();
    861     hb_font_t* font = createFont(face, &mShapingPaint, sizeX, sizeY);
    862     hb_face_destroy(face);
    863 
    864 #if DEBUG_GLYPHS
    865     ALOGD("Run typeface = %p, uniqueID = %d, face = %p",
    866             typeface, typeface->uniqueID(), face);
    867 #endif
    868     SkSafeUnref(typeface);
    869 
    870     hb_shape(font, mBuffer, NULL, 0);
    871     hb_font_destroy(font);
    872 
    873     mShapingPaint.setTypeface(paint->getTypeface());
    874     return baseGlyphCount;
    875 }
    876 
    877 hb_face_t* TextLayoutShaper::referenceCachedHBFace(SkTypeface* typeface) {
    878     SkFontID fontId = typeface->uniqueID();
    879     ssize_t index = mCachedHBFaces.indexOfKey(fontId);
    880     if (index >= 0) {
    881         return hb_face_reference(mCachedHBFaces.valueAt(index));
    882     }
    883     // TODO: destroy function
    884     hb_face_t* face = hb_face_create_for_tables(harfbuzzSkiaReferenceTable, typeface, NULL);
    885 #if DEBUG_GLYPHS
    886     ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface);
    887 #endif
    888     mCachedHBFaces.add(fontId, face);
    889     return hb_face_reference(face);
    890 }
    891 
    892 void TextLayoutShaper::purgeCaches() {
    893     size_t cacheSize = mCachedHBFaces.size();
    894     for (size_t i = 0; i < cacheSize; i++) {
    895         hb_face_destroy(mCachedHBFaces.valueAt(i));
    896     }
    897     mCachedHBFaces.clear();
    898 }
    899 
    900 TextLayoutEngine::TextLayoutEngine() {
    901     mShaper = new TextLayoutShaper();
    902 #if USE_TEXT_LAYOUT_CACHE
    903     mTextLayoutCache = new TextLayoutCache(mShaper);
    904 #else
    905     mTextLayoutCache = NULL;
    906 #endif
    907 }
    908 
    909 TextLayoutEngine::~TextLayoutEngine() {
    910     delete mTextLayoutCache;
    911     delete mShaper;
    912 }
    913 
    914 sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
    915         jint start, jint count, jint contextCount, jint dirFlags) {
    916     sp<TextLayoutValue> value;
    917 #if USE_TEXT_LAYOUT_CACHE
    918     value = mTextLayoutCache->getValue(paint, text, start, count,
    919             contextCount, dirFlags);
    920     if (value == NULL) {
    921         ALOGE("Cannot get TextLayoutCache value for text = '%s'",
    922                 String8(text + start, count).string());
    923     }
    924 #else
    925     value = new TextLayoutValue(count);
    926     mShaper->computeValues(value.get(), paint,
    927             reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
    928 #endif
    929     return value;
    930 }
    931 
    932 void TextLayoutEngine::purgeCaches() {
    933 #if USE_TEXT_LAYOUT_CACHE
    934     mTextLayoutCache->purgeCaches();
    935 #if DEBUG_GLYPHS
    936     ALOGD("Purged TextLayoutEngine caches");
    937 #endif
    938 #endif
    939 }
    940 
    941 
    942 } // namespace android
    943