TextLayoutCache.cpp revision ad169f49af9fac1b80faa124496103dff2ac29f4
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 23extern "C" { 24 #include "harfbuzz-unicode.h" 25} 26 27namespace android { 28 29//-------------------------------------------------------------------------------------------------- 30#define TYPEFACE_ARABIC "/system/fonts/DroidNaskh-Regular.ttf" 31#define TYPE_FACE_HEBREW_REGULAR "/system/fonts/DroidSansHebrew-Regular.ttf" 32#define TYPE_FACE_HEBREW_BOLD "/system/fonts/DroidSansHebrew-Bold.ttf" 33 34#if USE_TEXT_LAYOUT_CACHE 35 36 ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutCache); 37 38 static SkTypeface* gDefaultTypeface = SkFontHost::CreateTypeface( 39 NULL, NULL, NULL, 0, SkTypeface::kNormal); 40 41 static SkTypeface* gArabicTypeface = NULL; 42 static SkTypeface* gHebrewRegularTypeface = NULL; 43 static SkTypeface* gHebrewBoldTypeface = NULL; 44 45#endif 46//-------------------------------------------------------------------------------------------------- 47 48TextLayoutCache::TextLayoutCache() : 49 mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity), 50 mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)), 51 mCacheHitCount(0), mNanosecondsSaved(0) { 52 init(); 53} 54 55TextLayoutCache::~TextLayoutCache() { 56 mCache.clear(); 57} 58 59void TextLayoutCache::init() { 60 mCache.setOnEntryRemovedListener(this); 61 62 mDebugLevel = readRtlDebugLevel(); 63 mDebugEnabled = mDebugLevel & kRtlDebugCaches; 64 LOGD("Using debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled); 65 66 mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC); 67 68 if (mDebugEnabled) { 69 LOGD("Initialization is done - Start time: %lld", mCacheStartTime); 70 } 71 72 mInitialized = true; 73} 74 75/** 76 * Callbacks 77 */ 78void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) { 79 size_t totalSizeToDelete = text.getSize() + desc->getSize(); 80 mSize -= totalSizeToDelete; 81 if (mDebugEnabled) { 82 LOGD("Cache value %p deleted, size = %d", desc.get(), totalSizeToDelete); 83 } 84} 85 86/* 87 * Cache clearing 88 */ 89void TextLayoutCache::clear() { 90 mCache.clear(); 91} 92 93/* 94 * Caching 95 */ 96sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint, 97 const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) { 98 AutoMutex _l(mLock); 99 nsecs_t startTime = 0; 100 if (mDebugEnabled) { 101 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 102 } 103 104 // Create the key 105 TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags); 106 107 // Get value from cache if possible 108 sp<TextLayoutCacheValue> value = mCache.get(key); 109 110 // Value not found for the key, we need to add a new value in the cache 111 if (value == NULL) { 112 if (mDebugEnabled) { 113 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 114 } 115 116 value = new TextLayoutCacheValue(); 117 118 // Compute advances and store them 119 value->computeValues(paint, text, start, count, contextCount, 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 LOGD("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() != NULL; 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 LOGD("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 LOGD("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 value.clear(); 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 LOGD("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, count).string()); 197 } 198 if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) { 199 dumpCacheStats(); 200 } 201 } 202 } 203 return value; 204} 205 206void 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 LOGD("------------------------------------------------"); 217 LOGD("Cache stats"); 218 LOGD("------------------------------------------------"); 219 LOGD("pid : %d", getpid()); 220 LOGD("running : %.0f seconds", timeRunningInSec); 221 LOGD("entries : %d", cacheSize); 222 LOGD("max size : %d bytes", mMaxSize); 223 LOGD("used : %d bytes according to mSize, %d bytes actual", mSize, bytes); 224 LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent); 225 LOGD("hits : %d", mCacheHitCount); 226 LOGD("saved : %0.6f ms", mNanosecondsSaved * 0.000001f); 227 LOGD("------------------------------------------------"); 228} 229 230/** 231 * TextLayoutCacheKey 232 */ 233TextLayoutCacheKey::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 238TextLayoutCacheKey::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 250TextLayoutCacheKey::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 268int 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 302void TextLayoutCacheKey::internalTextCopy() { 303 textCopy.setTo(text, contextCount); 304 text = NULL; 305} 306 307size_t TextLayoutCacheKey::getSize() const { 308 return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount; 309} 310 311/** 312 * TextLayoutCacheValue 313 */ 314TextLayoutCacheValue::TextLayoutCacheValue() : 315 mTotalAdvance(0), mElapsedTime(0) { 316} 317 318void TextLayoutCacheValue::setElapsedTime(uint32_t time) { 319 mElapsedTime = time; 320} 321 322uint32_t TextLayoutCacheValue::getElapsedTime() { 323 return mElapsedTime; 324} 325 326void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars, 327 size_t start, size_t count, size_t contextCount, int dirFlags) { 328 // Give a hint for advances, glyphs and log clusters vectors size 329 mAdvances.setCapacity(contextCount); 330 mGlyphs.setCapacity(contextCount); 331 332 computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags, 333 &mAdvances, &mTotalAdvance, &mGlyphs); 334#if DEBUG_ADVANCES 335 LOGD("Advances - start=%d, count=%d, contextCount=%d, totalAdvance=%f", start, count, 336 contextCount, mTotalAdvance); 337#endif 338} 339 340size_t TextLayoutCacheValue::getSize() const { 341 return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvances.capacity() + 342 sizeof(jchar) * mGlyphs.capacity(); 343} 344 345void TextLayoutCacheValue::initShaperItem(HB_ShaperItem& shaperItem, HB_FontRec* font, 346 FontData* fontData, SkPaint* paint, const UChar* chars, size_t count) { 347 font->klass = &harfbuzzSkiaClass; 348 font->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 font->x_ppem = 1; 354 font->y_ppem = 1; 355 font->x_scale = 1; 356 font->y_scale = 1; 357 358 // Reset kerning 359 shaperItem.kerning_applied = false; 360 361 // Define font data 362 fontData->textSize = paint->getTextSize(); 363 fontData->textSkewX = paint->getTextSkewX(); 364 fontData->textScaleX = paint->getTextScaleX(); 365 fontData->flags = paint->getFlags(); 366 fontData->hinting = paint->getHinting(); 367 368 shaperItem.font = font; 369 shaperItem.font->userData = fontData; 370 371 // We cannot know, ahead of time, how many glyphs a given script run 372 // will produce. We take a guess that script runs will not produce more 373 // than twice as many glyphs as there are code points plus a bit of 374 // padding and fallback if we find that we are wrong. 375 createGlyphArrays(shaperItem, (count + 2) * 2); 376 377 // Create log clusters array 378 shaperItem.log_clusters = new unsigned short[count]; 379 380 // Set the string properties 381 shaperItem.string = chars; 382 shaperItem.stringLength = count; 383} 384 385void TextLayoutCacheValue::freeShaperItem(HB_ShaperItem& shaperItem) { 386 deleteGlyphArrays(shaperItem); 387 delete[] shaperItem.log_clusters; 388 HB_FreeFace(shaperItem.face); 389} 390 391unsigned TextLayoutCacheValue::shapeFontRun(HB_ShaperItem& shaperItem, SkPaint* paint, 392 size_t count, bool isRTL) { 393 // Update Harfbuzz Shaper 394 shaperItem.item.pos = 0; 395 shaperItem.item.length = count; 396 shaperItem.item.bidiLevel = isRTL; 397 398 // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz 399 // This is needed as the Typeface used for shaping can be not the default one 400 // when we are shapping any script that needs to use a fallback Font. 401 // If we are a "common" script we dont need to shift 402 unsigned result = 0; 403 switch(shaperItem.item.script) { 404 case HB_Script_Arabic: 405 case HB_Script_Hebrew: { 406 const uint16_t* text16 = (const uint16_t*)shaperItem.string; 407 SkUnichar firstUnichar = SkUTF16_NextUnichar(&text16); 408 result = paint->getBaseGlyphCount(firstUnichar); 409 break; 410 } 411 default: 412 break; 413 } 414 415 // Set the correct Typeface depending on the script 416 FontData* data = reinterpret_cast<FontData*>(shaperItem.font->userData); 417 switch(shaperItem.item.script) { 418 case HB_Script_Arabic: 419 data->typeFace = getCachedTypeface(gArabicTypeface, TYPEFACE_ARABIC); 420#if DEBUG_GLYPHS 421 LOGD("Using Arabic Typeface"); 422#endif 423 break; 424 425 case HB_Script_Hebrew: 426 if(paint->getTypeface()) { 427 switch(paint->getTypeface()->style()) { 428 case SkTypeface::kNormal: 429 case SkTypeface::kItalic: 430 default: 431 data->typeFace = getCachedTypeface(gHebrewRegularTypeface, TYPE_FACE_HEBREW_REGULAR); 432#if DEBUG_GLYPHS 433 LOGD("Using Hebrew Regular/Italic Typeface"); 434#endif 435 break; 436 case SkTypeface::kBold: 437 case SkTypeface::kBoldItalic: 438 data->typeFace = getCachedTypeface(gHebrewBoldTypeface, TYPE_FACE_HEBREW_BOLD); 439#if DEBUG_GLYPHS 440 LOGD("Using Hebrew Bold/BoldItalic Typeface"); 441#endif 442 break; 443 } 444 } else { 445 data->typeFace = getCachedTypeface(gHebrewRegularTypeface, TYPE_FACE_HEBREW_REGULAR); 446#if DEBUG_GLYPHS 447 LOGD("Using Hebrew Regular Typeface"); 448#endif 449 } 450 break; 451 452 default: 453 if(paint->getTypeface()) { 454 data->typeFace = paint->getTypeface(); 455#if DEBUG_GLYPHS 456 LOGD("Using Paint Typeface"); 457#endif 458 } else { 459 data->typeFace = gDefaultTypeface; 460#if DEBUG_GLYPHS 461 LOGD("Using Default Typeface"); 462#endif 463 } 464 break; 465 } 466 467 shaperItem.face = HB_NewFace(data, harfbuzzSkiaGetTable); 468 469#if DEBUG_GLYPHS 470 LOGD("Run typeFace = %p", data->typeFace); 471 LOGD("Run typeFace->uniqueID = %d", data->typeFace->uniqueID()); 472#endif 473 474 // Shape 475 while (!HB_ShapeItem(&shaperItem)) { 476 // We overflowed our arrays. Resize and retry. 477 // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size. 478 deleteGlyphArrays(shaperItem); 479 createGlyphArrays(shaperItem, shaperItem.num_glyphs << 1); 480 } 481 482 return result; 483} 484 485SkTypeface* TextLayoutCacheValue::getCachedTypeface(SkTypeface* typeface, const char path[]) { 486 if (!typeface) { 487 typeface = SkTypeface::CreateFromFile(path); 488 } 489 return typeface; 490} 491 492void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, 493 size_t start, size_t count, size_t contextCount, int dirFlags, 494 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 495 Vector<jchar>* const outGlyphs) { 496 497 UBiDiLevel bidiReq = 0; 498 bool forceLTR = false; 499 bool forceRTL = false; 500 501 switch (dirFlags) { 502 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level 503 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level 504 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break; 505 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break; 506 case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR 507 case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL 508 } 509 510 HB_ShaperItem shaperItem; 511 HB_FontRec font; 512 FontData fontData; 513 514 // Initialize Harfbuzz Shaper 515 initShaperItem(shaperItem, &font, &fontData, paint, chars, contextCount); 516 517 bool useSingleRun = false; 518 bool isRTL = forceRTL; 519 if (forceLTR || forceRTL) { 520 useSingleRun = true; 521 } else { 522 UBiDi* bidi = ubidi_open(); 523 if (bidi) { 524 UErrorCode status = U_ZERO_ERROR; 525#if DEBUG_GLYPHS 526 LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq); 527#endif 528 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); 529 if (U_SUCCESS(status)) { 530 int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl 531 ssize_t rc = ubidi_countRuns(bidi, &status); 532#if DEBUG_GLYPHS 533 LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d", 534 dirFlags, rc, paraDir); 535#endif 536 if (U_SUCCESS(status) && rc == 1) { 537 // Normal case: one run, status is ok 538 isRTL = (paraDir == 1); 539 useSingleRun = true; 540 } else if (!U_SUCCESS(status) || rc < 1) { 541 LOGW("computeValuesWithHarfbuzz -- need to force to single run"); 542 isRTL = (paraDir == 1); 543 useSingleRun = true; 544 } else { 545 int32_t end = start + count; 546 for (size_t i = 0; i < size_t(rc); ++i) { 547 int32_t startRun = -1; 548 int32_t lengthRun = -1; 549 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); 550 551 if (startRun == -1 || lengthRun == -1) { 552 // Something went wrong when getting the visual run, need to clear 553 // already computed data before doing a single run pass 554 LOGW("computeValuesWithHarfbuzz -- visual run is not valid"); 555 outGlyphs->clear(); 556 outAdvances->clear(); 557 *outTotalAdvance = 0; 558 isRTL = (paraDir == 1); 559 useSingleRun = true; 560 break; 561 } 562 563 if (startRun >= end) { 564 continue; 565 } 566 int32_t endRun = startRun + lengthRun; 567 if (endRun <= int32_t(start)) { 568 continue; 569 } 570 if (startRun < int32_t(start)) { 571 startRun = int32_t(start); 572 } 573 if (endRun > end) { 574 endRun = end; 575 } 576 577 lengthRun = endRun - startRun; 578 isRTL = (runDir == UBIDI_RTL); 579 jfloat runTotalAdvance = 0; 580#if DEBUG_GLYPHS 581 LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d isRTL=%d", 582 startRun, lengthRun, isRTL); 583#endif 584 computeRunValuesWithHarfbuzz(paint, chars + startRun, lengthRun, isRTL, 585 outAdvances, &runTotalAdvance, outGlyphs); 586 587 *outTotalAdvance += runTotalAdvance; 588 } 589 } 590 } else { 591 LOGW("computeValuesWithHarfbuzz -- cannot set Para"); 592 useSingleRun = true; 593 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 594 } 595 ubidi_close(bidi); 596 } else { 597 LOGW("computeValuesWithHarfbuzz -- cannot ubidi_open()"); 598 useSingleRun = true; 599 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 600 } 601 } 602 603 // Default single run case 604 if (useSingleRun){ 605#if DEBUG_GLYPHS 606 LOGD("computeValuesWithHarfbuzz -- Using a SINGLE Run " 607 "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL); 608#endif 609 computeRunValuesWithHarfbuzz(paint, chars + start, count, isRTL, 610 outAdvances, outTotalAdvance, outGlyphs); 611 } 612 613#if DEBUG_GLYPHS 614 LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", outGlyphs->size()); 615#endif 616} 617 618static void logGlyphs(HB_ShaperItem shaperItem) { 619 LOGD("Got glyphs - count=%d", shaperItem.num_glyphs); 620 for (size_t i = 0; i < shaperItem.num_glyphs; i++) { 621 LOGD(" glyph[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i], 622 HBFixedToFloat(shaperItem.offsets[i].x), 623 HBFixedToFloat(shaperItem.offsets[i].y)); 624 } 625} 626 627void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, 628 size_t count, bool isRTL, 629 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 630 Vector<jchar>* const outGlyphs) { 631 632 unsigned glyphBaseCount = 0; 633 634 *outTotalAdvance = 0; 635 jfloat totalAdvance = 0; 636 637 unsigned numCodePoints = 0; 638 639 ssize_t startFontRun = 0; 640 ssize_t endFontRun = 0; 641 ssize_t indexFontRun = isRTL ? count - 1 : 0; 642 size_t countFontRun = 0; 643 644 HB_ShaperItem shaperItem; 645 HB_FontRec font; 646 FontData fontData; 647 648 // Zero the Shaper struct 649 memset(&shaperItem, 0, sizeof(shaperItem)); 650 651 // Split the BiDi run into Script runs. Harfbuzz will populate the script into the shaperItem 652 while((isRTL) ? 653 hb_utf16_script_run_prev(&numCodePoints, &shaperItem.item, chars, 654 count, &indexFontRun): 655 hb_utf16_script_run_next(&numCodePoints, &shaperItem.item, chars, 656 count, &indexFontRun)) { 657 658 startFontRun = shaperItem.item.pos; 659 countFontRun = shaperItem.item.length; 660 endFontRun = startFontRun + countFontRun; 661 662#if DEBUG_GLYPHS 663 LOGD("Shaped Font Run with"); 664 LOGD(" -- isRTL=%d", isRTL); 665 LOGD(" -- HB script=%d", shaperItem.item.script); 666 LOGD(" -- startFontRun=%d", startFontRun); 667 LOGD(" -- endFontRun=%d", endFontRun); 668 LOGD(" -- countFontRun=%d", countFontRun); 669 LOGD(" -- run='%s'", String8(chars + startFontRun, countFontRun).string()); 670 LOGD(" -- string='%s'", String8(chars, count).string()); 671#endif 672 673 // Initialize Harfbuzz Shaper 674 initShaperItem(shaperItem, &font, &fontData, paint, chars + startFontRun, countFontRun); 675 676 // Shape the Font run and get the base glyph count for offsetting the glyphIDs later on 677 glyphBaseCount = shapeFontRun(shaperItem, paint, countFontRun, isRTL); 678 679#if DEBUG_GLYPHS 680 LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs, 681 shaperItem.kerning_applied); 682 LOGD(" -- isDevKernText=%d", paint->isDevKernText()); 683 LOGD(" -- glyphBaseCount=%d", glyphBaseCount); 684 685 logGlyphs(shaperItem); 686#endif 687 if (isRTL) { 688 endFontRun = startFontRun; 689#if DEBUG_GLYPHS 690 LOGD(" -- updated endFontRun=%d", endFontRun); 691#endif 692 } else { 693 startFontRun = endFontRun; 694#if DEBUG_GLYPHS 695 LOGD(" -- updated startFontRun=%d", startFontRun); 696#endif 697 } 698 699 if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) { 700#if DEBUG_GLYPHS 701 LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0"); 702#endif 703 outAdvances->insertAt(0, outAdvances->size(), countFontRun); 704 continue; 705 } 706 707 // Get Advances and their total 708 jfloat currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]); 709 jfloat totalFontRunAdvance = currentAdvance; 710 outAdvances->add(currentAdvance); 711 for (size_t i = 1; i < countFontRun; i++) { 712 size_t clusterPrevious = shaperItem.log_clusters[i - 1]; 713 size_t cluster = shaperItem.log_clusters[i]; 714 if (cluster == clusterPrevious) { 715 outAdvances->add(0); 716 } else { 717 currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]); 718 totalFontRunAdvance += currentAdvance; 719 outAdvances->add(currentAdvance); 720 } 721 } 722 totalAdvance += totalFontRunAdvance; 723 724#if DEBUG_ADVANCES 725 for (size_t i = 0; i < countFontRun; i++) { 726 LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i, 727 (*outAdvances)[i], shaperItem.log_clusters[i], totalFontRunAdvance); 728 } 729#endif 730 731 // Get Glyphs and reverse them in place if RTL 732 if (outGlyphs) { 733 size_t countGlyphs = shaperItem.num_glyphs; 734 for (size_t i = 0; i < countGlyphs; i++) { 735 jchar glyph = glyphBaseCount + 736 (jchar) shaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i]; 737#if DEBUG_GLYPHS 738 LOGD("HARFBUZZ -- glyph[%d]=%d", i, glyph); 739#endif 740 outGlyphs->add(glyph); 741 } 742 } 743 // Cleaning 744 freeShaperItem(shaperItem); 745 } 746 *outTotalAdvance = totalAdvance; 747} 748 749void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem& shaperItem) { 750 delete[] shaperItem.glyphs; 751 delete[] shaperItem.attributes; 752 delete[] shaperItem.advances; 753 delete[] shaperItem.offsets; 754} 755 756void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem& shaperItem, int size) { 757#if DEBUG_GLYPHS 758 LOGD("createGlyphArrays -- size=%d", size); 759#endif 760 shaperItem.glyphs = new HB_Glyph[size]; 761 shaperItem.attributes = new HB_GlyphAttributes[size]; 762 shaperItem.advances = new HB_Fixed[size]; 763 shaperItem.offsets = new HB_FixedPoint[size]; 764 shaperItem.num_glyphs = size; 765} 766 767} // namespace android 768