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 #include "SkFontHost.h"
22 #include "SkTypeface_android.h"
23 #include <unicode/unistr.h>
24 #include <unicode/normlzr.h>
25 #include <unicode/uchar.h>
26
27 extern "C" {
28 #include "harfbuzz-unicode.h"
29 }
30
31 namespace android {
32
33 //--------------------------------------------------------------------------------------------------
34
35 ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine);
36
37 //--------------------------------------------------------------------------------------------------
38
TextLayoutCache(TextLayoutShaper * shaper)39 TextLayoutCache::TextLayoutCache(TextLayoutShaper* shaper) :
40 mShaper(shaper),
41 mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutValue> >::kUnlimitedCapacity),
42 mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
43 mCacheHitCount(0), mNanosecondsSaved(0) {
44 init();
45 }
46
~TextLayoutCache()47 TextLayoutCache::~TextLayoutCache() {
48 mCache.clear();
49 }
50
init()51 void TextLayoutCache::init() {
52 mCache.setOnEntryRemovedListener(this);
53
54 mDebugLevel = readRtlDebugLevel();
55 mDebugEnabled = mDebugLevel & kRtlDebugCaches;
56 ALOGD("Using debug level = %d - Debug Enabled = %d", mDebugLevel, mDebugEnabled);
57
58 mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
59
60 if (mDebugEnabled) {
61 ALOGD("Initialization is done - Start time = %lld", mCacheStartTime);
62 }
63
64 mInitialized = true;
65 }
66
67 /**
68 * Callbacks
69 */
operator ()(TextLayoutCacheKey & text,sp<TextLayoutValue> & desc)70 void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutValue>& desc) {
71 size_t totalSizeToDelete = text.getSize() + desc->getSize();
72 mSize -= totalSizeToDelete;
73 if (mDebugEnabled) {
74 ALOGD("Cache value %p deleted, size = %d", desc.get(), totalSizeToDelete);
75 }
76 }
77
78 /*
79 * Cache clearing
80 */
purgeCaches()81 void TextLayoutCache::purgeCaches() {
82 AutoMutex _l(mLock);
83 mCache.clear();
84 mShaper->purgeCaches();
85 }
86
87 /*
88 * Caching
89 */
getValue(const SkPaint * paint,const jchar * text,jint start,jint count,jint contextCount,jint dirFlags)90 sp<TextLayoutValue> TextLayoutCache::getValue(const SkPaint* paint,
91 const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
92 AutoMutex _l(mLock);
93 nsecs_t startTime = 0;
94 if (mDebugEnabled) {
95 startTime = systemTime(SYSTEM_TIME_MONOTONIC);
96 }
97
98 // Create the key
99 TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
100
101 // Get value from cache if possible
102 sp<TextLayoutValue> value = mCache.get(key);
103
104 // Value not found for the key, we need to add a new value in the cache
105 if (value == NULL) {
106 if (mDebugEnabled) {
107 startTime = systemTime(SYSTEM_TIME_MONOTONIC);
108 }
109
110 value = new TextLayoutValue(contextCount);
111
112 // Compute advances and store them
113 mShaper->computeValues(value.get(), paint,
114 reinterpret_cast<const UChar*>(key.getText()), start, count,
115 size_t(contextCount), int(dirFlags));
116
117 if (mDebugEnabled) {
118 value->setElapsedTime(systemTime(SYSTEM_TIME_MONOTONIC) - startTime);
119 }
120
121 // Don't bother to add in the cache if the entry is too big
122 size_t size = key.getSize() + value->getSize();
123 if (size <= mMaxSize) {
124 // Cleanup to make some room if needed
125 if (mSize + size > mMaxSize) {
126 if (mDebugEnabled) {
127 ALOGD("Need to clean some entries for making some room for a new entry");
128 }
129 while (mSize + size > mMaxSize) {
130 // This will call the callback
131 bool removedOne = mCache.removeOldest();
132 LOG_ALWAYS_FATAL_IF(!removedOne, "The cache is non-empty but we "
133 "failed to remove the oldest entry. "
134 "mSize = %u, size = %u, mMaxSize = %u, mCache.size() = %u",
135 mSize, size, mMaxSize, mCache.size());
136 }
137 }
138
139 // Update current cache size
140 mSize += size;
141
142 bool putOne = mCache.put(key, value);
143 LOG_ALWAYS_FATAL_IF(!putOne, "Failed to put an entry into the cache. "
144 "This indicates that the cache already has an entry with the "
145 "same key but it should not since we checked earlier!"
146 " - start = %d, count = %d, contextCount = %d - Text = '%s'",
147 start, count, contextCount, String8(key.getText() + start, count).string());
148
149 if (mDebugEnabled) {
150 nsecs_t totalTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
151 ALOGD("CACHE MISS: Added entry %p "
152 "with start = %d, count = %d, contextCount = %d, "
153 "entry size %d bytes, remaining space %d bytes"
154 " - Compute time %0.6f ms - Put time %0.6f ms - Text = '%s'",
155 value.get(), start, count, contextCount, size, mMaxSize - mSize,
156 value->getElapsedTime() * 0.000001f,
157 (totalTime - value->getElapsedTime()) * 0.000001f,
158 String8(key.getText() + start, count).string());
159 }
160 } else {
161 if (mDebugEnabled) {
162 ALOGD("CACHE MISS: Calculated but not storing entry because it is too big "
163 "with start = %d, count = %d, contextCount = %d, "
164 "entry size %d bytes, remaining space %d bytes"
165 " - Compute time %0.6f ms - Text = '%s'",
166 start, count, contextCount, size, mMaxSize - mSize,
167 value->getElapsedTime() * 0.000001f,
168 String8(key.getText() + start, count).string());
169 }
170 }
171 } else {
172 // This is a cache hit, just log timestamp and user infos
173 if (mDebugEnabled) {
174 nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
175 mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
176 ++mCacheHitCount;
177
178 if (value->getElapsedTime() > 0) {
179 float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
180 / ((float)value->getElapsedTime()));
181 ALOGD("CACHE HIT #%d with start = %d, count = %d, contextCount = %d"
182 "- Compute time %0.6f ms - "
183 "Cache get time %0.6f ms - Gain in percent: %2.2f - Text = '%s'",
184 mCacheHitCount, start, count, contextCount,
185 value->getElapsedTime() * 0.000001f,
186 elapsedTimeThruCacheGet * 0.000001f,
187 deltaPercent,
188 String8(key.getText() + start, count).string());
189 }
190 if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
191 dumpCacheStats();
192 }
193 }
194 }
195 return value;
196 }
197
dumpCacheStats()198 void TextLayoutCache::dumpCacheStats() {
199 float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
200 float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
201
202 size_t bytes = 0;
203 size_t cacheSize = mCache.size();
204 for (size_t i = 0; i < cacheSize; i++) {
205 bytes += mCache.getKeyAt(i).getSize() + mCache.getValueAt(i)->getSize();
206 }
207
208 ALOGD("------------------------------------------------");
209 ALOGD("Cache stats");
210 ALOGD("------------------------------------------------");
211 ALOGD("pid : %d", getpid());
212 ALOGD("running : %.0f seconds", timeRunningInSec);
213 ALOGD("entries : %d", cacheSize);
214 ALOGD("max size : %d bytes", mMaxSize);
215 ALOGD("used : %d bytes according to mSize, %d bytes actual", mSize, bytes);
216 ALOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
217 ALOGD("hits : %d", mCacheHitCount);
218 ALOGD("saved : %0.6f ms", mNanosecondsSaved * 0.000001f);
219 ALOGD("------------------------------------------------");
220 }
221
222 /**
223 * TextLayoutCacheKey
224 */
TextLayoutCacheKey()225 TextLayoutCacheKey::TextLayoutCacheKey(): start(0), count(0), contextCount(0),
226 dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
227 hinting(SkPaint::kNo_Hinting), variant(SkPaint::kDefault_Variant), language() {
228 }
229
TextLayoutCacheKey(const SkPaint * paint,const UChar * text,size_t start,size_t count,size_t contextCount,int dirFlags)230 TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
231 size_t start, size_t count, size_t contextCount, int dirFlags) :
232 start(start), count(count), contextCount(contextCount),
233 dirFlags(dirFlags) {
234 textCopy.setTo(text, contextCount);
235 typeface = paint->getTypeface();
236 textSize = paint->getTextSize();
237 textSkewX = paint->getTextSkewX();
238 textScaleX = paint->getTextScaleX();
239 flags = paint->getFlags();
240 hinting = paint->getHinting();
241 variant = paint->getFontVariant();
242 language = paint->getLanguage();
243 }
244
TextLayoutCacheKey(const TextLayoutCacheKey & other)245 TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
246 textCopy(other.textCopy),
247 start(other.start),
248 count(other.count),
249 contextCount(other.contextCount),
250 dirFlags(other.dirFlags),
251 typeface(other.typeface),
252 textSize(other.textSize),
253 textSkewX(other.textSkewX),
254 textScaleX(other.textScaleX),
255 flags(other.flags),
256 hinting(other.hinting),
257 variant(other.variant),
258 language(other.language) {
259 }
260
compare(const TextLayoutCacheKey & lhs,const TextLayoutCacheKey & rhs)261 int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
262 int deltaInt = lhs.start - rhs.start;
263 if (deltaInt != 0) return (deltaInt);
264
265 deltaInt = lhs.count - rhs.count;
266 if (deltaInt != 0) return (deltaInt);
267
268 deltaInt = lhs.contextCount - rhs.contextCount;
269 if (deltaInt != 0) return (deltaInt);
270
271 if (lhs.typeface < rhs.typeface) return -1;
272 if (lhs.typeface > rhs.typeface) return +1;
273
274 if (lhs.textSize < rhs.textSize) return -1;
275 if (lhs.textSize > rhs.textSize) return +1;
276
277 if (lhs.textSkewX < rhs.textSkewX) return -1;
278 if (lhs.textSkewX > rhs.textSkewX) return +1;
279
280 if (lhs.textScaleX < rhs.textScaleX) return -1;
281 if (lhs.textScaleX > rhs.textScaleX) return +1;
282
283 deltaInt = lhs.flags - rhs.flags;
284 if (deltaInt != 0) return (deltaInt);
285
286 deltaInt = lhs.hinting - rhs.hinting;
287 if (deltaInt != 0) return (deltaInt);
288
289 deltaInt = lhs.dirFlags - rhs.dirFlags;
290 if (deltaInt) return (deltaInt);
291
292 deltaInt = lhs.variant - rhs.variant;
293 if (deltaInt) return (deltaInt);
294
295 if (lhs.language < rhs.language) return -1;
296 if (lhs.language > rhs.language) return +1;
297
298 return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
299 }
300
getSize() const301 size_t TextLayoutCacheKey::getSize() const {
302 return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
303 }
304
305 /**
306 * TextLayoutCacheValue
307 */
TextLayoutValue(size_t contextCount)308 TextLayoutValue::TextLayoutValue(size_t contextCount) :
309 mTotalAdvance(0), mElapsedTime(0) {
310 // Give a hint for advances and glyphs vectors size
311 mAdvances.setCapacity(contextCount);
312 mGlyphs.setCapacity(contextCount);
313 mPos.setCapacity(contextCount * 2);
314 }
315
getSize() const316 size_t TextLayoutValue::getSize() const {
317 return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
318 sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity();
319 }
320
setElapsedTime(uint32_t time)321 void TextLayoutValue::setElapsedTime(uint32_t time) {
322 mElapsedTime = time;
323 }
324
getElapsedTime()325 uint32_t TextLayoutValue::getElapsedTime() {
326 return mElapsedTime;
327 }
328
TextLayoutShaper()329 TextLayoutShaper::TextLayoutShaper() : mShaperItemGlyphArraySize(0) {
330 init();
331
332 mFontRec.klass = &harfbuzzSkiaClass;
333 mFontRec.userData = 0;
334
335 // Note that the scaling values (x_ and y_ppem, x_ and y_scale) will be set
336 // below, when the paint transform and em unit of the actual shaping font
337 // are known.
338
339 memset(&mShaperItem, 0, sizeof(mShaperItem));
340
341 mShaperItem.font = &mFontRec;
342 mShaperItem.font->userData = &mShapingPaint;
343 }
344
init()345 void TextLayoutShaper::init() {
346 mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, NULL, 0, SkTypeface::kNormal);
347 }
348
unrefTypefaces()349 void TextLayoutShaper::unrefTypefaces() {
350 SkSafeUnref(mDefaultTypeface);
351 }
352
~TextLayoutShaper()353 TextLayoutShaper::~TextLayoutShaper() {
354 unrefTypefaces();
355 deleteShaperItemGlyphArrays();
356 }
357
computeValues(TextLayoutValue * value,const SkPaint * paint,const UChar * chars,size_t start,size_t count,size_t contextCount,int dirFlags)358 void TextLayoutShaper::computeValues(TextLayoutValue* value, const SkPaint* paint, const UChar* chars,
359 size_t start, size_t count, size_t contextCount, int dirFlags) {
360
361 computeValues(paint, chars, start, count, contextCount, dirFlags,
362 &value->mAdvances, &value->mTotalAdvance, &value->mGlyphs, &value->mPos);
363 #if DEBUG_ADVANCES
364 ALOGD("Advances - start = %d, count = %d, contextCount = %d, totalAdvance = %f", start, count,
365 contextCount, value->mTotalAdvance);
366 #endif
367 }
368
computeValues(const SkPaint * paint,const UChar * chars,size_t start,size_t count,size_t contextCount,int dirFlags,Vector<jfloat> * const outAdvances,jfloat * outTotalAdvance,Vector<jchar> * const outGlyphs,Vector<jfloat> * const outPos)369 void TextLayoutShaper::computeValues(const SkPaint* paint, const UChar* chars,
370 size_t start, size_t count, size_t contextCount, int dirFlags,
371 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
372 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
373 *outTotalAdvance = 0;
374 if (!count) {
375 return;
376 }
377
378 UBiDiLevel bidiReq = 0;
379 bool forceLTR = false;
380 bool forceRTL = false;
381
382 switch (dirFlags) {
383 case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
384 case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
385 case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
386 case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
387 case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
388 case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
389 }
390
391 bool useSingleRun = false;
392 bool isRTL = forceRTL;
393 if (forceLTR || forceRTL) {
394 useSingleRun = true;
395 } else {
396 UBiDi* bidi = ubidi_open();
397 if (bidi) {
398 UErrorCode status = U_ZERO_ERROR;
399 #if DEBUG_GLYPHS
400 ALOGD("******** ComputeValues -- start");
401 ALOGD(" -- string = '%s'", String8(chars + start, count).string());
402 ALOGD(" -- start = %d", start);
403 ALOGD(" -- count = %d", count);
404 ALOGD(" -- contextCount = %d", contextCount);
405 ALOGD(" -- bidiReq = %d", bidiReq);
406 #endif
407 ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
408 if (U_SUCCESS(status)) {
409 int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
410 ssize_t rc = ubidi_countRuns(bidi, &status);
411 #if DEBUG_GLYPHS
412 ALOGD(" -- dirFlags = %d", dirFlags);
413 ALOGD(" -- paraDir = %d", paraDir);
414 ALOGD(" -- run-count = %d", int(rc));
415 #endif
416 if (U_SUCCESS(status) && rc == 1) {
417 // Normal case: one run, status is ok
418 isRTL = (paraDir == 1);
419 useSingleRun = true;
420 } else if (!U_SUCCESS(status) || rc < 1) {
421 ALOGW("Need to force to single run -- string = '%s',"
422 " status = %d, rc = %d",
423 String8(chars + start, count).string(), status, int(rc));
424 isRTL = (paraDir == 1);
425 useSingleRun = true;
426 } else {
427 int32_t end = start + count;
428 for (size_t i = 0; i < size_t(rc); ++i) {
429 int32_t startRun = -1;
430 int32_t lengthRun = -1;
431 UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
432
433 if (startRun == -1 || lengthRun == -1) {
434 // Something went wrong when getting the visual run, need to clear
435 // already computed data before doing a single run pass
436 ALOGW("Visual run is not valid");
437 outGlyphs->clear();
438 outAdvances->clear();
439 outPos->clear();
440 *outTotalAdvance = 0;
441 isRTL = (paraDir == 1);
442 useSingleRun = true;
443 break;
444 }
445
446 if (startRun >= end) {
447 continue;
448 }
449 int32_t endRun = startRun + lengthRun;
450 if (endRun <= int32_t(start)) {
451 continue;
452 }
453 if (startRun < int32_t(start)) {
454 startRun = int32_t(start);
455 }
456 if (endRun > end) {
457 endRun = end;
458 }
459
460 lengthRun = endRun - startRun;
461 isRTL = (runDir == UBIDI_RTL);
462 #if DEBUG_GLYPHS
463 ALOGD("Processing Bidi Run = %d -- run-start = %d, run-len = %d, isRTL = %d",
464 i, startRun, lengthRun, isRTL);
465 #endif
466 computeRunValues(paint, chars + startRun, lengthRun, isRTL,
467 outAdvances, outTotalAdvance, outGlyphs, outPos);
468
469 }
470 }
471 } else {
472 ALOGW("Cannot set Para");
473 useSingleRun = true;
474 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
475 }
476 ubidi_close(bidi);
477 } else {
478 ALOGW("Cannot ubidi_open()");
479 useSingleRun = true;
480 isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
481 }
482 }
483
484 // Default single run case
485 if (useSingleRun){
486 #if DEBUG_GLYPHS
487 ALOGD("Using a SINGLE BiDi Run "
488 "-- run-start = %d, run-len = %d, isRTL = %d", start, count, isRTL);
489 #endif
490 computeRunValues(paint, chars + start, count, isRTL,
491 outAdvances, outTotalAdvance, outGlyphs, outPos);
492 }
493
494 #if DEBUG_GLYPHS
495 ALOGD(" -- Total returned glyphs-count = %d", outGlyphs->size());
496 ALOGD("******** ComputeValues -- end");
497 #endif
498 }
499
logGlyphs(HB_ShaperItem shaperItem)500 static void logGlyphs(HB_ShaperItem shaperItem) {
501 ALOGD(" -- glyphs count=%d", shaperItem.num_glyphs);
502 for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
503 ALOGD(" -- glyph[%d] = %d, offset.x = %0.2f, offset.y = %0.2f", i,
504 shaperItem.glyphs[i],
505 HBFixedToFloat(shaperItem.offsets[i].x),
506 HBFixedToFloat(shaperItem.offsets[i].y));
507 }
508 }
509
computeRunValues(const SkPaint * paint,const UChar * chars,size_t count,bool isRTL,Vector<jfloat> * const outAdvances,jfloat * outTotalAdvance,Vector<jchar> * const outGlyphs,Vector<jfloat> * const outPos)510 void TextLayoutShaper::computeRunValues(const SkPaint* paint, const UChar* chars,
511 size_t count, bool isRTL,
512 Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
513 Vector<jchar>* const outGlyphs, Vector<jfloat>* const outPos) {
514 if (!count) {
515 // We cannot shape an empty run.
516 return;
517 }
518
519 // To be filled in later
520 for (size_t i = 0; i < count; i++) {
521 outAdvances->add(0);
522 }
523 UErrorCode error = U_ZERO_ERROR;
524 bool useNormalizedString = false;
525 for (ssize_t i = count - 1; i >= 0; --i) {
526 UChar ch1 = chars[i];
527 if (::ublock_getCode(ch1) == UBLOCK_COMBINING_DIACRITICAL_MARKS) {
528 // So we have found a diacritic, let's get now the main code point which is paired
529 // with it. As we can have several diacritics in a row, we need to iterate back again
530 #if DEBUG_GLYPHS
531 ALOGD("The BiDi run '%s' is containing a Diacritic at position %d",
532 String8(chars, count).string(), int(i));
533 #endif
534 ssize_t j = i - 1;
535 for (; j >= 0; --j) {
536 UChar ch2 = chars[j];
537 if (::ublock_getCode(ch2) != UBLOCK_COMBINING_DIACRITICAL_MARKS) {
538 break;
539 }
540 }
541
542 // We could not found the main code point, so we will just use the initial chars
543 if (j < 0) {
544 break;
545 }
546
547 #if DEBUG_GLYPHS
548 ALOGD("Found main code point at index %d", int(j));
549 #endif
550 // We found the main code point, so we can normalize the "chunk" and fill
551 // the remaining with ZWSP so that the Paint.getTextWidth() APIs will still be able
552 // to get one advance per char
553 mBuffer.remove();
554 Normalizer::normalize(UnicodeString(chars + j, i - j + 1),
555 UNORM_NFC, 0 /* no options */, mBuffer, error);
556 if (U_SUCCESS(error)) {
557 if (!useNormalizedString) {
558 useNormalizedString = true;
559 mNormalizedString.setTo(false /* not terminated*/, chars, count);
560 }
561 // Set the normalized chars
562 for (ssize_t k = j; k < j + mBuffer.length(); ++k) {
563 mNormalizedString.setCharAt(k, mBuffer.charAt(k - j));
564 }
565 // Fill the remain part with ZWSP (ZWNJ and ZWJ would lead to weird results
566 // because some fonts are missing those glyphs)
567 for (ssize_t k = j + mBuffer.length(); k <= i; ++k) {
568 mNormalizedString.setCharAt(k, UNICODE_ZWSP);
569 }
570 }
571 i = j - 1;
572 }
573 }
574
575 // Reverse "BiDi mirrored chars" in RTL mode only
576 // See: http://www.unicode.org/Public/6.0.0/ucd/extracted/DerivedBinaryProperties.txt
577 // This is a workaround because Harfbuzz is not able to do mirroring in all cases and
578 // script-run splitting with Harfbuzz is splitting on parenthesis
579 if (isRTL) {
580 for (ssize_t i = 0; i < ssize_t(count); i++) {
581 UChar32 ch = chars[i];
582 if (!u_isMirrored(ch)) continue;
583 if (!useNormalizedString) {
584 useNormalizedString = true;
585 mNormalizedString.setTo(false /* not terminated*/, chars, count);
586 }
587 UChar result = (UChar) u_charMirror(ch);
588 mNormalizedString.setCharAt(i, result);
589 #if DEBUG_GLYPHS
590 ALOGD("Rewriting codepoint '%d' to '%d' at position %d",
591 ch, mNormalizedString[i], int(i));
592 #endif
593 }
594 }
595
596 #if DEBUG_GLYPHS
597 if (useNormalizedString) {
598 ALOGD("Will use normalized string '%s', length = %d",
599 String8(mNormalizedString.getTerminatedBuffer(),
600 mNormalizedString.length()).string(),
601 mNormalizedString.length());
602 } else {
603 ALOGD("Normalization is not needed or cannot be done, using initial string");
604 }
605 #endif
606
607 assert(mNormalizedString.length() == count);
608
609 // Set the string properties
610 mShaperItem.string = useNormalizedString ? mNormalizedString.getTerminatedBuffer() : chars;
611 mShaperItem.stringLength = count;
612
613 // Define shaping paint properties
614 mShapingPaint.setTextSize(paint->getTextSize());
615 float skewX = paint->getTextSkewX();
616 mShapingPaint.setTextSkewX(skewX);
617 mShapingPaint.setTextScaleX(paint->getTextScaleX());
618 mShapingPaint.setFlags(paint->getFlags());
619 mShapingPaint.setHinting(paint->getHinting());
620 mShapingPaint.setFontVariant(paint->getFontVariant());
621 mShapingPaint.setLanguage(paint->getLanguage());
622
623 // Split the BiDi run into Script runs. Harfbuzz will populate the pos, length and script
624 // into the shaperItem
625 ssize_t indexFontRun = isRTL ? mShaperItem.stringLength - 1 : 0;
626 unsigned numCodePoints = 0;
627 jfloat totalAdvance = *outTotalAdvance;
628 while ((isRTL) ?
629 hb_utf16_script_run_prev(&numCodePoints, &mShaperItem.item, mShaperItem.string,
630 mShaperItem.stringLength, &indexFontRun):
631 hb_utf16_script_run_next(&numCodePoints, &mShaperItem.item, mShaperItem.string,
632 mShaperItem.stringLength, &indexFontRun)) {
633
634 ssize_t startScriptRun = mShaperItem.item.pos;
635 size_t countScriptRun = mShaperItem.item.length;
636 ssize_t endScriptRun = startScriptRun + countScriptRun;
637
638 #if DEBUG_GLYPHS
639 ALOGD("-------- Start of Script Run --------");
640 ALOGD("Shaping Script Run with");
641 ALOGD(" -- isRTL = %d", isRTL);
642 ALOGD(" -- HB script = %d", mShaperItem.item.script);
643 ALOGD(" -- startFontRun = %d", int(startScriptRun));
644 ALOGD(" -- endFontRun = %d", int(endScriptRun));
645 ALOGD(" -- countFontRun = %d", countScriptRun);
646 ALOGD(" -- run = '%s'", String8(chars + startScriptRun, countScriptRun).string());
647 ALOGD(" -- string = '%s'", String8(chars, count).string());
648 #endif
649
650 // Initialize Harfbuzz Shaper and get the base glyph count for offsetting the glyphIDs
651 // and shape the Font run
652 size_t glyphBaseCount = shapeFontRun(paint, isRTL);
653
654 #if DEBUG_GLYPHS
655 ALOGD("Got from Harfbuzz");
656 ALOGD(" -- glyphBaseCount = %d", glyphBaseCount);
657 ALOGD(" -- num_glypth = %d", mShaperItem.num_glyphs);
658 ALOGD(" -- kerning_applied = %d", mShaperItem.kerning_applied);
659 ALOGD(" -- isDevKernText = %d", paint->isDevKernText());
660
661 logGlyphs(mShaperItem);
662 #endif
663
664 if (mShaperItem.advances == NULL || mShaperItem.num_glyphs == 0) {
665 #if DEBUG_GLYPHS
666 ALOGD("Advances array is empty or num_glypth = 0");
667 #endif
668 continue;
669 }
670
671 #if DEBUG_GLYPHS
672 ALOGD("Returned logclusters");
673 for (size_t i = 0; i < mShaperItem.num_glyphs; i++) {
674 ALOGD(" -- lc[%d] = %d, hb-adv[%d] = %0.2f", i, mShaperItem.log_clusters[i],
675 i, HBFixedToFloat(mShaperItem.advances[i]));
676 }
677 #endif
678 jfloat totalFontRunAdvance = 0;
679 size_t clusterStart = 0;
680 for (size_t i = 0; i < countScriptRun; i++) {
681 size_t cluster = mShaperItem.log_clusters[i];
682 size_t clusterNext = i == countScriptRun - 1 ? mShaperItem.num_glyphs :
683 mShaperItem.log_clusters[i + 1];
684 if (cluster != clusterNext) {
685 jfloat advance = 0;
686 // The advance for the cluster is the sum of the advances of all glyphs within
687 // the cluster.
688 for (size_t j = cluster; j < clusterNext; j++) {
689 advance += HBFixedToFloat(mShaperItem.advances[j]);
690 }
691 totalFontRunAdvance += advance;
692 outAdvances->replaceAt(advance, startScriptRun + clusterStart);
693 clusterStart = i + 1;
694 }
695 }
696
697 #if DEBUG_ADVANCES
698 ALOGD("Returned advances");
699 for (size_t i = 0; i < countScriptRun; i++) {
700 ALOGD(" -- hb-adv[%d] = %0.2f, log_clusters = %d, total = %0.2f", i,
701 (*outAdvances)[i], mShaperItem.log_clusters[i], totalFontRunAdvance);
702 }
703 #endif
704
705 // Get Glyphs and reverse them in place if RTL
706 if (outGlyphs) {
707 size_t countGlyphs = mShaperItem.num_glyphs;
708 #if DEBUG_GLYPHS
709 ALOGD("Returned script run glyphs -- count = %d", countGlyphs);
710 #endif
711 for (size_t i = 0; i < countGlyphs; i++) {
712 jchar glyph = glyphBaseCount +
713 (jchar) mShaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i];
714 #if DEBUG_GLYPHS
715 ALOGD(" -- glyph[%d] = %d", i, glyph);
716 #endif
717 outGlyphs->add(glyph);
718 }
719 }
720
721 // Get glyph positions (and reverse them in place if RTL)
722 if (outPos) {
723 size_t countGlyphs = mShaperItem.num_glyphs;
724 jfloat x = totalAdvance;
725 for (size_t i = 0; i < countGlyphs; i++) {
726 size_t index = (!isRTL) ? i : countGlyphs - 1 - i;
727 float xo = HBFixedToFloat(mShaperItem.offsets[index].x);
728 float yo = HBFixedToFloat(mShaperItem.offsets[index].y);
729 // Apply skewX component of transform to position offsets. Note
730 // that scale has already been applied through x_ and y_scale
731 // set in the mFontRec.
732 outPos->add(x + xo + yo * skewX);
733 outPos->add(yo);
734 #if DEBUG_GLYPHS
735 ALOGD(" -- hb adv[%d] = %f, log_cluster[%d] = %d",
736 index, HBFixedToFloat(mShaperItem.advances[index]),
737 index, mShaperItem.log_clusters[index]);
738 #endif
739 x += HBFixedToFloat(mShaperItem.advances[index]);
740 }
741 }
742
743 totalAdvance += totalFontRunAdvance;
744 }
745
746 *outTotalAdvance = totalAdvance;
747
748 #if DEBUG_GLYPHS
749 ALOGD("-------- End of Script Run --------");
750 #endif
751 }
752
753 /**
754 * Return the first typeface in the logical change, starting with this typeface,
755 * that contains the specified unichar, or NULL if none is found.
756 *
757 * Note that this function does _not_ increment the reference count on the typeface, as the
758 * assumption is that its lifetime is managed elsewhere - in particular, the fallback typefaces
759 * for the default font live in a global cache.
760 */
typefaceForScript(const SkPaint * paint,SkTypeface * typeface,HB_Script script)761 SkTypeface* TextLayoutShaper::typefaceForScript(const SkPaint* paint, SkTypeface* typeface,
762 HB_Script script) {
763 SkTypeface::Style currentStyle = SkTypeface::kNormal;
764 if (typeface) {
765 currentStyle = typeface->style();
766 }
767 typeface = SkCreateTypefaceForScript(script, currentStyle);
768 #if DEBUG_GLYPHS
769 ALOGD("Using Harfbuzz Script %d, Style %d", script, currentStyle);
770 #endif
771 return typeface;
772 }
773
isComplexScript(HB_Script script)774 bool TextLayoutShaper::isComplexScript(HB_Script script) {
775 switch (script) {
776 case HB_Script_Common:
777 case HB_Script_Greek:
778 case HB_Script_Cyrillic:
779 case HB_Script_Hangul:
780 case HB_Script_Inherited:
781 return false;
782 default:
783 return true;
784 }
785 }
786
shapeFontRun(const SkPaint * paint,bool isRTL)787 size_t TextLayoutShaper::shapeFontRun(const SkPaint* paint, bool isRTL) {
788 // Reset kerning
789 mShaperItem.kerning_applied = false;
790
791 // Update Harfbuzz Shaper
792 mShaperItem.item.bidiLevel = isRTL;
793
794 SkTypeface* typeface = paint->getTypeface();
795
796 // Get the glyphs base count for offsetting the glyphIDs returned by Harfbuzz
797 // This is needed as the Typeface used for shaping can be not the default one
798 // when we are shaping any script that needs to use a fallback Font.
799 // If we are a "common" script we dont need to shift
800 size_t baseGlyphCount = 0;
801 SkUnichar firstUnichar = 0;
802 if (isComplexScript(mShaperItem.item.script)) {
803 const uint16_t* text16 = (const uint16_t*) (mShaperItem.string + mShaperItem.item.pos);
804 const uint16_t* text16End = text16 + mShaperItem.item.length;
805 firstUnichar = SkUTF16_NextUnichar(&text16);
806 while (firstUnichar == ' ' && text16 < text16End) {
807 firstUnichar = SkUTF16_NextUnichar(&text16);
808 }
809 baseGlyphCount = paint->getBaseGlyphCount(firstUnichar);
810 }
811
812 if (baseGlyphCount != 0) {
813 typeface = typefaceForScript(paint, typeface, mShaperItem.item.script);
814 if (!typeface) {
815 typeface = mDefaultTypeface;
816 SkSafeRef(typeface);
817 #if DEBUG_GLYPHS
818 ALOGD("Using Default Typeface");
819 #endif
820 }
821 } else {
822 if (!typeface) {
823 typeface = mDefaultTypeface;
824 #if DEBUG_GLYPHS
825 ALOGD("Using Default Typeface");
826 #endif
827 }
828 SkSafeRef(typeface);
829 }
830
831 mShapingPaint.setTypeface(typeface);
832 mShaperItem.face = getCachedHBFace(typeface);
833
834 int textSize = paint->getTextSize();
835 float scaleX = paint->getTextScaleX();
836 mFontRec.x_ppem = floor(scaleX * textSize + 0.5);
837 mFontRec.y_ppem = textSize;
838 uint32_t unitsPerEm = SkFontHost::GetUnitsPerEm(typeface->uniqueID());
839 // x_ and y_scale are the conversion factors from font design space
840 // (unitsPerEm) to 1/64th of device pixels in 16.16 format.
841 const int kDevicePixelFraction = 64;
842 const int kMultiplyFor16Dot16 = 1 << 16;
843 float emScale = kDevicePixelFraction * kMultiplyFor16Dot16 / (float)unitsPerEm;
844 mFontRec.x_scale = emScale * scaleX * textSize;
845 mFontRec.y_scale = emScale * textSize;
846
847 #if DEBUG_GLYPHS
848 ALOGD("Run typeface = %p, uniqueID = %d, hb_face = %p",
849 typeface, typeface->uniqueID(), mShaperItem.face);
850 #endif
851 SkSafeUnref(typeface);
852
853 // Shape
854 assert(mShaperItem.item.length > 0); // Harfbuzz will overwrite other memory if length is 0.
855 size_t size = mShaperItem.item.length * 3 / 2;
856 while (!doShaping(size)) {
857 // We overflowed our glyph arrays. Resize and retry.
858 // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
859 size = mShaperItem.num_glyphs * 2;
860 }
861 return baseGlyphCount;
862 }
863
doShaping(size_t size)864 bool TextLayoutShaper::doShaping(size_t size) {
865 if (size > mShaperItemGlyphArraySize) {
866 deleteShaperItemGlyphArrays();
867 createShaperItemGlyphArrays(size);
868 }
869 mShaperItem.num_glyphs = mShaperItemGlyphArraySize;
870 memset(mShaperItem.offsets, 0, mShaperItem.num_glyphs * sizeof(HB_FixedPoint));
871 return HB_ShapeItem(&mShaperItem);
872 }
873
createShaperItemGlyphArrays(size_t size)874 void TextLayoutShaper::createShaperItemGlyphArrays(size_t size) {
875 #if DEBUG_GLYPHS
876 ALOGD("Creating Glyph Arrays with size = %d", size);
877 #endif
878 mShaperItemGlyphArraySize = size;
879
880 // These arrays are all indexed by glyph.
881 mShaperItem.glyphs = new HB_Glyph[size];
882 mShaperItem.attributes = new HB_GlyphAttributes[size];
883 mShaperItem.advances = new HB_Fixed[size];
884 mShaperItem.offsets = new HB_FixedPoint[size];
885
886 // Although the log_clusters array is indexed by character, Harfbuzz expects that
887 // it is big enough to hold one element per glyph. So we allocate log_clusters along
888 // with the other glyph arrays above.
889 mShaperItem.log_clusters = new unsigned short[size];
890 }
891
deleteShaperItemGlyphArrays()892 void TextLayoutShaper::deleteShaperItemGlyphArrays() {
893 delete[] mShaperItem.glyphs;
894 delete[] mShaperItem.attributes;
895 delete[] mShaperItem.advances;
896 delete[] mShaperItem.offsets;
897 delete[] mShaperItem.log_clusters;
898 }
899
getCachedHBFace(SkTypeface * typeface)900 HB_Face TextLayoutShaper::getCachedHBFace(SkTypeface* typeface) {
901 SkFontID fontId = typeface->uniqueID();
902 ssize_t index = mCachedHBFaces.indexOfKey(fontId);
903 if (index >= 0) {
904 return mCachedHBFaces.valueAt(index);
905 }
906 HB_Face face = HB_NewFace(typeface, harfbuzzSkiaGetTable);
907 if (face) {
908 #if DEBUG_GLYPHS
909 ALOGD("Created HB_NewFace %p from paint typeface = %p", face, typeface);
910 #endif
911 mCachedHBFaces.add(fontId, face);
912 }
913 return face;
914 }
915
purgeCaches()916 void TextLayoutShaper::purgeCaches() {
917 size_t cacheSize = mCachedHBFaces.size();
918 for (size_t i = 0; i < cacheSize; i++) {
919 HB_FreeFace(mCachedHBFaces.valueAt(i));
920 }
921 mCachedHBFaces.clear();
922 unrefTypefaces();
923 init();
924 }
925
TextLayoutEngine()926 TextLayoutEngine::TextLayoutEngine() {
927 mShaper = new TextLayoutShaper();
928 #if USE_TEXT_LAYOUT_CACHE
929 mTextLayoutCache = new TextLayoutCache(mShaper);
930 #else
931 mTextLayoutCache = NULL;
932 #endif
933 }
934
~TextLayoutEngine()935 TextLayoutEngine::~TextLayoutEngine() {
936 delete mTextLayoutCache;
937 delete mShaper;
938 }
939
getValue(const SkPaint * paint,const jchar * text,jint start,jint count,jint contextCount,jint dirFlags)940 sp<TextLayoutValue> TextLayoutEngine::getValue(const SkPaint* paint, const jchar* text,
941 jint start, jint count, jint contextCount, jint dirFlags) {
942 sp<TextLayoutValue> value;
943 #if USE_TEXT_LAYOUT_CACHE
944 value = mTextLayoutCache->getValue(paint, text, start, count,
945 contextCount, dirFlags);
946 if (value == NULL) {
947 ALOGE("Cannot get TextLayoutCache value for text = '%s'",
948 String8(text + start, count).string());
949 }
950 #else
951 value = new TextLayoutValue(count);
952 mShaper->computeValues(value.get(), paint,
953 reinterpret_cast<const UChar*>(text), start, count, contextCount, dirFlags);
954 #endif
955 return value;
956 }
957
purgeCaches()958 void TextLayoutEngine::purgeCaches() {
959 #if USE_TEXT_LAYOUT_CACHE
960 mTextLayoutCache->purgeCaches();
961 #if DEBUG_GLYPHS
962 ALOGD("Purged TextLayoutEngine caches");
963 #endif
964 #endif
965 }
966
967
968 } // namespace android
969