TextLayoutCache.h revision aabe537f1ed3b64f755af9fc62022d6074eec169
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 * RunAdvanceDescription is the Cache entry 141 */ 142class RunAdvanceDescription { 143public: 144 RunAdvanceDescription() { 145 advances = NULL; 146 totalAdvance = 0; 147 } 148 149 ~RunAdvanceDescription() { 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(RunAdvanceDescription) + 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#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16) 262 263 static int adjust(int prev, int next) { 264 int delta = next - prev; 265 if (delta >= 32) { 266 return -1; 267 } 268 else if (delta < -32) { 269 return +1; 270 } 271 else { 272 return 0; 273 } 274 } 275 276 static void computeAdvancesWithHarfbuzz(SkPaint* paint, const UChar* chars, size_t start, 277 size_t count, size_t contextCount, int dirFlags, 278 jfloat* outAdvances, jfloat* outTotalAdvance) { 279 280 bool isRTL = dirFlags & 0x1; 281 282 HB_ShaperItem shaperItem; 283 HB_FontRec font; 284 FontData fontData; 285 shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, chars, start, count, 286 contextCount, dirFlags); 287 288#if DEBUG_ADVANCES 289 LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs, shaperItem.kerning_applied); 290 LOGD(" -- string= '%s'", String8(chars, contextCount).string()); 291 LOGD(" -- isDevKernText=%d", paint->isDevKernText()); 292#endif 293 294 jfloat totalAdvance = 0; 295 296 for (size_t i = 0; i < count; i++) { 297 totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[i]); 298 299#if DEBUG_ADVANCES 300 LOGD("hb-adv = %d - rebased = %f - total = %f", shaperItem.advances[i], outAdvances[i], 301 totalAdvance); 302#endif 303 } 304 305 deleteGlyphArrays(&shaperItem); 306 HB_FreeFace(shaperItem.face); 307 308 *outTotalAdvance = totalAdvance; 309 } 310 311 static void computeAdvancesWithICU(SkPaint* paint, const UChar* chars, size_t start, 312 size_t count, size_t contextCount, int dirFlags, 313 jfloat* outAdvances, jfloat* outTotalAdvance) { 314 315 SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount); 316 jchar* buffer = tempBuffer.get(); 317 318 SkScalar* scalarArray = (SkScalar*)outAdvances; 319 320 // this is where we'd call harfbuzz 321 // for now we just use ushape.c 322 size_t widths; 323 const jchar* text; 324 if (dirFlags & 0x1) { // rtl, call arabic shaping in case 325 UErrorCode status = U_ZERO_ERROR; 326 // Use fixed length since we need to keep start and count valid 327 u_shapeArabic(chars, contextCount, buffer, contextCount, 328 U_SHAPE_LENGTH_FIXED_SPACES_NEAR | 329 U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE | 330 U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status); 331 // we shouldn't fail unless there's an out of memory condition, 332 // in which case we're hosed anyway 333 for (int i = start, e = i + count; i < e; ++i) { 334 if (buffer[i] == UNICODE_NOT_A_CHAR) { 335 buffer[i] = UNICODE_ZWSP; // zero-width-space for skia 336 } 337 } 338 text = buffer + start; 339 widths = paint->getTextWidths(text, count << 1, scalarArray); 340 } else { 341 text = chars + start; 342 widths = paint->getTextWidths(text, count << 1, scalarArray); 343 } 344 345 jfloat totalAdvance = 0; 346 if (widths < count) { 347#if DEBUG_ADVANCES 348 LOGD("ICU -- count=%d", widths); 349#endif 350 // Skia operates on code points, not code units, so surrogate pairs return only 351 // one value. Expand the result so we have one value per UTF-16 code unit. 352 353 // Note, skia's getTextWidth gets confused if it encounters a surrogate pair, 354 // leaving the remaining widths zero. Not nice. 355 for (size_t i = 0, p = 0; i < widths; ++i) { 356 totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]); 357 if (p < count && 358 text[p] >= UNICODE_FIRST_LOW_SURROGATE && 359 text[p] < UNICODE_FIRST_PRIVATE_USE && 360 text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE && 361 text[p-1] < UNICODE_FIRST_LOW_SURROGATE) { 362 outAdvances[p++] = 0; 363 } 364#if DEBUG_ADVANCES 365 LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); 366#endif 367 } 368 } else { 369#if DEBUG_ADVANCES 370 LOGD("ICU -- count=%d", count); 371#endif 372 for (size_t i = 0; i < count; i++) { 373 totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]); 374#if DEBUG_ADVANCES 375 LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance); 376#endif 377 } 378 } 379 *outTotalAdvance = totalAdvance; 380 } 381 382private: 383 jfloat* advances; 384 jfloat totalAdvance; 385 size_t count; 386 387 uint32_t elapsedTime; 388 389 static void deleteGlyphArrays(HB_ShaperItem* shaperItem) { 390 delete[] shaperItem->glyphs; 391 delete[] shaperItem->attributes; 392 delete[] shaperItem->advances; 393 delete[] shaperItem->offsets; 394 } 395 396 static void createGlyphArrays(HB_ShaperItem* shaperItem, int size) { 397 shaperItem->glyphs = new HB_Glyph[size]; 398 shaperItem->attributes = new HB_GlyphAttributes[size]; 399 shaperItem->advances = new HB_Fixed[size]; 400 shaperItem->offsets = new HB_FixedPoint[size]; 401 shaperItem->num_glyphs = size; 402 } 403 404 static void resetGlyphArrays(HB_ShaperItem* shaperItem) { 405 int size = shaperItem->num_glyphs; 406 // All the types here don't have pointers. It is safe to reset to 407 // zero unless Harfbuzz breaks the compatibility in the future. 408 memset(shaperItem->glyphs, 0, size * sizeof(shaperItem->glyphs[0])); 409 memset(shaperItem->attributes, 0, size * sizeof(shaperItem->attributes[0])); 410 memset(shaperItem->advances, 0, size * sizeof(shaperItem->advances[0])); 411 memset(shaperItem->offsets, 0, size * sizeof(shaperItem->offsets[0])); 412 } 413 414}; // RunAdvanceDescription 415 416 417class TextLayoutCache: public OnEntryRemoved<TextLayoutCacheKey, RunAdvanceDescription*> 418{ 419public: 420 TextLayoutCache(); 421 TextLayoutCache(uint32_t maxByteSize); 422 423 virtual ~TextLayoutCache(); 424 425 bool isInitialized() { 426 return mInitialized; 427 } 428 429 /** 430 * Used as a callback when an entry is removed from the cache. 431 * Do not invoke directly. 432 */ 433 void operator()(TextLayoutCacheKey& text, RunAdvanceDescription*& desc); 434 435 /** 436 * Get cache entries 437 */ 438 void getRunAdvances(SkPaint* paint, const jchar* text, 439 jint start, jint count, jint contextCount, jint dirFlags, 440 jfloat* outAdvances, jfloat* outTotalAdvance); 441 442 /** 443 * Clear the cache 444 */ 445 void clear(); 446 447 /** 448 * Sets the maximum size of the cache in bytes. 449 */ 450 void setMaxSize(uint32_t maxSize); 451 452 /** 453 * Returns the maximum size of the cache in bytes. 454 */ 455 uint32_t getMaxSize(); 456 457 /** 458 * Returns the current size of the cache in bytes. 459 */ 460 uint32_t getSize(); 461 462private: 463 Mutex mLock; 464 bool mInitialized; 465 466 GenerationCache<TextLayoutCacheKey, RunAdvanceDescription*> mCache; 467 468 uint32_t mSize; 469 uint32_t mMaxSize; 470 471 uint32_t mCacheHitCount; 472 uint64_t mNanosecondsSaved; 473 474 uint64_t mCacheStartTime; 475 476 RtlDebugLevel mDebugLevel; 477 bool mDebugEnabled; 478 479 /* 480 * Class initialization 481 */ 482 void init(); 483 484 /** 485 * Remove oldest entries until we are having enough space 486 */ 487 void removeOldests(); 488 489 /** 490 * Dump Cache statistics 491 */ 492 void dumpCacheStats(); 493}; // TextLayoutCache 494 495} // namespace android 496#endif /* ANDROID_TEXT_LAYOUT_CACHE_H */ 497 498