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 "SkTypeface_android.h" 23#include <unicode/unistr.h> 24#include <unicode/normlzr.h> 25#include <unicode/uchar.h> 26 27extern "C" { 28 #include "harfbuzz-unicode.h" 29} 30 31namespace android { 32 33//-------------------------------------------------------------------------------------------------- 34 35ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine); 36 37//-------------------------------------------------------------------------------------------------- 38 39TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) : 40 mShaper(shaper), 41 mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutValue> >::kUnlimitedCapacity), 42 mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)), 43 mCacheHitCount(0), mNanosecondsSaved(0) { 44 init(); 45} 46 47TextLayoutCache::~TextLayoutCache() { 48 mCache.clear(); 49} 50 51void TextLayoutCache::init() { 52 mCache.setOnEntryRemovedListener(this); 53 54 mDebugLevel = readRtlDebugLevel(); 55 mDebugEnabled = mDebugLevel & kRtlDebugCaches; 56 ALOGD("Using debug level = %d - Debug Enabled = %d", mDebugLevel, mDebugEnabled); 57 58 mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC); 59 60 if (mDebugEnabled) { 61 ALOGD("Initialization is done - Start time = %lld", mCacheStartTime); 62 } 63 64 mInitialized = true; 65} 66 67/** 68 * Callbacks 69 */ 70void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc) { 71 size_t totalSizeToDelete = text.getSize() + desc->getSize(); 72 mSize -= totalSizeToDelete; 73 if (mDebugEnabled) { 74 ALOGD("Cache value %p deleted, size = %d", desc.get(), totalSizeToDelete); 75 } 76} 77 78/* 79 * Cache clearing 80 */ 81void TextLayoutCache::purgeCaches() { 82 AutoMutex _l(mLock); 83 mCache.clear(); 84 mShaper->purgeCaches(); 85} 86 87/* 88 * Caching 89 */ 90sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint, 91 const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) { 92 AutoMutex _l(mLock); 93 nsecs_t startTime = 0; 94 if (mDebugEnabled) { 95 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 96 } 97 98 // Create the key 99 TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags); 100 101 // Get value from cache if possible 102 sp<TextLayoutValue> value = mCache.get(key); 103 104 // Value not found for the key, we need to add a new value in the cache 105 if (value == NULL) { 106 if (mDebugEnabled) { 107 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 108 } 109 110 value = new TextLayoutValue(contextCount); 111 112 // Compute advances and store them 113 mShaper->computeValues(value.get(), paint, 114 reinterpret_cast<const UChar*>(key.getText()), start, count, 115 size_t(contextCount), int(dirFlags)); 116 117 if (mDebugEnabled) { 118 value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime); 119 } 120 121 // Don't bother to add in the cache if the entry is too big 122 size_t size = key.getSize() + value->getSize(); 123 if (size <= mMaxSize) { 124 // Cleanup to make some room if needed 125 if (mSize + size > mMaxSize) { 126 if (mDebugEnabled) { 127 ALOGD("Need to clean some entries for making some room for a new entry"); 128 } 129 while (mSize + size > mMaxSize) { 130 // This will call the callback 131 bool removedOne = mCache.removeOldest(); 132 LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we " 133 "failed to remove the oldest entry. " 134 "mSize = %u, size = %u, mMaxSize = %u, mCache.size() = %u", 135 mSize, size, mMaxSize, mCache.size()); 136 } 137 } 138 139 // Update current cache size 140 mSize += size; 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(key.getText() + 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(key.getText() + 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(key.getText() + 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(key.getText() + 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(): start(0), count(0), contextCount(0), 226 dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0), 227 hinting(SkPaint::kNo_Hinting), variant(SkPaint::kDefault_Variant), language() { 228} 229 230TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text, 231 size_t start, size_t count, size_t contextCount, int dirFlags) : 232 start(start), count(count), contextCount(contextCount), 233 dirFlags(dirFlags) { 234 textCopy.setTo(text, contextCount); 235 typeface = paint->getTypeface(); 236 textSize = paint->getTextSize(); 237 textSkewX = paint->getTextSkewX(); 238 textScaleX = paint->getTextScaleX(); 239 flags = paint->getFlags(); 240 hinting = paint->getHinting(); 241 variant = paint->getFontVariant(); 242 language = paint->getLanguage(); 243} 244 245TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) : 246 textCopy(other.textCopy), 247 start(other.start), 248 count(other.count), 249 contextCount(other.contextCount), 250 dirFlags(other.dirFlags), 251 typeface(other.typeface), 252 textSize(other.textSize), 253 textSkewX(other.textSkewX), 254 textScaleX(other.textScaleX), 255 flags(other.flags), 256 hinting(other.hinting), 257 variant(other.variant), 258 language(other.language) { 259} 260 261int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) { 262 int deltaInt = lhs.start - rhs.start; 263 if (deltaInt != 0) return (deltaInt); 264 265 deltaInt = lhs.count - rhs.count; 266 if (deltaInt != 0) return (deltaInt); 267 268 deltaInt = lhs.contextCount - rhs.contextCount; 269 if (deltaInt != 0) return (deltaInt); 270 271 if (lhs.typeface < rhs.typeface) return -1; 272 if (lhs.typeface > rhs.typeface) return +1; 273 274 if (lhs.textSize < rhs.textSize) return -1; 275 if (lhs.textSize > rhs.textSize) return +1; 276 277 if (lhs.textSkewX < rhs.textSkewX) return -1; 278 if (lhs.textSkewX > rhs.textSkewX) return +1; 279 280 if (lhs.textScaleX < rhs.textScaleX) return -1; 281 if (lhs.textScaleX > rhs.textScaleX) return +1; 282 283 deltaInt = lhs.flags - rhs.flags; 284 if (deltaInt != 0) return (deltaInt); 285 286 deltaInt = lhs.hinting - rhs.hinting; 287 if (deltaInt != 0) return (deltaInt); 288 289 deltaInt = lhs.dirFlags - rhs.dirFlags; 290 if (deltaInt) return (deltaInt); 291 292 deltaInt = lhs.variant - rhs.variant; 293 if (deltaInt) return (deltaInt); 294 295 if (lhs.language < rhs.language) return -1; 296 if (lhs.language > rhs.language) return +1; 297 298 return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar)); 299} 300 301size_t TextLayoutCacheKey::getSize() const { 302 return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount; 303} 304 305/** 306 * TextLayoutCacheValue 307 */ 308TextLayoutValue::TextLayoutValue(size_t contextCount) : 309 mTotalAdvance(0), mElapsedTime(0) { 310 // Give a hint for advances and glyphs vectors size 311 mAdvances.setCapacity(contextCount); 312 mGlyphs.setCapacity(contextCount); 313 mPos.setCapacity(contextCount * 2); 314} 315 316size_t TextLayoutValue::getSize() const { 317 return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() + 318 sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity(); 319} 320 321void TextLayoutValue::setElapsedTime(uint32_t time) { 322 mElapsedTime = time; 323} 324 325uint32_t TextLayoutValue::getElapsedTime() { 326 return mElapsedTime; 327} 328 329TextLayoutShaper::TextLayoutShaper() : mShaperItemGlyphArraySize(0) { 330 init(); 331 332 mFontRec.klass = &harfbuzzSkiaClass; 333 mFontRec.userData = 0; 334 335 // Note that the scaling values (x_ and y_ppem, x_ and y_scale) will be set 336 // below, when the paint transform and em unit of the actual shaping font 337 // are known. 338 339 memset(&mShaperItem, 0, sizeof(mShaperItem)); 340 341 mShaperItem.font = &mFontRec; 342 mShaperItem.font->userData = &mShapingPaint; 343} 344 345void TextLayoutShaper::init() { 346 mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, NULL, 0, SkTypeface::kNormal); 347} 348 349void TextLayoutShaper::unrefTypefaces() { 350 SkSafeUnref(mDefaultTypeface); 351} 352 353TextLayoutShaper::~TextLayoutShaper() { 354 unrefTypefaces(); 355 deleteShaperItemGlyphArrays(); 356} 357 358void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars, 359 size_t start, size_t count, size_t contextCount, int dirFlags) { 360 361 computeValues(paint, chars, start, count, contextCount, dirFlags, 362 &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos); 363#if DEBUG_ADVANCES 364 ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count, 365 contextCount, value->mTotalAdvance); 366#endif 367} 368 369void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars, 370 size_t start, size_t count, size_t contextCount, int dirFlags, 371 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 372 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) { 373 *outTotalAdvance = 0; 374 if (!count) { 375 return; 376 } 377 378 UBiDiLevel bidiReq = 0; 379 bool forceLTR = false; 380 bool forceRTL = false; 381 382 switch (dirFlags) { 383 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level 384 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level 385 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break; 386 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break; 387 case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR 388 case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL 389 } 390 391 bool useSingleRun = false; 392 bool isRTL = forceRTL; 393 if (forceLTR || forceRTL) { 394 useSingleRun = true; 395 } else { 396 UBiDi* bidi = ubidi_open(); 397 if (bidi) { 398 UErrorCode status = U_ZERO_ERROR; 399#if DEBUG_GLYPHS 400 ALOGD("******** ComputeValues -- start"); 401 ALOGD(" -- string = '%s'", String8(chars + start, count).string()); 402 ALOGD(" -- start = %d", start); 403 ALOGD(" -- count = %d", count); 404 ALOGD(" -- contextCount = %d", contextCount); 405 ALOGD(" -- bidiReq = %d", bidiReq); 406#endif 407 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); 408 if (U_SUCCESS(status)) { 409 int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl 410 ssize_t rc = ubidi_countRuns(bidi, &status); 411#if DEBUG_GLYPHS 412 ALOGD(" -- dirFlags = %d", dirFlags); 413 ALOGD(" -- paraDir = %d", paraDir); 414 ALOGD(" -- run-count = %d", int(rc)); 415#endif 416 if (U_SUCCESS(status) && rc == 1) { 417 // Normal case: one run, status is ok 418 isRTL = (paraDir == 1); 419 useSingleRun = true; 420 } else if (!U_SUCCESS(status) || rc < 1) { 421 ALOGW("Need to force to single run -- string = '%s'," 422 " status = %d, rc = %d", 423 String8(chars + start, count).string(), status, int(rc)); 424 isRTL = (paraDir == 1); 425 useSingleRun = true; 426 } else { 427 int32_t end = start + count; 428 for (size_t i = 0; i < size_t(rc); ++i) { 429 int32_t startRun = -1; 430 int32_t lengthRun = -1; 431 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); 432 433 if (startRun == -1 || lengthRun == -1) { 434 // Something went wrong when getting the visual run, need to clear 435 // already computed data before doing a single run pass 436 ALOGW("Visual run is not valid"); 437 outGlyphs->clear(); 438 outAdvances->clear(); 439 outPos->clear(); 440 *outTotalAdvance = 0; 441 isRTL = (paraDir == 1); 442 useSingleRun = true; 443 break; 444 } 445 446 if (startRun >= end) { 447 continue; 448 } 449 int32_t endRun = startRun + lengthRun; 450 if (endRun <= int32_t(start)) { 451 continue; 452 } 453 if (startRun < int32_t(start)) { 454 startRun = int32_t(start); 455 } 456 if (endRun > end) { 457 endRun = end; 458 } 459 460 lengthRun = endRun - startRun; 461 isRTL = (runDir == UBIDI_RTL); 462#if DEBUG_GLYPHS 463 ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d", 464 i, startRun, lengthRun, isRTL); 465#endif 466 computeRunValues(paint, chars + startRun, lengthRun, isRTL, 467 outAdvances, outTotalAdvance, outGlyphs, outPos); 468 469 } 470 } 471 } else { 472 ALOGW("Cannot set Para"); 473 useSingleRun = true; 474 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 475 } 476 ubidi_close(bidi); 477 } else { 478 ALOGW("Cannot ubidi_open()"); 479 useSingleRun = true; 480 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 481 } 482 } 483 484 // Default single run case 485 if (useSingleRun){ 486#if DEBUG_GLYPHS 487 ALOGD("Using a SINGLE BiDi Run " 488 "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL); 489#endif 490 computeRunValues(paint, chars + start, count, isRTL, 491 outAdvances, outTotalAdvance, outGlyphs, outPos); 492 } 493 494#if DEBUG_GLYPHS 495 ALOGD(" -- Total returned glyphs-count = %d", outGlyphs->size()); 496 ALOGD("******** ComputeValues -- end"); 497#endif 498} 499 500static void logGlyphs(HB_ShaperItem shaperItem) { 501 ALOGD(" -- glyphs count=%d", shaperItem.num_glyphs); 502 for (size_t i = 0; i < shaperItem.num_glyphs; i++) { 503 ALOGD(" -- glyph[%d] = %d, offset.x = %0.2f, offset.y = %0.2f", i, 504 shaperItem.glyphs[i], 505 HBFixedToFloat(shaperItem.offsets[i].x), 506 HBFixedToFloat(shaperItem.offsets[i].y)); 507 } 508} 509 510void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars, 511 size_t count, bool isRTL, 512 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 513 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) { 514 if (!count) { 515 // We cannot shape an empty run. 516 return; 517 } 518 519 // To be filled in later 520 for (size_t i = 0; i < count; i++) { 521 outAdvances->add(0); 522 } 523 UErrorCode error = U_ZERO_ERROR; 524 bool useNormalizedString = false; 525 for (ssize_t i = count - 1; i >= 0; --i) { 526 UChar ch1 = chars[i]; 527 if (::ublock_getCode(ch1) == UBLOCK_COMBINING_DIACRITICAL_MARKS) { 528 // So we have found a diacritic, let's get now the main code point which is paired 529 // with it. As we can have several diacritics in a row, we need to iterate back again 530#if DEBUG_GLYPHS 531 ALOGD("The BiDi run '%s' is containing a Diacritic at position %d", 532 String8(chars, count).string(), int(i)); 533#endif 534 ssize_t j = i - 1; 535 for (; j >= 0; --j) { 536 UChar ch2 = chars[j]; 537 if (::ublock_getCode(ch2) != UBLOCK_COMBINING_DIACRITICAL_MARKS) { 538 break; 539 } 540 } 541 542 // We could not found the main code point, so we will just use the initial chars 543 if (j < 0) { 544 break; 545 } 546 547#if DEBUG_GLYPHS 548 ALOGD("Found main code point at index %d", int(j)); 549#endif 550 // We found the main code point, so we can normalize the "chunk" and fill 551 // the remaining with ZWSP so that the Paint.getTextWidth() APIs will still be able 552 // to get one advance per char 553 mBuffer.remove(); 554 Normalizer::normalize(UnicodeString(chars + j, i - j + 1), 555 UNORM_NFC, 0 /* no options */, mBuffer, error); 556 if (U_SUCCESS(error)) { 557 if (!useNormalizedString) { 558 useNormalizedString = true; 559 mNormalizedString.setTo(false /* not terminated*/, chars, count); 560 } 561 // Set the normalized chars 562 for (ssize_t k = j; k < j + mBuffer.length(); ++k) { 563 mNormalizedString.setCharAt(k, mBuffer.charAt(k - j)); 564 } 565 // Fill the remain part with ZWSP (ZWNJ and ZWJ would lead to weird results 566 // because some fonts are missing those glyphs) 567 for (ssize_t k = j + mBuffer.length(); k <= i; ++k) { 568 mNormalizedString.setCharAt(k, UNICODE_ZWSP); 569 } 570 } 571 i = j - 1; 572 } 573 } 574 575 // Reverse "BiDi mirrored chars" in RTL mode only 576 // See: http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBinaryProperties.txt 577 // This is a workaround because Harfbuzz is not able to do mirroring in all cases and 578 // script-run splitting with Harfbuzz is splitting on parenthesis 579 if (isRTL) { 580 for (ssize_t i = 0; i < ssize_t(count); i++) { 581 UChar32 ch = chars[i]; 582 if (!u_isMirrored(ch)) continue; 583 if (!useNormalizedString) { 584 useNormalizedString = true; 585 mNormalizedString.setTo(false /* not terminated*/, chars, count); 586 } 587 UChar result = (UChar) u_charMirror(ch); 588 mNormalizedString.setCharAt(i, result); 589#if DEBUG_GLYPHS 590 ALOGD("Rewriting codepoint '%d' to '%d' at position %d", 591 ch, mNormalizedString[i], int(i)); 592#endif 593 } 594 } 595 596#if DEBUG_GLYPHS 597 if (useNormalizedString) { 598 ALOGD("Will use normalized string '%s', length = %d", 599 String8(mNormalizedString.getTerminatedBuffer(), 600 mNormalizedString.length()).string(), 601 mNormalizedString.length()); 602 } else { 603 ALOGD("Normalization is not needed or cannot be done, using initial string"); 604 } 605#endif 606 607 assert(mNormalizedString.length() == count); 608 609 // Set the string properties 610 mShaperItem.string = useNormalizedString ? mNormalizedString.getTerminatedBuffer() : chars; 611 mShaperItem.stringLength = count; 612 613 // Define shaping paint properties 614 mShapingPaint.setTextSize(paint->getTextSize()); 615 float skewX = paint->getTextSkewX(); 616 mShapingPaint.setTextSkewX(skewX); 617 mShapingPaint.setTextScaleX(paint->getTextScaleX()); 618 mShapingPaint.setFlags(paint->getFlags()); 619 mShapingPaint.setHinting(paint->getHinting()); 620 mShapingPaint.setFontVariant(paint->getFontVariant()); 621 mShapingPaint.setLanguage(paint->getLanguage()); 622 623 // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script 624 // into the shaperItem 625 ssize_t indexFontRun = isRTL ? mShaperItem.stringLength - 1 : 0; 626 unsigned numCodePoints = 0; 627 jfloat totalAdvance = *outTotalAdvance; 628 while ((isRTL) ? 629 hb_utf16_script_run_prev(&numCodePoints, &mShaperItem.item, mShaperItem.string, 630 mShaperItem.stringLength, &indexFontRun): 631 hb_utf16_script_run_next(&numCodePoints, &mShaperItem.item, mShaperItem.string, 632 mShaperItem.stringLength, &indexFontRun)) { 633 634 ssize_t startScriptRun = mShaperItem.item.pos; 635 size_t countScriptRun = mShaperItem.item.length; 636 ssize_t endScriptRun = startScriptRun + countScriptRun; 637 638#if DEBUG_GLYPHS 639 ALOGD("-------- Start of Script Run --------"); 640 ALOGD("Shaping Script Run with"); 641 ALOGD(" -- isRTL = %d", isRTL); 642 ALOGD(" -- HB script = %d", mShaperItem.item.script); 643 ALOGD(" -- startFontRun = %d", int(startScriptRun)); 644 ALOGD(" -- endFontRun = %d", int(endScriptRun)); 645 ALOGD(" -- countFontRun = %d", countScriptRun); 646 ALOGD(" -- run = '%s'", String8(chars + startScriptRun, countScriptRun).string()); 647 ALOGD(" -- string = '%s'", String8(chars, count).string()); 648#endif 649 650 // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs 651 // and shape the Font run 652 size_t glyphBaseCount = shapeFontRun(paint, isRTL); 653 654#if DEBUG_GLYPHS 655 ALOGD("Got from Harfbuzz"); 656 ALOGD(" -- glyphBaseCount = %d", glyphBaseCount); 657 ALOGD(" -- num_glypth = %d", mShaperItem.num_glyphs); 658 ALOGD(" -- kerning_applied = %d", mShaperItem.kerning_applied); 659 ALOGD(" -- isDevKernText = %d", paint->isDevKernText()); 660 661 logGlyphs(mShaperItem); 662#endif 663 664 if (mShaperItem.advances == NULL || mShaperItem.num_glyphs == 0) { 665#if DEBUG_GLYPHS 666 ALOGD("Advances array is empty or num_glypth = 0"); 667#endif 668 continue; 669 } 670 671#if DEBUG_GLYPHS 672 ALOGD("Returned logclusters"); 673 for (size_t i = 0; i < mShaperItem.num_glyphs; i++) { 674 ALOGD(" -- lc[%d] = %d, hb-adv[%d] = %0.2f", i, mShaperItem.log_clusters[i], 675 i, HBFixedToFloat(mShaperItem.advances[i])); 676 } 677#endif 678 jfloat totalFontRunAdvance = 0; 679 size_t clusterStart = 0; 680 for (size_t i = 0; i < countScriptRun; i++) { 681 size_t cluster = mShaperItem.log_clusters[i]; 682 size_t clusterNext = i == countScriptRun - 1 ? mShaperItem.num_glyphs : 683 mShaperItem.log_clusters[i + 1]; 684 if (cluster != clusterNext) { 685 jfloat advance = 0; 686 // The advance for the cluster is the sum of the advances of all glyphs within 687 // the cluster. 688 for (size_t j = cluster; j < clusterNext; j++) { 689 advance += HBFixedToFloat(mShaperItem.advances[j]); 690 } 691 totalFontRunAdvance += advance; 692 outAdvances->replaceAt(advance, startScriptRun + clusterStart); 693 clusterStart = i + 1; 694 } 695 } 696 697#if DEBUG_ADVANCES 698 ALOGD("Returned advances"); 699 for (size_t i = 0; i < countScriptRun; i++) { 700 ALOGD(" -- hb-adv[%d] = %0.2f, log_clusters = %d, total = %0.2f", i, 701 (*outAdvances)[i], mShaperItem.log_clusters[i], totalFontRunAdvance); 702 } 703#endif 704 705 // Get Glyphs and reverse them in place if RTL 706 if (outGlyphs) { 707 size_t countGlyphs = mShaperItem.num_glyphs; 708#if DEBUG_GLYPHS 709 ALOGD("Returned script run glyphs -- count = %d", countGlyphs); 710#endif 711 for (size_t i = 0; i < countGlyphs; i++) { 712 jchar glyph = glyphBaseCount + 713 (jchar) mShaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i]; 714#if DEBUG_GLYPHS 715 ALOGD(" -- glyph[%d] = %d", i, glyph); 716#endif 717 outGlyphs->add(glyph); 718 } 719 } 720 721 // Get glyph positions (and reverse them in place if RTL) 722 if (outPos) { 723 size_t countGlyphs = mShaperItem.num_glyphs; 724 jfloat x = totalAdvance; 725 for (size_t i = 0; i < countGlyphs; i++) { 726 size_t index = (!isRTL) ? i : countGlyphs - 1 - i; 727 float xo = HBFixedToFloat(mShaperItem.offsets[index].x); 728 float yo = HBFixedToFloat(mShaperItem.offsets[index].y); 729 // Apply skewX component of transform to position offsets. Note 730 // that scale has already been applied through x_ and y_scale 731 // set in the mFontRec. 732 outPos->add(x + xo + yo * skewX); 733 outPos->add(yo); 734#if DEBUG_GLYPHS 735 ALOGD(" -- hb adv[%d] = %f, log_cluster[%d] = %d", 736 index, HBFixedToFloat(mShaperItem.advances[index]), 737 index, mShaperItem.log_clusters[index]); 738#endif 739 x += HBFixedToFloat(mShaperItem.advances[index]); 740 } 741 } 742 743 totalAdvance += totalFontRunAdvance; 744 } 745 746 *outTotalAdvance = totalAdvance; 747 748#if DEBUG_GLYPHS 749 ALOGD("-------- End of Script Run --------"); 750#endif 751} 752 753/** 754 * Return the first typeface in the logical change, starting with this typeface, 755 * that contains the specified unichar, or NULL if none is found. 756 * 757 * Note that this function does _not_ increment the reference count on the typeface, as the 758 * assumption is that its lifetime is managed elsewhere - in particular, the fallback typefaces 759 * for the default font live in a global cache. 760 */ 761SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface, 762 HB_Script script) { 763 SkTypeface::Style currentStyle = SkTypeface::kNormal; 764 if (typeface) { 765 currentStyle = typeface->style(); 766 } 767 typeface = SkCreateTypefaceForScript(script, currentStyle); 768#if DEBUG_GLYPHS 769 ALOGD("Using Harfbuzz Script %d, Style %d", script, currentStyle); 770#endif 771 return typeface; 772} 773 774bool TextLayoutShaper::isComplexScript(HB_Script script) { 775 switch (script) { 776 case HB_Script_Common: 777 case HB_Script_Greek: 778 case HB_Script_Cyrillic: 779 case HB_Script_Hangul: 780 case HB_Script_Inherited: 781 return false; 782 default: 783 return true; 784 } 785} 786 787size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint, bool isRTL) { 788 // Reset kerning 789 mShaperItem.kerning_applied = false; 790 791 // Update Harfbuzz Shaper 792 mShaperItem.item.bidiLevel = isRTL; 793 794 SkTypeface* typeface = paint->getTypeface(); 795 796 // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz 797 // This is needed as the Typeface used for shaping can be not the default one 798 // when we are shaping any script that needs to use a fallback Font. 799 // If we are a "common" script we dont need to shift 800 size_t baseGlyphCount = 0; 801 SkUnichar firstUnichar = 0; 802 if (isComplexScript(mShaperItem.item.script)) { 803 const uint16_t* text16 = (const uint16_t*) (mShaperItem.string + mShaperItem.item.pos); 804 const uint16_t* text16End = text16 + mShaperItem.item.length; 805 firstUnichar = SkUTF16_NextUnichar(&text16); 806 while (firstUnichar == ' ' && text16 < text16End) { 807 firstUnichar = SkUTF16_NextUnichar(&text16); 808 } 809 baseGlyphCount = paint->getBaseGlyphCount(firstUnichar); 810 } 811 812 if (baseGlyphCount != 0) { 813 typeface = typefaceForScript(paint, typeface, mShaperItem.item.script); 814 if (!typeface) { 815 typeface = mDefaultTypeface; 816 SkSafeRef(typeface); 817#if DEBUG_GLYPHS 818 ALOGD("Using Default Typeface"); 819#endif 820 } 821 } else { 822 if (!typeface) { 823 typeface = mDefaultTypeface; 824#if DEBUG_GLYPHS 825 ALOGD("Using Default Typeface"); 826#endif 827 } 828 SkSafeRef(typeface); 829 } 830 831 mShapingPaint.setTypeface(typeface); 832 mShaperItem.face = getCachedHBFace(typeface); 833 834 int textSize = paint->getTextSize(); 835 float scaleX = paint->getTextScaleX(); 836 mFontRec.x_ppem = floor(scaleX * textSize + 0.5); 837 mFontRec.y_ppem = textSize; 838 uint32_t unitsPerEm = SkFontHost::GetUnitsPerEm(typeface->uniqueID()); 839 // x_ and y_scale are the conversion factors from font design space 840 // (unitsPerEm) to 1/64th of device pixels in 16.16 format. 841 const int kDevicePixelFraction = 64; 842 const int kMultiplyFor16Dot16 = 1 << 16; 843 float emScale = kDevicePixelFraction * kMultiplyFor16Dot16 / (float)unitsPerEm; 844 mFontRec.x_scale = emScale * scaleX * textSize; 845 mFontRec.y_scale = emScale * textSize; 846 847#if DEBUG_GLYPHS 848 ALOGD("Run typeface = %p, uniqueID = %d, hb_face = %p", 849 typeface, typeface->uniqueID(), mShaperItem.face); 850#endif 851 SkSafeUnref(typeface); 852 853 // Shape 854 assert(mShaperItem.item.length > 0); // Harfbuzz will overwrite other memory if length is 0. 855 size_t size = mShaperItem.item.length * 3 / 2; 856 while (!doShaping(size)) { 857 // We overflowed our glyph arrays. Resize and retry. 858 // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size. 859 size = mShaperItem.num_glyphs * 2; 860 } 861 return baseGlyphCount; 862} 863 864bool TextLayoutShaper::doShaping(size_t size) { 865 if (size > mShaperItemGlyphArraySize) { 866 deleteShaperItemGlyphArrays(); 867 createShaperItemGlyphArrays(size); 868 } 869 mShaperItem.num_glyphs = mShaperItemGlyphArraySize; 870 memset(mShaperItem.offsets, 0, mShaperItem.num_glyphs * sizeof(HB_FixedPoint)); 871 return HB_ShapeItem(&mShaperItem); 872} 873 874void TextLayoutShaper::createShaperItemGlyphArrays(size_t size) { 875#if DEBUG_GLYPHS 876 ALOGD("Creating Glyph Arrays with size = %d", size); 877#endif 878 mShaperItemGlyphArraySize = size; 879 880 // These arrays are all indexed by glyph. 881 mShaperItem.glyphs = new HB_Glyph[size]; 882 mShaperItem.attributes = new HB_GlyphAttributes[size]; 883 mShaperItem.advances = new HB_Fixed[size]; 884 mShaperItem.offsets = new HB_FixedPoint[size]; 885 886 // Although the log_clusters array is indexed by character, Harfbuzz expects that 887 // it is big enough to hold one element per glyph. So we allocate log_clusters along 888 // with the other glyph arrays above. 889 mShaperItem.log_clusters = new unsigned short[size]; 890} 891 892void TextLayoutShaper::deleteShaperItemGlyphArrays() { 893 delete[] mShaperItem.glyphs; 894 delete[] mShaperItem.attributes; 895 delete[] mShaperItem.advances; 896 delete[] mShaperItem.offsets; 897 delete[] mShaperItem.log_clusters; 898} 899 900HB_Face TextLayoutShaper::getCachedHBFace(SkTypeface* typeface) { 901 SkFontID fontId = typeface->uniqueID(); 902 ssize_t index = mCachedHBFaces.indexOfKey(fontId); 903 if (index >= 0) { 904 return mCachedHBFaces.valueAt(index); 905 } 906 HB_Face face = HB_NewFace(typeface, harfbuzzSkiaGetTable); 907 if (face) { 908#if DEBUG_GLYPHS 909 ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface); 910#endif 911 mCachedHBFaces.add(fontId, face); 912 } 913 return face; 914} 915 916void TextLayoutShaper::purgeCaches() { 917 size_t cacheSize = mCachedHBFaces.size(); 918 for (size_t i = 0; i < cacheSize; i++) { 919 HB_FreeFace(mCachedHBFaces.valueAt(i)); 920 } 921 mCachedHBFaces.clear(); 922 unrefTypefaces(); 923 init(); 924} 925 926TextLayoutEngine::TextLayoutEngine() { 927 mShaper = new TextLayoutShaper(); 928#if USE_TEXT_LAYOUT_CACHE 929 mTextLayoutCache = new TextLayoutCache(mShaper); 930#else 931 mTextLayoutCache = NULL; 932#endif 933} 934 935TextLayoutEngine::~TextLayoutEngine() { 936 delete mTextLayoutCache; 937 delete mShaper; 938} 939 940sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text, 941 jint start, jint count, jint contextCount, jint dirFlags) { 942 sp<TextLayoutValue> value; 943#if USE_TEXT_LAYOUT_CACHE 944 value = mTextLayoutCache->getValue(paint, text, start, count, 945 contextCount, dirFlags); 946 if (value == NULL) { 947 ALOGE("Cannot get TextLayoutCache value for text = '%s'", 948 String8(text + start, count).string()); 949 } 950#else 951 value = new TextLayoutValue(count); 952 mShaper->computeValues(value.get(), paint, 953 reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags); 954#endif 955 return value; 956} 957 958void TextLayoutEngine::purgeCaches() { 959#if USE_TEXT_LAYOUT_CACHE 960 mTextLayoutCache->purgeCaches(); 961#if DEBUG_GLYPHS 962 ALOGD("Purged TextLayoutEngine caches"); 963#endif 964#endif 965} 966 967 968} // namespace android 969