TextLayoutCache.cpp revision 79df5323e7ed541b854cea5684a89e8be8c2dfc9
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 22extern "C" { 23 #include "harfbuzz-unicode.h" 24} 25 26namespace android { 27 28//-------------------------------------------------------------------------------------------------- 29#if USE_TEXT_LAYOUT_CACHE 30 ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutCache); 31#endif 32//-------------------------------------------------------------------------------------------------- 33 34TextLayoutCache::TextLayoutCache() : 35 mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity), 36 mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)), 37 mCacheHitCount(0), mNanosecondsSaved(0) { 38 init(); 39} 40 41TextLayoutCache::~TextLayoutCache() { 42 mCache.clear(); 43} 44 45void TextLayoutCache::init() { 46 mCache.setOnEntryRemovedListener(this); 47 48 mDebugLevel = readRtlDebugLevel(); 49 mDebugEnabled = mDebugLevel & kRtlDebugCaches; 50 LOGD("Using debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled); 51 52 mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC); 53 54 if (mDebugEnabled) { 55 LOGD("Initialization is done - Start time: %lld", mCacheStartTime); 56 } 57 58 mInitialized = true; 59} 60 61/* 62 * Size management 63 */ 64 65uint32_t TextLayoutCache::getSize() { 66 return mSize; 67} 68 69uint32_t TextLayoutCache::getMaxSize() { 70 return mMaxSize; 71} 72 73void TextLayoutCache::setMaxSize(uint32_t maxSize) { 74 mMaxSize = maxSize; 75 removeOldests(); 76} 77 78void TextLayoutCache::removeOldests() { 79 while (mSize > mMaxSize) { 80 mCache.removeOldest(); 81 } 82} 83 84/** 85 * Callbacks 86 */ 87void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) { 88 if (desc != NULL) { 89 size_t totalSizeToDelete = text.getSize() + desc->getSize(); 90 mSize -= totalSizeToDelete; 91 if (mDebugEnabled) { 92 LOGD("Cache value deleted, size = %d", totalSizeToDelete); 93 } 94 desc.clear(); 95 } 96} 97 98/* 99 * Cache clearing 100 */ 101void TextLayoutCache::clear() { 102 mCache.clear(); 103} 104 105/* 106 * Caching 107 */ 108sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint, 109 const jchar* text, jint count, jint dirFlags) { 110 AutoMutex _l(mLock); 111 nsecs_t startTime = 0; 112 if (mDebugEnabled) { 113 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 114 } 115 116 // Create the key 117 TextLayoutCacheKey key(paint, text, count, dirFlags); 118 119 // Get value from cache if possible 120 sp<TextLayoutCacheValue> value = mCache.get(key); 121 122 // Value not found for the key, we need to add a new value in the cache 123 if (value == NULL) { 124 if (mDebugEnabled) { 125 startTime = systemTime(SYSTEM_TIME_MONOTONIC); 126 } 127 128 value = new TextLayoutCacheValue(); 129 130 // Compute advances and store them 131 value->computeValues(paint, text, count, dirFlags); 132 133 nsecs_t endTime = systemTime(SYSTEM_TIME_MONOTONIC); 134 135 // Don't bother to add in the cache if the entry is too big 136 size_t size = key.getSize() + value->getSize(); 137 if (size <= mMaxSize) { 138 // Cleanup to make some room if needed 139 if (mSize + size > mMaxSize) { 140 if (mDebugEnabled) { 141 LOGD("Need to clean some entries for making some room for a new entry"); 142 } 143 while (mSize + size > mMaxSize) { 144 // This will call the callback 145 mCache.removeOldest(); 146 } 147 } 148 149 // Update current cache size 150 mSize += size; 151 152 // Copy the text when we insert the new entry 153 key.internalTextCopy(); 154 mCache.put(key, value); 155 156 if (mDebugEnabled) { 157 // Update timing information for statistics 158 value->setElapsedTime(endTime - startTime); 159 160 LOGD("CACHE MISS: Added entry with " 161 "count=%d, entry size %d bytes, remaining space %d bytes" 162 " - Compute time in nanos: %d - Text='%s' ", 163 count, size, mMaxSize - mSize, value->getElapsedTime(), 164 String8(text, count).string()); 165 } 166 } else { 167 if (mDebugEnabled) { 168 LOGD("CACHE MISS: Calculated but not storing entry because it is too big " 169 "with count=%d, " 170 "entry size %d bytes, remaining space %d bytes" 171 " - Compute time in nanos: %lld - Text='%s'", 172 count, size, mMaxSize - mSize, endTime, 173 String8(text, count).string()); 174 } 175 value.clear(); 176 } 177 } else { 178 // This is a cache hit, just log timestamp and user infos 179 if (mDebugEnabled) { 180 nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; 181 mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet); 182 ++mCacheHitCount; 183 184 if (value->getElapsedTime() > 0) { 185 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet) 186 / ((float)value->getElapsedTime())); 187 LOGD("CACHE HIT #%d with count=%d " 188 "- Compute time in nanos: %d - " 189 "Cache get time in nanos: %lld - Gain in percent: %2.2f - Text='%s' ", 190 mCacheHitCount, count, 191 value->getElapsedTime(), elapsedTimeThruCacheGet, deltaPercent, 192 String8(text, count).string()); 193 } 194 if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) { 195 dumpCacheStats(); 196 } 197 } 198 } 199 return value; 200} 201 202void TextLayoutCache::dumpCacheStats() { 203 float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize)); 204 float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000; 205 LOGD("------------------------------------------------"); 206 LOGD("Cache stats"); 207 LOGD("------------------------------------------------"); 208 LOGD("pid : %d", getpid()); 209 LOGD("running : %.0f seconds", timeRunningInSec); 210 LOGD("entries : %d", mCache.size()); 211 LOGD("size : %d bytes", mMaxSize); 212 LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent); 213 LOGD("hits : %d", mCacheHitCount); 214 LOGD("saved : %lld milliseconds", mNanosecondsSaved / 1000000); 215 LOGD("------------------------------------------------"); 216} 217 218/** 219 * TextLayoutCacheKey 220 */ 221TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), count(0), 222 dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0), 223 hinting(SkPaint::kNo_Hinting) { 224} 225 226TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, 227 const UChar* text, size_t count, int dirFlags) : 228 text(text), count(count), 229 dirFlags(dirFlags) { 230 typeface = paint->getTypeface(); 231 textSize = paint->getTextSize(); 232 textSkewX = paint->getTextSkewX(); 233 textScaleX = paint->getTextScaleX(); 234 flags = paint->getFlags(); 235 hinting = paint->getHinting(); 236} 237 238TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) : 239 text(NULL), 240 textCopy(other.textCopy), 241 count(other.count), 242 dirFlags(other.dirFlags), 243 typeface(other.typeface), 244 textSize(other.textSize), 245 textSkewX(other.textSkewX), 246 textScaleX(other.textScaleX), 247 flags(other.flags), 248 hinting(other.hinting) { 249 if (other.text) { 250 textCopy.setTo(other.text); 251 } 252} 253 254bool TextLayoutCacheKey::operator<(const TextLayoutCacheKey& rhs) const { 255 LTE_INT(count) { 256 LTE_INT(typeface) { 257 LTE_FLOAT(textSize) { 258 LTE_FLOAT(textSkewX) { 259 LTE_FLOAT(textScaleX) { 260 LTE_INT(flags) { 261 LTE_INT(hinting) { 262 LTE_INT(dirFlags) { 263 return memcmp(getText(), rhs.getText(), 264 count * sizeof(UChar)) < 0; 265 } 266 } 267 } 268 } 269 } 270 } 271 } 272 } 273 return false; 274} 275 276void TextLayoutCacheKey::internalTextCopy() { 277 textCopy.setTo(text, count); 278 text = NULL; 279} 280 281size_t TextLayoutCacheKey::getSize() { 282 return sizeof(TextLayoutCacheKey) + sizeof(UChar) * count; 283} 284 285/** 286 * TextLayoutCacheValue 287 */ 288TextLayoutCacheValue::TextLayoutCacheValue() : 289 mTotalAdvance(0), mElapsedTime(0) { 290} 291 292void TextLayoutCacheValue::setElapsedTime(uint32_t time) { 293 mElapsedTime = time; 294} 295 296uint32_t TextLayoutCacheValue::getElapsedTime() { 297 return mElapsedTime; 298} 299 300void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars, 301 size_t contextCount, int dirFlags) { 302 // Give a hint for advances, glyphs and log clusters vectors size 303 mAdvances.setCapacity(contextCount); 304 mGlyphs.setCapacity(contextCount); 305 mLogClusters.setCapacity(contextCount); 306 307 computeValuesWithHarfbuzz(paint, chars, contextCount, dirFlags, 308 &mAdvances, &mTotalAdvance, &mGlyphs, &mLogClusters); 309#if DEBUG_ADVANCES 310 LOGD("Advances - countextCount=%d - totalAdvance=%f", contextCount, mTotalAdvance); 311#endif 312} 313 314size_t TextLayoutCacheValue::getSize() { 315 return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvances.capacity() + 316 sizeof(jchar) * mGlyphs.capacity() + sizeof(unsigned short) * mLogClusters.capacity(); 317} 318 319void TextLayoutCacheValue::setupShaperItem(HB_ShaperItem* shaperItem, HB_FontRec* font, 320 FontData* fontData, SkPaint* paint, const UChar* chars, size_t start, size_t count, 321 size_t contextCount, bool isRTL) { 322 font->klass = &harfbuzzSkiaClass; 323 font->userData = 0; 324 // The values which harfbuzzSkiaClass returns are already scaled to 325 // pixel units, so we just set all these to one to disable further 326 // scaling. 327 font->x_ppem = 1; 328 font->y_ppem = 1; 329 font->x_scale = 1; 330 font->y_scale = 1; 331 332 memset(shaperItem, 0, sizeof(*shaperItem)); 333 shaperItem->font = font; 334 shaperItem->face = HB_NewFace(shaperItem->font, harfbuzzSkiaGetTable); 335 336 shaperItem->kerning_applied = false; 337 338 // We cannot know, ahead of time, how many glyphs a given script run 339 // will produce. We take a guess that script runs will not produce more 340 // than twice as many glyphs as there are code points plus a bit of 341 // padding and fallback if we find that we are wrong. 342 createGlyphArrays(shaperItem, (contextCount + 2) * 2); 343 344 // Free memory for clusters if needed and recreate the clusters array 345 if (shaperItem->log_clusters) { 346 delete shaperItem->log_clusters; 347 } 348 shaperItem->log_clusters = new unsigned short[contextCount]; 349 350 shaperItem->item.pos = start; 351 shaperItem->item.length = count; 352 shaperItem->item.bidiLevel = isRTL; 353 354 shaperItem->item.script = isRTL ? HB_Script_Arabic : HB_Script_Common; 355 356 shaperItem->string = chars; 357 shaperItem->stringLength = contextCount; 358 359 fontData->typeFace = paint->getTypeface(); 360 fontData->textSize = paint->getTextSize(); 361 fontData->textSkewX = paint->getTextSkewX(); 362 fontData->textScaleX = paint->getTextScaleX(); 363 fontData->flags = paint->getFlags(); 364 fontData->hinting = paint->getHinting(); 365 366 shaperItem->font->userData = fontData; 367} 368 369void TextLayoutCacheValue::shapeWithHarfbuzz(HB_ShaperItem* shaperItem, HB_FontRec* font, 370 FontData* fontData, SkPaint* paint, const UChar* chars, size_t start, size_t count, 371 size_t contextCount, bool isRTL) { 372 // Setup Harfbuzz Shaper 373 setupShaperItem(shaperItem, font, fontData, paint, chars, start, count, 374 contextCount, isRTL); 375 376 // Shape 377 resetGlyphArrays(shaperItem); 378 while (!HB_ShapeItem(shaperItem)) { 379 // We overflowed our arrays. Resize and retry. 380 // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size. 381 deleteGlyphArrays(shaperItem); 382 createGlyphArrays(shaperItem, shaperItem->num_glyphs << 1); 383 resetGlyphArrays(shaperItem); 384 } 385} 386 387void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, 388 size_t contextCount, int dirFlags, 389 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 390 Vector<jchar>* const outGlyphs, Vector<unsigned short>* const outLogClusters) { 391 392 UBiDiLevel bidiReq = 0; 393 bool forceLTR = false; 394 bool forceRTL = false; 395 396 switch (dirFlags) { 397 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level 398 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level 399 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break; 400 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break; 401 case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR 402 case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL 403 } 404 405 if (forceLTR || forceRTL) { 406#if DEBUG_GLYPHS 407 LOGD("computeValuesWithHarfbuzz -- forcing run with LTR=%d RTL=%d", 408 forceLTR, forceRTL); 409#endif 410 computeRunValuesWithHarfbuzz(paint, chars, 0, contextCount, contextCount, forceRTL, 411 outAdvances, outTotalAdvance, outGlyphs, outLogClusters); 412 } else { 413 UBiDi* bidi = ubidi_open(); 414 if (bidi) { 415 UErrorCode status = U_ZERO_ERROR; 416#if DEBUG_GLYPHS 417 LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq); 418#endif 419 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); 420 if (U_SUCCESS(status)) { 421 int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl 422 size_t rc = ubidi_countRuns(bidi, &status); 423#if DEBUG_GLYPHS 424 LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d", dirFlags, rc, paraDir); 425#endif 426 if (rc == 1 || !U_SUCCESS(status)) { 427 bool isRTL = (paraDir == 1); 428#if DEBUG_GLYPHS 429 LOGD("computeValuesWithHarfbuzz -- processing SINGLE run " 430 "-- run-start=%d run-len=%d isRTL=%d", 0, contextCount, isRTL); 431#endif 432 computeRunValuesWithHarfbuzz(paint, chars, 0, contextCount, contextCount, 433 isRTL, outAdvances, outTotalAdvance, outGlyphs, outLogClusters); 434 } else { 435 for (size_t i = 0; i < rc; ++i) { 436 int32_t startRun; 437 int32_t lengthRun; 438 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); 439 440 bool isRTL = (runDir == UBIDI_RTL); 441 jfloat runTotalAdvance = 0; 442#if DEBUG_GLYPHS 443 LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d isRTL=%d", 444 startRun, lengthRun, isRTL); 445#endif 446 computeRunValuesWithHarfbuzz(paint, chars, startRun, 447 lengthRun, contextCount, isRTL, 448 outAdvances, &runTotalAdvance, 449 outGlyphs, outLogClusters); 450 451 *outTotalAdvance += runTotalAdvance; 452 } 453 } 454 } 455 ubidi_close(bidi); 456 } else { 457 // Cannot run BiDi, just consider one Run 458 bool isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 459#if DEBUG_GLYPHS 460 LOGD("computeValuesWithHarfbuzz -- cannot run BiDi, considering a SINGLE Run " 461 "-- run-start=%d run-len=%d isRTL=%d", 0, contextCount, isRTL); 462#endif 463 computeRunValuesWithHarfbuzz(paint, chars, 0, contextCount, contextCount, isRTL, 464 outAdvances, outTotalAdvance, outGlyphs, outLogClusters); 465 } 466 } 467#if DEBUG_GLYPHS 468 LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", outGlyphs->size()); 469#endif 470} 471 472static void logGlyphs(HB_ShaperItem shaperItem) { 473 LOGD("Got glyphs - count=%d", shaperItem.num_glyphs); 474 for (size_t i = 0; i < shaperItem.num_glyphs; i++) { 475 LOGD(" glyph[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i], 476 HBFixedToFloat(shaperItem.offsets[i].x), 477 HBFixedToFloat(shaperItem.offsets[i].y)); 478 } 479} 480 481void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, 482 size_t start, size_t count, size_t contextCount, bool isRTL, 483 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 484 Vector<jchar>* const outGlyphs, Vector<unsigned short>* const outLogClusters) { 485 486 HB_ShaperItem shaperItem; 487 HB_FontRec font; 488 FontData fontData; 489 490 shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, chars, start, count, 491 contextCount, isRTL); 492 493#if DEBUG_GLYPHS 494 LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs, 495 shaperItem.kerning_applied); 496 LOGD(" -- string= '%s'", String8(chars + start, count).string()); 497 LOGD(" -- isDevKernText=%d", paint->isDevKernText()); 498 499 logGlyphs(shaperItem); 500#endif 501 502 if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) { 503#if DEBUG_GLYPHS 504 LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0"); 505#endif 506 outAdvances->insertAt(0, outAdvances->size(), count); 507 *outTotalAdvance = 0; 508 509 // Cleaning 510 deleteGlyphArrays(&shaperItem); 511 HB_FreeFace(shaperItem.face); 512 return; 513 } 514 515 // Get Advances and their total 516 jfloat currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]); 517 jfloat totalAdvance = currentAdvance; 518 outAdvances->add(currentAdvance); 519 for (size_t i = 1; i < count; i++) { 520 size_t clusterPrevious = shaperItem.log_clusters[i - 1]; 521 size_t cluster = shaperItem.log_clusters[i]; 522 if (cluster == clusterPrevious) { 523 outAdvances->add(0); 524 } else { 525 currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]); 526 totalAdvance += currentAdvance; 527 outAdvances->add(currentAdvance); 528 } 529 } 530 *outTotalAdvance = totalAdvance; 531 532#if DEBUG_ADVANCES 533 for (size_t i = 0; i < count; i++) { 534 LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i, 535 (*outAdvances)[i], shaperItem.log_clusters[i], totalAdvance); 536 } 537#endif 538 539 // Get Glyphs and reverse them in place if RTL 540 if (outGlyphs) { 541 size_t countGlyphs = shaperItem.num_glyphs; 542 for (size_t i = 0; i < countGlyphs; i++) { 543 jchar glyph = (jchar) shaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i]; 544#if DEBUG_GLYPHS 545 LOGD("HARFBUZZ -- glyph[%d]=%d", i, glyph); 546#endif 547 outGlyphs->add(glyph); 548 } 549 } 550 551 // Get LogClusters 552 if (outLogClusters) { 553 size_t countLogClusters = outLogClusters->size(); 554 size_t countGlyphs = shaperItem.num_glyphs; 555 for (size_t i = 0; i < countGlyphs; i++) { 556 // As there may be successive runs, we need to shift the log clusters 557 unsigned short logCluster = shaperItem.log_clusters[i] + countLogClusters; 558#if DEBUG_GLYPHS 559 LOGD("HARFBUZZ -- logCluster[%d] relative=%d - absolute=%d", i, shaperItem.log_clusters[i], logCluster); 560#endif 561 outLogClusters->add(logCluster); 562 } 563 } 564 565 // Cleaning 566 deleteGlyphArrays(&shaperItem); 567 HB_FreeFace(shaperItem.face); 568} 569 570void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem* shaperItem) { 571 delete[] shaperItem->glyphs; 572 delete[] shaperItem->attributes; 573 delete[] shaperItem->advances; 574 delete[] shaperItem->offsets; 575} 576 577void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem* shaperItem, int size) { 578 shaperItem->glyphs = new HB_Glyph[size]; 579 shaperItem->attributes = new HB_GlyphAttributes[size]; 580 shaperItem->advances = new HB_Fixed[size]; 581 shaperItem->offsets = new HB_FixedPoint[size]; 582 shaperItem->num_glyphs = size; 583} 584 585void TextLayoutCacheValue::resetGlyphArrays(HB_ShaperItem* shaperItem) { 586 int size = shaperItem->num_glyphs; 587 // All the types here don't have pointers. It is safe to reset to 588 // zero unless Harfbuzz breaks the compatibility in the future. 589 memset(shaperItem->glyphs, 0, size * sizeof(shaperItem->glyphs[0])); 590 memset(shaperItem->attributes, 0, size * sizeof(shaperItem->attributes[0])); 591 memset(shaperItem->advances, 0, size * sizeof(shaperItem->advances[0])); 592 memset(shaperItem->offsets, 0, size * sizeof(shaperItem->offsets[0])); 593} 594 595void TextLayoutCacheValue::getAdvances(size_t start, size_t count, jfloat* outAdvances) const { 596 memcpy(outAdvances, mAdvances.array() + start, count * sizeof(jfloat)); 597#if DEBUG_ADVANCES 598 LOGD("getAdvances - start=%d count=%d", start, count); 599 for (size_t i = 0; i < count; i++) { 600 LOGD(" adv[%d] = %f", i, outAdvances[i]); 601 } 602#endif 603} 604 605jfloat TextLayoutCacheValue::getTotalAdvance(size_t start, size_t count) const { 606 jfloat outTotalAdvance = 0; 607 for (size_t i = start; i < start + count; i++) { 608 outTotalAdvance += mAdvances[i]; 609 } 610#if DEBUG_ADVANCES 611 LOGD("getTotalAdvance - start=%d count=%d - total=%f", start, count, outTotalAdvance); 612#endif 613 return outTotalAdvance; 614} 615 616void TextLayoutCacheValue::getGlyphsIndexAndCount(size_t start, size_t count, size_t* outStartIndex, 617 size_t* outGlyphsCount) const { 618 *outStartIndex = 0; 619 if (count == 0) { 620 *outGlyphsCount = 0; 621 return; 622 } 623 size_t endIndex = 0; 624 for(size_t i = 0; i < mGlyphs.size(); i++) { 625 if (mLogClusters[i] <= start) { 626 *outStartIndex = i; 627 endIndex = i; 628 continue; 629 } 630 if (mLogClusters[i] <= start + count) { 631 endIndex = i; 632 } 633 } 634 *outGlyphsCount = endIndex - *outStartIndex + 1; 635#if DEBUG_GLYPHS 636 LOGD("getGlyphsIndexes - start=%d count=%d - startIndex=%d count=%d", start, count, 637 *outStartIndex, *outGlyphsCount); 638 for(size_t i = 0; i < mGlyphs.size(); i++) { 639 LOGD("getGlyphs - all - glyph[%d] = %d", i, mGlyphs[i]); 640 } 641 for(size_t i = 0; i < mGlyphs.size(); i++) { 642 LOGD("getGlyphs - all - logcl[%d] = %d", i, mLogClusters[i]); 643 } 644#endif 645} 646 647const jchar* TextLayoutCacheValue::getGlyphs(size_t startIndex, size_t count) { 648 const jchar* glyphs = mGlyphs.array() + startIndex; 649#if DEBUG_GLYPHS 650 LOGD("getGlyphs - with startIndex = %d count = %d", startIndex, count); 651 for (size_t i = 0; i < count; i++) { 652 LOGD("getGlyphs - result - glyph[%d] = %d", i, glyphs[i]); 653 } 654#endif 655 return glyphs; 656} 657 658} // namespace android 659