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