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 start, jint count, jint contextCount, 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, start, count, contextCount, 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, start, count, contextCount, 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 start=%d count=%d contextCount=%d, " 170 "entry size %d bytes, remaining space %d bytes" 171 " - Compute time in nanos: %lld - Text='%s'", 172 start, count, contextCount, size, mMaxSize - mSize, endTime, 173 String8(text, count).string()); 174 } 175 } 176 } else { 177 // This is a cache hit, just log timestamp and user infos 178 if (mDebugEnabled) { 179 nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; 180 mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet); 181 ++mCacheHitCount; 182 183 if (value->getElapsedTime() > 0) { 184 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet) 185 / ((float)value->getElapsedTime())); 186 LOGD("CACHE HIT #%d with start=%d count=%d contextCount=%d" 187 "- Compute time in nanos: %d - " 188 "Cache get time in nanos: %lld - Gain in percent: %2.2f - Text='%s' ", 189 mCacheHitCount, start, count, contextCount, 190 value->getElapsedTime(), elapsedTimeThruCacheGet, deltaPercent, 191 String8(text, count).string()); 192 } 193 if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) { 194 dumpCacheStats(); 195 } 196 } 197 } 198 return value; 199} 200 201void TextLayoutCache::dumpCacheStats() { 202 float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize)); 203 float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000; 204 LOGD("------------------------------------------------"); 205 LOGD("Cache stats"); 206 LOGD("------------------------------------------------"); 207 LOGD("pid : %d", getpid()); 208 LOGD("running : %.0f seconds", timeRunningInSec); 209 LOGD("entries : %d", mCache.size()); 210 LOGD("size : %d bytes", mMaxSize); 211 LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent); 212 LOGD("hits : %d", mCacheHitCount); 213 LOGD("saved : %lld milliseconds", mNanosecondsSaved / 1000000); 214 LOGD("------------------------------------------------"); 215} 216 217/** 218 * TextLayoutCacheKey 219 */ 220TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0), 221 dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0), 222 hinting(SkPaint::kNo_Hinting) { 223} 224 225TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text, 226 size_t start, size_t count, size_t contextCount, int dirFlags) : 227 text(text), start(start), count(count), contextCount(contextCount), 228 dirFlags(dirFlags) { 229 typeface = paint->getTypeface(); 230 textSize = paint->getTextSize(); 231 textSkewX = paint->getTextSkewX(); 232 textScaleX = paint->getTextScaleX(); 233 flags = paint->getFlags(); 234 hinting = paint->getHinting(); 235} 236 237TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) : 238 text(NULL), 239 textCopy(other.textCopy), 240 start(other.start), 241 count(other.count), 242 contextCount(other.contextCount), 243 dirFlags(other.dirFlags), 244 typeface(other.typeface), 245 textSize(other.textSize), 246 textSkewX(other.textSkewX), 247 textScaleX(other.textScaleX), 248 flags(other.flags), 249 hinting(other.hinting) { 250 if (other.text) { 251 textCopy.setTo(other.text, other.contextCount); 252 } 253} 254 255int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) { 256 int deltaInt = lhs.start - rhs.start; 257 if (deltaInt != 0) return (deltaInt); 258 259 deltaInt = lhs.count - rhs.count; 260 if (deltaInt != 0) return (deltaInt); 261 262 deltaInt = lhs.contextCount - rhs.contextCount; 263 if (deltaInt != 0) return (deltaInt); 264 265 if (lhs.typeface < rhs.typeface) return -1; 266 if (lhs.typeface > rhs.typeface) return +1; 267 268 if (lhs.textSize < rhs.textSize) return -1; 269 if (lhs.textSize > rhs.textSize) return +1; 270 271 if (lhs.textSkewX < rhs.textSkewX) return -1; 272 if (lhs.textSkewX > rhs.textSkewX) return +1; 273 274 if (lhs.textScaleX < rhs.textScaleX) return -1; 275 if (lhs.textScaleX > rhs.textScaleX) return +1; 276 277 deltaInt = lhs.flags - rhs.flags; 278 if (deltaInt != 0) return (deltaInt); 279 280 deltaInt = lhs.hinting - rhs.hinting; 281 if (deltaInt != 0) return (deltaInt); 282 283 deltaInt = lhs.dirFlags - rhs.dirFlags; 284 if (deltaInt) return (deltaInt); 285 286 return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar)); 287} 288 289void TextLayoutCacheKey::internalTextCopy() { 290 textCopy.setTo(text, contextCount); 291 text = NULL; 292} 293 294size_t TextLayoutCacheKey::getSize() { 295 return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount; 296} 297 298/** 299 * TextLayoutCacheValue 300 */ 301TextLayoutCacheValue::TextLayoutCacheValue() : 302 mTotalAdvance(0), mElapsedTime(0) { 303} 304 305void TextLayoutCacheValue::setElapsedTime(uint32_t time) { 306 mElapsedTime = time; 307} 308 309uint32_t TextLayoutCacheValue::getElapsedTime() { 310 return mElapsedTime; 311} 312 313void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars, 314 size_t start, size_t count, size_t contextCount, int dirFlags) { 315 // Give a hint for advances, glyphs and log clusters vectors size 316 mAdvances.setCapacity(contextCount); 317 mGlyphs.setCapacity(contextCount); 318 319 computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags, 320 &mAdvances, &mTotalAdvance, &mGlyphs); 321#if DEBUG_ADVANCES 322 LOGD("Advances - start=%d, count=%d, countextCount=%d, totalAdvance=%f", start, count, 323 contextCount, mTotalAdvance); 324#endif 325} 326 327size_t TextLayoutCacheValue::getSize() { 328 return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvances.capacity() + 329 sizeof(jchar) * mGlyphs.capacity(); 330} 331 332void TextLayoutCacheValue::initShaperItem(HB_ShaperItem& shaperItem, HB_FontRec* font, 333 FontData* fontData, SkPaint* paint, const UChar* chars, size_t contextCount) { 334 // Zero the Shaper struct 335 memset(&shaperItem, 0, sizeof(shaperItem)); 336 337 font->klass = &harfbuzzSkiaClass; 338 font->userData = 0; 339 340 // The values which harfbuzzSkiaClass returns are already scaled to 341 // pixel units, so we just set all these to one to disable further 342 // scaling. 343 font->x_ppem = 1; 344 font->y_ppem = 1; 345 font->x_scale = 1; 346 font->y_scale = 1; 347 348 // Reset kerning 349 shaperItem.kerning_applied = false; 350 351 // Define font data 352 fontData->typeFace = paint->getTypeface(); 353 fontData->textSize = paint->getTextSize(); 354 fontData->textSkewX = paint->getTextSkewX(); 355 fontData->textScaleX = paint->getTextScaleX(); 356 fontData->flags = paint->getFlags(); 357 fontData->hinting = paint->getHinting(); 358 359 shaperItem.font = font; 360 shaperItem.font->userData = fontData; 361 362 shaperItem.face = HB_NewFace(NULL, harfbuzzSkiaGetTable); 363 364 // We cannot know, ahead of time, how many glyphs a given script run 365 // will produce. We take a guess that script runs will not produce more 366 // than twice as many glyphs as there are code points plus a bit of 367 // padding and fallback if we find that we are wrong. 368 createGlyphArrays(shaperItem, (contextCount + 2) * 2); 369 370 // Set the string properties 371 shaperItem.string = chars; 372 shaperItem.stringLength = contextCount; 373} 374 375void TextLayoutCacheValue::freeShaperItem(HB_ShaperItem& shaperItem) { 376 deleteGlyphArrays(shaperItem); 377 HB_FreeFace(shaperItem.face); 378} 379 380void TextLayoutCacheValue::shapeRun(HB_ShaperItem& shaperItem, size_t start, size_t count, 381 bool isRTL) { 382 // Update Harfbuzz Shaper 383 shaperItem.item.pos = start; 384 shaperItem.item.length = count; 385 shaperItem.item.bidiLevel = isRTL; 386 387 shaperItem.item.script = isRTL ? HB_Script_Arabic : HB_Script_Common; 388 389 // Shape 390 assert(shaperItem.item.length > 0); // Harfbuzz will overwrite other memory if length is 0. 391 while (!HB_ShapeItem(&shaperItem)) { 392 // We overflowed our arrays. Resize and retry. 393 // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size. 394 deleteGlyphArrays(shaperItem); 395 createGlyphArrays(shaperItem, shaperItem.num_glyphs << 1); 396 } 397} 398 399void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars, 400 size_t start, size_t count, size_t contextCount, int dirFlags, 401 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 402 Vector<jchar>* const outGlyphs) { 403 if (!count) { 404 *outTotalAdvance = 0; 405 return; 406 } 407 408 UBiDiLevel bidiReq = 0; 409 bool forceLTR = false; 410 bool forceRTL = false; 411 412 switch (dirFlags) { 413 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level 414 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level 415 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break; 416 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break; 417 case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR 418 case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL 419 } 420 421 HB_ShaperItem shaperItem; 422 HB_FontRec font; 423 FontData fontData; 424 425 // Initialize Harfbuzz Shaper 426 initShaperItem(shaperItem, &font, &fontData, paint, chars, contextCount); 427 428 bool useSingleRun = false; 429 bool isRTL = forceRTL; 430 if (forceLTR || forceRTL) { 431 useSingleRun = true; 432 } else { 433 UBiDi* bidi = ubidi_open(); 434 if (bidi) { 435 UErrorCode status = U_ZERO_ERROR; 436#if DEBUG_GLYPHS 437 LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq); 438#endif 439 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status); 440 if (U_SUCCESS(status)) { 441 int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl 442 ssize_t rc = ubidi_countRuns(bidi, &status); 443#if DEBUG_GLYPHS 444 LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d", 445 dirFlags, rc, paraDir); 446#endif 447 if (U_SUCCESS(status) && rc == 1) { 448 // Normal case: one run, status is ok 449 isRTL = (paraDir == 1); 450 useSingleRun = true; 451 } else if (!U_SUCCESS(status) || rc < 1) { 452 LOGW("computeValuesWithHarfbuzz -- need to force to single run"); 453 isRTL = (paraDir == 1); 454 useSingleRun = true; 455 } else { 456 int32_t end = start + count; 457 for (size_t i = 0; i < size_t(rc); ++i) { 458 int32_t startRun = -1; 459 int32_t lengthRun = -1; 460 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun); 461 462 if (startRun == -1 || lengthRun == -1) { 463 // Something went wrong when getting the visual run, need to clear 464 // already computed data before doing a single run pass 465 LOGW("computeValuesWithHarfbuzz -- visual run is not valid"); 466 outGlyphs->clear(); 467 outAdvances->clear(); 468 *outTotalAdvance = 0; 469 isRTL = (paraDir == 1); 470 useSingleRun = true; 471 break; 472 } 473 474 if (startRun >= end) { 475 continue; 476 } 477 int32_t endRun = startRun + lengthRun; 478 if (endRun <= int32_t(start)) { 479 continue; 480 } 481 if (startRun < int32_t(start)) { 482 startRun = int32_t(start); 483 } 484 if (endRun > end) { 485 endRun = end; 486 } 487 488 lengthRun = endRun - startRun; 489 isRTL = (runDir == UBIDI_RTL); 490 jfloat runTotalAdvance = 0; 491#if DEBUG_GLYPHS 492 LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d isRTL=%d", 493 startRun, lengthRun, isRTL); 494#endif 495 computeRunValuesWithHarfbuzz(shaperItem, paint, 496 startRun, lengthRun, isRTL, 497 outAdvances, &runTotalAdvance, outGlyphs); 498 499 *outTotalAdvance += runTotalAdvance; 500 } 501 } 502 } else { 503 LOGW("computeValuesWithHarfbuzz -- cannot set Para"); 504 useSingleRun = true; 505 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 506 } 507 ubidi_close(bidi); 508 } else { 509 LOGW("computeValuesWithHarfbuzz -- cannot ubidi_open()"); 510 useSingleRun = true; 511 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL); 512 } 513 } 514 515 // Default single run case 516 if (useSingleRun){ 517#if DEBUG_GLYPHS 518 LOGD("computeValuesWithHarfbuzz -- Using a SINGLE Run " 519 "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL); 520#endif 521 computeRunValuesWithHarfbuzz(shaperItem, paint, 522 start, count, isRTL, 523 outAdvances, outTotalAdvance, outGlyphs); 524 } 525 526 // Cleaning 527 freeShaperItem(shaperItem); 528 529#if DEBUG_GLYPHS 530 LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", outGlyphs->size()); 531#endif 532} 533 534static void logGlyphs(HB_ShaperItem shaperItem) { 535 LOGD("Got glyphs - count=%d", shaperItem.num_glyphs); 536 for (size_t i = 0; i < shaperItem.num_glyphs; i++) { 537 LOGD(" glyph[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i], 538 HBFixedToFloat(shaperItem.offsets[i].x), 539 HBFixedToFloat(shaperItem.offsets[i].y)); 540 } 541} 542 543void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(HB_ShaperItem& shaperItem, SkPaint* paint, 544 size_t start, size_t count, bool isRTL, 545 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance, 546 Vector<jchar>* const outGlyphs) { 547 if (!count) { 548 *outTotalAdvance = 0; 549 return; 550 } 551 552 shapeRun(shaperItem, start, count, isRTL); 553 554#if DEBUG_GLYPHS 555 LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs, 556 shaperItem.kerning_applied); 557 LOGD(" -- string= '%s'", String8(shaperItem.string + start, count).string()); 558 LOGD(" -- isDevKernText=%d", paint->isDevKernText()); 559 560 logGlyphs(shaperItem); 561#endif 562 563 if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) { 564#if DEBUG_GLYPHS 565 LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0"); 566#endif 567 outAdvances->insertAt(0, outAdvances->size(), count); 568 *outTotalAdvance = 0; 569 return; 570 } 571 572 // Get Advances and their total 573 jfloat currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]); 574 jfloat totalAdvance = currentAdvance; 575 outAdvances->add(currentAdvance); 576 for (size_t i = 1; i < count; i++) { 577 size_t clusterPrevious = shaperItem.log_clusters[i - 1]; 578 size_t cluster = shaperItem.log_clusters[i]; 579 if (cluster == clusterPrevious) { 580 outAdvances->add(0); 581 } else { 582 currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]); 583 totalAdvance += currentAdvance; 584 outAdvances->add(currentAdvance); 585 } 586 } 587 *outTotalAdvance = totalAdvance; 588 589#if DEBUG_ADVANCES 590 for (size_t i = 0; i < count; i++) { 591 LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i, 592 (*outAdvances)[i], shaperItem.log_clusters[i], totalAdvance); 593 } 594#endif 595 596 // Get Glyphs and reverse them in place if RTL 597 if (outGlyphs) { 598 size_t countGlyphs = shaperItem.num_glyphs; 599 for (size_t i = 0; i < countGlyphs; i++) { 600 jchar glyph = (jchar) shaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i]; 601#if DEBUG_GLYPHS 602 LOGD("HARFBUZZ -- glyph[%d]=%d", i, glyph); 603#endif 604 outGlyphs->add(glyph); 605 } 606 } 607} 608 609void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem& shaperItem) { 610 delete[] shaperItem.glyphs; 611 delete[] shaperItem.attributes; 612 delete[] shaperItem.advances; 613 delete[] shaperItem.offsets; 614 delete[] shaperItem.log_clusters; 615} 616 617void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem& shaperItem, int size) { 618 shaperItem.num_glyphs = size; 619 620 // These arrays are all indexed by glyph 621 shaperItem.glyphs = new HB_Glyph[size]; 622 shaperItem.attributes = new HB_GlyphAttributes[size]; 623 shaperItem.advances = new HB_Fixed[size]; 624 shaperItem.offsets = new HB_FixedPoint[size]; 625 626 // Although the log_clusters array is indexed by character, Harfbuzz expects that 627 // it is big enough to hold one element per glyph. So we allocate log_clusters along 628 // with the other glyph arrays above. 629 shaperItem.log_clusters = new unsigned short[size]; 630} 631 632} // namespace android 633