TextLayoutCache.cpp revision 78b868ff42cc368c45f851443678a822560dc266
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#include "TextLayoutCache.h"
18#include "TextLayout.h"
19
20namespace android {
21
22TextLayoutCache::TextLayoutCache() :
23        mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
24        mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
25        mCacheHitCount(0), mNanosecondsSaved(0) {
26    init();
27}
28
29TextLayoutCache::TextLayoutCache(uint32_t max):
30        mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
31        mSize(0), mMaxSize(max),
32        mCacheHitCount(0), mNanosecondsSaved(0) {
33    init();
34}
35
36TextLayoutCache::~TextLayoutCache() {
37    mCache.clear();
38}
39
40void TextLayoutCache::init() {
41    mCache.setOnEntryRemovedListener(this);
42
43    mDebugLevel = readRtlDebugLevel();
44    mDebugEnabled = mDebugLevel & kRtlDebugCaches;
45    LOGD("Using TextLayoutCache debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
46
47    mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
48    if (mDebugEnabled) {
49        LOGD("TextLayoutCache start time: %lld", mCacheStartTime);
50    }
51    mInitialized = true;
52
53    if (mDebugEnabled) {
54#if RTL_USE_HARFBUZZ
55        LOGD("TextLayoutCache is using HARFBUZZ");
56#else
57        LOGD("TextLayoutCache is using ICU");
58#endif
59    }
60
61    if (mDebugEnabled) {
62        LOGD("TextLayoutCache initialization is done");
63    }
64}
65
66/*
67 * Size management
68 */
69
70uint32_t TextLayoutCache::getSize() {
71    return mSize;
72}
73
74uint32_t TextLayoutCache::getMaxSize() {
75    return mMaxSize;
76}
77
78void TextLayoutCache::setMaxSize(uint32_t maxSize) {
79    mMaxSize = maxSize;
80    removeOldests();
81}
82
83void TextLayoutCache::removeOldests() {
84    while (mSize > mMaxSize) {
85        mCache.removeOldest();
86    }
87}
88
89/**
90 *  Callbacks
91 */
92void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) {
93    if (desc != NULL) {
94        size_t totalSizeToDelete = text.getSize() + desc->getSize();
95        mSize -= totalSizeToDelete;
96        if (mDebugEnabled) {
97            LOGD("Cache value deleted, size = %d", totalSizeToDelete);
98        }
99        desc.clear();
100    }
101}
102
103/*
104 * Cache clearing
105 */
106void TextLayoutCache::clear() {
107    mCache.clear();
108}
109
110/*
111 * Caching
112 */
113sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint,
114            const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
115    AutoMutex _l(mLock);
116    nsecs_t startTime = 0;
117    if (mDebugEnabled) {
118        startTime = systemTime(SYSTEM_TIME_MONOTONIC);
119    }
120
121    // Create the key
122    TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
123
124    // Get value from cache if possible
125    sp<TextLayoutCacheValue> value = mCache.get(key);
126
127    // Value not found for the key, we need to add a new value in the cache
128    if (value == NULL) {
129        if (mDebugEnabled) {
130            startTime = systemTime(SYSTEM_TIME_MONOTONIC);
131        }
132
133        value = new TextLayoutCacheValue();
134
135        // Compute advances and store them
136        value->computeValues(paint, text, start, count, contextCount, dirFlags);
137
138        nsecs_t endTime = systemTime(SYSTEM_TIME_MONOTONIC);
139
140        // Don't bother to add in the cache if the entry is too big
141        size_t size = key.getSize() + value->getSize();
142        if (size <= mMaxSize) {
143            // Cleanup to make some room if needed
144            if (mSize + size > mMaxSize) {
145                if (mDebugEnabled) {
146                    LOGD("TextLayoutCache: need to clean some entries "
147                            "for making some room for a new entry");
148                }
149                while (mSize + size > mMaxSize) {
150                    // This will call the callback
151                    mCache.removeOldest();
152                }
153            }
154
155            // Update current cache size
156            mSize += size;
157
158            // Copy the text when we insert the new entry
159            key.internalTextCopy();
160            mCache.put(key, value);
161
162            if (mDebugEnabled) {
163                // Update timing information for statistics
164                value->setElapsedTime(endTime - startTime);
165
166                LOGD("CACHE MISS: Added entry for text='%s' with start=%d, count=%d, "
167                        "contextCount=%d, entry size %d bytes, remaining space %d bytes"
168                        " - Compute time in nanos: %d",
169                        String8(text, contextCount).string(), start, count, contextCount,
170                        size, mMaxSize - mSize, value->getElapsedTime());
171            }
172        } else {
173            if (mDebugEnabled) {
174                LOGD("CACHE MISS: Calculated but not storing entry because it is too big "
175                        "for text='%s' with start=%d, count=%d, contextCount=%d, "
176                        "entry size %d bytes, remaining space %d bytes"
177                        " - Compute time in nanos: %lld",
178                        String8(text, contextCount).string(), start, count, contextCount,
179                        size, mMaxSize - mSize, endTime);
180            }
181            value.clear();
182        }
183    } else {
184        // This is a cache hit, just log timestamp and user infos
185        if (mDebugEnabled) {
186            nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
187            mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
188            ++mCacheHitCount;
189
190            if (value->getElapsedTime() > 0) {
191                float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
192                        / ((float)value->getElapsedTime()));
193                LOGD("CACHE HIT #%d for text='%s' with start=%d, count=%d, contextCount=%d "
194                        "- Compute time in nanos: %d - "
195                        "Cache get time in nanos: %lld - Gain in percent: %2.2f",
196                        mCacheHitCount, String8(text, contextCount).string(), start, count,
197                        contextCount,
198                        value->getElapsedTime(), elapsedTimeThruCacheGet, deltaPercent);
199            }
200            if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
201                dumpCacheStats();
202            }
203        }
204    }
205    return value;
206}
207
208void TextLayoutCache::dumpCacheStats() {
209    float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
210    float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
211    LOGD("------------------------------------------------");
212    LOGD("TextLayoutCache stats");
213    LOGD("------------------------------------------------");
214    LOGD("pid       : %d", getpid());
215    LOGD("running   : %.0f seconds", timeRunningInSec);
216    LOGD("entries   : %d", mCache.size());
217    LOGD("size      : %d bytes", mMaxSize);
218    LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
219    LOGD("hits      : %d", mCacheHitCount);
220    LOGD("saved     : %lld milliseconds", mNanosecondsSaved / 1000000);
221    LOGD("------------------------------------------------");
222}
223
224/**
225 * TextLayoutCacheKey
226 */
227TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0),
228        dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
229        hinting(SkPaint::kNo_Hinting)  {
230}
231
232TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint,
233        const UChar* text, size_t start, size_t count,
234        size_t contextCount, int dirFlags) :
235            text(text), start(start), count(count), contextCount(contextCount),
236            dirFlags(dirFlags) {
237    typeface = paint->getTypeface();
238    textSize = paint->getTextSize();
239    textSkewX = paint->getTextSkewX();
240    textScaleX = paint->getTextScaleX();
241    flags = paint->getFlags();
242    hinting = paint->getHinting();
243}
244
245bool TextLayoutCacheKey::operator<(const TextLayoutCacheKey& rhs) const {
246    LTE_INT(count) {
247        LTE_INT(contextCount) {
248            LTE_INT(start) {
249                LTE_INT(typeface) {
250                    LTE_FLOAT(textSize) {
251                        LTE_FLOAT(textSkewX) {
252                            LTE_FLOAT(textScaleX) {
253                                LTE_INT(flags) {
254                                    LTE_INT(hinting) {
255                                        LTE_INT(dirFlags) {
256                                            return strncmp16(text, rhs.text, contextCount) < 0;
257                                        }
258                                    }
259                                }
260                            }
261                        }
262                    }
263                }
264            }
265        }
266    }
267    return false;
268}
269
270void TextLayoutCacheKey::internalTextCopy() {
271    textCopy.setTo(text, contextCount);
272    text = textCopy.string();
273}
274
275size_t TextLayoutCacheKey::getSize() {
276    return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
277}
278
279/**
280 * TextLayoutCacheValue
281 */
282TextLayoutCacheValue::TextLayoutCacheValue() :
283        mAdvances(NULL), mTotalAdvance(0), mAdvancesCount(0),
284        mGlyphs(NULL), mGlyphsCount(0), mElapsedTime(0) {
285}
286
287TextLayoutCacheValue::~TextLayoutCacheValue() {
288    delete[] mAdvances;
289    delete[] mGlyphs;
290}
291
292void TextLayoutCacheValue::setElapsedTime(uint32_t time) {
293    mElapsedTime = time;
294}
295
296uint32_t TextLayoutCacheValue::getElapsedTime() {
297    return mElapsedTime;
298}
299
300void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars, size_t start,
301        size_t count, size_t contextCount, int dirFlags) {
302    mAdvancesCount = count;
303    mAdvances = new float[count];
304
305#if RTL_USE_HARFBUZZ
306    computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
307            mAdvances, &mTotalAdvance, &mGlyphs, &mGlyphsCount);
308#else
309    computeAdvancesWithICU(paint, chars, start, count, contextCount, dirFlags,
310            mAdvances, &mTotalAdvance);
311#endif
312#if DEBUG_ADVANCES
313    LOGD("Advances - count=%d - countextCount=%d - totalAdvance=%f - "
314            "adv[0]=%f adv[1]=%f adv[2]=%f adv[3]=%f", count, contextCount, mTotalAdvance,
315            mAdvances[0], mAdvances[1], mAdvances[2], mAdvances[3]);
316#endif
317}
318
319size_t TextLayoutCacheValue::getSize() {
320    return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvancesCount +
321            sizeof(jchar) * mGlyphsCount;
322}
323
324void TextLayoutCacheValue::setupShaperItem(HB_ShaperItem* shaperItem, HB_FontRec* font,
325        FontData* fontData, SkPaint* paint, const UChar* chars, size_t start, size_t count,
326        size_t contextCount, int dirFlags) {
327    bool isRTL = dirFlags & 0x1;
328
329    font->klass = &harfbuzzSkiaClass;
330    font->userData = 0;
331    // The values which harfbuzzSkiaClass returns are already scaled to
332    // pixel units, so we just set all these to one to disable further
333    // scaling.
334    font->x_ppem = 1;
335    font->y_ppem = 1;
336    font->x_scale = 1;
337    font->y_scale = 1;
338
339    memset(shaperItem, 0, sizeof(*shaperItem));
340    shaperItem->font = font;
341    shaperItem->face = HB_NewFace(shaperItem->font, harfbuzzSkiaGetTable);
342
343    shaperItem->kerning_applied = false;
344
345    // We cannot know, ahead of time, how many glyphs a given script run
346    // will produce. We take a guess that script runs will not produce more
347    // than twice as many glyphs as there are code points plus a bit of
348    // padding and fallback if we find that we are wrong.
349    createGlyphArrays(shaperItem, (contextCount + 2) * 2);
350
351    // Free memory for clusters if needed and recreate the clusters array
352    if (shaperItem->log_clusters) {
353        delete shaperItem->log_clusters;
354    }
355    shaperItem->log_clusters = new unsigned short[contextCount];
356
357    shaperItem->item.pos = start;
358    shaperItem->item.length = count;
359    shaperItem->item.bidiLevel = isRTL;
360    shaperItem->item.script = isRTL ? HB_Script_Arabic : HB_Script_Common;
361
362    shaperItem->string = chars;
363    shaperItem->stringLength = contextCount;
364
365    fontData->typeFace = paint->getTypeface();
366    fontData->textSize = paint->getTextSize();
367    fontData->textSkewX = paint->getTextSkewX();
368    fontData->textScaleX = paint->getTextScaleX();
369    fontData->flags = paint->getFlags();
370    fontData->hinting = paint->getHinting();
371
372    shaperItem->font->userData = fontData;
373}
374
375void TextLayoutCacheValue::shapeWithHarfbuzz(HB_ShaperItem* shaperItem, HB_FontRec* font,
376        FontData* fontData, SkPaint* paint, const UChar* chars, size_t start, size_t count,
377        size_t contextCount, int dirFlags) {
378    // Setup Harfbuzz Shaper
379    setupShaperItem(shaperItem, font, fontData, paint, chars, start, count,
380            contextCount, dirFlags);
381
382    // Shape
383    resetGlyphArrays(shaperItem);
384    while (!HB_ShapeItem(shaperItem)) {
385        // We overflowed our arrays. Resize and retry.
386        // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
387        deleteGlyphArrays(shaperItem);
388        createGlyphArrays(shaperItem, shaperItem->num_glyphs << 1);
389        resetGlyphArrays(shaperItem);
390    }
391}
392
393struct GlyphRun {
394    inline GlyphRun() {}
395    inline GlyphRun(jchar* glyphs, size_t glyphsCount, bool isRTL) :
396            glyphs(glyphs), glyphsCount(glyphsCount), isRTL(isRTL) { }
397    jchar* glyphs;
398    size_t glyphsCount;
399    int isRTL;
400};
401
402void static reverseGlyphArray(jchar* glyphs, size_t count) {
403    for (size_t i = 0; i < count / 2; i++) {
404        jchar temp = glyphs[i];
405        glyphs[i] = glyphs[count - 1 - i];
406        glyphs[count - 1 - i] = temp;
407    }
408}
409
410void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
411        size_t start, size_t count, size_t contextCount, int dirFlags,
412        jfloat* outAdvances, jfloat* outTotalAdvance,
413        jchar** outGlyphs, size_t* outGlyphsCount) {
414
415        UBiDiLevel bidiReq = 0;
416        bool forceLTR = false;
417        bool forceRTL = false;
418
419        switch (dirFlags) {
420            case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
421            case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
422            case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
423            case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
424            case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
425            case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
426        }
427
428        if (forceLTR || forceRTL) {
429#if DEBUG_GLYPHS
430                    LOGD("computeValuesWithHarfbuzz -- forcing run with LTR=%d RTL=%d",
431                            forceLTR, forceRTL);
432#endif
433            computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
434                    outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
435
436            if (forceRTL && *outGlyphsCount > 1) {
437                reverseGlyphArray(*outGlyphs, *outGlyphsCount);
438            }
439        } else {
440            UBiDi* bidi = ubidi_open();
441            if (bidi) {
442                UErrorCode status = U_ZERO_ERROR;
443#if DEBUG_GLYPHS
444                LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq);
445#endif
446                ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
447                if (U_SUCCESS(status)) {
448                    int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
449                    size_t rc = ubidi_countRuns(bidi, &status);
450#if DEBUG_GLYPHS
451                    LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d", dirFlags, rc, paraDir);
452#endif
453                    if (rc == 1 || !U_SUCCESS(status)) {
454                        computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount,
455                                dirFlags, outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
456
457                        if (dirFlags == 1 && *outGlyphsCount > 1) {
458                            reverseGlyphArray(*outGlyphs, *outGlyphsCount);
459                        }
460                    } else {
461                        Vector<GlyphRun> glyphRuns;
462                        jchar* runGlyphs;
463                        size_t runGlyphsCount = 0;
464                        size_t runIndex = 0;
465                        for (size_t i = 0; i < rc; ++i) {
466                            int32_t startRun;
467                            int32_t lengthRun;
468                            UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
469
470                            int newFlags = (runDir == UBIDI_RTL) ? kDirection_RTL : kDirection_LTR;
471                            jfloat runTotalAdvance = 0;
472#if DEBUG_GLYPHS
473                            LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d newFlags=%d",
474                                    startRun, lengthRun, newFlags);
475#endif
476                            computeRunValuesWithHarfbuzz(paint, chars, startRun,
477                                    lengthRun, contextCount, newFlags,
478                                    outAdvances + runIndex, &runTotalAdvance,
479                                    &runGlyphs, &runGlyphsCount);
480
481                            runIndex += lengthRun;
482
483                            *outTotalAdvance += runTotalAdvance;
484                            *outGlyphsCount += runGlyphsCount;
485#if DEBUG_GLYPHS
486                            LOGD("computeValuesWithHarfbuzz -- run=%d run-glyphs-count=%d",
487                                    i, runGlyphsCount);
488                            for (size_t j = 0; j < runGlyphsCount; j++) {
489                                LOGD("                          -- glyphs[%d]=%d", j, runGlyphs[j]);
490                            }
491#endif
492                            glyphRuns.push(GlyphRun(runGlyphs, runGlyphsCount, newFlags));
493                        }
494                        *outGlyphs = new jchar[*outGlyphsCount];
495
496                        jchar* glyphs = *outGlyphs;
497                        for (size_t i = 0; i < glyphRuns.size(); i++) {
498                            const GlyphRun& glyphRun = glyphRuns.itemAt(i);
499                            if (glyphRun.isRTL) {
500                                for (size_t n = 0; n < glyphRun.glyphsCount; n++) {
501                                    glyphs[glyphRun.glyphsCount - n - 1] = glyphRun.glyphs[n];
502                                }
503                            } else {
504                                memcpy(glyphs, glyphRun.glyphs, glyphRun.glyphsCount * sizeof(jchar));
505                            }
506                            glyphs += glyphRun.glyphsCount;
507                            delete[] glyphRun.glyphs;
508                        }
509                    }
510                }
511                ubidi_close(bidi);
512            } else {
513                // Cannot run BiDi, just consider one Run
514#if DEBUG_GLYPHS
515                LOGD("computeValuesWithHarfbuzz -- cannot run BiDi, considering only one Run");
516#endif
517                computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
518                        outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
519
520                if (dirFlags == 1 && *outGlyphsCount > 1) {
521                    reverseGlyphArray(*outGlyphs, *outGlyphsCount);
522                }
523            }
524        }
525#if DEBUG_GLYPHS
526        LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", *outGlyphsCount);
527#endif
528}
529
530void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
531        size_t start, size_t count, size_t contextCount, int dirFlags,
532        jfloat* outAdvances, jfloat* outTotalAdvance,
533        jchar** outGlyphs, size_t* outGlyphsCount) {
534
535    bool isRTL = dirFlags & 0x1;
536
537    HB_ShaperItem shaperItem;
538    HB_FontRec font;
539    FontData fontData;
540    shapeWithHarfbuzz(&shaperItem, &font, &fontData, paint, chars, start, count,
541            contextCount, dirFlags);
542
543#if DEBUG_GLYPHS
544    LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs,
545            shaperItem.kerning_applied);
546    LOGD("         -- string= '%s'", String8(chars + start, count).string());
547    LOGD("         -- isDevKernText=%d", paint->isDevKernText());
548#endif
549
550    if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) {
551#if DEBUG_GLYPHS
552    LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0");
553#endif
554        for (size_t i = 0; i < count; i++) {
555            outAdvances[i] = 0;
556        }
557        *outTotalAdvance = 0;
558
559        if (outGlyphs) {
560            *outGlyphsCount = 0;
561            *outGlyphs = new jchar[0];
562        }
563
564        // Cleaning
565        deleteGlyphArrays(&shaperItem);
566        HB_FreeFace(shaperItem.face);
567        return;
568    }
569    // Get Advances and their total
570    jfloat totalAdvance = outAdvances[0] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]);
571    for (size_t i = 1; i < count; i++) {
572        size_t clusterPrevious = shaperItem.log_clusters[i - 1];
573        size_t cluster = shaperItem.log_clusters[i];
574        if (cluster == clusterPrevious) {
575            outAdvances[i] = 0;
576        } else {
577            totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]);
578        }
579    }
580    *outTotalAdvance = totalAdvance;
581
582#if DEBUG_ADVANCES
583    for (size_t i = 0; i < count; i++) {
584        LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i,
585                outAdvances[i], shaperItem.log_clusters[i], totalAdvance);
586    }
587#endif
588
589    // Get Glyphs
590    if (outGlyphs) {
591        *outGlyphsCount = shaperItem.num_glyphs;
592        *outGlyphs = new jchar[shaperItem.num_glyphs];
593        for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
594            (*outGlyphs)[i] = (jchar) shaperItem.glyphs[i];
595        }
596    }
597
598    // Cleaning
599    deleteGlyphArrays(&shaperItem);
600    HB_FreeFace(shaperItem.face);
601}
602
603void TextLayoutCacheValue::computeAdvancesWithICU(SkPaint* paint, const UChar* chars,
604        size_t start, size_t count, size_t contextCount, int dirFlags,
605        jfloat* outAdvances, jfloat* outTotalAdvance) {
606    SkAutoSTMalloc<CHAR_BUFFER_SIZE, jchar> tempBuffer(contextCount);
607    jchar* buffer = tempBuffer.get();
608    SkScalar* scalarArray = (SkScalar*)outAdvances;
609
610    // this is where we'd call harfbuzz
611    // for now we just use ushape.c
612    size_t widths;
613    const jchar* text;
614    if (dirFlags & 0x1) { // rtl, call arabic shaping in case
615        UErrorCode status = U_ZERO_ERROR;
616        // Use fixed length since we need to keep start and count valid
617        u_shapeArabic(chars, contextCount, buffer, contextCount,
618                U_SHAPE_LENGTH_FIXED_SPACES_NEAR |
619                U_SHAPE_TEXT_DIRECTION_LOGICAL | U_SHAPE_LETTERS_SHAPE |
620                U_SHAPE_X_LAMALEF_SUB_ALTERNATE, &status);
621        // we shouldn't fail unless there's an out of memory condition,
622        // in which case we're hosed anyway
623        for (int i = start, e = i + count; i < e; ++i) {
624            if (buffer[i] == UNICODE_NOT_A_CHAR) {
625                buffer[i] = UNICODE_ZWSP; // zero-width-space for skia
626            }
627        }
628        text = buffer + start;
629        widths = paint->getTextWidths(text, count << 1, scalarArray);
630    } else {
631        text = chars + start;
632        widths = paint->getTextWidths(text, count << 1, scalarArray);
633    }
634
635    jfloat totalAdvance = 0;
636    if (widths < count) {
637#if DEBUG_ADVANCES
638    LOGD("ICU -- count=%d", widths);
639#endif
640        // Skia operates on code points, not code units, so surrogate pairs return only
641        // one value. Expand the result so we have one value per UTF-16 code unit.
642
643        // Note, skia's getTextWidth gets confused if it encounters a surrogate pair,
644        // leaving the remaining widths zero.  Not nice.
645        for (size_t i = 0, p = 0; i < widths; ++i) {
646            totalAdvance += outAdvances[p++] = SkScalarToFloat(scalarArray[i]);
647            if (p < count &&
648                    text[p] >= UNICODE_FIRST_LOW_SURROGATE &&
649                    text[p] < UNICODE_FIRST_PRIVATE_USE &&
650                    text[p-1] >= UNICODE_FIRST_HIGH_SURROGATE &&
651                    text[p-1] < UNICODE_FIRST_LOW_SURROGATE) {
652                outAdvances[p++] = 0;
653            }
654#if DEBUG_ADVANCES
655            LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
656#endif
657        }
658    } else {
659#if DEBUG_ADVANCES
660    LOGD("ICU -- count=%d", count);
661#endif
662        for (size_t i = 0; i < count; i++) {
663            totalAdvance += outAdvances[i] = SkScalarToFloat(scalarArray[i]);
664#if DEBUG_ADVANCES
665            LOGD("icu-adv = %f - total = %f", outAdvances[i], totalAdvance);
666#endif
667        }
668    }
669    *outTotalAdvance = totalAdvance;
670}
671
672void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem* shaperItem) {
673    delete[] shaperItem->glyphs;
674    delete[] shaperItem->attributes;
675    delete[] shaperItem->advances;
676    delete[] shaperItem->offsets;
677}
678
679void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem* shaperItem, int size) {
680    shaperItem->glyphs = new HB_Glyph[size];
681    shaperItem->attributes = new HB_GlyphAttributes[size];
682    shaperItem->advances = new HB_Fixed[size];
683    shaperItem->offsets = new HB_FixedPoint[size];
684    shaperItem->num_glyphs = size;
685}
686
687void TextLayoutCacheValue::resetGlyphArrays(HB_ShaperItem* shaperItem) {
688    int size = shaperItem->num_glyphs;
689    // All the types here don't have pointers. It is safe to reset to
690    // zero unless Harfbuzz breaks the compatibility in the future.
691    memset(shaperItem->glyphs, 0, size * sizeof(shaperItem->glyphs[0]));
692    memset(shaperItem->attributes, 0, size * sizeof(shaperItem->attributes[0]));
693    memset(shaperItem->advances, 0, size * sizeof(shaperItem->advances[0]));
694    memset(shaperItem->offsets, 0, size * sizeof(shaperItem->offsets[0]));
695}
696
697
698} // namespace android
699