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