TextLayoutCache.cpp revision a4f5aa87c73de7a2581dc4dd72e0f90ccea79a18
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(); 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#if DEBUG_GLYPHS 489 LOGD("Created SkTypeface from file: %s", path); 490#endif 491 } 492 return *typeface; 493} 494 495void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, 496 size_t start, size_t count, size_t contextCount, int dirFlags, 497 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 498 Vector<jchar>* const outGlyphs) { 499 500 UBiDiLevel bidiReq = 0; 501 bool forceLTR = false; 502 bool forceRTL = false; 503 504 switch (dirFlags) { 505 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level 506 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level 507 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break; 508 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break; 509 case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR 510 case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL 511 } 512 513 HB_ShaperItem shaperItem; 514 HB_FontRec font; 515 FontData fontData; 516 517 // Initialize Harfbuzz Shaper 518 initShaperItem(shaperItem, &font, &fontData, paint, chars, contextCount); 519 520 bool useSingleRun = false; 521 bool isRTL = forceRTL; 522 if (forceLTR || forceRTL) { 523 useSingleRun = true; 524 } else { 525 UBiDi* bidi = ubidi_open(); 526 if (bidi) { 527 UErrorCode status = U_ZERO_ERROR; 528#if DEBUG_GLYPHS 529 LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq); 530#endif 531 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); 532 if (U_SUCCESS(status)) { 533 int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl 534 ssize_t rc = ubidi_countRuns(bidi, &status); 535#if DEBUG_GLYPHS 536 LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d", 537 dirFlags, rc, paraDir); 538#endif 539 if (U_SUCCESS(status) && rc == 1) { 540 // Normal case: one run, status is ok 541 isRTL = (paraDir == 1); 542 useSingleRun = true; 543 } else if (!U_SUCCESS(status) || rc < 1) { 544 LOGW("computeValuesWithHarfbuzz -- need to force to single run"); 545 isRTL = (paraDir == 1); 546 useSingleRun = true; 547 } else { 548 int32_t end = start + count; 549 for (size_t i = 0; i < size_t(rc); ++i) { 550 int32_t startRun = -1; 551 int32_t lengthRun = -1; 552 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); 553 554 if (startRun == -1 || lengthRun == -1) { 555 // Something went wrong when getting the visual run, need to clear 556 // already computed data before doing a single run pass 557 LOGW("computeValuesWithHarfbuzz -- visual run is not valid"); 558 outGlyphs->clear(); 559 outAdvances->clear(); 560 *outTotalAdvance = 0; 561 isRTL = (paraDir == 1); 562 useSingleRun = true; 563 break; 564 } 565 566 if (startRun >= end) { 567 continue; 568 } 569 int32_t endRun = startRun + lengthRun; 570 if (endRun <= int32_t(start)) { 571 continue; 572 } 573 if (startRun < int32_t(start)) { 574 startRun = int32_t(start); 575 } 576 if (endRun > end) { 577 endRun = end; 578 } 579 580 lengthRun = endRun - startRun; 581 isRTL = (runDir == UBIDI_RTL); 582 jfloat runTotalAdvance = 0; 583#if DEBUG_GLYPHS 584 LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d isRTL=%d", 585 startRun, lengthRun, isRTL); 586#endif 587 computeRunValuesWithHarfbuzz(paint, chars + startRun, lengthRun, isRTL, 588 outAdvances, &runTotalAdvance, outGlyphs); 589 590 *outTotalAdvance += runTotalAdvance; 591 } 592 } 593 } else { 594 LOGW("computeValuesWithHarfbuzz -- cannot set Para"); 595 useSingleRun = true; 596 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 597 } 598 ubidi_close(bidi); 599 } else { 600 LOGW("computeValuesWithHarfbuzz -- cannot ubidi_open()"); 601 useSingleRun = true; 602 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 603 } 604 } 605 606 // Default single run case 607 if (useSingleRun){ 608#if DEBUG_GLYPHS 609 LOGD("computeValuesWithHarfbuzz -- Using a SINGLE Run " 610 "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL); 611#endif 612 computeRunValuesWithHarfbuzz(paint, chars + start, count, isRTL, 613 outAdvances, outTotalAdvance, outGlyphs); 614 } 615 616#if DEBUG_GLYPHS 617 LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", outGlyphs->size()); 618#endif 619} 620 621static void logGlyphs(HB_ShaperItem shaperItem) { 622 LOGD("Got glyphs - count=%d", shaperItem.num_glyphs); 623 for (size_t i = 0; i < shaperItem.num_glyphs; i++) { 624 LOGD(" glyph[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i], 625 HBFixedToFloat(shaperItem.offsets[i].x), 626 HBFixedToFloat(shaperItem.offsets[i].y)); 627 } 628} 629 630void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, 631 size_t count, bool isRTL, 632 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 633 Vector<jchar>* const outGlyphs) { 634 635 unsigned glyphBaseCount = 0; 636 637 *outTotalAdvance = 0; 638 jfloat totalAdvance = 0; 639 640 unsigned numCodePoints = 0; 641 642 ssize_t startFontRun = 0; 643 ssize_t endFontRun = 0; 644 ssize_t indexFontRun = isRTL ? count - 1 : 0; 645 size_t countFontRun = 0; 646 647 HB_ShaperItem shaperItem; 648 HB_FontRec font; 649 FontData fontData; 650 651 // Zero the Shaper struct 652 memset(&shaperItem, 0, sizeof(shaperItem)); 653 654 // Split the BiDi run into Script runs. Harfbuzz will populate the script into the shaperItem 655 while((isRTL) ? 656 hb_utf16_script_run_prev(&numCodePoints, &shaperItem.item, chars, 657 count, &indexFontRun): 658 hb_utf16_script_run_next(&numCodePoints, &shaperItem.item, chars, 659 count, &indexFontRun)) { 660 661 startFontRun = shaperItem.item.pos; 662 countFontRun = shaperItem.item.length; 663 endFontRun = startFontRun + countFontRun; 664 665#if DEBUG_GLYPHS 666 LOGD("Shaped Font Run with"); 667 LOGD(" -- isRTL=%d", isRTL); 668 LOGD(" -- HB script=%d", shaperItem.item.script); 669 LOGD(" -- startFontRun=%d", startFontRun); 670 LOGD(" -- endFontRun=%d", endFontRun); 671 LOGD(" -- countFontRun=%d", countFontRun); 672 LOGD(" -- run='%s'", String8(chars + startFontRun, countFontRun).string()); 673 LOGD(" -- string='%s'", String8(chars, count).string()); 674#endif 675 676 // Initialize Harfbuzz Shaper 677 initShaperItem(shaperItem, &font, &fontData, paint, chars + startFontRun, countFontRun); 678 679 // Shape the Font run and get the base glyph count for offsetting the glyphIDs later on 680 glyphBaseCount = shapeFontRun(shaperItem, paint, countFontRun, isRTL); 681 682#if DEBUG_GLYPHS 683 LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs, 684 shaperItem.kerning_applied); 685 LOGD(" -- isDevKernText=%d", paint->isDevKernText()); 686 LOGD(" -- glyphBaseCount=%d", glyphBaseCount); 687 688 logGlyphs(shaperItem); 689#endif 690 if (isRTL) { 691 endFontRun = startFontRun; 692#if DEBUG_GLYPHS 693 LOGD(" -- updated endFontRun=%d", endFontRun); 694#endif 695 } else { 696 startFontRun = endFontRun; 697#if DEBUG_GLYPHS 698 LOGD(" -- updated startFontRun=%d", startFontRun); 699#endif 700 } 701 702 if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) { 703#if DEBUG_GLYPHS 704 LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0"); 705#endif 706 outAdvances->insertAt(0, outAdvances->size(), countFontRun); 707 continue; 708 } 709 710 // Get Advances and their total 711 jfloat currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]); 712 jfloat totalFontRunAdvance = currentAdvance; 713 outAdvances->add(currentAdvance); 714 for (size_t i = 1; i < countFontRun; i++) { 715 size_t clusterPrevious = shaperItem.log_clusters[i - 1]; 716 size_t cluster = shaperItem.log_clusters[i]; 717 if (cluster == clusterPrevious) { 718 outAdvances->add(0); 719 } else { 720 currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]); 721 totalFontRunAdvance += currentAdvance; 722 outAdvances->add(currentAdvance); 723 } 724 } 725 totalAdvance += totalFontRunAdvance; 726 727#if DEBUG_ADVANCES 728 for (size_t i = 0; i < countFontRun; i++) { 729 LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i, 730 (*outAdvances)[i], shaperItem.log_clusters[i], totalFontRunAdvance); 731 } 732#endif 733 734 // Get Glyphs and reverse them in place if RTL 735 if (outGlyphs) { 736 size_t countGlyphs = shaperItem.num_glyphs; 737 for (size_t i = 0; i < countGlyphs; i++) { 738 jchar glyph = glyphBaseCount + 739 (jchar) shaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i]; 740#if DEBUG_GLYPHS 741 LOGD("HARFBUZZ -- glyph[%d]=%d", i, glyph); 742#endif 743 outGlyphs->add(glyph); 744 } 745 } 746 // Cleaning 747 freeShaperItem(shaperItem); 748 } 749 *outTotalAdvance = totalAdvance; 750} 751 752void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem& shaperItem) { 753 delete[] shaperItem.glyphs; 754 delete[] shaperItem.attributes; 755 delete[] shaperItem.advances; 756 delete[] shaperItem.offsets; 757} 758 759void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem& shaperItem, int size) { 760#if DEBUG_GLYPHS 761 LOGD("createGlyphArrays -- size=%d", size); 762#endif 763 shaperItem.glyphs = new HB_Glyph[size]; 764 shaperItem.attributes = new HB_GlyphAttributes[size]; 765 shaperItem.advances = new HB_Fixed[size]; 766 shaperItem.offsets = new HB_FixedPoint[size]; 767 shaperItem.num_glyphs = size; 768} 769 770} // namespace android 771