TextLayoutCache.cpp revision aaedde51b76901ff05f2a2348eb41f0f5323d954
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#define LOG_TAG "TextLayoutCache" 18 19#include <utils/JenkinsHash.h> 20 21#include "TextLayoutCache.h" 22#include "TextLayout.h" 23#include "SkFontHost.h" 24#include "SkTypeface_android.h" 25#include "HarfBuzzNGFaceSkia.h" 26#include <unicode/unistr.h> 27#include <unicode/uchar.h> 28#include <hb-icu.h> 29 30namespace android { 31 32//-------------------------------------------------------------------------------------------------- 33 34ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine); 35 36//-------------------------------------------------------------------------------------------------- 37 38TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) : 39 mShaper(shaper), 40 mCache(LruCache<TextLayoutCacheKey, sp<TextLayoutValue> >::kUnlimitedCapacity), 41 mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)), 42 mCacheHitCount(0), mNanosecondsSaved(0) { 43 init(); 44} 45 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::purgeCaches() { 81 AutoMutex _l(mLock); 82 mCache.clear(); 83 mShaper->purgeCaches(); 84} 85 86/* 87 * Caching 88 */ 89sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint, 90 const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) { 91 AutoMutex _l(mLock); 92 nsecs_t startTime = 0; 93 if (mDebugEnabled) { 94 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 95 } 96 97 // Create the key 98 TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags); 99 100 // Get value from cache if possible 101 sp<TextLayoutValue> value = mCache.get(key); 102 103 // Value not found for the key, we need to add a new value in the cache 104 if (value == NULL) { 105 if (mDebugEnabled) { 106 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 107 } 108 109 value = new TextLayoutValue(contextCount); 110 111 // Compute advances and store them 112 mShaper->computeValues(value.get(), paint, 113 reinterpret_cast<const UChar*>(key.getText()), start, count, 114 size_t(contextCount), int(dirFlags)); 115 116 if (mDebugEnabled) { 117 value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime); 118 } 119 120 // Don't bother to add in the cache if the entry is too big 121 size_t size = key.getSize() + value->getSize(); 122 if (size <= mMaxSize) { 123 // Cleanup to make some room if needed 124 if (mSize + size > mMaxSize) { 125 if (mDebugEnabled) { 126 ALOGD("Need to clean some entries for making some room for a new entry"); 127 } 128 while (mSize + size > mMaxSize) { 129 // This will call the callback 130 bool removedOne = mCache.removeOldest(); 131 LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we " 132 "failed to remove the oldest entry. " 133 "mSize = %u, size = %u, mMaxSize = %u, mCache.size() = %u", 134 mSize, size, mMaxSize, mCache.size()); 135 } 136 } 137 138 // Update current cache size 139 mSize += size; 140 141 bool putOne = mCache.put(key, value); 142 LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache. " 143 "This indicates that the cache already has an entry with the " 144 "same key but it should not since we checked earlier!" 145 " - start = %d, count = %d, contextCount = %d - Text = '%s'", 146 start, count, contextCount, String8(key.getText() + start, count).string()); 147 148 if (mDebugEnabled) { 149 nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; 150 ALOGD("CACHE MISS: Added entry %p " 151 "with start = %d, count = %d, contextCount = %d, " 152 "entry size %d bytes, remaining space %d bytes" 153 " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'", 154 value.get(), start, count, contextCount, size, mMaxSize - mSize, 155 value->getElapsedTime() * 0.000001f, 156 (totalTime - value->getElapsedTime()) * 0.000001f, 157 String8(key.getText() + start, count).string()); 158 } 159 } else { 160 if (mDebugEnabled) { 161 ALOGD("CACHE MISS: Calculated but not storing entry because it is too big " 162 "with start = %d, count = %d, contextCount = %d, " 163 "entry size %d bytes, remaining space %d bytes" 164 " - Compute time %0.6f ms - Text = '%s'", 165 start, count, contextCount, size, mMaxSize - mSize, 166 value->getElapsedTime() * 0.000001f, 167 String8(key.getText() + start, count).string()); 168 } 169 } 170 } else { 171 // This is a cache hit, just log timestamp and user infos 172 if (mDebugEnabled) { 173 nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; 174 mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet); 175 ++mCacheHitCount; 176 177 if (value->getElapsedTime() > 0) { 178 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet) 179 / ((float)value->getElapsedTime())); 180 ALOGD("CACHE HIT #%d with start = %d, count = %d, contextCount = %d" 181 "- Compute time %0.6f ms - " 182 "Cache get time %0.6f ms - Gain in percent: %2.2f - Text = '%s'", 183 mCacheHitCount, start, count, contextCount, 184 value->getElapsedTime() * 0.000001f, 185 elapsedTimeThruCacheGet * 0.000001f, 186 deltaPercent, 187 String8(key.getText() + start, count).string()); 188 } 189 if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) { 190 dumpCacheStats(); 191 } 192 } 193 } 194 return value; 195} 196 197void TextLayoutCache::dumpCacheStats() { 198 float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize)); 199 float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000; 200 201 size_t cacheSize = mCache.size(); 202 203 ALOGD("------------------------------------------------"); 204 ALOGD("Cache stats"); 205 ALOGD("------------------------------------------------"); 206 ALOGD("pid : %d", getpid()); 207 ALOGD("running : %.0f seconds", timeRunningInSec); 208 ALOGD("entries : %d", cacheSize); 209 ALOGD("max size : %d bytes", mMaxSize); 210 ALOGD("used : %d bytes according to mSize", mSize); 211 ALOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent); 212 ALOGD("hits : %d", mCacheHitCount); 213 ALOGD("saved : %0.6f ms", mNanosecondsSaved * 0.000001f); 214 ALOGD("------------------------------------------------"); 215} 216 217/** 218 * TextLayoutCacheKey 219 */ 220TextLayoutCacheKey::TextLayoutCacheKey(): start(0), count(0), contextCount(0), 221 dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0), 222 hinting(SkPaint::kNo_Hinting), variant(SkPaint::kDefault_Variant), language() { 223} 224 225TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text, 226 size_t start, size_t count, size_t contextCount, int dirFlags) : 227 start(start), count(count), contextCount(contextCount), 228 dirFlags(dirFlags) { 229 textCopy.setTo(text, contextCount); 230 typeface = paint->getTypeface(); 231 textSize = paint->getTextSize(); 232 textSkewX = paint->getTextSkewX(); 233 textScaleX = paint->getTextScaleX(); 234 flags = paint->getFlags(); 235 hinting = paint->getHinting(); 236 variant = paint->getFontVariant(); 237 language = paint->getLanguage(); 238} 239 240TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) : 241 textCopy(other.textCopy), 242 start(other.start), 243 count(other.count), 244 contextCount(other.contextCount), 245 dirFlags(other.dirFlags), 246 typeface(other.typeface), 247 textSize(other.textSize), 248 textSkewX(other.textSkewX), 249 textScaleX(other.textScaleX), 250 flags(other.flags), 251 hinting(other.hinting), 252 variant(other.variant), 253 language(other.language) { 254} 255 256int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) { 257 int deltaInt = lhs.start - rhs.start; 258 if (deltaInt != 0) return (deltaInt); 259 260 deltaInt = lhs.count - rhs.count; 261 if (deltaInt != 0) return (deltaInt); 262 263 deltaInt = lhs.contextCount - rhs.contextCount; 264 if (deltaInt != 0) return (deltaInt); 265 266 if (lhs.typeface < rhs.typeface) return -1; 267 if (lhs.typeface > rhs.typeface) return +1; 268 269 if (lhs.textSize < rhs.textSize) return -1; 270 if (lhs.textSize > rhs.textSize) return +1; 271 272 if (lhs.textSkewX < rhs.textSkewX) return -1; 273 if (lhs.textSkewX > rhs.textSkewX) return +1; 274 275 if (lhs.textScaleX < rhs.textScaleX) return -1; 276 if (lhs.textScaleX > rhs.textScaleX) return +1; 277 278 deltaInt = lhs.flags - rhs.flags; 279 if (deltaInt != 0) return (deltaInt); 280 281 deltaInt = lhs.hinting - rhs.hinting; 282 if (deltaInt != 0) return (deltaInt); 283 284 deltaInt = lhs.dirFlags - rhs.dirFlags; 285 if (deltaInt) return (deltaInt); 286 287 deltaInt = lhs.variant - rhs.variant; 288 if (deltaInt) return (deltaInt); 289 290 if (lhs.language < rhs.language) return -1; 291 if (lhs.language > rhs.language) return +1; 292 293 return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar)); 294} 295 296size_t TextLayoutCacheKey::getSize() const { 297 return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount; 298} 299 300hash_t TextLayoutCacheKey::hash() const { 301 uint32_t hash = JenkinsHashMix(0, start); 302 hash = JenkinsHashMix(hash, count); 303 /* contextCount not needed because it's included in text, below */ 304 hash = JenkinsHashMix(hash, hash_type(typeface)); 305 hash = JenkinsHashMix(hash, hash_type(textSize)); 306 hash = JenkinsHashMix(hash, hash_type(textSkewX)); 307 hash = JenkinsHashMix(hash, hash_type(textScaleX)); 308 hash = JenkinsHashMix(hash, flags); 309 hash = JenkinsHashMix(hash, hinting); 310 hash = JenkinsHashMix(hash, variant); 311 // Note: leaving out language is not problematic, as equality comparisons 312 // are still valid - the only bad thing that could happen is collisions. 313 hash = JenkinsHashMixShorts(hash, getText(), contextCount); 314 return JenkinsHashWhiten(hash); 315} 316 317/** 318 * TextLayoutCacheValue 319 */ 320TextLayoutValue::TextLayoutValue(size_t contextCount) : 321 mTotalAdvance(0), mElapsedTime(0) { 322 // Give a hint for advances and glyphs vectors size 323 mAdvances.setCapacity(contextCount); 324 mGlyphs.setCapacity(contextCount); 325 mPos.setCapacity(contextCount * 2); 326} 327 328size_t TextLayoutValue::getSize() const { 329 return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() + 330 sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity(); 331} 332 333void TextLayoutValue::setElapsedTime(uint32_t time) { 334 mElapsedTime = time; 335} 336 337uint32_t TextLayoutValue::getElapsedTime() { 338 return mElapsedTime; 339} 340 341TextLayoutShaper::TextLayoutShaper() { 342 init(); 343 344 mBuffer = hb_buffer_create(); 345} 346 347void TextLayoutShaper::init() { 348 mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, NULL, 0, SkTypeface::kNormal); 349} 350 351void TextLayoutShaper::unrefTypefaces() { 352 SkSafeUnref(mDefaultTypeface); 353} 354 355TextLayoutShaper::~TextLayoutShaper() { 356 hb_buffer_destroy(mBuffer); 357 358 unrefTypefaces(); 359} 360 361void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars, 362 size_t start, size_t count, size_t contextCount, int dirFlags) { 363 364 computeValues(paint, chars, start, count, contextCount, dirFlags, 365 &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos); 366#if DEBUG_ADVANCES 367 ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count, 368 contextCount, value->mTotalAdvance); 369#endif 370} 371 372void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars, 373 size_t start, size_t count, size_t contextCount, int dirFlags, 374 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 375 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) { 376 *outTotalAdvance = 0; 377 if (!count) { 378 return; 379 } 380 381 UBiDiLevel bidiReq = 0; 382 bool forceLTR = false; 383 bool forceRTL = false; 384 385 switch (dirFlags & kBidi_Mask) { 386 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level 387 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level 388 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break; 389 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break; 390 case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR 391 case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL 392 } 393 394 bool useSingleRun = false; 395 bool isRTL = forceRTL; 396 if (forceLTR || forceRTL) { 397 useSingleRun = true; 398 } else { 399 UBiDi* bidi = ubidi_open(); 400 if (bidi) { 401 UErrorCode status = U_ZERO_ERROR; 402#if DEBUG_GLYPHS 403 ALOGD("******** ComputeValues -- start"); 404 ALOGD(" -- string = '%s'", String8(chars + start, count).string()); 405 ALOGD(" -- start = %d", start); 406 ALOGD(" -- count = %d", count); 407 ALOGD(" -- contextCount = %d", contextCount); 408 ALOGD(" -- bidiReq = %d", bidiReq); 409#endif 410 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); 411 if (U_SUCCESS(status)) { 412 int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl 413 ssize_t rc = ubidi_countRuns(bidi, &status); 414#if DEBUG_GLYPHS 415 ALOGD(" -- dirFlags = %d", dirFlags); 416 ALOGD(" -- paraDir = %d", paraDir); 417 ALOGD(" -- run-count = %d", int(rc)); 418#endif 419 if (U_SUCCESS(status) && rc == 1) { 420 // Normal case: one run, status is ok 421 isRTL = (paraDir == 1); 422 useSingleRun = true; 423 } else if (!U_SUCCESS(status) || rc < 1) { 424 ALOGW("Need to force to single run -- string = '%s'," 425 " status = %d, rc = %d", 426 String8(chars + start, count).string(), status, int(rc)); 427 isRTL = (paraDir == 1); 428 useSingleRun = true; 429 } else { 430 int32_t end = start + count; 431 for (size_t i = 0; i < size_t(rc); ++i) { 432 int32_t startRun = -1; 433 int32_t lengthRun = -1; 434 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); 435 436 if (startRun == -1 || lengthRun == -1) { 437 // Something went wrong when getting the visual run, need to clear 438 // already computed data before doing a single run pass 439 ALOGW("Visual run is not valid"); 440 outGlyphs->clear(); 441 outAdvances->clear(); 442 outPos->clear(); 443 *outTotalAdvance = 0; 444 isRTL = (paraDir == 1); 445 useSingleRun = true; 446 break; 447 } 448 449 if (startRun >= end) { 450 continue; 451 } 452 int32_t endRun = startRun + lengthRun; 453 if (endRun <= int32_t(start)) { 454 continue; 455 } 456 if (startRun < int32_t(start)) { 457 startRun = int32_t(start); 458 } 459 if (endRun > end) { 460 endRun = end; 461 } 462 463 lengthRun = endRun - startRun; 464 isRTL = (runDir == UBIDI_RTL); 465#if DEBUG_GLYPHS 466 ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d", 467 i, startRun, lengthRun, isRTL); 468#endif 469 computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL, 470 outAdvances, outTotalAdvance, outGlyphs, outPos); 471 472 } 473 } 474 } else { 475 ALOGW("Cannot set Para"); 476 useSingleRun = true; 477 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 478 } 479 ubidi_close(bidi); 480 } else { 481 ALOGW("Cannot ubidi_open()"); 482 useSingleRun = true; 483 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 484 } 485 } 486 487 // Default single run case 488 if (useSingleRun){ 489#if DEBUG_GLYPHS 490 ALOGD("Using a SINGLE BiDi Run " 491 "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL); 492#endif 493 computeRunValues(paint, chars, start, count, contextCount, isRTL, 494 outAdvances, outTotalAdvance, outGlyphs, outPos); 495 } 496 497#if DEBUG_GLYPHS 498 ALOGD(" -- Total returned glyphs-count = %d", outGlyphs->size()); 499 ALOGD("******** ComputeValues -- end"); 500#endif 501} 502 503#define HB_IsHighSurrogate(ucs) \ 504 (((ucs) & 0xfc00) == 0xd800) 505 506#define HB_IsLowSurrogate(ucs) \ 507 (((ucs) & 0xfc00) == 0xdc00) 508 509#ifndef HB_SurrogateToUcs4 510#define HB_SurrogateToUcs4_(high, low) \ 511 (((hb_codepoint_t)(high))<<10) + (low) - 0x35fdc00; 512#endif 513 514#define HB_InvalidCodePoint ~0u 515 516hb_codepoint_t 517utf16_to_code_point(const uint16_t *chars, size_t len, ssize_t *iter) { 518 const uint16_t v = chars[(*iter)++]; 519 if (HB_IsHighSurrogate(v)) { 520 // surrogate pair 521 if (size_t(*iter) >= len) { 522 // the surrogate is incomplete. 523 return HB_InvalidCodePoint; 524 } 525 const uint16_t v2 = chars[(*iter)++]; 526 if (!HB_IsLowSurrogate(v2)) { 527 // invalidate surrogate pair. 528 (*iter)--; 529 return HB_InvalidCodePoint; 530 } 531 532 return HB_SurrogateToUcs4(v, v2); 533 } 534 535 if (HB_IsLowSurrogate(v)) { 536 // this isn't a valid code point 537 return HB_InvalidCodePoint; 538 } 539 540 return v; 541} 542 543hb_codepoint_t 544utf16_to_code_point_prev(const uint16_t *chars, size_t len, ssize_t *iter) { 545 const uint16_t v = chars[(*iter)--]; 546 if (HB_IsLowSurrogate(v)) { 547 // surrogate pair 548 if (*iter < 0) { 549 // the surrogate is incomplete. 550 return HB_InvalidCodePoint; 551 } 552 const uint16_t v2 = chars[(*iter)--]; 553 if (!HB_IsHighSurrogate(v2)) { 554 // invalidate surrogate pair. 555 (*iter)++; 556 return HB_InvalidCodePoint; 557 } 558 559 return HB_SurrogateToUcs4(v2, v); 560 } 561 562 if (HB_IsHighSurrogate(v)) { 563 // this isn't a valid code point 564 return HB_InvalidCodePoint; 565 } 566 567 return v; 568} 569 570struct ScriptRun { 571 hb_script_t script; 572 size_t pos; 573 size_t length; 574}; 575 576hb_script_t code_point_to_script(hb_codepoint_t codepoint) { 577 static hb_unicode_funcs_t* u; 578 if (!u) { 579 u = hb_icu_get_unicode_funcs(); 580 } 581 return hb_unicode_script(u, codepoint); 582} 583 584bool 585hb_utf16_script_run_next(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) { 586 if (size_t(*iter) == len) 587 return false; 588 589 run->pos = *iter; 590 const uint32_t init_cp = utf16_to_code_point(chars, len, iter); 591 const hb_script_t init_script = code_point_to_script(init_cp); 592 hb_script_t current_script = init_script; 593 run->script = init_script; 594 595 for (;;) { 596 if (size_t(*iter) == len) 597 break; 598 const ssize_t prev_iter = *iter; 599 const uint32_t cp = utf16_to_code_point(chars, len, iter); 600 const hb_script_t script = code_point_to_script(cp); 601 602 if (script != current_script) { 603 /* BEGIN android-changed 604 The condition was not correct by doing "a == b == constant" 605 END android-changed */ 606 if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) { 607 // If we started off as inherited, we take whatever we can find. 608 run->script = script; 609 current_script = script; 610 continue; 611 } else if (script == HB_SCRIPT_INHERITED) { 612 continue; 613 } else { 614 *iter = prev_iter; 615 break; 616 } 617 } 618 } 619 620 if (run->script == HB_SCRIPT_INHERITED) 621 run->script = HB_SCRIPT_COMMON; 622 623 run->length = *iter - run->pos; 624 return true; 625} 626 627bool 628hb_utf16_script_run_prev(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) { 629 if (*iter == -1) 630 return false; 631 632 const size_t ending_index = *iter; 633 const uint32_t init_cp = utf16_to_code_point_prev(chars, len, iter); 634 const hb_script_t init_script = code_point_to_script(init_cp); 635 hb_script_t current_script = init_script; 636 run->script = init_script; 637 638 for (;;) { 639 if (*iter < 0) 640 break; 641 const ssize_t prev_iter = *iter; 642 const uint32_t cp = utf16_to_code_point_prev(chars, len, iter); 643 const hb_script_t script = code_point_to_script(cp); 644 645 if (script != current_script) { 646 if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) { 647 // If we started off as inherited, we take whatever we can find. 648 run->script = script; 649 current_script = script; 650 continue; 651 } else if (script == HB_SCRIPT_INHERITED) { 652 /* BEGIN android-changed 653 We apply the same fix for Chrome to Android. 654 Chrome team will talk with upsteam about it. 655 Just assume that whatever follows this combining character is within 656 the same script. This is incorrect if you had language1 + combining 657 char + language 2, but that is rare and this code is suspicious 658 anyway. 659 END android-changed */ 660 continue; 661 } else { 662 *iter = prev_iter; 663 break; 664 } 665 } 666 } 667 668 if (run->script == HB_SCRIPT_INHERITED) 669 run->script = HB_SCRIPT_COMMON; 670 671 run->pos = *iter + 1; 672 run->length = ending_index - *iter; 673 return true; 674} 675 676 677static void logGlyphs(hb_buffer_t* buffer) { 678 unsigned int numGlyphs; 679 hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs); 680 hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL); 681 ALOGD(" -- glyphs count=%d", numGlyphs); 682 for (size_t i = 0; i < numGlyphs; i++) { 683 ALOGD(" -- glyph[%d] = %d, cluster = %u, advance = %0.2f, offset.x = %0.2f, offset.y = %0.2f", i, 684 info[i].codepoint, 685 info[i].cluster, 686 HBFixedToFloat(positions[i].x_advance), 687 HBFixedToFloat(positions[i].x_offset), 688 HBFixedToFloat(positions[i].y_offset)); 689 } 690} 691 692void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* contextChars, 693 size_t start, size_t count, size_t contextCount, bool isRTL, 694 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 695 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) { 696 if (!count) { 697 // We cannot shape an empty run. 698 return; 699 } 700 701 // To be filled in later 702 for (size_t i = 0; i < count; i++) { 703 outAdvances->add(0); 704 } 705 706 // Set the string properties 707 const UChar* chars = contextChars + start; 708 709 // Define shaping paint properties 710 mShapingPaint.setTextSize(paint->getTextSize()); 711 float skewX = paint->getTextSkewX(); 712 mShapingPaint.setTextSkewX(skewX); 713 mShapingPaint.setTextScaleX(paint->getTextScaleX()); 714 mShapingPaint.setFlags(paint->getFlags()); 715 mShapingPaint.setHinting(paint->getHinting()); 716 mShapingPaint.setFontVariant(paint->getFontVariant()); 717 mShapingPaint.setLanguage(paint->getLanguage()); 718 719 // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script 720 // into the shaperItem 721 ssize_t indexFontRun = isRTL ? count - 1 : 0; 722 jfloat totalAdvance = *outTotalAdvance; 723 ScriptRun run; // relative to chars 724 while ((isRTL) ? 725 hb_utf16_script_run_prev(&run, chars, count, &indexFontRun): 726 hb_utf16_script_run_next(&run, chars, count, &indexFontRun)) { 727 728#if DEBUG_GLYPHS 729 ALOGD("-------- Start of Script Run --------"); 730 ALOGD("Shaping Script Run with"); 731 ALOGD(" -- isRTL = %d", isRTL); 732 ALOGD(" -- HB script = %c%c%c%c", HB_UNTAG(run.script)); 733 ALOGD(" -- run.pos = %d", int(run.pos)); 734 ALOGD(" -- run.length = %d", int(run.length)); 735 ALOGD(" -- run = '%s'", String8(chars + run.pos, run.length).string()); 736 ALOGD(" -- string = '%s'", String8(chars, count).string()); 737#endif 738 739 hb_buffer_reset(mBuffer); 740 // Note: if we want to set unicode functions, etc., this is the place. 741 742 hb_buffer_set_direction(mBuffer, isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); 743 hb_buffer_set_script(mBuffer, run.script); 744 // Should set language here (for bug 7004056) 745 hb_buffer_add_utf16(mBuffer, contextChars, contextCount, start + run.pos, run.length); 746 747 // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs 748 // and shape the Font run 749 size_t glyphBaseCount = shapeFontRun(paint); 750 unsigned int numGlyphs; 751 hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs); 752 hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(mBuffer, NULL); 753 754#if DEBUG_GLYPHS 755 ALOGD("Got from Harfbuzz"); 756 ALOGD(" -- glyphBaseCount = %d", glyphBaseCount); 757 ALOGD(" -- num_glyph = %d", numGlyphs); 758 ALOGD(" -- isDevKernText = %d", paint->isDevKernText()); 759 ALOGD(" -- initial totalAdvance = %f", totalAdvance); 760 761 logGlyphs(mBuffer); 762#endif 763 764 for (size_t i = 0; i < numGlyphs; i++) { 765 size_t cluster = info[i].cluster - start; 766 float xAdvance = HBFixedToFloat(positions[i].x_advance); 767 outAdvances->replaceAt(outAdvances->itemAt(cluster) + xAdvance, cluster); 768 outGlyphs->add(info[i].codepoint + glyphBaseCount); 769 float xo = HBFixedToFloat(positions[i].x_offset); 770 float yo = -HBFixedToFloat(positions[i].y_offset); 771 outPos->add(totalAdvance + xo + yo * skewX); 772 outPos->add(yo); 773 totalAdvance += xAdvance; 774 } 775 } 776 777 *outTotalAdvance = totalAdvance; 778 779#if DEBUG_GLYPHS 780 ALOGD(" -- final totalAdvance = %f", totalAdvance); 781 ALOGD("-------- End of Script Run --------"); 782#endif 783} 784 785/** 786 * Return the first typeface in the logical change, starting with this typeface, 787 * that contains the specified unichar, or NULL if none is found. 788 * 789 * Note that this function does _not_ increment the reference count on the typeface, as the 790 * assumption is that its lifetime is managed elsewhere - in particular, the fallback typefaces 791 * for the default font live in a global cache. 792 */ 793SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface, 794 hb_script_t script) { 795 SkTypeface::Style currentStyle = SkTypeface::kNormal; 796 if (typeface) { 797 currentStyle = typeface->style(); 798 } 799 typeface = SkCreateTypefaceForScriptNG(script, currentStyle); 800#if DEBUG_GLYPHS 801 ALOGD("Using Harfbuzz Script %d, Style %d", script, currentStyle); 802#endif 803 return typeface; 804} 805 806bool TextLayoutShaper::isComplexScript(hb_script_t script) { 807 switch (script) { 808 case HB_SCRIPT_COMMON: 809 case HB_SCRIPT_GREEK: 810 case HB_SCRIPT_CYRILLIC: 811 case HB_SCRIPT_HANGUL: 812 case HB_SCRIPT_INHERITED: 813 return false; 814 default: 815 return true; 816 } 817} 818 819size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint) { 820 // Update Harfbuzz Shaper 821 822 SkTypeface* typeface = paint->getTypeface(); 823 824 // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz 825 // This is needed as the Typeface used for shaping can be not the default one 826 // when we are shaping any script that needs to use a fallback Font. 827 // If we are a "common" script we dont need to shift 828 size_t baseGlyphCount = 0; 829 hb_codepoint_t firstUnichar = 0; 830 if (isComplexScript(hb_buffer_get_script(mBuffer))) { 831 unsigned int numGlyphs; 832 hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs); 833 for (size_t i = 0; i < numGlyphs; i++) { 834 firstUnichar = info[i].codepoint; 835 if (firstUnichar != ' ') { 836 break; 837 } 838 } 839 baseGlyphCount = paint->getBaseGlyphCount(firstUnichar); 840 } 841 842 if (baseGlyphCount != 0) { 843 typeface = typefaceForScript(paint, typeface, hb_buffer_get_script(mBuffer)); 844 if (!typeface) { 845 typeface = mDefaultTypeface; 846 SkSafeRef(typeface); 847#if DEBUG_GLYPHS 848 ALOGD("Using Default Typeface"); 849#endif 850 } 851 } else { 852 if (!typeface) { 853 typeface = mDefaultTypeface; 854#if DEBUG_GLYPHS 855 ALOGD("Using Default Typeface"); 856#endif 857 } 858 SkSafeRef(typeface); 859 } 860 861 mShapingPaint.setTypeface(typeface); 862 hb_face_t* face = referenceCachedHBFace(typeface); 863 864 float sizeY = paint->getTextSize(); 865 float sizeX = sizeY * paint->getTextScaleX(); 866 hb_font_t* font = createFont(face, &mShapingPaint, sizeX, sizeY); 867 hb_face_destroy(face); 868 869#if DEBUG_GLYPHS 870 ALOGD("Run typeface = %p, uniqueID = %d, face = %p", 871 typeface, typeface->uniqueID(), face); 872#endif 873 SkSafeUnref(typeface); 874 875 hb_shape(font, mBuffer, NULL, 0); 876 hb_font_destroy(font); 877 878 return baseGlyphCount; 879} 880 881hb_face_t* TextLayoutShaper::referenceCachedHBFace(SkTypeface* typeface) { 882 SkFontID fontId = typeface->uniqueID(); 883 ssize_t index = mCachedHBFaces.indexOfKey(fontId); 884 if (index >= 0) { 885 return hb_face_reference(mCachedHBFaces.valueAt(index)); 886 } 887 // TODO: destroy function 888 hb_face_t* face = hb_face_create_for_tables(harfbuzzSkiaReferenceTable, typeface, NULL); 889#if DEBUG_GLYPHS 890 ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface); 891#endif 892 mCachedHBFaces.add(fontId, face); 893 return hb_face_reference(face); 894} 895 896void TextLayoutShaper::purgeCaches() { 897 size_t cacheSize = mCachedHBFaces.size(); 898 for (size_t i = 0; i < cacheSize; i++) { 899 hb_face_destroy(mCachedHBFaces.valueAt(i)); 900 } 901 mCachedHBFaces.clear(); 902 unrefTypefaces(); 903 init(); 904} 905 906TextLayoutEngine::TextLayoutEngine() { 907 mShaper = new TextLayoutShaper(); 908#if USE_TEXT_LAYOUT_CACHE 909 mTextLayoutCache = new TextLayoutCache(mShaper); 910#else 911 mTextLayoutCache = NULL; 912#endif 913} 914 915TextLayoutEngine::~TextLayoutEngine() { 916 delete mTextLayoutCache; 917 delete mShaper; 918} 919 920sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text, 921 jint start, jint count, jint contextCount, jint dirFlags) { 922 sp<TextLayoutValue> value; 923#if USE_TEXT_LAYOUT_CACHE 924 value = mTextLayoutCache->getValue(paint, text, start, count, 925 contextCount, dirFlags); 926 if (value == NULL) { 927 ALOGE("Cannot get TextLayoutCache value for text = '%s'", 928 String8(text + start, count).string()); 929 } 930#else 931 value = new TextLayoutValue(count); 932 mShaper->computeValues(value.get(), paint, 933 reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags); 934#endif 935 return value; 936} 937 938void TextLayoutEngine::purgeCaches() { 939#if USE_TEXT_LAYOUT_CACHE 940 mTextLayoutCache->purgeCaches(); 941#if DEBUG_GLYPHS 942 ALOGD("Purged TextLayoutEngine caches"); 943#endif 944#endif 945} 946 947 948} // namespace android 949