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