TextLayoutCache.cpp revision 65194adc9a5174fc88fb579472799e8b4771796b
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 37#if USE_TEXT_LAYOUT_CACHE 38 39 ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutCache); 40 41#endif 42 43 ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine); 44 45//-------------------------------------------------------------------------------------------------- 46 47TextLayoutCache::TextLayoutCache() : 48 mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity), 49 mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)), 50 mCacheHitCount(0), mNanosecondsSaved(0) { 51 init(); 52} 53 54TextLayoutCache::~TextLayoutCache() { 55 mCache.clear(); 56} 57 58void TextLayoutCache::init() { 59 mCache.setOnEntryRemovedListener(this); 60 61 mDebugLevel = readRtlDebugLevel(); 62 mDebugEnabled = mDebugLevel & kRtlDebugCaches; 63 LOGD("Using debug level = %d - Debug Enabled = %d", mDebugLevel, mDebugEnabled); 64 65 mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC); 66 67 if (mDebugEnabled) { 68 LOGD("Initialization is done - Start time = %lld", mCacheStartTime); 69 } 70 71 mInitialized = true; 72} 73 74/** 75 * Callbacks 76 */ 77void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) { 78 size_t totalSizeToDelete = text.getSize() + desc->getSize(); 79 mSize -= totalSizeToDelete; 80 if (mDebugEnabled) { 81 LOGD("Cache value %p deleted, size = %d", desc.get(), totalSizeToDelete); 82 } 83} 84 85/* 86 * Cache clearing 87 */ 88void TextLayoutCache::clear() { 89 mCache.clear(); 90} 91 92/* 93 * Caching 94 */ 95sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint, 96 const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) { 97 AutoMutex _l(mLock); 98 nsecs_t startTime = 0; 99 if (mDebugEnabled) { 100 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 101 } 102 103 // Create the key 104 TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags); 105 106 // Get value from cache if possible 107 sp<TextLayoutCacheValue> value = mCache.get(key); 108 109 // Value not found for the key, we need to add a new value in the cache 110 if (value == NULL) { 111 if (mDebugEnabled) { 112 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 113 } 114 115 value = new TextLayoutCacheValue(contextCount); 116 117 // Compute advances and store them 118 TextLayoutEngine::getInstance().computeValues(value.get(), paint, 119 reinterpret_cast<const UChar*>(text), start, count, 120 size_t(contextCount), int(dirFlags)); 121 122 if (mDebugEnabled) { 123 value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime); 124 } 125 126 // Don't bother to add in the cache if the entry is too big 127 size_t size = key.getSize() + value->getSize(); 128 if (size <= mMaxSize) { 129 // Cleanup to make some room if needed 130 if (mSize + size > mMaxSize) { 131 if (mDebugEnabled) { 132 LOGD("Need to clean some entries for making some room for a new entry"); 133 } 134 while (mSize + size > mMaxSize) { 135 // This will call the callback 136 bool removedOne = mCache.removeOldest(); 137 LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we " 138 "failed to remove the oldest entry. " 139 "mSize = %u, size = %u, mMaxSize = %u, mCache.size() = %u", 140 mSize, size, mMaxSize, mCache.size()); 141 } 142 } 143 144 // Update current cache size 145 mSize += size; 146 147 // Copy the text when we insert the new entry 148 key.internalTextCopy(); 149 150 bool putOne = mCache.put(key, value); 151 LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache. " 152 "This indicates that the cache already has an entry with the " 153 "same key but it should not since we checked earlier!" 154 " - start = %d, count = %d, contextCount = %d - Text = '%s'", 155 start, count, contextCount, String8(text + start, count).string()); 156 157 if (mDebugEnabled) { 158 nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; 159 LOGD("CACHE MISS: Added entry %p " 160 "with start = %d, count = %d, contextCount = %d, " 161 "entry size %d bytes, remaining space %d bytes" 162 " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'", 163 value.get(), start, count, contextCount, size, mMaxSize - mSize, 164 value->getElapsedTime() * 0.000001f, 165 (totalTime - value->getElapsedTime()) * 0.000001f, 166 String8(text + start, count).string()); 167 } 168 } else { 169 if (mDebugEnabled) { 170 LOGD("CACHE MISS: Calculated but not storing entry because it is too big " 171 "with start = %d, count = %d, contextCount = %d, " 172 "entry size %d bytes, remaining space %d bytes" 173 " - Compute time %0.6f ms - Text = '%s'", 174 start, count, contextCount, size, mMaxSize - mSize, 175 value->getElapsedTime() * 0.000001f, 176 String8(text + start, count).string()); 177 } 178 value.clear(); 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 LOGD("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 LOGD("------------------------------------------------"); 218 LOGD("Cache stats"); 219 LOGD("------------------------------------------------"); 220 LOGD("pid : %d", getpid()); 221 LOGD("running : %.0f seconds", timeRunningInSec); 222 LOGD("entries : %d", cacheSize); 223 LOGD("max size : %d bytes", mMaxSize); 224 LOGD("used : %d bytes according to mSize, %d bytes actual", mSize, bytes); 225 LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent); 226 LOGD("hits : %d", mCacheHitCount); 227 LOGD("saved : %0.6f ms", mNanosecondsSaved * 0.000001f); 228 LOGD("------------------------------------------------"); 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, 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 LOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count, 370 contextCount, value->mTotalAdvance); 371#endif 372} 373 374void TextLayoutEngine::computeValues(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 LOGD("******** ComputeValues -- start"); 406 LOGD(" -- string = '%s'", String8(chars + start, count).string()); 407 LOGD(" -- start = %d", start); 408 LOGD(" -- count = %d", count); 409 LOGD(" -- contextCount = %d", contextCount); 410 LOGD(" -- 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 LOGD(" -- dirFlags = %d", dirFlags); 418 LOGD(" -- paraDir = %d", paraDir); 419 LOGD(" -- 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 LOGW("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 LOGW("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 LOGD("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 LOGW("Cannot set Para"); 479 useSingleRun = true; 480 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 481 } 482 ubidi_close(bidi); 483 } else { 484 LOGW("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 LOGD("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 LOGD(" -- Total returned glyphs-count = %d", outGlyphs->size()); 502 LOGD("******** ComputeValues -- end"); 503#endif 504} 505 506static void logGlyphs(HB_ShaperItem shaperItem) { 507 LOGD(" -- glyphs count=%d", shaperItem.num_glyphs); 508 for (size_t i = 0; i < shaperItem.num_glyphs; i++) { 509 LOGD(" -- 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(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 LOGD("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 LOGD("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 LOGD("Will use normalized string '%s', length = %d", 581 String8(mNormalizedString.getTerminatedBuffer(), 582 mNormalizedString.length()).string(), 583 mNormalizedString.length()); 584 } else { 585 LOGD("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 LOGD("-------- Start of Script Run --------"); 619 LOGD("Shaping Script Run with"); 620 LOGD(" -- isRTL = %d", isRTL); 621 LOGD(" -- HB script = %d", mShaperItem.item.script); 622 LOGD(" -- startFontRun = %d", int(startScriptRun)); 623 LOGD(" -- endFontRun = %d", int(endScriptRun)); 624 LOGD(" -- countFontRun = %d", countScriptRun); 625 LOGD(" -- run = '%s'", String8(chars + startScriptRun, countScriptRun).string()); 626 LOGD(" -- 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 LOGD("Got from Harfbuzz"); 635 LOGD(" -- glyphBaseCount = %d", glyphBaseCount); 636 LOGD(" -- num_glypth = %d", mShaperItem.num_glyphs); 637 LOGD(" -- kerning_applied = %d", mShaperItem.kerning_applied); 638 LOGD(" -- isDevKernText = %d", paint->isDevKernText()); 639 640 logGlyphs(mShaperItem); 641#endif 642 if (isRTL) { 643 endScriptRun = startScriptRun; 644#if DEBUG_GLYPHS 645 LOGD("Updated endScriptRun = %d", int(endScriptRun)); 646#endif 647 } else { 648 startScriptRun = endScriptRun; 649#if DEBUG_GLYPHS 650 LOGD("Updated startScriptRun = %d", int(startScriptRun)); 651#endif 652 } 653 654 if (mShaperItem.advances == NULL || mShaperItem.num_glyphs == 0) { 655#if DEBUG_GLYPHS 656 LOGD("Advances array is empty or num_glypth = 0"); 657#endif 658 outAdvances->insertAt(0, outAdvances->size(), countScriptRun); 659 continue; 660 } 661 662 // Get Advances and their total 663 jfloat currentAdvance = HBFixedToFloat(mShaperItem.advances[mShaperItem.log_clusters[0]]); 664 jfloat totalFontRunAdvance = currentAdvance; 665 outAdvances->add(currentAdvance); 666 for (size_t i = 1; i < countScriptRun; i++) { 667 size_t clusterPrevious = mShaperItem.log_clusters[i - 1]; 668 size_t cluster = mShaperItem.log_clusters[i]; 669 if (cluster == clusterPrevious) { 670 outAdvances->add(0); 671 } else { 672 currentAdvance = HBFixedToFloat(mShaperItem.advances[mShaperItem.log_clusters[i]]); 673 totalFontRunAdvance += currentAdvance; 674 outAdvances->add(currentAdvance); 675 } 676 } 677 678 totalAdvance += totalFontRunAdvance; 679 680#if DEBUG_ADVANCES 681 LOGD("Returned advances"); 682 for (size_t i = 0; i < countScriptRun; i++) { 683 LOGD(" -- hb-adv[%d] = %0.2f, log_clusters = %d, total = %0.2f", i, 684 (*outAdvances)[i], mShaperItem.log_clusters[i], totalFontRunAdvance); 685 } 686#endif 687 688 // Get Glyphs and reverse them in place if RTL 689 if (outGlyphs) { 690 size_t countGlyphs = mShaperItem.num_glyphs; 691#if DEBUG_GLYPHS 692 LOGD("Returned script run glyphs -- count = %d", countGlyphs); 693#endif 694 for (size_t i = 0; i < countGlyphs; i++) { 695 jchar glyph = glyphBaseCount + 696 (jchar) mShaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i]; 697#if DEBUG_GLYPHS 698 LOGD(" -- glyph[%d] = %d", i, glyph); 699#endif 700 outGlyphs->add(glyph); 701 } 702 } 703 } 704 705 *outTotalAdvance = totalAdvance; 706 707#if DEBUG_GLYPHS 708 LOGD("-------- End of Script Run --------"); 709#endif 710} 711 712 713size_t TextLayoutEngine::shapeFontRun(SkPaint* paint, bool isRTL) { 714 // Reset kerning 715 mShaperItem.kerning_applied = false; 716 717 // Update Harfbuzz Shaper 718 mShaperItem.item.bidiLevel = isRTL; 719 720 SkTypeface* typeface = paint->getTypeface(); 721 722 // Set the correct Typeface depending on the script 723 switch (mShaperItem.item.script) { 724 case HB_Script_Arabic: 725 typeface = getCachedTypeface(&mArabicTypeface, TYPEFACE_ARABIC); 726#if DEBUG_GLYPHS 727 LOGD("Using Arabic Typeface"); 728#endif 729 break; 730 731 case HB_Script_Hebrew: 732 if (typeface) { 733 switch (typeface->style()) { 734 case SkTypeface::kBold: 735 case SkTypeface::kBoldItalic: 736 typeface = getCachedTypeface(&mHebrewBoldTypeface, TYPE_FACE_HEBREW_BOLD); 737#if DEBUG_GLYPHS 738 LOGD("Using Hebrew Bold/BoldItalic Typeface"); 739#endif 740 break; 741 742 case SkTypeface::kNormal: 743 case SkTypeface::kItalic: 744 default: 745 typeface = getCachedTypeface(&mHebrewRegularTypeface, TYPE_FACE_HEBREW_REGULAR); 746#if DEBUG_GLYPHS 747 LOGD("Using Hebrew Regular/Italic Typeface"); 748#endif 749 break; 750 } 751 } else { 752 typeface = getCachedTypeface(&mHebrewRegularTypeface, TYPE_FACE_HEBREW_REGULAR); 753#if DEBUG_GLYPHS 754 LOGD("Using Hebrew Regular Typeface"); 755#endif 756 } 757 break; 758 759 case HB_Script_Bengali: 760 typeface = getCachedTypeface(&mBengaliTypeface, TYPEFACE_BENGALI); 761#if DEBUG_GLYPHS 762 LOGD("Using Bengali Typeface"); 763#endif 764 break; 765 766 default: 767 if (!typeface) { 768 typeface = mDefaultTypeface; 769#if DEBUG_GLYPHS 770 LOGD("Using Default Typeface"); 771#endif 772 } else { 773#if DEBUG_GLYPHS 774 LOGD("Using Paint Typeface"); 775#endif 776 } 777 break; 778 } 779 780 mShapingPaint.setTypeface(typeface); 781 mShaperItem.face = getCachedHBFace(typeface); 782 783#if DEBUG_GLYPHS 784 LOGD("Run typeface = %p, uniqueID = %d, hb_face = %p", 785 typeface, typeface->uniqueID(), mShaperItem.face); 786#endif 787 788 // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz 789 // This is needed as the Typeface used for shaping can be not the default one 790 // when we are shaping any script that needs to use a fallback Font. 791 // If we are a "common" script we dont need to shift 792 size_t baseGlyphCount = 0; 793 switch (mShaperItem.item.script) { 794 case HB_Script_Arabic: 795 case HB_Script_Hebrew: 796 case HB_Script_Bengali:{ 797 const uint16_t* text16 = (const uint16_t*)mShaperItem.string; 798 SkUnichar firstUnichar = SkUTF16_NextUnichar(&text16); 799 baseGlyphCount = paint->getBaseGlyphCount(firstUnichar); 800 break; 801 } 802 default: 803 break; 804 } 805 806 // Shape 807 assert(mShaperItem.item.length > 0); // Harfbuzz will overwrite other memory if length is 0. 808 ensureShaperItemGlyphArrays(mShaperItem.item.length * 3 / 2); 809 mShaperItem.num_glyphs = mShaperItemGlyphArraySize; 810 while (!HB_ShapeItem(&mShaperItem)) { 811 // We overflowed our glyph arrays. Resize and retry. 812 // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size. 813 ensureShaperItemGlyphArrays(mShaperItem.num_glyphs * 2); 814 mShaperItem.num_glyphs = mShaperItemGlyphArraySize; 815 } 816 return baseGlyphCount; 817} 818 819void TextLayoutEngine::ensureShaperItemGlyphArrays(size_t size) { 820 if (size > mShaperItemGlyphArraySize) { 821 deleteShaperItemGlyphArrays(); 822 createShaperItemGlyphArrays(size); 823 } 824} 825 826void TextLayoutEngine::createShaperItemGlyphArrays(size_t size) { 827#if DEBUG_GLYPHS 828 LOGD("Creating Glyph Arrays with size = %d", size); 829#endif 830 mShaperItemGlyphArraySize = size; 831 832 // These arrays are all indexed by glyph. 833 mShaperItem.glyphs = new HB_Glyph[size]; 834 mShaperItem.attributes = new HB_GlyphAttributes[size]; 835 mShaperItem.advances = new HB_Fixed[size]; 836 mShaperItem.offsets = new HB_FixedPoint[size]; 837 838 // Although the log_clusters array is indexed by character, Harfbuzz expects that 839 // it is big enough to hold one element per glyph. So we allocate log_clusters along 840 // with the other glyph arrays above. 841 mShaperItem.log_clusters = new unsigned short[size]; 842} 843 844void TextLayoutEngine::deleteShaperItemGlyphArrays() { 845 delete[] mShaperItem.glyphs; 846 delete[] mShaperItem.attributes; 847 delete[] mShaperItem.advances; 848 delete[] mShaperItem.offsets; 849 delete[] mShaperItem.log_clusters; 850} 851 852SkTypeface* TextLayoutEngine::getCachedTypeface(SkTypeface** typeface, const char path[]) { 853 if (!*typeface) { 854 *typeface = SkTypeface::CreateFromFile(path); 855 // CreateFromFile(path) can return NULL if the path is non existing 856 if (!*typeface) { 857#if DEBUG_GLYPHS 858 LOGD("Font path '%s' is not valid, will use default font", path); 859#endif 860 return mDefaultTypeface; 861 } 862 (*typeface)->ref(); 863#if DEBUG_GLYPHS 864 LOGD("Created SkTypeface from file '%s' with uniqueID = %d", path, (*typeface)->uniqueID()); 865#endif 866 } 867 return *typeface; 868} 869 870HB_Face TextLayoutEngine::getCachedHBFace(SkTypeface* typeface) { 871 SkFontID fontId = typeface->uniqueID(); 872 ssize_t index = mCachedHBFaces.indexOfKey(fontId); 873 if (index >= 0) { 874 return mCachedHBFaces.valueAt(index); 875 } 876 HB_Face face = HB_NewFace(typeface, harfbuzzSkiaGetTable); 877 if (face) { 878#if DEBUG_GLYPHS 879 LOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface); 880#endif 881 mCachedHBFaces.add(fontId, face); 882 } 883 return face; 884} 885 886} // namespace android 887