TextLayoutCache.cpp revision 2301d32f7e2ba584abc31ac177dde754385d3c04
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 mPos.setCapacity(contextCount * 2); 312} 313 314size_t TextLayoutValue::getSize() const { 315 return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() + 316 sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity(); 317} 318 319void TextLayoutValue::setElapsedTime(uint32_t time) { 320 mElapsedTime = time; 321} 322 323uint32_t TextLayoutValue::getElapsedTime() { 324 return mElapsedTime; 325} 326 327TextLayoutShaper::TextLayoutShaper() : mShaperItemGlyphArraySize(0) { 328 init(); 329 330 mFontRec.klass = &harfbuzzSkiaClass; 331 mFontRec.userData = 0; 332 333 // Note that the scaling values (x_ and y_ppem, x_ and y_scale) will be set 334 // below, when the paint transform and em unit of the actual shaping font 335 // are known. 336 337 memset(&mShaperItem, 0, sizeof(mShaperItem)); 338 339 mShaperItem.font = &mFontRec; 340 mShaperItem.font->userData = &mShapingPaint; 341} 342 343void TextLayoutShaper::init() { 344 mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, NULL, 0, SkTypeface::kNormal); 345} 346 347void TextLayoutShaper::unrefTypefaces() { 348 SkSafeUnref(mDefaultTypeface); 349} 350 351TextLayoutShaper::~TextLayoutShaper() { 352 unrefTypefaces(); 353 deleteShaperItemGlyphArrays(); 354} 355 356void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars, 357 size_t start, size_t count, size_t contextCount, int dirFlags) { 358 359 computeValues(paint, chars, start, count, contextCount, dirFlags, 360 &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos); 361#if DEBUG_ADVANCES 362 ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count, 363 contextCount, value->mTotalAdvance); 364#endif 365} 366 367void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars, 368 size_t start, size_t count, size_t contextCount, int dirFlags, 369 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 370 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) { 371 *outTotalAdvance = 0; 372 if (!count) { 373 return; 374 } 375 376 UBiDiLevel bidiReq = 0; 377 bool forceLTR = false; 378 bool forceRTL = false; 379 380 switch (dirFlags) { 381 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level 382 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level 383 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break; 384 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break; 385 case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR 386 case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL 387 } 388 389 bool useSingleRun = false; 390 bool isRTL = forceRTL; 391 if (forceLTR || forceRTL) { 392 useSingleRun = true; 393 } else { 394 UBiDi* bidi = ubidi_open(); 395 if (bidi) { 396 UErrorCode status = U_ZERO_ERROR; 397#if DEBUG_GLYPHS 398 ALOGD("******** ComputeValues -- start"); 399 ALOGD(" -- string = '%s'", String8(chars + start, count).string()); 400 ALOGD(" -- start = %d", start); 401 ALOGD(" -- count = %d", count); 402 ALOGD(" -- contextCount = %d", contextCount); 403 ALOGD(" -- bidiReq = %d", bidiReq); 404#endif 405 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); 406 if (U_SUCCESS(status)) { 407 int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl 408 ssize_t rc = ubidi_countRuns(bidi, &status); 409#if DEBUG_GLYPHS 410 ALOGD(" -- dirFlags = %d", dirFlags); 411 ALOGD(" -- paraDir = %d", paraDir); 412 ALOGD(" -- run-count = %d", int(rc)); 413#endif 414 if (U_SUCCESS(status) && rc == 1) { 415 // Normal case: one run, status is ok 416 isRTL = (paraDir == 1); 417 useSingleRun = true; 418 } else if (!U_SUCCESS(status) || rc < 1) { 419 ALOGW("Need to force to single run -- string = '%s'," 420 " status = %d, rc = %d", 421 String8(chars + start, count).string(), status, int(rc)); 422 isRTL = (paraDir == 1); 423 useSingleRun = true; 424 } else { 425 int32_t end = start + count; 426 for (size_t i = 0; i < size_t(rc); ++i) { 427 int32_t startRun = -1; 428 int32_t lengthRun = -1; 429 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); 430 431 if (startRun == -1 || lengthRun == -1) { 432 // Something went wrong when getting the visual run, need to clear 433 // already computed data before doing a single run pass 434 ALOGW("Visual run is not valid"); 435 outGlyphs->clear(); 436 outAdvances->clear(); 437 outPos->clear(); 438 *outTotalAdvance = 0; 439 isRTL = (paraDir == 1); 440 useSingleRun = true; 441 break; 442 } 443 444 if (startRun >= end) { 445 continue; 446 } 447 int32_t endRun = startRun + lengthRun; 448 if (endRun <= int32_t(start)) { 449 continue; 450 } 451 if (startRun < int32_t(start)) { 452 startRun = int32_t(start); 453 } 454 if (endRun > end) { 455 endRun = end; 456 } 457 458 lengthRun = endRun - startRun; 459 isRTL = (runDir == UBIDI_RTL); 460#if DEBUG_GLYPHS 461 ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d", 462 i, startRun, lengthRun, isRTL); 463#endif 464 computeRunValues(paint, chars + startRun, lengthRun, isRTL, 465 outAdvances, outTotalAdvance, outGlyphs, outPos); 466 467 } 468 } 469 } else { 470 ALOGW("Cannot set Para"); 471 useSingleRun = true; 472 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 473 } 474 ubidi_close(bidi); 475 } else { 476 ALOGW("Cannot ubidi_open()"); 477 useSingleRun = true; 478 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 479 } 480 } 481 482 // Default single run case 483 if (useSingleRun){ 484#if DEBUG_GLYPHS 485 ALOGD("Using a SINGLE BiDi Run " 486 "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL); 487#endif 488 computeRunValues(paint, chars + start, count, isRTL, 489 outAdvances, outTotalAdvance, outGlyphs, outPos); 490 } 491 492#if DEBUG_GLYPHS 493 ALOGD(" -- Total returned glyphs-count = %d", outGlyphs->size()); 494 ALOGD("******** ComputeValues -- end"); 495#endif 496} 497 498static void logGlyphs(HB_ShaperItem shaperItem) { 499 ALOGD(" -- glyphs count=%d", shaperItem.num_glyphs); 500 for (size_t i = 0; i < shaperItem.num_glyphs; i++) { 501 ALOGD(" -- glyph[%d] = %d, offset.x = %0.2f, offset.y = %0.2f", i, 502 shaperItem.glyphs[i], 503 HBFixedToFloat(shaperItem.offsets[i].x), 504 HBFixedToFloat(shaperItem.offsets[i].y)); 505 } 506} 507 508void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars, 509 size_t count, bool isRTL, 510 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 511 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) { 512 if (!count) { 513 // We cannot shape an empty run. 514 return; 515 } 516 517 // To be filled in later 518 for (size_t i = 0; i < count; i++) { 519 outAdvances->add(0); 520 } 521 UErrorCode error = U_ZERO_ERROR; 522 bool useNormalizedString = false; 523 for (ssize_t i = count - 1; i >= 0; --i) { 524 UChar ch1 = chars[i]; 525 if (::ublock_getCode(ch1) == UBLOCK_COMBINING_DIACRITICAL_MARKS) { 526 // So we have found a diacritic, let's get now the main code point which is paired 527 // with it. As we can have several diacritics in a row, we need to iterate back again 528#if DEBUG_GLYPHS 529 ALOGD("The BiDi run '%s' is containing a Diacritic at position %d", 530 String8(chars, count).string(), int(i)); 531#endif 532 ssize_t j = i - 1; 533 for (; j >= 0; --j) { 534 UChar ch2 = chars[j]; 535 if (::ublock_getCode(ch2) != UBLOCK_COMBINING_DIACRITICAL_MARKS) { 536 break; 537 } 538 } 539 540 // We could not found the main code point, so we will just use the initial chars 541 if (j < 0) { 542 break; 543 } 544 545#if DEBUG_GLYPHS 546 ALOGD("Found main code point at index %d", int(j)); 547#endif 548 // We found the main code point, so we can normalize the "chunk" and fill 549 // the remaining with ZWSP so that the Paint.getTextWidth() APIs will still be able 550 // to get one advance per char 551 mBuffer.remove(); 552 Normalizer::normalize(UnicodeString(chars + j, i - j + 1), 553 UNORM_NFC, 0 /* no options */, mBuffer, error); 554 if (U_SUCCESS(error)) { 555 if (!useNormalizedString) { 556 useNormalizedString = true; 557 mNormalizedString.setTo(false /* not terminated*/, chars, count); 558 } 559 // Set the normalized chars 560 for (ssize_t k = j; k < j + mBuffer.length(); ++k) { 561 mNormalizedString.setCharAt(k, mBuffer.charAt(k - j)); 562 } 563 // Fill the remain part with ZWSP (ZWNJ and ZWJ would lead to weird results 564 // because some fonts are missing those glyphs) 565 for (ssize_t k = j + mBuffer.length(); k <= i; ++k) { 566 mNormalizedString.setCharAt(k, UNICODE_ZWSP); 567 } 568 } 569 i = j - 1; 570 } 571 } 572 573 // Reverse "BiDi mirrored chars" in RTL mode only 574 // See: http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBinaryProperties.txt 575 // This is a workaround because Harfbuzz is not able to do mirroring in all cases and 576 // script-run splitting with Harfbuzz is splitting on parenthesis 577 if (isRTL) { 578 for (ssize_t i = 0; i < ssize_t(count); i++) { 579 UChar32 ch = chars[i]; 580 if (!u_isMirrored(ch)) continue; 581 if (!useNormalizedString) { 582 useNormalizedString = true; 583 mNormalizedString.setTo(false /* not terminated*/, chars, count); 584 } 585 UChar result = (UChar) u_charMirror(ch); 586 mNormalizedString.setCharAt(i, result); 587#if DEBUG_GLYPHS 588 ALOGD("Rewriting codepoint '%d' to '%d' at position %d", 589 ch, mNormalizedString[i], int(i)); 590#endif 591 } 592 } 593 594#if DEBUG_GLYPHS 595 if (useNormalizedString) { 596 ALOGD("Will use normalized string '%s', length = %d", 597 String8(mNormalizedString.getTerminatedBuffer(), 598 mNormalizedString.length()).string(), 599 mNormalizedString.length()); 600 } else { 601 ALOGD("Normalization is not needed or cannot be done, using initial string"); 602 } 603#endif 604 605 assert(mNormalizedString.length() == count); 606 607 // Set the string properties 608 mShaperItem.string = useNormalizedString ? mNormalizedString.getTerminatedBuffer() : chars; 609 mShaperItem.stringLength = count; 610 611 // Define shaping paint properties 612 mShapingPaint.setTextSize(paint->getTextSize()); 613 float skewX = paint->getTextSkewX(); 614 mShapingPaint.setTextSkewX(skewX); 615 mShapingPaint.setTextScaleX(paint->getTextScaleX()); 616 mShapingPaint.setFlags(paint->getFlags()); 617 mShapingPaint.setHinting(paint->getHinting()); 618 619 // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script 620 // into the shaperItem 621 ssize_t indexFontRun = isRTL ? mShaperItem.stringLength - 1 : 0; 622 unsigned numCodePoints = 0; 623 jfloat totalAdvance = *outTotalAdvance; 624 while ((isRTL) ? 625 hb_utf16_script_run_prev(&numCodePoints, &mShaperItem.item, mShaperItem.string, 626 mShaperItem.stringLength, &indexFontRun): 627 hb_utf16_script_run_next(&numCodePoints, &mShaperItem.item, mShaperItem.string, 628 mShaperItem.stringLength, &indexFontRun)) { 629 630 ssize_t startScriptRun = mShaperItem.item.pos; 631 size_t countScriptRun = mShaperItem.item.length; 632 ssize_t endScriptRun = startScriptRun + countScriptRun; 633 634#if DEBUG_GLYPHS 635 ALOGD("-------- Start of Script Run --------"); 636 ALOGD("Shaping Script Run with"); 637 ALOGD(" -- isRTL = %d", isRTL); 638 ALOGD(" -- HB script = %d", mShaperItem.item.script); 639 ALOGD(" -- startFontRun = %d", int(startScriptRun)); 640 ALOGD(" -- endFontRun = %d", int(endScriptRun)); 641 ALOGD(" -- countFontRun = %d", countScriptRun); 642 ALOGD(" -- run = '%s'", String8(chars + startScriptRun, countScriptRun).string()); 643 ALOGD(" -- string = '%s'", String8(chars, count).string()); 644#endif 645 646 // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs 647 // and shape the Font run 648 size_t glyphBaseCount = shapeFontRun(paint, isRTL); 649 650#if DEBUG_GLYPHS 651 ALOGD("Got from Harfbuzz"); 652 ALOGD(" -- glyphBaseCount = %d", glyphBaseCount); 653 ALOGD(" -- num_glypth = %d", mShaperItem.num_glyphs); 654 ALOGD(" -- kerning_applied = %d", mShaperItem.kerning_applied); 655 ALOGD(" -- isDevKernText = %d", paint->isDevKernText()); 656 657 logGlyphs(mShaperItem); 658#endif 659 660 if (mShaperItem.advances == NULL || mShaperItem.num_glyphs == 0) { 661#if DEBUG_GLYPHS 662 ALOGD("Advances array is empty or num_glypth = 0"); 663#endif 664 continue; 665 } 666 667#if DEBUG_GLYPHS 668 ALOGD("Returned logclusters"); 669 for (size_t i = 0; i < mShaperItem.num_glyphs; i++) { 670 ALOGD(" -- lc[%d] = %d, hb-adv[%d] = %0.2f", i, mShaperItem.log_clusters[i], 671 i, HBFixedToFloat(mShaperItem.advances[i])); 672 } 673#endif 674 // Get Advances and their total 675 jfloat currentAdvance = HBFixedToFloat(mShaperItem.advances[mShaperItem.log_clusters[0]]); 676 jfloat totalFontRunAdvance = currentAdvance; 677 outAdvances->replaceAt(currentAdvance, startScriptRun); 678 for (size_t i = 1; i < countScriptRun; i++) { 679 size_t clusterPrevious = mShaperItem.log_clusters[i - 1]; 680 size_t cluster = mShaperItem.log_clusters[i]; 681 if (cluster != clusterPrevious) { 682 currentAdvance = HBFixedToFloat(mShaperItem.advances[mShaperItem.log_clusters[i]]); 683 outAdvances->replaceAt(currentAdvance, startScriptRun + i); 684 } 685 } 686 // TODO: can be removed and go back in the previous loop when Harfbuzz log clusters are fixed 687 for (size_t i = 1; i < mShaperItem.num_glyphs; i++) { 688 currentAdvance = HBFixedToFloat(mShaperItem.advances[i]); 689 totalFontRunAdvance += currentAdvance; 690 } 691 692#if DEBUG_ADVANCES 693 ALOGD("Returned advances"); 694 for (size_t i = 0; i < countScriptRun; i++) { 695 ALOGD(" -- hb-adv[%d] = %0.2f, log_clusters = %d, total = %0.2f", i, 696 (*outAdvances)[i], mShaperItem.log_clusters[i], totalFontRunAdvance); 697 } 698#endif 699 700 // Get Glyphs and reverse them in place if RTL 701 if (outGlyphs) { 702 size_t countGlyphs = mShaperItem.num_glyphs; 703#if DEBUG_GLYPHS 704 ALOGD("Returned script run glyphs -- count = %d", countGlyphs); 705#endif 706 for (size_t i = 0; i < countGlyphs; i++) { 707 jchar glyph = glyphBaseCount + 708 (jchar) mShaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i]; 709#if DEBUG_GLYPHS 710 ALOGD(" -- glyph[%d] = %d", i, glyph); 711#endif 712 outGlyphs->add(glyph); 713 } 714 } 715 716 // Get glyph positions (and reverse them in place if RTL) 717 if (outPos) { 718 size_t countGlyphs = mShaperItem.num_glyphs; 719 jfloat x = totalAdvance; 720 for (size_t i = 0; i < countGlyphs; i++) { 721 size_t index = (!isRTL) ? i : countGlyphs - 1 - i; 722 float xo = HBFixedToFloat(mShaperItem.offsets[index].x); 723 float yo = HBFixedToFloat(mShaperItem.offsets[index].y); 724 // Apply skewX component of transform to position offsets. Note 725 // that scale has already been applied through x_ and y_scale 726 // set in the mFontRec. 727 outPos->add(x + xo + yo * skewX); 728 outPos->add(yo); 729#ifdef DEBUG_GLYPHS 730 ALOGD(" -- hb adv[%d] = %f, log_cluster[%d] = %d", 731 index, HBFixedToFloat(mShaperItem.advances[index]), 732 index, mShaperItem.log_clusters[index]); 733#endif 734 x += HBFixedToFloat(mShaperItem.advances[index]); 735 } 736 } 737 738 totalAdvance += totalFontRunAdvance; 739 } 740 741 *outTotalAdvance = totalAdvance; 742 743#if DEBUG_GLYPHS 744 ALOGD("-------- End of Script Run --------"); 745#endif 746} 747 748/** 749 * Return the first typeface in the logical change, starting with this typeface, 750 * that contains the specified unichar, or NULL if none is found. 751 * 752 * Note that this function does _not_ increment the reference count on the typeface, as the 753 * assumption is that its lifetime is managed elsewhere - in particular, the fallback typefaces 754 * for the default font live in a global cache. 755 */ 756SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface, 757 HB_Script script) { 758 SkTypeface::Style currentStyle = SkTypeface::kNormal; 759 if (typeface) { 760 currentStyle = typeface->style(); 761 } 762 typeface = SkCreateTypefaceForScript(script, currentStyle); 763#if DEBUG_GLYPHS 764 ALOGD("Using Harfbuzz Script %d, Style %d", script, currentStyle); 765#endif 766 return typeface; 767} 768 769bool TextLayoutShaper::isComplexScript(HB_Script script) { 770 switch (script) { 771 case HB_Script_Common: 772 case HB_Script_Greek: 773 case HB_Script_Cyrillic: 774 case HB_Script_Hangul: 775 case HB_Script_Inherited: 776 return false; 777 default: 778 return true; 779 } 780} 781 782size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint, bool isRTL) { 783 // Reset kerning 784 mShaperItem.kerning_applied = false; 785 786 // Update Harfbuzz Shaper 787 mShaperItem.item.bidiLevel = isRTL; 788 789 SkTypeface* typeface = paint->getTypeface(); 790 791 // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz 792 // This is needed as the Typeface used for shaping can be not the default one 793 // when we are shaping any script that needs to use a fallback Font. 794 // If we are a "common" script we dont need to shift 795 size_t baseGlyphCount = 0; 796 SkUnichar firstUnichar = 0; 797 if (isComplexScript(mShaperItem.item.script)) { 798 const uint16_t* text16 = (const uint16_t*) (mShaperItem.string + mShaperItem.item.pos); 799 const uint16_t* text16End = text16 + mShaperItem.item.length; 800 firstUnichar = SkUTF16_NextUnichar(&text16); 801 while (firstUnichar == ' ' && text16 < text16End) { 802 firstUnichar = SkUTF16_NextUnichar(&text16); 803 } 804 baseGlyphCount = paint->getBaseGlyphCount(firstUnichar); 805 } 806 807 if (baseGlyphCount != 0) { 808 typeface = typefaceForScript(paint, typeface, mShaperItem.item.script); 809 if (!typeface) { 810 typeface = mDefaultTypeface; 811 SkSafeRef(typeface); 812#if DEBUG_GLYPHS 813 ALOGD("Using Default Typeface"); 814#endif 815 } 816 } else { 817 if (!typeface) { 818 typeface = mDefaultTypeface; 819#if DEBUG_GLYPHS 820 ALOGD("Using Default Typeface"); 821#endif 822 } 823 SkSafeRef(typeface); 824 } 825 826 mShapingPaint.setTypeface(typeface); 827 mShaperItem.face = getCachedHBFace(typeface); 828 829 int textSize = paint->getTextSize(); 830 float scaleX = paint->getTextScaleX(); 831 mFontRec.x_ppem = floor(scaleX * textSize + 0.5); 832 mFontRec.y_ppem = textSize; 833 uint32_t unitsPerEm = SkFontHost::GetUnitsPerEm(typeface->uniqueID()); 834 // x_ and y_scale are the conversion factors from font design space 835 // (unitsPerEm) to 1/64th of device pixels in 16.16 format. 836 const int kDevicePixelFraction = 64; 837 const int kMultiplyFor16Dot16 = 1 << 16; 838 float emScale = kDevicePixelFraction * kMultiplyFor16Dot16 / (float)unitsPerEm; 839 mFontRec.x_scale = emScale * scaleX * textSize; 840 mFontRec.y_scale = emScale * textSize; 841 842#if DEBUG_GLYPHS 843 ALOGD("Run typeface = %p, uniqueID = %d, hb_face = %p", 844 typeface, typeface->uniqueID(), mShaperItem.face); 845#endif 846 SkSafeUnref(typeface); 847 848 // Shape 849 assert(mShaperItem.item.length > 0); // Harfbuzz will overwrite other memory if length is 0. 850 size_t size = mShaperItem.item.length * 3 / 2; 851 while (!doShaping(size)) { 852 // We overflowed our glyph arrays. Resize and retry. 853 // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size. 854 size = mShaperItem.num_glyphs * 2; 855 } 856 return baseGlyphCount; 857} 858 859bool TextLayoutShaper::doShaping(size_t size) { 860 if (size > mShaperItemGlyphArraySize) { 861 deleteShaperItemGlyphArrays(); 862 createShaperItemGlyphArrays(size); 863 } 864 mShaperItem.num_glyphs = mShaperItemGlyphArraySize; 865 memset(mShaperItem.offsets, 0, mShaperItem.num_glyphs * sizeof(HB_FixedPoint)); 866 return HB_ShapeItem(&mShaperItem); 867} 868 869void TextLayoutShaper::createShaperItemGlyphArrays(size_t size) { 870#if DEBUG_GLYPHS 871 ALOGD("Creating Glyph Arrays with size = %d", size); 872#endif 873 mShaperItemGlyphArraySize = size; 874 875 // These arrays are all indexed by glyph. 876 mShaperItem.glyphs = new HB_Glyph[size]; 877 mShaperItem.attributes = new HB_GlyphAttributes[size]; 878 mShaperItem.advances = new HB_Fixed[size]; 879 mShaperItem.offsets = new HB_FixedPoint[size]; 880 881 // Although the log_clusters array is indexed by character, Harfbuzz expects that 882 // it is big enough to hold one element per glyph. So we allocate log_clusters along 883 // with the other glyph arrays above. 884 mShaperItem.log_clusters = new unsigned short[size]; 885} 886 887void TextLayoutShaper::deleteShaperItemGlyphArrays() { 888 delete[] mShaperItem.glyphs; 889 delete[] mShaperItem.attributes; 890 delete[] mShaperItem.advances; 891 delete[] mShaperItem.offsets; 892 delete[] mShaperItem.log_clusters; 893} 894 895HB_Face TextLayoutShaper::getCachedHBFace(SkTypeface* typeface) { 896 SkFontID fontId = typeface->uniqueID(); 897 ssize_t index = mCachedHBFaces.indexOfKey(fontId); 898 if (index >= 0) { 899 return mCachedHBFaces.valueAt(index); 900 } 901 HB_Face face = HB_NewFace(typeface, harfbuzzSkiaGetTable); 902 if (face) { 903#if DEBUG_GLYPHS 904 ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface); 905#endif 906 mCachedHBFaces.add(fontId, face); 907 } 908 return face; 909} 910 911void TextLayoutShaper::purgeCaches() { 912 size_t cacheSize = mCachedHBFaces.size(); 913 for (size_t i = 0; i < cacheSize; i++) { 914 HB_FreeFace(mCachedHBFaces.valueAt(i)); 915 } 916 mCachedHBFaces.clear(); 917 unrefTypefaces(); 918 init(); 919} 920 921TextLayoutEngine::TextLayoutEngine() { 922 mShaper = new TextLayoutShaper(); 923#if USE_TEXT_LAYOUT_CACHE 924 mTextLayoutCache = new TextLayoutCache(mShaper); 925#else 926 mTextLayoutCache = NULL; 927#endif 928} 929 930TextLayoutEngine::~TextLayoutEngine() { 931 delete mTextLayoutCache; 932 delete mShaper; 933} 934 935sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text, 936 jint start, jint count, jint contextCount, jint dirFlags) { 937 sp<TextLayoutValue> value; 938#if USE_TEXT_LAYOUT_CACHE 939 value = mTextLayoutCache->getValue(paint, text, start, count, 940 contextCount, dirFlags); 941 if (value == NULL) { 942 ALOGE("Cannot get TextLayoutCache value for text = '%s'", 943 String8(text + start, count).string()); 944 } 945#else 946 value = new TextLayoutValue(count); 947 mShaper->computeValues(value.get(), paint, 948 reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags); 949#endif 950 return value; 951} 952 953void TextLayoutEngine::purgeCaches() { 954#if USE_TEXT_LAYOUT_CACHE 955 mTextLayoutCache->clear(); 956 mShaper->purgeCaches(); 957#if DEBUG_GLYPHS 958 ALOGD("Purged TextLayoutEngine caches"); 959#endif 960#endif 961} 962 963 964} // namespace android 965