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
27extern "C" {
28  #include "harfbuzz-unicode.h"
29}
30
31namespace android {
32
33//--------------------------------------------------------------------------------------------------
34
35ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutEngine);
36
37//--------------------------------------------------------------------------------------------------
38
39TextLayoutCache::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
47TextLayoutCache::~TextLayoutCache() {
48    mCache.clear();
49}
50
51void 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 */
70void 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 */
81void TextLayoutCache::purgeCaches() {
82    AutoMutex _l(mLock);
83    mCache.clear();
84    mShaper->purgeCaches();
85}
86
87/*
88 * Caching
89 */
90sp<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
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(): 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
230TextLayoutCacheKey::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
245TextLayoutCacheKey::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
261int 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
301size_t TextLayoutCacheKey::getSize() const {
302    return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
303}
304
305/**
306 * TextLayoutCacheValue
307 */
308TextLayoutValue::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
316size_t TextLayoutValue::getSize() const {
317    return sizeof(TextLayoutValue) + sizeof(jfloat) * mAdvances.capacity() +
318            sizeof(jchar) * mGlyphs.capacity() + sizeof(jfloat) * mPos.capacity();
319}
320
321void TextLayoutValue::setElapsedTime(uint32_t time) {
322    mElapsedTime = time;
323}
324
325uint32_t TextLayoutValue::getElapsedTime() {
326    return mElapsedTime;
327}
328
329TextLayoutShaper::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
345void TextLayoutShaper::init() {
346    mDefaultTypeface = SkFontHost::CreateTypeface(NULL, NULL, NULL, 0, SkTypeface::kNormal);
347}
348
349void TextLayoutShaper::unrefTypefaces() {
350    SkSafeUnref(mDefaultTypeface);
351}
352
353TextLayoutShaper::~TextLayoutShaper() {
354    unrefTypefaces();
355    deleteShaperItemGlyphArrays();
356}
357
358void 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
369void 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
500static 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
510void 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 */
761SkTypeface* 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
774bool 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
787size_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
864bool 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
874void 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
892void 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
900HB_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
916void 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
926TextLayoutEngine::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
935TextLayoutEngine::~TextLayoutEngine() {
936    delete mTextLayoutCache;
937    delete mShaper;
938}
939
940sp<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
958void 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