TextLayoutCache.cpp revision e187a2f55fe8684c853a0701cbc4a71392f437e0
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 * Size management 77 */ 78 79uint32_t TextLayoutCache::getSize() { 80 return mSize; 81} 82 83uint32_t TextLayoutCache::getMaxSize() { 84 return mMaxSize; 85} 86 87void TextLayoutCache::setMaxSize(uint32_t maxSize) { 88 mMaxSize = maxSize; 89 removeOldests(); 90} 91 92void TextLayoutCache::removeOldests() { 93 while (mSize > mMaxSize) { 94 mCache.removeOldest(); 95 } 96} 97 98/** 99 * Callbacks 100 */ 101void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) { 102 if (desc != NULL) { 103 size_t totalSizeToDelete = text.getSize() + desc->getSize(); 104 mSize -= totalSizeToDelete; 105 if (mDebugEnabled) { 106 LOGD("Cache value deleted, size = %d", totalSizeToDelete); 107 } 108 desc.clear(); 109 } 110} 111 112/* 113 * Cache clearing 114 */ 115void TextLayoutCache::clear() { 116 mCache.clear(); 117} 118 119/* 120 * Caching 121 */ 122sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint, 123 const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) { 124 AutoMutex _l(mLock); 125 nsecs_t startTime = 0; 126 if (mDebugEnabled) { 127 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 128 } 129 130 // Create the key 131 TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags); 132 133 // Get value from cache if possible 134 sp<TextLayoutCacheValue> value = mCache.get(key); 135 136 // Value not found for the key, we need to add a new value in the cache 137 if (value == NULL) { 138 if (mDebugEnabled) { 139 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 140 } 141 142 value = new TextLayoutCacheValue(); 143 144 // Compute advances and store them 145 value->computeValues(paint, text, start, count, contextCount, dirFlags); 146 147 nsecs_t endTime = systemTime(SYSTEM_TIME_MONOTONIC); 148 149 // Don't bother to add in the cache if the entry is too big 150 size_t size = key.getSize() + value->getSize(); 151 if (size <= mMaxSize) { 152 // Cleanup to make some room if needed 153 if (mSize + size > mMaxSize) { 154 if (mDebugEnabled) { 155 LOGD("Need to clean some entries for making some room for a new entry"); 156 } 157 while (mSize + size > mMaxSize) { 158 // This will call the callback 159 mCache.removeOldest(); 160 } 161 } 162 163 // Update current cache size 164 mSize += size; 165 166 // Copy the text when we insert the new entry 167 key.internalTextCopy(); 168 mCache.put(key, value); 169 170 if (mDebugEnabled) { 171 // Update timing information for statistics 172 value->setElapsedTime(endTime - startTime); 173 174 LOGD("CACHE MISS: Added entry with " 175 "count=%d, entry size %d bytes, remaining space %d bytes" 176 " - Compute time in nanos: %d - Text='%s' ", 177 count, size, mMaxSize - mSize, value->getElapsedTime(), 178 String8(text, count).string()); 179 } 180 } else { 181 if (mDebugEnabled) { 182 LOGD("CACHE MISS: Calculated but not storing entry because it is too big " 183 "with start=%d count=%d contextCount=%d, " 184 "entry size %d bytes, remaining space %d bytes" 185 " - Compute time in nanos: %lld - Text='%s'", 186 start, count, contextCount, size, mMaxSize - mSize, endTime, 187 String8(text, count).string()); 188 } 189 value.clear(); 190 } 191 } else { 192 // This is a cache hit, just log timestamp and user infos 193 if (mDebugEnabled) { 194 nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; 195 mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet); 196 ++mCacheHitCount; 197 198 if (value->getElapsedTime() > 0) { 199 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet) 200 / ((float)value->getElapsedTime())); 201 LOGD("CACHE HIT #%d with start=%d count=%d contextCount=%d" 202 "- Compute time in nanos: %d - " 203 "Cache get time in nanos: %lld - Gain in percent: %2.2f - Text='%s' ", 204 mCacheHitCount, start, count, contextCount, 205 value->getElapsedTime(), elapsedTimeThruCacheGet, deltaPercent, 206 String8(text, count).string()); 207 } 208 if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) { 209 dumpCacheStats(); 210 } 211 } 212 } 213 return value; 214} 215 216void TextLayoutCache::dumpCacheStats() { 217 float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize)); 218 float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000; 219 LOGD("------------------------------------------------"); 220 LOGD("Cache stats"); 221 LOGD("------------------------------------------------"); 222 LOGD("pid : %d", getpid()); 223 LOGD("running : %.0f seconds", timeRunningInSec); 224 LOGD("entries : %d", mCache.size()); 225 LOGD("size : %d bytes", mMaxSize); 226 LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent); 227 LOGD("hits : %d", mCacheHitCount); 228 LOGD("saved : %lld milliseconds", mNanosecondsSaved / 1000000); 229 LOGD("------------------------------------------------"); 230} 231 232/** 233 * TextLayoutCacheKey 234 */ 235TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0), 236 dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0), 237 hinting(SkPaint::kNo_Hinting) { 238} 239 240TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text, 241 size_t start, size_t count, size_t contextCount, int dirFlags) : 242 text(text), start(start), count(count), contextCount(contextCount), 243 dirFlags(dirFlags) { 244 typeface = paint->getTypeface(); 245 textSize = paint->getTextSize(); 246 textSkewX = paint->getTextSkewX(); 247 textScaleX = paint->getTextScaleX(); 248 flags = paint->getFlags(); 249 hinting = paint->getHinting(); 250} 251 252TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) : 253 text(NULL), 254 textCopy(other.textCopy), 255 start(other.start), 256 count(other.count), 257 contextCount(other.contextCount), 258 dirFlags(other.dirFlags), 259 typeface(other.typeface), 260 textSize(other.textSize), 261 textSkewX(other.textSkewX), 262 textScaleX(other.textScaleX), 263 flags(other.flags), 264 hinting(other.hinting) { 265 if (other.text) { 266 textCopy.setTo(other.text); 267 } 268} 269 270int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) { 271 int deltaInt = lhs.start - rhs.start; 272 if (deltaInt != 0) return (deltaInt); 273 274 deltaInt = lhs.count - rhs.count; 275 if (deltaInt != 0) return (deltaInt); 276 277 deltaInt = lhs.contextCount - rhs.contextCount; 278 if (deltaInt != 0) return (deltaInt); 279 280 if (lhs.typeface < rhs.typeface) return -1; 281 if (lhs.typeface > rhs.typeface) return +1; 282 283 if (lhs.textSize < rhs.textSize) return -1; 284 if (lhs.textSize > rhs.textSize) return +1; 285 286 if (lhs.textSkewX < rhs.textSkewX) return -1; 287 if (lhs.textSkewX > rhs.textSkewX) return +1; 288 289 if (lhs.textScaleX < rhs.textScaleX) return -1; 290 if (lhs.textScaleX > rhs.textScaleX) return +1; 291 292 deltaInt = lhs.flags - rhs.flags; 293 if (deltaInt != 0) return (deltaInt); 294 295 deltaInt = lhs.hinting - rhs.hinting; 296 if (deltaInt != 0) return (deltaInt); 297 298 deltaInt = lhs.dirFlags - rhs.dirFlags; 299 if (deltaInt) return (deltaInt); 300 301 return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar)); 302} 303 304void TextLayoutCacheKey::internalTextCopy() { 305 textCopy.setTo(text, contextCount); 306 text = NULL; 307} 308 309size_t TextLayoutCacheKey::getSize() { 310 return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount; 311} 312 313/** 314 * TextLayoutCacheValue 315 */ 316TextLayoutCacheValue::TextLayoutCacheValue() : 317 mTotalAdvance(0), mElapsedTime(0) { 318} 319 320void TextLayoutCacheValue::setElapsedTime(uint32_t time) { 321 mElapsedTime = time; 322} 323 324uint32_t TextLayoutCacheValue::getElapsedTime() { 325 return mElapsedTime; 326} 327 328void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars, 329 size_t start, size_t count, size_t contextCount, int dirFlags) { 330 // Give a hint for advances, glyphs and log clusters vectors size 331 mAdvances.setCapacity(contextCount); 332 mGlyphs.setCapacity(contextCount); 333 334 computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags, 335 &mAdvances, &mTotalAdvance, &mGlyphs); 336#if DEBUG_ADVANCES 337 LOGD("Advances - start=%d, count=%d, contextCount=%d, totalAdvance=%f", start, count, 338 contextCount, mTotalAdvance); 339#endif 340} 341 342size_t TextLayoutCacheValue::getSize() { 343 return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvances.capacity() + 344 sizeof(jchar) * mGlyphs.capacity(); 345} 346 347void TextLayoutCacheValue::initShaperItem(HB_ShaperItem& shaperItem, HB_FontRec* font, 348 FontData* fontData, SkPaint* paint, const UChar* chars, size_t count) { 349 font->klass = &harfbuzzSkiaClass; 350 font->userData = 0; 351 352 // The values which harfbuzzSkiaClass returns are already scaled to 353 // pixel units, so we just set all these to one to disable further 354 // scaling. 355 font->x_ppem = 1; 356 font->y_ppem = 1; 357 font->x_scale = 1; 358 font->y_scale = 1; 359 360 // Reset kerning 361 shaperItem.kerning_applied = false; 362 363 // Define font data 364 fontData->textSize = paint->getTextSize(); 365 fontData->textSkewX = paint->getTextSkewX(); 366 fontData->textScaleX = paint->getTextScaleX(); 367 fontData->flags = paint->getFlags(); 368 fontData->hinting = paint->getHinting(); 369 370 shaperItem.font = font; 371 shaperItem.font->userData = fontData; 372 373 // We cannot know, ahead of time, how many glyphs a given script run 374 // will produce. We take a guess that script runs will not produce more 375 // than twice as many glyphs as there are code points plus a bit of 376 // padding and fallback if we find that we are wrong. 377 createGlyphArrays(shaperItem, (count + 2) * 2); 378 379 // Create log clusters array 380 shaperItem.log_clusters = new unsigned short[count]; 381 382 // Set the string properties 383 shaperItem.string = chars; 384 shaperItem.stringLength = count; 385} 386 387void TextLayoutCacheValue::freeShaperItem(HB_ShaperItem& shaperItem) { 388 deleteGlyphArrays(shaperItem); 389 delete[] shaperItem.log_clusters; 390 HB_FreeFace(shaperItem.face); 391} 392 393unsigned TextLayoutCacheValue::shapeFontRun(HB_ShaperItem& shaperItem, SkPaint* paint, 394 size_t count, bool isRTL) { 395 // Update Harfbuzz Shaper 396 shaperItem.item.pos = 0; 397 shaperItem.item.length = count; 398 shaperItem.item.bidiLevel = isRTL; 399 400 // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz 401 // This is needed as the Typeface used for shaping can be not the default one 402 // when we are shapping any script that needs to use a fallback Font. 403 // If we are a "common" script we dont need to shift 404 unsigned result = 0; 405 switch(shaperItem.item.script) { 406 case HB_Script_Arabic: 407 case HB_Script_Hebrew: 408 const uint16_t* text16 = (const uint16_t*)shaperItem.string; 409 SkUnichar firstUnichar = SkUTF16_NextUnichar(&text16); 410 result = paint->getBaseGlyphCount(firstUnichar); 411 break; 412 } 413 414 // Set the correct Typeface depending on the script 415 FontData* data = reinterpret_cast<FontData*>(shaperItem.font->userData); 416 switch(shaperItem.item.script) { 417 case HB_Script_Arabic: 418 if (!gArabicTypeface) { 419 gArabicTypeface = SkTypeface::CreateFromFile(TYPEFACE_ARABIC); 420 } 421 data->typeFace = gArabicTypeface; 422#if DEBUG_GLYPHS 423 LOGD("Using Arabic Typeface"); 424#endif 425 break; 426 427 case HB_Script_Hebrew: 428 if(paint->getTypeface()) { 429 switch(paint->getTypeface()->style()) { 430 case SkTypeface::kNormal: 431 case SkTypeface::kItalic: 432 default: 433 if (!gHebrewRegularTypeface) { 434 gHebrewRegularTypeface = SkTypeface::CreateFromFile( 435 TYPE_FACE_HEBREW_REGULAR); 436 } 437 data->typeFace = gHebrewRegularTypeface; 438#if DEBUG_GLYPHS 439 LOGD("Using Hebrew Regular/Italic Typeface"); 440#endif 441 break; 442 case SkTypeface::kBold: 443 case SkTypeface::kBoldItalic: 444 if (!gHebrewBoldTypeface) { 445 gHebrewBoldTypeface = SkTypeface::CreateFromFile( 446 TYPE_FACE_HEBREW_BOLD); 447 } 448 data->typeFace = gHebrewBoldTypeface; 449#if DEBUG_GLYPHS 450 LOGD("Using Hebrew Bold/BoldItalic Typeface"); 451#endif 452 break; 453 } 454 } else { 455 data->typeFace = gHebrewRegularTypeface; 456#if DEBUG_GLYPHS 457 LOGD("Using Hebrew Regular Typeface"); 458#endif 459 } 460 break; 461 462 default: 463 if(paint->getTypeface()) { 464 data->typeFace = paint->getTypeface(); 465#if DEBUG_GLYPHS 466 LOGD("Using Paint Typeface"); 467#endif 468 } else { 469 data->typeFace = gDefaultTypeface; 470#if DEBUG_GLYPHS 471 LOGD("Using Default Typeface"); 472#endif 473 } 474 break; 475 } 476 477 shaperItem.face = HB_NewFace(data, harfbuzzSkiaGetTable); 478 479#if DEBUG_GLYPHS 480 LOGD("Run typeFace = %p", data->typeFace); 481 LOGD("Run typeFace->uniqueID = %d", data->typeFace->uniqueID()); 482#endif 483 484 // Shape 485 while (!HB_ShapeItem(&shaperItem)) { 486 // We overflowed our arrays. Resize and retry. 487 // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size. 488 deleteGlyphArrays(shaperItem); 489 createGlyphArrays(shaperItem, shaperItem.num_glyphs << 1); 490 } 491 492 return result; 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