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