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