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