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