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 "SkGlyphCache.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 30 namespace android { 31 32 //-------------------------------------------------------------------------------------------------- 33 34 ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine); 35 36 //-------------------------------------------------------------------------------------------------- 37 38 TextLayoutCache::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 46 TextLayoutCache::~TextLayoutCache() { 47 mCache.clear(); 48 } 49 50 void 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 */ 69 void 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 */ 80 void TextLayoutCache::purgeCaches() { 81 AutoMutex _l(mLock); 82 mCache.clear(); 83 mShaper->purgeCaches(); 84 } 85 86 /* 87 * Caching 88 */ 89 sp<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 197 void 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 */ 220 TextLayoutCacheKey::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) { 223 paintOpts.setUseFontFallbacks(true); 224 } 225 226 TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text, 227 size_t start, size_t count, size_t contextCount, int dirFlags) : 228 start(start), count(count), contextCount(contextCount), 229 dirFlags(dirFlags) { 230 textCopy.setTo(text, contextCount); 231 typeface = paint->getTypeface(); 232 textSize = paint->getTextSize(); 233 textSkewX = paint->getTextSkewX(); 234 textScaleX = paint->getTextScaleX(); 235 flags = paint->getFlags(); 236 hinting = paint->getHinting(); 237 paintOpts = paint->getPaintOptionsAndroid(); 238 } 239 240 TextLayoutCacheKey::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 paintOpts(other.paintOpts) { 253 } 254 255 int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) { 256 int deltaInt = lhs.start - rhs.start; 257 if (deltaInt != 0) return (deltaInt); 258 259 deltaInt = lhs.count - rhs.count; 260 if (deltaInt != 0) return (deltaInt); 261 262 deltaInt = lhs.contextCount - rhs.contextCount; 263 if (deltaInt != 0) return (deltaInt); 264 265 if (lhs.typeface < rhs.typeface) return -1; 266 if (lhs.typeface > rhs.typeface) return +1; 267 268 if (lhs.textSize < rhs.textSize) return -1; 269 if (lhs.textSize > rhs.textSize) return +1; 270 271 if (lhs.textSkewX < rhs.textSkewX) return -1; 272 if (lhs.textSkewX > rhs.textSkewX) return +1; 273 274 if (lhs.textScaleX < rhs.textScaleX) return -1; 275 if (lhs.textScaleX > rhs.textScaleX) return +1; 276 277 deltaInt = lhs.flags - rhs.flags; 278 if (deltaInt != 0) return (deltaInt); 279 280 deltaInt = lhs.hinting - rhs.hinting; 281 if (deltaInt != 0) return (deltaInt); 282 283 deltaInt = lhs.dirFlags - rhs.dirFlags; 284 if (deltaInt) return (deltaInt); 285 286 if (lhs.paintOpts != rhs.paintOpts) 287 return memcmp(&lhs.paintOpts, &rhs.paintOpts, sizeof(SkPaintOptionsAndroid)); 288 289 return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar)); 290 } 291 292 size_t TextLayoutCacheKey::getSize() const { 293 return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount; 294 } 295 296 hash_t TextLayoutCacheKey::hash() const { 297 uint32_t hash = JenkinsHashMix(0, start); 298 hash = JenkinsHashMix(hash, count); 299 /* contextCount not needed because it's included in text, below */ 300 hash = JenkinsHashMix(hash, hash_type(typeface)); 301 hash = JenkinsHashMix(hash, hash_type(textSize)); 302 hash = JenkinsHashMix(hash, hash_type(textSkewX)); 303 hash = JenkinsHashMix(hash, hash_type(textScaleX)); 304 hash = JenkinsHashMix(hash, flags); 305 hash = JenkinsHashMix(hash, hinting); 306 hash = JenkinsHashMix(hash, paintOpts.getFontVariant()); 307 // Note: leaving out language is not problematic, as equality comparisons 308 // are still valid - the only bad thing that could happen is collisions. 309 hash = JenkinsHashMixShorts(hash, getText(), contextCount); 310 return JenkinsHashWhiten(hash); 311 } 312 313 /** 314 * TextLayoutCacheValue 315 */ 316 TextLayoutValue::TextLayoutValue(size_t contextCount) : 317 mTotalAdvance(0), mElapsedTime(0) { 318 mBounds.setEmpty(); 319 // Give a hint for advances and glyphs vectors size 320 mAdvances.setCapacity(contextCount); 321 mGlyphs.setCapacity(contextCount); 322 mPos.setCapacity(contextCount * 2); 323 } 324 325 size_t TextLayoutValue::getSize() const { 326 return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() + 327 sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity(); 328 } 329 330 void TextLayoutValue::setElapsedTime(uint32_t time) { 331 mElapsedTime = time; 332 } 333 334 uint32_t TextLayoutValue::getElapsedTime() { 335 return mElapsedTime; 336 } 337 338 TextLayoutShaper::TextLayoutShaper() { 339 mBuffer = hb_buffer_create(); 340 } 341 342 TextLayoutShaper::~TextLayoutShaper() { 343 hb_buffer_destroy(mBuffer); 344 } 345 346 void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, 347 const UChar* chars, size_t start, size_t count, size_t contextCount, int dirFlags) { 348 computeValues(paint, chars, start, count, contextCount, dirFlags, 349 &value->mAdvances, &value->mTotalAdvance, &value->mBounds, 350 &value->mGlyphs, &value->mPos); 351 #if DEBUG_ADVANCES 352 ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count, 353 contextCount, value->mTotalAdvance); 354 #endif 355 } 356 357 void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars, 358 size_t start, size_t count, size_t contextCount, int dirFlags, 359 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds, 360 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) { 361 *outTotalAdvance = 0; 362 if (!count) { 363 return; 364 } 365 366 UBiDiLevel bidiReq = 0; 367 bool forceLTR = false; 368 bool forceRTL = false; 369 370 switch (dirFlags & kBidi_Mask) { 371 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level 372 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level 373 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break; 374 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break; 375 case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR 376 case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL 377 } 378 379 bool useSingleRun = false; 380 bool isRTL = forceRTL; 381 if (forceLTR || forceRTL) { 382 useSingleRun = true; 383 } else { 384 UBiDi* bidi = ubidi_open(); 385 if (bidi) { 386 UErrorCode status = U_ZERO_ERROR; 387 #if DEBUG_GLYPHS 388 ALOGD("******** ComputeValues -- start"); 389 ALOGD(" -- string = '%s'", String8(chars + start, count).string()); 390 ALOGD(" -- start = %d", start); 391 ALOGD(" -- count = %d", count); 392 ALOGD(" -- contextCount = %d", contextCount); 393 ALOGD(" -- bidiReq = %d", bidiReq); 394 #endif 395 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); 396 if (U_SUCCESS(status)) { 397 int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl 398 ssize_t rc = ubidi_countRuns(bidi, &status); 399 #if DEBUG_GLYPHS 400 ALOGD(" -- dirFlags = %d", dirFlags); 401 ALOGD(" -- paraDir = %d", paraDir); 402 ALOGD(" -- run-count = %d", int(rc)); 403 #endif 404 if (U_SUCCESS(status) && rc == 1) { 405 // Normal case: one run, status is ok 406 isRTL = (paraDir == 1); 407 useSingleRun = true; 408 } else if (!U_SUCCESS(status) || rc < 1) { 409 ALOGW("Need to force to single run -- string = '%s'," 410 " status = %d, rc = %d", 411 String8(chars + start, count).string(), status, int(rc)); 412 isRTL = (paraDir == 1); 413 useSingleRun = true; 414 } else { 415 int32_t end = start + count; 416 for (size_t i = 0; i < size_t(rc); ++i) { 417 int32_t startRun = -1; 418 int32_t lengthRun = -1; 419 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); 420 421 if (startRun == -1 || lengthRun == -1) { 422 // Something went wrong when getting the visual run, need to clear 423 // already computed data before doing a single run pass 424 ALOGW("Visual run is not valid"); 425 outGlyphs->clear(); 426 outAdvances->clear(); 427 outPos->clear(); 428 *outTotalAdvance = 0; 429 isRTL = (paraDir == 1); 430 useSingleRun = true; 431 break; 432 } 433 434 if (startRun >= end) { 435 continue; 436 } 437 int32_t endRun = startRun + lengthRun; 438 if (endRun <= int32_t(start)) { 439 continue; 440 } 441 if (startRun < int32_t(start)) { 442 startRun = int32_t(start); 443 } 444 if (endRun > end) { 445 endRun = end; 446 } 447 448 lengthRun = endRun - startRun; 449 isRTL = (runDir == UBIDI_RTL); 450 #if DEBUG_GLYPHS 451 ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d", 452 i, startRun, lengthRun, isRTL); 453 #endif 454 computeRunValues(paint, chars, startRun, lengthRun, contextCount, isRTL, 455 outAdvances, outTotalAdvance, outBounds, outGlyphs, outPos); 456 457 } 458 } 459 } else { 460 ALOGW("Cannot set Para"); 461 useSingleRun = true; 462 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 463 } 464 ubidi_close(bidi); 465 } else { 466 ALOGW("Cannot ubidi_open()"); 467 useSingleRun = true; 468 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 469 } 470 } 471 472 // Default single run case 473 if (useSingleRun){ 474 #if DEBUG_GLYPHS 475 ALOGD("Using a SINGLE BiDi Run " 476 "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL); 477 #endif 478 computeRunValues(paint, chars, start, count, contextCount, isRTL, 479 outAdvances, outTotalAdvance, outBounds, outGlyphs, outPos); 480 } 481 482 #if DEBUG_GLYPHS 483 ALOGD(" -- Total returned glyphs-count = %d", outGlyphs->size()); 484 ALOGD("******** ComputeValues -- end"); 485 #endif 486 } 487 488 #define HB_IsHighSurrogate(ucs) \ 489 (((ucs) & 0xfc00) == 0xd800) 490 491 #define HB_IsLowSurrogate(ucs) \ 492 (((ucs) & 0xfc00) == 0xdc00) 493 494 #ifndef HB_SurrogateToUcs4 495 #define HB_SurrogateToUcs4_(high, low) \ 496 (((hb_codepoint_t)(high))<<10) + (low) - 0x35fdc00; 497 #endif 498 499 #define HB_InvalidCodePoint ~0u 500 501 hb_codepoint_t 502 utf16_to_code_point(const uint16_t *chars, size_t len, ssize_t *iter) { 503 const uint16_t v = chars[(*iter)++]; 504 if (HB_IsHighSurrogate(v)) { 505 // surrogate pair 506 if (size_t(*iter) >= len) { 507 // the surrogate is incomplete. 508 return HB_InvalidCodePoint; 509 } 510 const uint16_t v2 = chars[(*iter)++]; 511 if (!HB_IsLowSurrogate(v2)) { 512 // invalidate surrogate pair. 513 (*iter)--; 514 return HB_InvalidCodePoint; 515 } 516 517 return HB_SurrogateToUcs4(v, v2); 518 } 519 520 if (HB_IsLowSurrogate(v)) { 521 // this isn't a valid code point 522 return HB_InvalidCodePoint; 523 } 524 525 return v; 526 } 527 528 hb_codepoint_t 529 utf16_to_code_point_prev(const uint16_t *chars, size_t len, ssize_t *iter) { 530 const uint16_t v = chars[(*iter)--]; 531 if (HB_IsLowSurrogate(v)) { 532 // surrogate pair 533 if (*iter < 0) { 534 // the surrogate is incomplete. 535 return HB_InvalidCodePoint; 536 } 537 const uint16_t v2 = chars[(*iter)--]; 538 if (!HB_IsHighSurrogate(v2)) { 539 // invalidate surrogate pair. 540 (*iter)++; 541 return HB_InvalidCodePoint; 542 } 543 544 return HB_SurrogateToUcs4(v2, v); 545 } 546 547 if (HB_IsHighSurrogate(v)) { 548 // this isn't a valid code point 549 return HB_InvalidCodePoint; 550 } 551 552 return v; 553 } 554 555 struct ScriptRun { 556 hb_script_t script; 557 size_t pos; 558 size_t length; 559 }; 560 561 hb_script_t code_point_to_script(hb_codepoint_t codepoint) { 562 static hb_unicode_funcs_t* u; 563 if (!u) { 564 u = hb_icu_get_unicode_funcs(); 565 } 566 return hb_unicode_script(u, codepoint); 567 } 568 569 bool 570 hb_utf16_script_run_next(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) { 571 if (size_t(*iter) == len) 572 return false; 573 574 run->pos = *iter; 575 const uint32_t init_cp = utf16_to_code_point(chars, len, iter); 576 const hb_script_t init_script = code_point_to_script(init_cp); 577 hb_script_t current_script = init_script; 578 run->script = init_script; 579 580 for (;;) { 581 if (size_t(*iter) == len) 582 break; 583 const ssize_t prev_iter = *iter; 584 const uint32_t cp = utf16_to_code_point(chars, len, iter); 585 const hb_script_t script = code_point_to_script(cp); 586 587 if (script != current_script) { 588 /* BEGIN android-changed 589 The condition was not correct by doing "a == b == constant" 590 END android-changed */ 591 if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) { 592 // If we started off as inherited, we take whatever we can find. 593 run->script = script; 594 current_script = script; 595 continue; 596 } else if (script == HB_SCRIPT_INHERITED) { 597 continue; 598 } else { 599 *iter = prev_iter; 600 break; 601 } 602 } 603 } 604 605 if (run->script == HB_SCRIPT_INHERITED) 606 run->script = HB_SCRIPT_COMMON; 607 608 run->length = *iter - run->pos; 609 return true; 610 } 611 612 bool 613 hb_utf16_script_run_prev(ScriptRun* run, const uint16_t *chars, size_t len, ssize_t *iter) { 614 if (*iter == -1) 615 return false; 616 617 const size_t ending_index = *iter; 618 const uint32_t init_cp = utf16_to_code_point_prev(chars, len, iter); 619 const hb_script_t init_script = code_point_to_script(init_cp); 620 hb_script_t current_script = init_script; 621 run->script = init_script; 622 size_t break_iter = *iter; 623 624 for (;;) { 625 if (*iter < 0) 626 break; 627 const uint32_t cp = utf16_to_code_point_prev(chars, len, iter); 628 const hb_script_t script = code_point_to_script(cp); 629 630 if (script != current_script) { 631 if (current_script == HB_SCRIPT_INHERITED && init_script == HB_SCRIPT_INHERITED) { 632 // If we started off as inherited, we take whatever we can find. 633 run->script = script; 634 current_script = script; 635 // In cases of script1 + inherited + script2, always group the inherited 636 // with script1. 637 break_iter = *iter; 638 continue; 639 } else if (script == HB_SCRIPT_INHERITED) { 640 continue; 641 } else { 642 *iter = break_iter; 643 break; 644 } 645 } else { 646 break_iter = *iter; 647 } 648 } 649 650 if (run->script == HB_SCRIPT_INHERITED) 651 run->script = HB_SCRIPT_COMMON; 652 653 run->pos = *iter + 1; 654 run->length = ending_index - *iter; 655 return true; 656 } 657 658 659 static void logGlyphs(hb_buffer_t* buffer) { 660 unsigned int numGlyphs; 661 hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs); 662 hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL); 663 ALOGD(" -- glyphs count=%d", numGlyphs); 664 for (size_t i = 0; i < numGlyphs; i++) { 665 ALOGD(" -- glyph[%d] = %d, cluster = %u, advance = %0.2f, offset.x = %0.2f, offset.y = %0.2f", i, 666 info[i].codepoint, 667 info[i].cluster, 668 HBFixedToFloat(positions[i].x_advance), 669 HBFixedToFloat(positions[i].x_offset), 670 HBFixedToFloat(positions[i].y_offset)); 671 } 672 } 673 674 void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* contextChars, 675 size_t start, size_t count, size_t contextCount, bool isRTL, 676 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, SkRect* outBounds, 677 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) { 678 if (!count) { 679 // We cannot shape an empty run. 680 return; 681 } 682 683 // To be filled in later 684 for (size_t i = 0; i < count; i++) { 685 outAdvances->add(0); 686 } 687 688 // Set the string properties 689 const UChar* chars = contextChars + start; 690 691 // Define shaping paint properties 692 mShapingPaint.setTextSize(paint->getTextSize()); 693 float skewX = paint->getTextSkewX(); 694 mShapingPaint.setTextSkewX(skewX); 695 mShapingPaint.setTextScaleX(paint->getTextScaleX()); 696 mShapingPaint.setFlags(paint->getFlags()); 697 mShapingPaint.setHinting(paint->getHinting()); 698 mShapingPaint.setPaintOptionsAndroid(paint->getPaintOptionsAndroid()); 699 700 // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script 701 // into the shaperItem 702 ssize_t indexFontRun = isRTL ? count - 1 : 0; 703 jfloat totalAdvance = *outTotalAdvance; 704 ScriptRun run; // relative to chars 705 while ((isRTL) ? 706 hb_utf16_script_run_prev(&run, chars, count, &indexFontRun): 707 hb_utf16_script_run_next(&run, chars, count, &indexFontRun)) { 708 709 #if DEBUG_GLYPHS 710 ALOGD("-------- Start of Script Run --------"); 711 ALOGD("Shaping Script Run with"); 712 ALOGD(" -- isRTL = %d", isRTL); 713 ALOGD(" -- HB script = %c%c%c%c", HB_UNTAG(run.script)); 714 ALOGD(" -- run.pos = %d", int(run.pos)); 715 ALOGD(" -- run.length = %d", int(run.length)); 716 ALOGD(" -- run = '%s'", String8(chars + run.pos, run.length).string()); 717 ALOGD(" -- string = '%s'", String8(chars, count).string()); 718 #endif 719 720 hb_buffer_reset(mBuffer); 721 // Note: if we want to set unicode functions, etc., this is the place. 722 723 hb_buffer_set_direction(mBuffer, isRTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR); 724 hb_buffer_set_script(mBuffer, run.script); 725 SkString langString = paint->getPaintOptionsAndroid().getLanguage().getTag(); 726 hb_buffer_set_language(mBuffer, hb_language_from_string(langString.c_str(), -1)); 727 hb_buffer_add_utf16(mBuffer, contextChars, contextCount, start + run.pos, run.length); 728 729 // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs 730 // and shape the Font run 731 size_t glyphBaseCount = shapeFontRun(paint); 732 unsigned int numGlyphs; 733 hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs); 734 hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(mBuffer, NULL); 735 736 #if DEBUG_GLYPHS 737 ALOGD("Got from Harfbuzz"); 738 ALOGD(" -- glyphBaseCount = %d", glyphBaseCount); 739 ALOGD(" -- num_glyph = %d", numGlyphs); 740 ALOGD(" -- isDevKernText = %d", paint->isDevKernText()); 741 ALOGD(" -- initial totalAdvance = %f", totalAdvance); 742 743 logGlyphs(mBuffer); 744 #endif 745 746 for (size_t i = 0; i < numGlyphs; i++) { 747 size_t cluster = info[i].cluster - start; 748 float xAdvance = HBFixedToFloat(positions[i].x_advance); 749 outAdvances->replaceAt(outAdvances->itemAt(cluster) + xAdvance, cluster); 750 jchar glyphId = info[i].codepoint + glyphBaseCount; 751 outGlyphs->add(glyphId); 752 float xo = HBFixedToFloat(positions[i].x_offset); 753 float yo = -HBFixedToFloat(positions[i].y_offset); 754 755 float xpos = totalAdvance + xo + yo * skewX; 756 float ypos = yo; 757 outPos->add(xpos); 758 outPos->add(ypos); 759 totalAdvance += xAdvance; 760 761 SkAutoGlyphCache autoCache(mShapingPaint, NULL, NULL); 762 const SkGlyph& metrics = autoCache.getCache()->getGlyphIDMetrics(glyphId); 763 outBounds->join(xpos + metrics.fLeft, ypos + metrics.fTop, 764 xpos + metrics.fLeft + metrics.fWidth, ypos + metrics.fTop + metrics.fHeight); 765 766 } 767 } 768 769 *outTotalAdvance = totalAdvance; 770 771 #if DEBUG_GLYPHS 772 ALOGD(" -- final totalAdvance = %f", totalAdvance); 773 ALOGD("-------- End of Script Run --------"); 774 #endif 775 } 776 777 /** 778 * Return the first typeface in the logical change, starting with this typeface, 779 * that contains the specified unichar, or NULL if none is found. 780 */ 781 SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface, 782 hb_script_t script) { 783 SkTypeface::Style currentStyle = SkTypeface::kNormal; 784 if (typeface) { 785 currentStyle = typeface->style(); 786 } 787 typeface = SkCreateTypefaceForScriptNG(script, currentStyle); 788 #if DEBUG_GLYPHS 789 ALOGD("Using Harfbuzz Script %c%c%c%c, Style %d", HB_UNTAG(script), currentStyle); 790 #endif 791 return typeface; 792 } 793 794 bool TextLayoutShaper::isComplexScript(hb_script_t script) { 795 switch (script) { 796 case HB_SCRIPT_COMMON: 797 case HB_SCRIPT_GREEK: 798 case HB_SCRIPT_CYRILLIC: 799 case HB_SCRIPT_HANGUL: 800 case HB_SCRIPT_INHERITED: 801 case HB_SCRIPT_HAN: 802 case HB_SCRIPT_KATAKANA: 803 case HB_SCRIPT_HIRAGANA: 804 return false; 805 default: 806 return true; 807 } 808 } 809 810 size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint) { 811 // Update Harfbuzz Shaper 812 813 SkTypeface* typeface = paint->getTypeface(); 814 815 // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz 816 // This is needed as the Typeface used for shaping can be not the default one 817 // when we are shaping any script that needs to use a fallback Font. 818 // If we are a "common" script we dont need to shift 819 size_t baseGlyphCount = 0; 820 hb_codepoint_t firstUnichar = 0; 821 if (isComplexScript(hb_buffer_get_script(mBuffer))) { 822 unsigned int numGlyphs; 823 hb_glyph_info_t* info = hb_buffer_get_glyph_infos(mBuffer, &numGlyphs); 824 for (size_t i = 0; i < numGlyphs; i++) { 825 firstUnichar = info[i].codepoint; 826 if (firstUnichar != ' ') { 827 break; 828 } 829 } 830 baseGlyphCount = paint->getBaseGlyphCount(firstUnichar); 831 } 832 833 SkTypeface* scriptTypeface = NULL; 834 if (baseGlyphCount != 0) { 835 scriptTypeface = typefaceForScript(paint, typeface, 836 hb_buffer_get_script(mBuffer)); 837 #if DEBUG_GLYPHS 838 ALOGD("Using Default Typeface for script %c%c%c%c", 839 HB_UNTAG(hb_buffer_get_script(mBuffer))); 840 #endif 841 } 842 if (scriptTypeface) { 843 typeface = scriptTypeface; 844 } else { 845 baseGlyphCount = 0; 846 if (typeface) { 847 SkSafeRef(typeface); 848 } else { 849 typeface = SkTypeface::CreateFromName(NULL, SkTypeface::kNormal); 850 #if DEBUG_GLYPHS 851 ALOGD("Using Default Typeface (normal style)"); 852 #endif 853 } 854 } 855 856 mShapingPaint.setTypeface(typeface); 857 hb_face_t* face = referenceCachedHBFace(typeface); 858 859 float sizeY = paint->getTextSize(); 860 float sizeX = sizeY * paint->getTextScaleX(); 861 hb_font_t* font = createFont(face, &mShapingPaint, sizeX, sizeY); 862 hb_face_destroy(face); 863 864 #if DEBUG_GLYPHS 865 ALOGD("Run typeface = %p, uniqueID = %d, face = %p", 866 typeface, typeface->uniqueID(), face); 867 #endif 868 SkSafeUnref(typeface); 869 870 hb_shape(font, mBuffer, NULL, 0); 871 hb_font_destroy(font); 872 873 mShapingPaint.setTypeface(paint->getTypeface()); 874 return baseGlyphCount; 875 } 876 877 hb_face_t* TextLayoutShaper::referenceCachedHBFace(SkTypeface* typeface) { 878 SkFontID fontId = typeface->uniqueID(); 879 ssize_t index = mCachedHBFaces.indexOfKey(fontId); 880 if (index >= 0) { 881 return hb_face_reference(mCachedHBFaces.valueAt(index)); 882 } 883 // TODO: destroy function 884 hb_face_t* face = hb_face_create_for_tables(harfbuzzSkiaReferenceTable, typeface, NULL); 885 #if DEBUG_GLYPHS 886 ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface); 887 #endif 888 mCachedHBFaces.add(fontId, face); 889 return hb_face_reference(face); 890 } 891 892 void TextLayoutShaper::purgeCaches() { 893 size_t cacheSize = mCachedHBFaces.size(); 894 for (size_t i = 0; i < cacheSize; i++) { 895 hb_face_destroy(mCachedHBFaces.valueAt(i)); 896 } 897 mCachedHBFaces.clear(); 898 } 899 900 TextLayoutEngine::TextLayoutEngine() { 901 mShaper = new TextLayoutShaper(); 902 #if USE_TEXT_LAYOUT_CACHE 903 mTextLayoutCache = new TextLayoutCache(mShaper); 904 #else 905 mTextLayoutCache = NULL; 906 #endif 907 } 908 909 TextLayoutEngine::~TextLayoutEngine() { 910 delete mTextLayoutCache; 911 delete mShaper; 912 } 913 914 sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text, 915 jint start, jint count, jint contextCount, jint dirFlags) { 916 sp<TextLayoutValue> value; 917 #if USE_TEXT_LAYOUT_CACHE 918 value = mTextLayoutCache->getValue(paint, text, start, count, 919 contextCount, dirFlags); 920 if (value == NULL) { 921 ALOGE("Cannot get TextLayoutCache value for text = '%s'", 922 String8(text + start, count).string()); 923 } 924 #else 925 value = new TextLayoutValue(count); 926 mShaper->computeValues(value.get(), paint, 927 reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags); 928 #endif 929 return value; 930 } 931 932 void TextLayoutEngine::purgeCaches() { 933 #if USE_TEXT_LAYOUT_CACHE 934 mTextLayoutCache->purgeCaches(); 935 #if DEBUG_GLYPHS 936 ALOGD("Purged TextLayoutEngine caches"); 937 #endif 938 #endif 939 } 940 941 942 } // namespace android 943