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
22extern "C" {
23  #include "harfbuzz-unicode.h"
24}
25
26namespace android {
27
28//--------------------------------------------------------------------------------------------------
29#if USE_TEXT_LAYOUT_CACHE
30    ANDROID_SINGLETON_STATIC_INSTANCE(TextLayoutCache);
31#endif
32//--------------------------------------------------------------------------------------------------
33
34TextLayoutCache::TextLayoutCache() :
35        mCache(GenerationCache<TextLayoutCacheKey, sp<TextLayoutCacheValue> >::kUnlimitedCapacity),
36        mSize(0), mMaxSize(MB(DEFAULT_TEXT_LAYOUT_CACHE_SIZE_IN_MB)),
37        mCacheHitCount(0), mNanosecondsSaved(0) {
38    init();
39}
40
41TextLayoutCache::~TextLayoutCache() {
42    mCache.clear();
43}
44
45void TextLayoutCache::init() {
46    mCache.setOnEntryRemovedListener(this);
47
48    mDebugLevel = readRtlDebugLevel();
49    mDebugEnabled = mDebugLevel & kRtlDebugCaches;
50    LOGD("Using debug level: %d - Debug Enabled: %d", mDebugLevel, mDebugEnabled);
51
52    mCacheStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
53
54    if (mDebugEnabled) {
55        LOGD("Initialization is done - Start time: %lld", mCacheStartTime);
56    }
57
58    mInitialized = true;
59}
60
61/*
62 * Size management
63 */
64
65uint32_t TextLayoutCache::getSize() {
66    return mSize;
67}
68
69uint32_t TextLayoutCache::getMaxSize() {
70    return mMaxSize;
71}
72
73void TextLayoutCache::setMaxSize(uint32_t maxSize) {
74    mMaxSize = maxSize;
75    removeOldests();
76}
77
78void TextLayoutCache::removeOldests() {
79    while (mSize > mMaxSize) {
80        mCache.removeOldest();
81    }
82}
83
84/**
85 *  Callbacks
86 */
87void TextLayoutCache::operator()(TextLayoutCacheKey& text, sp<TextLayoutCacheValue>& desc) {
88    if (desc != NULL) {
89        size_t totalSizeToDelete = text.getSize() + desc->getSize();
90        mSize -= totalSizeToDelete;
91        if (mDebugEnabled) {
92            LOGD("Cache value deleted, size = %d", totalSizeToDelete);
93        }
94        desc.clear();
95    }
96}
97
98/*
99 * Cache clearing
100 */
101void TextLayoutCache::clear() {
102    mCache.clear();
103}
104
105/*
106 * Caching
107 */
108sp<TextLayoutCacheValue> TextLayoutCache::getValue(SkPaint* paint,
109            const jchar* text, jint start, jint count, jint contextCount, jint dirFlags) {
110    AutoMutex _l(mLock);
111    nsecs_t startTime = 0;
112    if (mDebugEnabled) {
113        startTime = systemTime(SYSTEM_TIME_MONOTONIC);
114    }
115
116    // Create the key
117    TextLayoutCacheKey key(paint, text, start, count, contextCount, dirFlags);
118
119    // Get value from cache if possible
120    sp<TextLayoutCacheValue> value = mCache.get(key);
121
122    // Value not found for the key, we need to add a new value in the cache
123    if (value == NULL) {
124        if (mDebugEnabled) {
125            startTime = systemTime(SYSTEM_TIME_MONOTONIC);
126        }
127
128        value = new TextLayoutCacheValue();
129
130        // Compute advances and store them
131        value->computeValues(paint, text, start, count, contextCount, dirFlags);
132
133        nsecs_t endTime = systemTime(SYSTEM_TIME_MONOTONIC);
134
135        // Don't bother to add in the cache if the entry is too big
136        size_t size = key.getSize() + value->getSize();
137        if (size <= mMaxSize) {
138            // Cleanup to make some room if needed
139            if (mSize + size > mMaxSize) {
140                if (mDebugEnabled) {
141                    LOGD("Need to clean some entries for making some room for a new entry");
142                }
143                while (mSize + size > mMaxSize) {
144                    // This will call the callback
145                    mCache.removeOldest();
146                }
147            }
148
149            // Update current cache size
150            mSize += size;
151
152            // Copy the text when we insert the new entry
153            key.internalTextCopy();
154            mCache.put(key, value);
155
156            if (mDebugEnabled) {
157                // Update timing information for statistics
158                value->setElapsedTime(endTime - startTime);
159
160                LOGD("CACHE MISS: Added entry with "
161                        "count=%d, entry size %d bytes, remaining space %d bytes"
162                        " - Compute time in nanos: %d - Text='%s' ",
163                        count, size, mMaxSize - mSize, value->getElapsedTime(),
164                        String8(text, count).string());
165            }
166        } else {
167            if (mDebugEnabled) {
168                LOGD("CACHE MISS: Calculated but not storing entry because it is too big "
169                        "with start=%d count=%d contextCount=%d, "
170                        "entry size %d bytes, remaining space %d bytes"
171                        " - Compute time in nanos: %lld - Text='%s'",
172                        start, count, contextCount, size, mMaxSize - mSize, endTime,
173                        String8(text, count).string());
174            }
175        }
176    } else {
177        // This is a cache hit, just log timestamp and user infos
178        if (mDebugEnabled) {
179            nsecs_t elapsedTimeThruCacheGet = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
180            mNanosecondsSaved += (value->getElapsedTime() - elapsedTimeThruCacheGet);
181            ++mCacheHitCount;
182
183            if (value->getElapsedTime() > 0) {
184                float deltaPercent = 100 * ((value->getElapsedTime() - elapsedTimeThruCacheGet)
185                        / ((float)value->getElapsedTime()));
186                LOGD("CACHE HIT #%d with start=%d count=%d contextCount=%d"
187                        "- Compute time in nanos: %d - "
188                        "Cache get time in nanos: %lld - Gain in percent: %2.2f - Text='%s' ",
189                        mCacheHitCount, start, count, contextCount,
190                        value->getElapsedTime(), elapsedTimeThruCacheGet, deltaPercent,
191                        String8(text, count).string());
192            }
193            if (mCacheHitCount % DEFAULT_DUMP_STATS_CACHE_HIT_INTERVAL == 0) {
194                dumpCacheStats();
195            }
196        }
197    }
198    return value;
199}
200
201void TextLayoutCache::dumpCacheStats() {
202    float remainingPercent = 100 * ((mMaxSize - mSize) / ((float)mMaxSize));
203    float timeRunningInSec = (systemTime(SYSTEM_TIME_MONOTONIC) - mCacheStartTime) / 1000000000;
204    LOGD("------------------------------------------------");
205    LOGD("Cache stats");
206    LOGD("------------------------------------------------");
207    LOGD("pid       : %d", getpid());
208    LOGD("running   : %.0f seconds", timeRunningInSec);
209    LOGD("entries   : %d", mCache.size());
210    LOGD("size      : %d bytes", mMaxSize);
211    LOGD("remaining : %d bytes or %2.2f percent", mMaxSize - mSize, remainingPercent);
212    LOGD("hits      : %d", mCacheHitCount);
213    LOGD("saved     : %lld milliseconds", mNanosecondsSaved / 1000000);
214    LOGD("------------------------------------------------");
215}
216
217/**
218 * TextLayoutCacheKey
219 */
220TextLayoutCacheKey::TextLayoutCacheKey(): text(NULL), start(0), count(0), contextCount(0),
221        dirFlags(0), typeface(NULL), textSize(0), textSkewX(0), textScaleX(0), flags(0),
222        hinting(SkPaint::kNo_Hinting)  {
223}
224
225TextLayoutCacheKey::TextLayoutCacheKey(const SkPaint* paint, const UChar* text,
226        size_t start, size_t count, size_t contextCount, int dirFlags) :
227            text(text), start(start), count(count), contextCount(contextCount),
228            dirFlags(dirFlags) {
229    typeface = paint->getTypeface();
230    textSize = paint->getTextSize();
231    textSkewX = paint->getTextSkewX();
232    textScaleX = paint->getTextScaleX();
233    flags = paint->getFlags();
234    hinting = paint->getHinting();
235}
236
237TextLayoutCacheKey::TextLayoutCacheKey(const TextLayoutCacheKey& other) :
238        text(NULL),
239        textCopy(other.textCopy),
240        start(other.start),
241        count(other.count),
242        contextCount(other.contextCount),
243        dirFlags(other.dirFlags),
244        typeface(other.typeface),
245        textSize(other.textSize),
246        textSkewX(other.textSkewX),
247        textScaleX(other.textScaleX),
248        flags(other.flags),
249        hinting(other.hinting) {
250    if (other.text) {
251        textCopy.setTo(other.text, other.contextCount);
252    }
253}
254
255int TextLayoutCacheKey::compare(const TextLayoutCacheKey& lhs, const TextLayoutCacheKey& rhs) {
256    int deltaInt = lhs.start - rhs.start;
257    if (deltaInt != 0) return (deltaInt);
258
259    deltaInt = lhs.count - rhs.count;
260    if (deltaInt != 0) return (deltaInt);
261
262    deltaInt = lhs.contextCount - rhs.contextCount;
263    if (deltaInt != 0) return (deltaInt);
264
265    if (lhs.typeface < rhs.typeface) return -1;
266    if (lhs.typeface > rhs.typeface) return +1;
267
268    if (lhs.textSize < rhs.textSize) return -1;
269    if (lhs.textSize > rhs.textSize) return +1;
270
271    if (lhs.textSkewX < rhs.textSkewX) return -1;
272    if (lhs.textSkewX > rhs.textSkewX) return +1;
273
274    if (lhs.textScaleX < rhs.textScaleX) return -1;
275    if (lhs.textScaleX > rhs.textScaleX) return +1;
276
277    deltaInt = lhs.flags - rhs.flags;
278    if (deltaInt != 0) return (deltaInt);
279
280    deltaInt = lhs.hinting - rhs.hinting;
281    if (deltaInt != 0) return (deltaInt);
282
283    deltaInt = lhs.dirFlags - rhs.dirFlags;
284    if (deltaInt) return (deltaInt);
285
286    return memcmp(lhs.getText(), rhs.getText(), lhs.contextCount * sizeof(UChar));
287}
288
289void TextLayoutCacheKey::internalTextCopy() {
290    textCopy.setTo(text, contextCount);
291    text = NULL;
292}
293
294size_t TextLayoutCacheKey::getSize() {
295    return sizeof(TextLayoutCacheKey) + sizeof(UChar) * contextCount;
296}
297
298/**
299 * TextLayoutCacheValue
300 */
301TextLayoutCacheValue::TextLayoutCacheValue() :
302        mTotalAdvance(0), mElapsedTime(0) {
303}
304
305void TextLayoutCacheValue::setElapsedTime(uint32_t time) {
306    mElapsedTime = time;
307}
308
309uint32_t TextLayoutCacheValue::getElapsedTime() {
310    return mElapsedTime;
311}
312
313void TextLayoutCacheValue::computeValues(SkPaint* paint, const UChar* chars,
314        size_t start, size_t count, size_t contextCount, int dirFlags) {
315    // Give a hint for advances, glyphs and log clusters vectors size
316    mAdvances.setCapacity(contextCount);
317    mGlyphs.setCapacity(contextCount);
318
319    computeValuesWithHarfbuzz(paint, chars, start, count, contextCount, dirFlags,
320            &mAdvances, &mTotalAdvance, &mGlyphs);
321#if DEBUG_ADVANCES
322    LOGD("Advances - start=%d, count=%d, countextCount=%d, totalAdvance=%f", start, count,
323            contextCount, mTotalAdvance);
324#endif
325}
326
327size_t TextLayoutCacheValue::getSize() {
328    return sizeof(TextLayoutCacheValue) + sizeof(jfloat) * mAdvances.capacity() +
329            sizeof(jchar) * mGlyphs.capacity();
330}
331
332void TextLayoutCacheValue::initShaperItem(HB_ShaperItem& shaperItem, HB_FontRec* font,
333        FontData* fontData, SkPaint* paint, const UChar* chars, size_t contextCount) {
334    // Zero the Shaper struct
335    memset(&shaperItem, 0, sizeof(shaperItem));
336
337    font->klass = &harfbuzzSkiaClass;
338    font->userData = 0;
339
340    // The values which harfbuzzSkiaClass returns are already scaled to
341    // pixel units, so we just set all these to one to disable further
342    // scaling.
343    font->x_ppem = 1;
344    font->y_ppem = 1;
345    font->x_scale = 1;
346    font->y_scale = 1;
347
348    // Reset kerning
349    shaperItem.kerning_applied = false;
350
351    // Define font data
352    fontData->typeFace = paint->getTypeface();
353    fontData->textSize = paint->getTextSize();
354    fontData->textSkewX = paint->getTextSkewX();
355    fontData->textScaleX = paint->getTextScaleX();
356    fontData->flags = paint->getFlags();
357    fontData->hinting = paint->getHinting();
358
359    shaperItem.font = font;
360    shaperItem.font->userData = fontData;
361
362    shaperItem.face = HB_NewFace(NULL, harfbuzzSkiaGetTable);
363
364    // We cannot know, ahead of time, how many glyphs a given script run
365    // will produce. We take a guess that script runs will not produce more
366    // than twice as many glyphs as there are code points plus a bit of
367    // padding and fallback if we find that we are wrong.
368    createGlyphArrays(shaperItem, (contextCount + 2) * 2);
369
370    // Set the string properties
371    shaperItem.string = chars;
372    shaperItem.stringLength = contextCount;
373}
374
375void TextLayoutCacheValue::freeShaperItem(HB_ShaperItem& shaperItem) {
376    deleteGlyphArrays(shaperItem);
377    HB_FreeFace(shaperItem.face);
378}
379
380void TextLayoutCacheValue::shapeRun(HB_ShaperItem& shaperItem, size_t start, size_t count,
381        bool isRTL) {
382    // Update Harfbuzz Shaper
383    shaperItem.item.pos = start;
384    shaperItem.item.length = count;
385    shaperItem.item.bidiLevel = isRTL;
386
387    shaperItem.item.script = isRTL ? HB_Script_Arabic : HB_Script_Common;
388
389    // Shape
390    assert(shaperItem.item.length > 0); // Harfbuzz will overwrite other memory if length is 0.
391    while (!HB_ShapeItem(&shaperItem)) {
392        // We overflowed our arrays. Resize and retry.
393        // HB_ShapeItem fills in shaperItem.num_glyphs with the needed size.
394        deleteGlyphArrays(shaperItem);
395        createGlyphArrays(shaperItem, shaperItem.num_glyphs << 1);
396    }
397}
398
399void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar* chars,
400        size_t start, size_t count, size_t contextCount, int dirFlags,
401        Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
402        Vector<jchar>* const outGlyphs) {
403        if (!count) {
404            *outTotalAdvance = 0;
405            return;
406        }
407
408        UBiDiLevel bidiReq = 0;
409        bool forceLTR = false;
410        bool forceRTL = false;
411
412        switch (dirFlags) {
413            case kBidi_LTR: bidiReq = 0; break; // no ICU constant, canonical LTR level
414            case kBidi_RTL: bidiReq = 1; break; // no ICU constant, canonical RTL level
415            case kBidi_Default_LTR: bidiReq = UBIDI_DEFAULT_LTR; break;
416            case kBidi_Default_RTL: bidiReq = UBIDI_DEFAULT_RTL; break;
417            case kBidi_Force_LTR: forceLTR = true; break; // every char is LTR
418            case kBidi_Force_RTL: forceRTL = true; break; // every char is RTL
419        }
420
421        HB_ShaperItem shaperItem;
422        HB_FontRec font;
423        FontData fontData;
424
425        // Initialize Harfbuzz Shaper
426        initShaperItem(shaperItem, &font, &fontData, paint, chars, contextCount);
427
428        bool useSingleRun = false;
429        bool isRTL = forceRTL;
430        if (forceLTR || forceRTL) {
431            useSingleRun = true;
432        } else {
433            UBiDi* bidi = ubidi_open();
434            if (bidi) {
435                UErrorCode status = U_ZERO_ERROR;
436#if DEBUG_GLYPHS
437                LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq);
438#endif
439                ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
440                if (U_SUCCESS(status)) {
441                    int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
442                    ssize_t rc = ubidi_countRuns(bidi, &status);
443#if DEBUG_GLYPHS
444                    LOGD("computeValuesWithHarfbuzz -- dirFlags=%d run-count=%d paraDir=%d",
445                            dirFlags, rc, paraDir);
446#endif
447                    if (U_SUCCESS(status) && rc == 1) {
448                        // Normal case: one run, status is ok
449                        isRTL = (paraDir == 1);
450                        useSingleRun = true;
451                    } else if (!U_SUCCESS(status) || rc < 1) {
452                        LOGW("computeValuesWithHarfbuzz -- need to force to single run");
453                        isRTL = (paraDir == 1);
454                        useSingleRun = true;
455                    } else {
456                        int32_t end = start + count;
457                        for (size_t i = 0; i < size_t(rc); ++i) {
458                            int32_t startRun = -1;
459                            int32_t lengthRun = -1;
460                            UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
461
462                            if (startRun == -1 || lengthRun == -1) {
463                                // Something went wrong when getting the visual run, need to clear
464                                // already computed data before doing a single run pass
465                                LOGW("computeValuesWithHarfbuzz -- visual run is not valid");
466                                outGlyphs->clear();
467                                outAdvances->clear();
468                                *outTotalAdvance = 0;
469                                isRTL = (paraDir == 1);
470                                useSingleRun = true;
471                                break;
472                            }
473
474                            if (startRun >= end) {
475                                continue;
476                            }
477                            int32_t endRun = startRun + lengthRun;
478                            if (endRun <= int32_t(start)) {
479                                continue;
480                            }
481                            if (startRun < int32_t(start)) {
482                                startRun = int32_t(start);
483                            }
484                            if (endRun > end) {
485                                endRun = end;
486                            }
487
488                            lengthRun = endRun - startRun;
489                            isRTL = (runDir == UBIDI_RTL);
490                            jfloat runTotalAdvance = 0;
491#if DEBUG_GLYPHS
492                            LOGD("computeValuesWithHarfbuzz -- run-start=%d run-len=%d isRTL=%d",
493                                    startRun, lengthRun, isRTL);
494#endif
495                            computeRunValuesWithHarfbuzz(shaperItem, paint,
496                                    startRun, lengthRun, isRTL,
497                                    outAdvances, &runTotalAdvance, outGlyphs);
498
499                            *outTotalAdvance += runTotalAdvance;
500                        }
501                    }
502                } else {
503                    LOGW("computeValuesWithHarfbuzz -- cannot set Para");
504                    useSingleRun = true;
505                    isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
506                }
507                ubidi_close(bidi);
508            } else {
509                LOGW("computeValuesWithHarfbuzz -- cannot ubidi_open()");
510                useSingleRun = true;
511                isRTL = (bidiReq = 1) || (bidiReq = UBIDI_DEFAULT_RTL);
512            }
513        }
514
515        // Default single run case
516        if (useSingleRun){
517#if DEBUG_GLYPHS
518            LOGD("computeValuesWithHarfbuzz -- Using a SINGLE Run "
519                    "-- run-start=%d run-len=%d isRTL=%d", start, count, isRTL);
520#endif
521            computeRunValuesWithHarfbuzz(shaperItem, paint,
522                    start, count, isRTL,
523                    outAdvances, outTotalAdvance, outGlyphs);
524        }
525
526        // Cleaning
527        freeShaperItem(shaperItem);
528
529#if DEBUG_GLYPHS
530        LOGD("computeValuesWithHarfbuzz -- total-glyphs-count=%d", outGlyphs->size());
531#endif
532}
533
534static void logGlyphs(HB_ShaperItem shaperItem) {
535    LOGD("Got glyphs - count=%d", shaperItem.num_glyphs);
536    for (size_t i = 0; i < shaperItem.num_glyphs; i++) {
537        LOGD("      glyph[%d]=%d - offset.x=%f offset.y=%f", i, shaperItem.glyphs[i],
538                HBFixedToFloat(shaperItem.offsets[i].x),
539                HBFixedToFloat(shaperItem.offsets[i].y));
540    }
541}
542
543void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(HB_ShaperItem& shaperItem, SkPaint* paint,
544        size_t start, size_t count, bool isRTL,
545        Vector<jfloat>* const outAdvances, jfloat* outTotalAdvance,
546        Vector<jchar>* const outGlyphs) {
547    if (!count) {
548        *outTotalAdvance = 0;
549        return;
550    }
551
552    shapeRun(shaperItem, start, count, isRTL);
553
554#if DEBUG_GLYPHS
555    LOGD("HARFBUZZ -- num_glypth=%d - kerning_applied=%d", shaperItem.num_glyphs,
556            shaperItem.kerning_applied);
557    LOGD("         -- string= '%s'", String8(shaperItem.string + start, count).string());
558    LOGD("         -- isDevKernText=%d", paint->isDevKernText());
559
560    logGlyphs(shaperItem);
561#endif
562
563    if (shaperItem.advances == NULL || shaperItem.num_glyphs == 0) {
564#if DEBUG_GLYPHS
565    LOGD("HARFBUZZ -- advances array is empty or num_glypth = 0");
566#endif
567        outAdvances->insertAt(0, outAdvances->size(), count);
568        *outTotalAdvance = 0;
569        return;
570    }
571
572    // Get Advances and their total
573    jfloat currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]);
574    jfloat totalAdvance = currentAdvance;
575    outAdvances->add(currentAdvance);
576    for (size_t i = 1; i < count; i++) {
577        size_t clusterPrevious = shaperItem.log_clusters[i - 1];
578        size_t cluster = shaperItem.log_clusters[i];
579        if (cluster == clusterPrevious) {
580            outAdvances->add(0);
581        } else {
582            currentAdvance = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]);
583            totalAdvance += currentAdvance;
584            outAdvances->add(currentAdvance);
585        }
586    }
587    *outTotalAdvance = totalAdvance;
588
589#if DEBUG_ADVANCES
590    for (size_t i = 0; i < count; i++) {
591        LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i,
592                (*outAdvances)[i], shaperItem.log_clusters[i], totalAdvance);
593    }
594#endif
595
596    // Get Glyphs and reverse them in place if RTL
597    if (outGlyphs) {
598        size_t countGlyphs = shaperItem.num_glyphs;
599        for (size_t i = 0; i < countGlyphs; i++) {
600            jchar glyph = (jchar) shaperItem.glyphs[(!isRTL) ? i : countGlyphs - 1 - i];
601#if DEBUG_GLYPHS
602            LOGD("HARFBUZZ  -- glyph[%d]=%d", i, glyph);
603#endif
604            outGlyphs->add(glyph);
605        }
606    }
607}
608
609void TextLayoutCacheValue::deleteGlyphArrays(HB_ShaperItem& shaperItem) {
610    delete[] shaperItem.glyphs;
611    delete[] shaperItem.attributes;
612    delete[] shaperItem.advances;
613    delete[] shaperItem.offsets;
614    delete[] shaperItem.log_clusters;
615}
616
617void TextLayoutCacheValue::createGlyphArrays(HB_ShaperItem& shaperItem, int size) {
618    shaperItem.num_glyphs = size;
619
620    // These arrays are all indexed by glyph
621    shaperItem.glyphs = new HB_Glyph[size];
622    shaperItem.attributes = new HB_GlyphAttributes[size];
623    shaperItem.advances = new HB_Fixed[size];
624    shaperItem.offsets = new HB_FixedPoint[size];
625
626    // Although the log_clusters array is indexed by character, Harfbuzz expects that
627    // it is big enough to hold one element per glyph.  So we allocate log_clusters along
628    // with the other glyph arrays above.
629    shaperItem.log_clusters = new unsigned short[size];
630}
631
632} // namespace android
633