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