TextLayoutCache.h revision 1de9e7a9dffb4391a446000f748e4c017d948f6b
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#ifndef ANDROID_TEXT_LAYOUT_CACHE_H 18#define ANDROID_TEXT_LAYOUT_CACHE_H 19 20#include "RtlProperties.h" 21 22#include <stddef.h> 23#include <utils/threads.h> 24#include <utils/String16.h> 25#include <utils/GenerationCache.h> 26#include <utils/Compare.h> 27 28#include <SkPaint.h> 29#include <SkTemplates.h> 30#include <SkUtils.h> 31#include <SkScalerContext.h> 32#include <SkAutoKern.h> 33 34#include <unicode/ubidi.h> 35#include <unicode/ushape.h> 36#include "HarfbuzzSkia.h" 37#include "harfbuzz-shaper.h" 38 39#include <android_runtime/AndroidRuntime.h> 40 41#define UNICODE_NOT_A_CHAR 0xffff 42#define UNICODE_ZWSP 0x200b 43#define UNICODE_FIRST_LOW_SURROGATE 0xdc00 44#define UNICODE_FIRST_HIGH_SURROGATE 0xd800 45#define UNICODE_FIRST_PRIVATE_USE 0xe000 46#define UNICODE_FIRST_RTL_CHAR 0x0590 47 48// Temporary buffer size 49#define CHAR_BUFFER_SIZE 80 50 51// Converts a number of mega-bytes into bytes 52#define MB(s) s * 1024 * 1024 53 54// Define the default cache size in Mb 55#define DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB 0.125f 56 57// Define the interval in number of cache hits between two statistics dump 58#define DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL 100 59 60namespace android { 61 62/** 63 * TextLayoutCacheKey is the Cache key 64 */ 65class TextLayoutCacheKey { 66public: 67 TextLayoutCacheKey() : text(NULL), start(0), count(0), contextCount(0), 68 dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0), 69 hinting(SkPaint::kNo_Hinting) { 70 } 71 72 TextLayoutCacheKey(const SkPaint* paint, 73 const UChar* text, size_t start, size_t count, 74 size_t contextCount, int dirFlags) : 75 text(text), start(start), count(count), contextCount(contextCount), 76 dirFlags(dirFlags) { 77 typeface = paint->getTypeface(); 78 textSize = paint->getTextSize(); 79 textSkewX = paint->getTextSkewX(); 80 textScaleX = paint->getTextScaleX(); 81 flags = paint->getFlags(); 82 hinting = paint->getHinting(); 83 } 84 85 bool operator<(const TextLayoutCacheKey& rhs) const { 86 LTE_INT(count) { 87 LTE_INT(contextCount) { 88 LTE_INT(start) { 89 LTE_INT(typeface) { 90 LTE_FLOAT(textSize) { 91 LTE_FLOAT(textSkewX) { 92 LTE_FLOAT(textScaleX) { 93 LTE_INT(flags) { 94 LTE_INT(hinting) { 95 LTE_INT(dirFlags) { 96 return strncmp16(text, rhs.text, contextCount) < 0; 97 } 98 } 99 } 100 } 101 } 102 } 103 } 104 } 105 } 106 } 107 return false; 108 } 109 110 // We need to copy the text when we insert the key into the cache itself. 111 // We don't need to copy the text when we are only comparing keys. 112 void internalTextCopy() { 113 textCopy.setTo(text, contextCount); 114 text = textCopy.string(); 115 } 116 117 /** 118 * Get the size of the Cache key. 119 */ 120 size_t getSize() { 121 return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount; 122 } 123 124private: 125 const UChar* text; 126 String16 textCopy; 127 size_t start; 128 size_t count; 129 size_t contextCount; 130 int dirFlags; 131 SkTypeface* typeface; 132 SkScalar textSize; 133 SkScalar textSkewX; 134 SkScalar textScaleX; 135 uint32_t flags; 136 SkPaint::Hinting hinting; 137}; // TextLayoutCacheKey 138 139/* 140 * TextLayoutCacheEntry is the Cache entry 141 */ 142class TextLayoutCacheValue { 143public: 144 TextLayoutCacheValue() { 145 advances = NULL; 146 totalAdvance = 0; 147 } 148 149 ~TextLayoutCacheValue() { 150 delete[] advances; 151 } 152 153 void setElapsedTime(uint32_t time) { 154 elapsedTime = time; 155 } 156 157 uint32_t getElapsedTime() { 158 return elapsedTime; 159 } 160 161 void computeAdvances(SkPaint* paint, const UChar* chars, size_t start, size_t count, 162 size_t contextCount, int dirFlags) { 163 advances = new float[count]; 164 this->count = count; 165 166#if RTL_USE_HARFBUZZ 167 computeAdvancesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags, 168 advances, &totalAdvance); 169#else 170 computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags, 171 advances, &totalAdvance); 172#endif 173#if DEBUG_ADVANCES 174 LOGD("Advances - count=%d - countextCount=%d - totalAdvance=%f - " 175 "adv[0]=%f adv[1]=%f adv[2]=%f adv[3]=%f", count, contextCount, totalAdvance, 176 advances[0], advances[1], advances[2], advances[3]); 177#endif 178 } 179 180 void copyResult(jfloat* outAdvances, jfloat* outTotalAdvance) { 181 memcpy(outAdvances, advances, count * sizeof(jfloat)); 182 *outTotalAdvance = totalAdvance; 183 } 184 185 /** 186 * Get the size of the Cache entry 187 */ 188 size_t getSize() { 189 return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * count; 190 } 191 192 static void setupShaperItem(HB_ShaperItem* shaperItem, HB_FontRec* font, FontData* fontData, 193 SkPaint* paint, const UChar* chars, size_t start, size_t count, size_t contextCount, 194 int dirFlags) { 195 bool isRTL = dirFlags & 0x1; 196 197 font->klass = &harfbuzzSkiaClass; 198 font->userData = 0; 199 // The values which harfbuzzSkiaClass returns are already scaled to 200 // pixel units, so we just set all these to one to disable further 201 // scaling. 202 font->x_ppem = 1; 203 font->y_ppem = 1; 204 font->x_scale = 1; 205 font->y_scale = 1; 206 207 memset(shaperItem, 0, sizeof(*shaperItem)); 208 shaperItem->font = font; 209 shaperItem->face = HB_NewFace(shaperItem->font, harfbuzzSkiaGetTable); 210 211 shaperItem->kerning_applied = false; 212 213 // We cannot know, ahead of time, how many glyphs a given script run 214 // will produce. We take a guess that script runs will not produce more 215 // than twice as many glyphs as there are code points plus a bit of 216 // padding and fallback if we find that we are wrong. 217 createGlyphArrays(shaperItem, (contextCount + 2) * 2); 218 219 // Free memory for clusters if needed and recreate the clusters array 220 if (shaperItem->log_clusters) { 221 delete shaperItem->log_clusters; 222 } 223 shaperItem->log_clusters = new unsigned short[contextCount]; 224 225 shaperItem->item.pos = start; 226 shaperItem->item.length = count; 227 shaperItem->item.bidiLevel = isRTL; 228 shaperItem->item.script = isRTL ? HB_Script_Arabic : HB_Script_Common; 229 230 shaperItem->string = chars; 231 shaperItem->stringLength = contextCount; 232 233 fontData->typeFace = paint->getTypeface(); 234 fontData->textSize = paint->getTextSize(); 235 fontData->textSkewX = paint->getTextSkewX(); 236 fontData->textScaleX = paint->getTextScaleX(); 237 fontData->flags = paint->getFlags(); 238 fontData->hinting = paint->getHinting(); 239 240 shaperItem->font->userData = fontData; 241 } 242 243 static void shapeWithHarfbuzz(HB_ShaperItem* shaperItem, HB_FontRec* font, FontData* fontData, 244 SkPaint* paint, const UChar* chars, size_t start, size_t count, size_t contextCount, 245 int dirFlags) { 246 // Setup Harfbuzz Shaper 247 setupShaperItem(shaperItem, font, fontData, paint, chars, start, count, 248 contextCount, dirFlags); 249 250 // Shape 251 resetGlyphArrays(shaperItem); 252 while (!HB_ShapeItem(shaperItem)) { 253 // We overflowed our arrays. Resize and retry. 254 // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size. 255 deleteGlyphArrays(shaperItem); 256 createGlyphArrays(shaperItem, shaperItem->num_glyphs << 1); 257 resetGlyphArrays(shaperItem); 258 } 259 } 260 261 static void computeAdvancesWithHarfbuzz(SkPaint* paint, const UChar* chars, size_t start, 262 size_t count, size_t contextCount, int dirFlags, 263 jfloat* outAdvances, jfloat* outTotalAdvance) { 264 265 bool isRTL = dirFlags & 0x1; 266 267 HB_ShaperItem shaperItem; 268 HB_FontRec font; 269 FontData fontData; 270 shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, chars, start, count, 271 contextCount, dirFlags); 272 273#if DEBUG_ADVANCES 274 LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs, shaperItem.kerning_applied); 275 LOGD(" -- string= '%s'", String8(chars, contextCount).string()); 276 LOGD(" -- isDevKernText=%d", paint->isDevKernText()); 277#endif 278 279 jfloat totalAdvance = 0; 280 281 for (size_t i = 0; i < count; i++) { 282 totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[i]); 283 284#if DEBUG_ADVANCES 285 LOGD("hb-adv = %d - rebased = %f - total = %f", shaperItem.advances[i], outAdvances[i], 286 totalAdvance); 287#endif 288 } 289 290 deleteGlyphArrays(&shaperItem); 291 HB_FreeFace(shaperItem.face); 292 293 *outTotalAdvance = totalAdvance; 294 } 295 296 static void computeAdvancesWithICU(SkPaint* paint, const UChar* chars, size_t start, 297 size_t count, size_t contextCount, int dirFlags, 298 jfloat* outAdvances, jfloat* outTotalAdvance) { 299 300 SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount); 301 jchar* buffer = tempBuffer.get(); 302 303 SkScalar* scalarArray = (SkScalar*)outAdvances; 304 305 // this is where we'd call harfbuzz 306 // for now we just use ushape.c 307 size_t widths; 308 const jchar* text; 309 if (dirFlags & 0x1) { // rtl, call arabic shaping in case 310 UErrorCode status = U_ZERO_ERROR; 311 // Use fixed length since we need to keep start and count valid 312 u_shapeArabic(chars, contextCount, buffer, contextCount, 313 U_SHAPE_LENGTH_FIXED_SPACES_NEAR | 314 U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE | 315 U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status); 316 // we shouldn't fail unless there's an out of memory condition, 317 // in which case we're hosed anyway 318 for (int i = start, e = i + count; i < e; ++i) { 319 if (buffer[i] == UNICODE_NOT_A_CHAR) { 320 buffer[i] = UNICODE_ZWSP; // zero-width-space for skia 321 } 322 } 323 text = buffer + start; 324 widths = paint->getTextWidths(text, count << 1, scalarArray); 325 } else { 326 text = chars + start; 327 widths = paint->getTextWidths(text, count << 1, scalarArray); 328 } 329 330 jfloat totalAdvance = 0; 331 if (widths < count) { 332#if DEBUG_ADVANCES 333 LOGD("ICU -- count=%d", widths); 334#endif 335 // Skia operates on code points, not code units, so surrogate pairs return only 336 // one value. Expand the result so we have one value per UTF-16 code unit. 337 338 // Note, skia's getTextWidth gets confused if it encounters a surrogate pair, 339 // leaving the remaining widths zero. Not nice. 340 for (size_t i = 0, p = 0; i < widths; ++i) { 341 totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]); 342 if (p < count && 343 text[p] >= UNICODE_FIRST_LOW_SURROGATE && 344 text[p] < UNICODE_FIRST_PRIVATE_USE && 345 text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE && 346 text[p-1] < UNICODE_FIRST_LOW_SURROGATE) { 347 outAdvances[p++] = 0; 348 } 349#if DEBUG_ADVANCES 350 LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); 351#endif 352 } 353 } else { 354#if DEBUG_ADVANCES 355 LOGD("ICU -- count=%d", count); 356#endif 357 for (size_t i = 0; i < count; i++) { 358 totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]); 359#if DEBUG_ADVANCES 360 LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); 361#endif 362 } 363 } 364 *outTotalAdvance = totalAdvance; 365 } 366 367private: 368 jfloat* advances; 369 jfloat totalAdvance; 370 size_t count; 371 372 uint32_t elapsedTime; 373 374 static void deleteGlyphArrays(HB_ShaperItem* shaperItem) { 375 delete[] shaperItem->glyphs; 376 delete[] shaperItem->attributes; 377 delete[] shaperItem->advances; 378 delete[] shaperItem->offsets; 379 } 380 381 static void createGlyphArrays(HB_ShaperItem* shaperItem, int size) { 382 shaperItem->glyphs = new HB_Glyph[size]; 383 shaperItem->attributes = new HB_GlyphAttributes[size]; 384 shaperItem->advances = new HB_Fixed[size]; 385 shaperItem->offsets = new HB_FixedPoint[size]; 386 shaperItem->num_glyphs = size; 387 } 388 389 static void resetGlyphArrays(HB_ShaperItem* shaperItem) { 390 int size = shaperItem->num_glyphs; 391 // All the types here don't have pointers. It is safe to reset to 392 // zero unless Harfbuzz breaks the compatibility in the future. 393 memset(shaperItem->glyphs, 0, size * sizeof(shaperItem->glyphs[0])); 394 memset(shaperItem->attributes, 0, size * sizeof(shaperItem->attributes[0])); 395 memset(shaperItem->advances, 0, size * sizeof(shaperItem->advances[0])); 396 memset(shaperItem->offsets, 0, size * sizeof(shaperItem->offsets[0])); 397 } 398 399}; // TextLayoutCacheEntry 400 401 402class TextLayoutCache: public OnEntryRemoved<TextLayoutCacheKey, TextLayoutCacheValue*> 403{ 404public: 405 TextLayoutCache(); 406 TextLayoutCache(uint32_t maxByteSize); 407 408 virtual ~TextLayoutCache(); 409 410 bool isInitialized() { 411 return mInitialized; 412 } 413 414 /** 415 * Used as a callback when an entry is removed from the cache. 416 * Do not invoke directly. 417 */ 418 void operator()(TextLayoutCacheKey& text, TextLayoutCacheValue*& desc); 419 420 /** 421 * Get cache entries 422 */ 423 void getRunAdvances(SkPaint* paint, const jchar* text, 424 jint start, jint count, jint contextCount, jint dirFlags, 425 jfloat* outAdvances, jfloat* outTotalAdvance); 426 427 /** 428 * Clear the cache 429 */ 430 void clear(); 431 432 /** 433 * Sets the maximum size of the cache in bytes. 434 */ 435 void setMaxSize(uint32_t maxSize); 436 437 /** 438 * Returns the maximum size of the cache in bytes. 439 */ 440 uint32_t getMaxSize(); 441 442 /** 443 * Returns the current size of the cache in bytes. 444 */ 445 uint32_t getSize(); 446 447private: 448 Mutex mLock; 449 bool mInitialized; 450 451 GenerationCache<TextLayoutCacheKey, TextLayoutCacheValue*> mCache; 452 453 uint32_t mSize; 454 uint32_t mMaxSize; 455 456 uint32_t mCacheHitCount; 457 uint64_t mNanosecondsSaved; 458 459 uint64_t mCacheStartTime; 460 461 RtlDebugLevel mDebugLevel; 462 bool mDebugEnabled; 463 464 /* 465 * Class initialization 466 */ 467 void init(); 468 469 /** 470 * Remove oldest entries until we are having enough space 471 */ 472 void removeOldests(); 473 474 /** 475 * Dump Cache statistics 476 */ 477 void dumpCacheStats(); 478}; // TextLayoutCache 479 480} // namespace android 481#endif /* ANDROID_TEXT_LAYOUT_CACHE_H */ 482 483