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