• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 "Minikin"
18 
19 #include <algorithm>
20 #include <fstream>
21 #include <iostream>  // for debugging
22 #include <math.h>
23 #include <string>
24 #include <unicode/ubidi.h>
25 #include <unicode/utf16.h>
26 #include <vector>
27 
28 #include <log/log.h>
29 #include <utils/JenkinsHash.h>
30 #include <utils/LruCache.h>
31 #include <utils/Singleton.h>
32 #include <utils/String16.h>
33 
34 #include <hb-icu.h>
35 #include <hb-ot.h>
36 
37 #include "FontLanguage.h"
38 #include "FontLanguageListCache.h"
39 #include "HbFontCache.h"
40 #include "LayoutUtils.h"
41 #include "MinikinInternal.h"
42 #include <minikin/Emoji.h>
43 #include <minikin/Layout.h>
44 
45 using std::string;
46 using std::vector;
47 
48 namespace minikin {
49 
50 const int kDirection_Mask = 0x1;
51 
52 struct LayoutContext {
53     MinikinPaint paint;
54     FontStyle style;
55     std::vector<hb_font_t*> hbFonts;  // parallel to mFaces
56 
clearHbFontsminikin::LayoutContext57     void clearHbFonts() {
58         for (size_t i = 0; i < hbFonts.size(); i++) {
59             hb_font_set_funcs(hbFonts[i], nullptr, nullptr, nullptr);
60             hb_font_destroy(hbFonts[i]);
61         }
62         hbFonts.clear();
63     }
64 };
65 
66 // Layout cache datatypes
67 
68 class LayoutCacheKey {
69 public:
LayoutCacheKey(const std::shared_ptr<FontCollection> & collection,const MinikinPaint & paint,FontStyle style,const uint16_t * chars,size_t start,size_t count,size_t nchars,bool dir)70     LayoutCacheKey(const std::shared_ptr<FontCollection>& collection, const MinikinPaint& paint,
71             FontStyle style, const uint16_t* chars, size_t start, size_t count, size_t nchars,
72             bool dir)
73             : mChars(chars), mNchars(nchars),
74             mStart(start), mCount(count), mId(collection->getId()), mStyle(style),
75             mSize(paint.size), mScaleX(paint.scaleX), mSkewX(paint.skewX),
76             mLetterSpacing(paint.letterSpacing),
77             mPaintFlags(paint.paintFlags), mHyphenEdit(paint.hyphenEdit), mIsRtl(dir),
78             mHash(computeHash()) {
79     }
80     bool operator==(const LayoutCacheKey &other) const;
81 
hash() const82     android::hash_t hash() const {
83         return mHash;
84     }
85 
copyText()86     void copyText() {
87         uint16_t* charsCopy = new uint16_t[mNchars];
88         memcpy(charsCopy, mChars, mNchars * sizeof(uint16_t));
89         mChars = charsCopy;
90     }
freeText()91     void freeText() {
92         delete[] mChars;
93         mChars = NULL;
94     }
95 
doLayout(Layout * layout,LayoutContext * ctx,const std::shared_ptr<FontCollection> & collection) const96     void doLayout(Layout* layout, LayoutContext* ctx,
97             const std::shared_ptr<FontCollection>& collection) const {
98         layout->mAdvances.resize(mCount, 0);
99         ctx->clearHbFonts();
100         layout->doLayoutRun(mChars, mStart, mCount, mNchars, mIsRtl, ctx, collection);
101     }
102 
103 private:
104     const uint16_t* mChars;
105     size_t mNchars;
106     size_t mStart;
107     size_t mCount;
108     uint32_t mId;  // for the font collection
109     FontStyle mStyle;
110     float mSize;
111     float mScaleX;
112     float mSkewX;
113     float mLetterSpacing;
114     int32_t mPaintFlags;
115     HyphenEdit mHyphenEdit;
116     bool mIsRtl;
117     // Note: any fields added to MinikinPaint must also be reflected here.
118     // TODO: language matching (possibly integrate into style)
119     android::hash_t mHash;
120 
121     android::hash_t computeHash() const;
122 };
123 
124 class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, Layout*> {
125 public:
LayoutCache()126     LayoutCache() : mCache(kMaxEntries) {
127         mCache.setOnEntryRemovedListener(this);
128     }
129 
clear()130     void clear() {
131         mCache.clear();
132     }
133 
get(LayoutCacheKey & key,LayoutContext * ctx,const std::shared_ptr<FontCollection> & collection)134     Layout* get(LayoutCacheKey& key, LayoutContext* ctx,
135             const std::shared_ptr<FontCollection>& collection) {
136         Layout* layout = mCache.get(key);
137         if (layout == NULL) {
138             key.copyText();
139             layout = new Layout();
140             key.doLayout(layout, ctx, collection);
141             mCache.put(key, layout);
142         }
143         return layout;
144     }
145 
146 private:
147     // callback for OnEntryRemoved
operator ()(LayoutCacheKey & key,Layout * & value)148     void operator()(LayoutCacheKey& key, Layout*& value) {
149         key.freeText();
150         delete value;
151     }
152 
153     android::LruCache<LayoutCacheKey, Layout*> mCache;
154 
155     //static const size_t kMaxEntries = LruCache<LayoutCacheKey, Layout*>::kUnlimitedCapacity;
156 
157     // TODO: eviction based on memory footprint; for now, we just use a constant
158     // number of strings
159     static const size_t kMaxEntries = 5000;
160 };
161 
disabledDecomposeCompatibility(hb_unicode_funcs_t *,hb_codepoint_t,hb_codepoint_t *,void *)162 static unsigned int disabledDecomposeCompatibility(hb_unicode_funcs_t*, hb_codepoint_t,
163                                                    hb_codepoint_t*, void*) {
164     return 0;
165 }
166 
167 class LayoutEngine : public ::android::Singleton<LayoutEngine> {
168 public:
LayoutEngine()169     LayoutEngine() {
170         unicodeFunctions = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
171         /* Disable the function used for compatibility decomposition */
172         hb_unicode_funcs_set_decompose_compatibility_func(
173                 unicodeFunctions, disabledDecomposeCompatibility, NULL, NULL);
174         hbBuffer = hb_buffer_create();
175         hb_buffer_set_unicode_funcs(hbBuffer, unicodeFunctions);
176     }
177 
178     hb_buffer_t* hbBuffer;
179     hb_unicode_funcs_t* unicodeFunctions;
180     LayoutCache layoutCache;
181 };
182 
operator ==(const LayoutCacheKey & other) const183 bool LayoutCacheKey::operator==(const LayoutCacheKey& other) const {
184     return mId == other.mId
185             && mStart == other.mStart
186             && mCount == other.mCount
187             && mStyle == other.mStyle
188             && mSize == other.mSize
189             && mScaleX == other.mScaleX
190             && mSkewX == other.mSkewX
191             && mLetterSpacing == other.mLetterSpacing
192             && mPaintFlags == other.mPaintFlags
193             && mHyphenEdit == other.mHyphenEdit
194             && mIsRtl == other.mIsRtl
195             && mNchars == other.mNchars
196             && !memcmp(mChars, other.mChars, mNchars * sizeof(uint16_t));
197 }
198 
computeHash() const199 android::hash_t LayoutCacheKey::computeHash() const {
200     uint32_t hash = android::JenkinsHashMix(0, mId);
201     hash = android::JenkinsHashMix(hash, mStart);
202     hash = android::JenkinsHashMix(hash, mCount);
203     hash = android::JenkinsHashMix(hash, hash_type(mStyle));
204     hash = android::JenkinsHashMix(hash, hash_type(mSize));
205     hash = android::JenkinsHashMix(hash, hash_type(mScaleX));
206     hash = android::JenkinsHashMix(hash, hash_type(mSkewX));
207     hash = android::JenkinsHashMix(hash, hash_type(mLetterSpacing));
208     hash = android::JenkinsHashMix(hash, hash_type(mPaintFlags));
209     hash = android::JenkinsHashMix(hash, hash_type(mHyphenEdit.getHyphen()));
210     hash = android::JenkinsHashMix(hash, hash_type(mIsRtl));
211     hash = android::JenkinsHashMixShorts(hash, mChars, mNchars);
212     return android::JenkinsHashWhiten(hash);
213 }
214 
hash_type(const LayoutCacheKey & key)215 android::hash_t hash_type(const LayoutCacheKey& key) {
216     return key.hash();
217 }
218 
join(const MinikinRect & r)219 void MinikinRect::join(const MinikinRect& r) {
220     if (isEmpty()) {
221         set(r);
222     } else if (!r.isEmpty()) {
223         mLeft = std::min(mLeft, r.mLeft);
224         mTop = std::min(mTop, r.mTop);
225         mRight = std::max(mRight, r.mRight);
226         mBottom = std::max(mBottom, r.mBottom);
227     }
228 }
229 
reset()230 void Layout::reset() {
231     mGlyphs.clear();
232     mFaces.clear();
233     mBounds.setEmpty();
234     mAdvances.clear();
235     mAdvance = 0;
236 }
237 
harfbuzzGetGlyphHorizontalAdvance(hb_font_t *,void * fontData,hb_codepoint_t glyph,void *)238 static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* /* hbFont */, void* fontData,
239         hb_codepoint_t glyph, void* /* userData */) {
240     MinikinPaint* paint = reinterpret_cast<MinikinPaint*>(fontData);
241     float advance = paint->font->GetHorizontalAdvance(glyph, *paint);
242     return 256 * advance + 0.5;
243 }
244 
harfbuzzGetGlyphHorizontalOrigin(hb_font_t *,void *,hb_codepoint_t,hb_position_t *,hb_position_t *,void *)245 static hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* /* hbFont */, void* /* fontData */,
246         hb_codepoint_t /* glyph */, hb_position_t* /* x */, hb_position_t* /* y */,
247         void* /* userData */) {
248     // Just return true, following the way that Harfbuzz-FreeType
249     // implementation does.
250     return true;
251 }
252 
getHbFontFuncs(bool forColorBitmapFont)253 hb_font_funcs_t* getHbFontFuncs(bool forColorBitmapFont) {
254     assertMinikinLocked();
255 
256     static hb_font_funcs_t* hbFuncs = nullptr;
257     static hb_font_funcs_t* hbFuncsForColorBitmap = nullptr;
258 
259     hb_font_funcs_t** funcs = forColorBitmapFont ? &hbFuncs : &hbFuncsForColorBitmap;
260     if (*funcs == nullptr) {
261         *funcs = hb_font_funcs_create();
262         if (forColorBitmapFont) {
263             // Don't override the h_advance function since we use HarfBuzz's implementation for
264             // emoji for performance reasons.
265             // Note that it is technically possible for a TrueType font to have outline and embedded
266             // bitmap at the same time. We ignore modified advances of hinted outline glyphs in that
267             // case.
268         } else {
269             // Override the h_advance function since we can't use HarfBuzz's implemenation. It may
270             // return the wrong value if the font uses hinting aggressively.
271             hb_font_funcs_set_glyph_h_advance_func(*funcs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);
272         }
273         hb_font_funcs_set_glyph_h_origin_func(*funcs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
274         hb_font_funcs_make_immutable(*funcs);
275     }
276     return *funcs;
277 }
278 
isColorBitmapFont(hb_font_t * font)279 static bool isColorBitmapFont(hb_font_t* font) {
280     hb_face_t* face = hb_font_get_face(font);
281     HbBlob cbdt(hb_face_reference_table(face, HB_TAG('C', 'B', 'D', 'T')));
282     return cbdt.size() > 0;
283 }
284 
HBFixedToFloat(hb_position_t v)285 static float HBFixedToFloat(hb_position_t v)
286 {
287     return scalbnf (v, -8);
288 }
289 
HBFloatToFixed(float v)290 static hb_position_t HBFloatToFixed(float v)
291 {
292     return scalbnf (v, +8);
293 }
294 
dump() const295 void Layout::dump() const {
296     for (size_t i = 0; i < mGlyphs.size(); i++) {
297         const LayoutGlyph& glyph = mGlyphs[i];
298         std::cout << glyph.glyph_id << ": " << glyph.x << ", " << glyph.y << std::endl;
299     }
300 }
301 
findFace(const FakedFont & face,LayoutContext * ctx)302 int Layout::findFace(const FakedFont& face, LayoutContext* ctx) {
303     unsigned int ix;
304     for (ix = 0; ix < mFaces.size(); ix++) {
305         if (mFaces[ix].font == face.font) {
306             return ix;
307         }
308     }
309     mFaces.push_back(face);
310     // Note: ctx == NULL means we're copying from the cache, no need to create
311     // corresponding hb_font object.
312     if (ctx != NULL) {
313         hb_font_t* font = getHbFontLocked(face.font);
314         hb_font_set_funcs(font, getHbFontFuncs(isColorBitmapFont(font)), &ctx->paint, 0);
315         ctx->hbFonts.push_back(font);
316     }
317     return ix;
318 }
319 
codePointToScript(hb_codepoint_t codepoint)320 static hb_script_t codePointToScript(hb_codepoint_t codepoint) {
321     static hb_unicode_funcs_t* u = 0;
322     if (!u) {
323         u = LayoutEngine::getInstance().unicodeFunctions;
324     }
325     return hb_unicode_script(u, codepoint);
326 }
327 
decodeUtf16(const uint16_t * chars,size_t len,ssize_t * iter)328 static hb_codepoint_t decodeUtf16(const uint16_t* chars, size_t len, ssize_t* iter) {
329     UChar32 result;
330     U16_NEXT(chars, *iter, (ssize_t) len, result);
331     if (U_IS_SURROGATE(result)) { // isolated surrogate
332         result = 0xFFFDu; // U+FFFD REPLACEMENT CHARACTER
333     }
334     return (hb_codepoint_t) result;
335 }
336 
getScriptRun(const uint16_t * chars,size_t len,ssize_t * iter)337 static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter) {
338     if (size_t(*iter) == len) {
339         return HB_SCRIPT_UNKNOWN;
340     }
341     uint32_t cp = decodeUtf16(chars, len, iter);
342     hb_script_t current_script = codePointToScript(cp);
343     for (;;) {
344         if (size_t(*iter) == len)
345             break;
346         const ssize_t prev_iter = *iter;
347         cp = decodeUtf16(chars, len, iter);
348         const hb_script_t script = codePointToScript(cp);
349         if (script != current_script) {
350             if (current_script == HB_SCRIPT_INHERITED ||
351                 current_script == HB_SCRIPT_COMMON) {
352                 current_script = script;
353             } else if (script == HB_SCRIPT_INHERITED ||
354                 script == HB_SCRIPT_COMMON) {
355                 continue;
356             } else {
357                 *iter = prev_iter;
358                 break;
359             }
360         }
361     }
362     if (current_script == HB_SCRIPT_INHERITED) {
363         current_script = HB_SCRIPT_COMMON;
364     }
365 
366     return current_script;
367 }
368 
369 /**
370  * Disable certain scripts (mostly those with cursive connection) from having letterspacing
371  * applied. See https://github.com/behdad/harfbuzz/issues/64 for more details.
372  */
isScriptOkForLetterspacing(hb_script_t script)373 static bool isScriptOkForLetterspacing(hb_script_t script) {
374     return !(
375             script == HB_SCRIPT_ARABIC ||
376             script == HB_SCRIPT_NKO ||
377             script == HB_SCRIPT_PSALTER_PAHLAVI ||
378             script == HB_SCRIPT_MANDAIC ||
379             script == HB_SCRIPT_MONGOLIAN ||
380             script == HB_SCRIPT_PHAGS_PA ||
381             script == HB_SCRIPT_DEVANAGARI ||
382             script == HB_SCRIPT_BENGALI ||
383             script == HB_SCRIPT_GURMUKHI ||
384             script == HB_SCRIPT_MODI ||
385             script == HB_SCRIPT_SHARADA ||
386             script == HB_SCRIPT_SYLOTI_NAGRI ||
387             script == HB_SCRIPT_TIRHUTA ||
388             script == HB_SCRIPT_OGHAM
389             );
390 }
391 
392 class BidiText {
393 public:
394     class Iter {
395     public:
396         struct RunInfo {
397             int32_t mRunStart;
398             int32_t mRunLength;
399             bool mIsRtl;
400         };
401 
402         Iter(UBiDi* bidi, size_t start, size_t end, size_t runIndex, size_t runCount, bool isRtl);
403 
operator !=(const Iter & other) const404         bool operator!= (const Iter& other) const {
405             return mIsEnd != other.mIsEnd || mNextRunIndex != other.mNextRunIndex
406                     || mBidi != other.mBidi;
407         }
408 
operator *() const409         const RunInfo& operator* () const {
410             return mRunInfo;
411         }
412 
operator ++()413         const Iter& operator++ () {
414             updateRunInfo();
415             return *this;
416         }
417 
418     private:
419         UBiDi* const mBidi;
420         bool mIsEnd;
421         size_t mNextRunIndex;
422         const size_t mRunCount;
423         const int32_t mStart;
424         const int32_t mEnd;
425         RunInfo mRunInfo;
426 
427         void updateRunInfo();
428     };
429 
430     BidiText(const uint16_t* buf, size_t start, size_t count, size_t bufSize, int bidiFlags);
431 
~BidiText()432     ~BidiText() {
433         if (mBidi) {
434             ubidi_close(mBidi);
435         }
436     }
437 
begin() const438     Iter begin () const {
439         return Iter(mBidi, mStart, mEnd, 0, mRunCount, mIsRtl);
440     }
441 
end() const442     Iter end() const {
443         return Iter(mBidi, mStart, mEnd, mRunCount, mRunCount, mIsRtl);
444     }
445 
446 private:
447     const size_t mStart;
448     const size_t mEnd;
449     const size_t mBufSize;
450     UBiDi* mBidi;
451     size_t mRunCount;
452     bool mIsRtl;
453 
454     BidiText(const BidiText&) = delete;
455     void operator=(const BidiText&) = delete;
456 };
457 
Iter(UBiDi * bidi,size_t start,size_t end,size_t runIndex,size_t runCount,bool isRtl)458 BidiText::Iter::Iter(UBiDi* bidi, size_t start, size_t end, size_t runIndex, size_t runCount,
459         bool isRtl)
460     : mBidi(bidi), mIsEnd(runIndex == runCount), mNextRunIndex(runIndex), mRunCount(runCount),
461       mStart(start), mEnd(end), mRunInfo() {
462     if (mRunCount == 1) {
463         mRunInfo.mRunStart = start;
464         mRunInfo.mRunLength = end - start;
465         mRunInfo.mIsRtl = isRtl;
466         mNextRunIndex = mRunCount;
467         return;
468     }
469     updateRunInfo();
470 }
471 
updateRunInfo()472 void BidiText::Iter::updateRunInfo() {
473     if (mNextRunIndex == mRunCount) {
474         // All runs have been iterated.
475         mIsEnd = true;
476         return;
477     }
478     int32_t startRun = -1;
479     int32_t lengthRun = -1;
480     const UBiDiDirection runDir = ubidi_getVisualRun(mBidi, mNextRunIndex, &startRun, &lengthRun);
481     mNextRunIndex++;
482     if (startRun == -1 || lengthRun == -1) {
483         ALOGE("invalid visual run");
484         // skip the invalid run.
485         updateRunInfo();
486         return;
487     }
488     const int32_t runEnd = std::min(startRun + lengthRun, mEnd);
489     mRunInfo.mRunStart = std::max(startRun, mStart);
490     mRunInfo.mRunLength = runEnd - mRunInfo.mRunStart;
491     if (mRunInfo.mRunLength <= 0) {
492         // skip the empty run.
493         updateRunInfo();
494         return;
495     }
496     mRunInfo.mIsRtl = (runDir == UBIDI_RTL);
497 }
498 
BidiText(const uint16_t * buf,size_t start,size_t count,size_t bufSize,int bidiFlags)499 BidiText::BidiText(const uint16_t* buf, size_t start, size_t count, size_t bufSize, int bidiFlags)
500     : mStart(start), mEnd(start + count), mBufSize(bufSize), mBidi(NULL), mRunCount(1),
501       mIsRtl((bidiFlags & kDirection_Mask) != 0) {
502     if (bidiFlags == kBidi_Force_LTR || bidiFlags == kBidi_Force_RTL) {
503         // force single run.
504         return;
505     }
506     mBidi = ubidi_open();
507     if (!mBidi) {
508         ALOGE("error creating bidi object");
509         return;
510     }
511     UErrorCode status = U_ZERO_ERROR;
512     // Set callbacks to override bidi classes of new emoji
513     ubidi_setClassCallback(mBidi, emojiBidiOverride, nullptr, nullptr, nullptr, &status);
514     if (!U_SUCCESS(status)) {
515         ALOGE("error setting bidi callback function, status = %d", status);
516         return;
517     }
518 
519     UBiDiLevel bidiReq = bidiFlags;
520     if (bidiFlags == kBidi_Default_LTR) {
521         bidiReq = UBIDI_DEFAULT_LTR;
522     } else if (bidiFlags == kBidi_Default_RTL) {
523         bidiReq = UBIDI_DEFAULT_RTL;
524     }
525     ubidi_setPara(mBidi, reinterpret_cast<const UChar*>(buf), mBufSize, bidiReq, NULL, &status);
526     if (!U_SUCCESS(status)) {
527         ALOGE("error calling ubidi_setPara, status = %d", status);
528         return;
529     }
530     const int paraDir = ubidi_getParaLevel(mBidi) & kDirection_Mask;
531     const ssize_t rc = ubidi_countRuns(mBidi, &status);
532     if (!U_SUCCESS(status) || rc < 0) {
533         ALOGW("error counting bidi runs, status = %d", status);
534     }
535     if (!U_SUCCESS(status) || rc <= 0) {
536         mIsRtl = (paraDir == kBidi_RTL);
537         return;
538     }
539     if (rc == 1) {
540         const UBiDiDirection runDir = ubidi_getVisualRun(mBidi, 0, nullptr, nullptr);
541         mIsRtl = (runDir == UBIDI_RTL);
542         return;
543     }
544     mRunCount = rc;
545 }
546 
doLayout(const uint16_t * buf,size_t start,size_t count,size_t bufSize,int bidiFlags,const FontStyle & style,const MinikinPaint & paint,const std::shared_ptr<FontCollection> & collection)547 void Layout::doLayout(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
548         int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
549         const std::shared_ptr<FontCollection>& collection) {
550     android::AutoMutex _l(gMinikinLock);
551 
552     LayoutContext ctx;
553     ctx.style = style;
554     ctx.paint = paint;
555 
556     reset();
557     mAdvances.resize(count, 0);
558 
559     for (const BidiText::Iter::RunInfo& runInfo : BidiText(buf, start, count, bufSize, bidiFlags)) {
560         doLayoutRunCached(buf, runInfo.mRunStart, runInfo.mRunLength, bufSize, runInfo.mIsRtl, &ctx,
561                 start, collection, this, NULL);
562     }
563     ctx.clearHbFonts();
564 }
565 
measureText(const uint16_t * buf,size_t start,size_t count,size_t bufSize,int bidiFlags,const FontStyle & style,const MinikinPaint & paint,const std::shared_ptr<FontCollection> & collection,float * advances)566 float Layout::measureText(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
567         int bidiFlags, const FontStyle &style, const MinikinPaint &paint,
568         const std::shared_ptr<FontCollection>& collection, float* advances) {
569     android::AutoMutex _l(gMinikinLock);
570 
571     LayoutContext ctx;
572     ctx.style = style;
573     ctx.paint = paint;
574 
575     float advance = 0;
576     for (const BidiText::Iter::RunInfo& runInfo : BidiText(buf, start, count, bufSize, bidiFlags)) {
577         float* advancesForRun = advances ? advances + (runInfo.mRunStart - start) : advances;
578         advance += doLayoutRunCached(buf, runInfo.mRunStart, runInfo.mRunLength, bufSize,
579                 runInfo.mIsRtl, &ctx, 0, collection, NULL, advancesForRun);
580     }
581 
582     ctx.clearHbFonts();
583     return advance;
584 }
585 
doLayoutRunCached(const uint16_t * buf,size_t start,size_t count,size_t bufSize,bool isRtl,LayoutContext * ctx,size_t dstStart,const std::shared_ptr<FontCollection> & collection,Layout * layout,float * advances)586 float Layout::doLayoutRunCached(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
587         bool isRtl, LayoutContext* ctx, size_t dstStart,
588         const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances) {
589     const uint32_t originalHyphen = ctx->paint.hyphenEdit.getHyphen();
590     float advance = 0;
591     if (!isRtl) {
592         // left to right
593         size_t wordstart =
594                 start == bufSize ? start : getPrevWordBreakForCache(buf, start + 1, bufSize);
595         size_t wordend;
596         for (size_t iter = start; iter < start + count; iter = wordend) {
597             wordend = getNextWordBreakForCache(buf, iter, bufSize);
598             // Only apply hyphen to the first or last word in the string.
599             uint32_t hyphen = originalHyphen;
600             if (iter != start) { // Not the first word
601                 hyphen &= ~HyphenEdit::MASK_START_OF_LINE;
602             }
603             if (wordend < start + count) { // Not the last word
604                 hyphen &= ~HyphenEdit::MASK_END_OF_LINE;
605             }
606             ctx->paint.hyphenEdit = hyphen;
607             size_t wordcount = std::min(start + count, wordend) - iter;
608             advance += doLayoutWord(buf + wordstart, iter - wordstart, wordcount,
609                     wordend - wordstart, isRtl, ctx, iter - dstStart, collection, layout,
610                     advances ? advances + (iter - start) : advances);
611             wordstart = wordend;
612         }
613     } else {
614         // right to left
615         size_t wordstart;
616         size_t end = start + count;
617         size_t wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize);
618         for (size_t iter = end; iter > start; iter = wordstart) {
619             wordstart = getPrevWordBreakForCache(buf, iter, bufSize);
620             // Only apply hyphen to the first (rightmost) or last (leftmost) word in the string.
621             uint32_t hyphen = originalHyphen;
622             if (wordstart > start) { // Not the first word
623                 hyphen &= ~HyphenEdit::MASK_START_OF_LINE;
624             }
625             if (iter != end) { // Not the last word
626                 hyphen &= ~HyphenEdit::MASK_END_OF_LINE;
627             }
628             ctx->paint.hyphenEdit = hyphen;
629             size_t bufStart = std::max(start, wordstart);
630             advance += doLayoutWord(buf + wordstart, bufStart - wordstart, iter - bufStart,
631                     wordend - wordstart, isRtl, ctx, bufStart - dstStart, collection, layout,
632                     advances ? advances + (bufStart - start) : advances);
633             wordend = wordstart;
634         }
635     }
636     return advance;
637 }
638 
doLayoutWord(const uint16_t * buf,size_t start,size_t count,size_t bufSize,bool isRtl,LayoutContext * ctx,size_t bufStart,const std::shared_ptr<FontCollection> & collection,Layout * layout,float * advances)639 float Layout::doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
640         bool isRtl, LayoutContext* ctx, size_t bufStart,
641         const std::shared_ptr<FontCollection>& collection, Layout* layout, float* advances) {
642     LayoutCache& cache = LayoutEngine::getInstance().layoutCache;
643     LayoutCacheKey key(collection, ctx->paint, ctx->style, buf, start, count, bufSize, isRtl);
644 
645     float wordSpacing = count == 1 && isWordSpace(buf[start]) ? ctx->paint.wordSpacing : 0;
646 
647     float advance;
648     if (ctx->paint.skipCache()) {
649         Layout layoutForWord;
650         key.doLayout(&layoutForWord, ctx, collection);
651         if (layout) {
652             layout->appendLayout(&layoutForWord, bufStart, wordSpacing);
653         }
654         if (advances) {
655             layoutForWord.getAdvances(advances);
656         }
657         advance = layoutForWord.getAdvance();
658     } else {
659         Layout* layoutForWord = cache.get(key, ctx, collection);
660         if (layout) {
661             layout->appendLayout(layoutForWord, bufStart, wordSpacing);
662         }
663         if (advances) {
664             layoutForWord->getAdvances(advances);
665         }
666         advance = layoutForWord->getAdvance();
667     }
668 
669     if (wordSpacing != 0) {
670         advance += wordSpacing;
671         if (advances) {
672             advances[0] += wordSpacing;
673         }
674     }
675     return advance;
676 }
677 
addFeatures(const string & str,vector<hb_feature_t> * features)678 static void addFeatures(const string &str, vector<hb_feature_t>* features) {
679     if (!str.size())
680         return;
681 
682     const char* start = str.c_str();
683     const char* end = start + str.size();
684 
685     while (start < end) {
686         static hb_feature_t feature;
687         const char* p = strchr(start, ',');
688         if (!p)
689             p = end;
690         /* We do not allow setting features on ranges.  As such, reject any
691          * setting that has non-universal range. */
692         if (hb_feature_from_string (start, p - start, &feature)
693                 && feature.start == 0 && feature.end == (unsigned int) -1)
694             features->push_back(feature);
695         start = p + 1;
696     }
697 }
698 
699 static const hb_codepoint_t CHAR_HYPHEN = 0x2010; /* HYPHEN */
700 
determineHyphenChar(hb_codepoint_t preferredHyphen,hb_font_t * font)701 static inline hb_codepoint_t determineHyphenChar(hb_codepoint_t preferredHyphen, hb_font_t* font) {
702     hb_codepoint_t glyph;
703     if (preferredHyphen == 0x058A /* ARMENIAN_HYPHEN */
704                 || preferredHyphen == 0x05BE /* HEBREW PUNCTUATION MAQAF */
705                 || preferredHyphen == 0x1400 /* CANADIAN SYLLABIC HYPHEN */) {
706         if (hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
707             return preferredHyphen;
708         } else {
709             // The original hyphen requested was not supported. Let's try and see if the
710             // Unicode hyphen is supported.
711             preferredHyphen = CHAR_HYPHEN;
712         }
713     }
714     if (preferredHyphen == CHAR_HYPHEN) { /* HYPHEN */
715         // Fallback to ASCII HYPHEN-MINUS if the font didn't have a glyph for the preferred hyphen.
716         // Note that we intentionally don't do anything special if the font doesn't have a
717         // HYPHEN-MINUS either, so a tofu could be shown, hinting towards something missing.
718         if (!hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
719             return 0x002D; // HYPHEN-MINUS
720         }
721     }
722     return preferredHyphen;
723 }
724 
addHyphenToHbBuffer(hb_buffer_t * buffer,hb_font_t * font,uint32_t hyphen,uint32_t cluster)725 static inline void addHyphenToHbBuffer(hb_buffer_t* buffer, hb_font_t* font, uint32_t hyphen,
726         uint32_t cluster) {
727     const uint32_t* hyphenStr = HyphenEdit::getHyphenString(hyphen);
728     while (*hyphenStr != 0) {
729         hb_codepoint_t hyphenChar = determineHyphenChar(*hyphenStr, font);
730         hb_buffer_add(buffer, hyphenChar, cluster);
731         hyphenStr++;
732     }
733 }
734 
735 // Returns the cluster value assigned to the first codepoint added to the buffer, which can be used
736 // to translate cluster values returned by HarfBuzz to input indices.
addToHbBuffer(hb_buffer_t * buffer,const uint16_t * buf,size_t start,size_t count,size_t bufSize,ssize_t scriptRunStart,ssize_t scriptRunEnd,HyphenEdit hyphenEdit,hb_font_t * hbFont)737 static inline uint32_t addToHbBuffer(hb_buffer_t* buffer,
738         const uint16_t* buf, size_t start, size_t count, size_t bufSize,
739         ssize_t scriptRunStart, ssize_t scriptRunEnd,
740         HyphenEdit hyphenEdit, hb_font_t* hbFont) {
741 
742     // Only hyphenate the very first script run for starting hyphens.
743     const uint32_t startHyphen = (scriptRunStart == 0)
744             ? hyphenEdit.getStart()
745             : HyphenEdit::NO_EDIT;
746     // Only hyphenate the very last script run for ending hyphens.
747     const uint32_t endHyphen = (static_cast<size_t>(scriptRunEnd) == count)
748             ? hyphenEdit.getEnd()
749             : HyphenEdit::NO_EDIT;
750 
751     // In the following code, we drop the pre-context and/or post-context if there is a
752     // hyphen edit at that end. This is not absolutely necessary, since HarfBuzz uses
753     // contexts only for joining scripts at the moment, e.g. to determine if the first or
754     // last letter of a text range to shape should take a joining form based on an
755     // adjacent letter or joiner (that comes from the context).
756     //
757     // TODO: Revisit this for:
758     // 1. Desperate breaks for joining scripts like Arabic (where it may be better to keep
759     //    the context);
760     // 2. Special features like start-of-word font features (not implemented in HarfBuzz
761     //    yet).
762 
763     // We don't have any start-of-line replacement edit yet, so we don't need to check for
764     // those.
765     if (HyphenEdit::isInsertion(startHyphen)) {
766         // A cluster value of zero guarantees that the inserted hyphen will be in the same
767         // cluster with the next codepoint, since there is no pre-context.
768         addHyphenToHbBuffer(buffer, hbFont, startHyphen, 0 /* cluster value */);
769     }
770 
771     const uint16_t* hbText;
772     int hbTextLength;
773     unsigned int hbItemOffset;
774     unsigned int hbItemLength = scriptRunEnd - scriptRunStart; // This is >= 1.
775 
776     const bool hasEndInsertion = HyphenEdit::isInsertion(endHyphen);
777     const bool hasEndReplacement = HyphenEdit::isReplacement(endHyphen);
778     if (hasEndReplacement) {
779         // Skip the last code unit while copying the buffer for HarfBuzz if it's a replacement. We
780         // don't need to worry about non-BMP characters yet since replacements are only done for
781         // code units at the moment.
782         hbItemLength -= 1;
783     }
784 
785     if (startHyphen == HyphenEdit::NO_EDIT) {
786         // No edit at the beginning. Use the whole pre-context.
787         hbText = buf;
788         hbItemOffset = start + scriptRunStart;
789     } else {
790         // There's an edit at the beginning. Drop the pre-context and start the buffer at where we
791         // want to start shaping.
792         hbText = buf + start + scriptRunStart;
793         hbItemOffset = 0;
794     }
795 
796     if (endHyphen == HyphenEdit::NO_EDIT) {
797         // No edit at the end, use the whole post-context.
798         hbTextLength = (buf + bufSize) - hbText;
799     } else {
800         // There is an edit at the end. Drop the post-context.
801         hbTextLength = hbItemOffset + hbItemLength;
802     }
803 
804     hb_buffer_add_utf16(buffer, hbText, hbTextLength, hbItemOffset, hbItemLength);
805 
806     unsigned int numCodepoints;
807     hb_glyph_info_t* cpInfo = hb_buffer_get_glyph_infos(buffer, &numCodepoints);
808 
809     // Add the hyphen at the end, if there's any.
810     if (hasEndInsertion || hasEndReplacement) {
811         // When a hyphen is inserted, by assigning the added hyphen and the last
812         // codepoint added to the HarfBuzz buffer to the same cluster, we can make sure
813         // that they always remain in the same cluster, even if the last codepoint gets
814         // merged into another cluster (for example when it's a combining mark).
815         //
816         // When a replacement happens instead, we want it to get the cluster value of
817         // the character it's replacing, which is one "codepoint length" larger than
818         // the last cluster. But since the character replaced is always just one
819         // code unit, we can just add 1.
820         uint32_t hyphenCluster;
821         if (numCodepoints == 0) {
822             // Nothing was added to the HarfBuzz buffer. This can only happen if
823             // we have a replacement that is replacing a one-code unit script run.
824             hyphenCluster = 0;
825         } else {
826             hyphenCluster = cpInfo[numCodepoints - 1].cluster + (uint32_t) hasEndReplacement;
827         }
828         addHyphenToHbBuffer(buffer, hbFont, endHyphen, hyphenCluster);
829         // Since we have just added to the buffer, cpInfo no longer necessarily points to
830         // the right place. Refresh it.
831         cpInfo = hb_buffer_get_glyph_infos(buffer, nullptr /* we don't need the size */);
832     }
833     return cpInfo[0].cluster;
834 }
835 
836 
doLayoutRun(const uint16_t * buf,size_t start,size_t count,size_t bufSize,bool isRtl,LayoutContext * ctx,const std::shared_ptr<FontCollection> & collection)837 void Layout::doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
838         bool isRtl, LayoutContext* ctx, const std::shared_ptr<FontCollection>& collection) {
839     hb_buffer_t* buffer = LayoutEngine::getInstance().hbBuffer;
840     vector<FontCollection::Run> items;
841     collection->itemize(buf + start, count, ctx->style, &items);
842 
843     vector<hb_feature_t> features;
844     // Disable default-on non-required ligature features if letter-spacing
845     // See http://dev.w3.org/csswg/css-text-3/#letter-spacing-property
846     // "When the effective spacing between two characters is not zero (due to
847     // either justification or a non-zero value of letter-spacing), user agents
848     // should not apply optional ligatures."
849     if (fabs(ctx->paint.letterSpacing) > 0.03)
850     {
851         static const hb_feature_t no_liga = { HB_TAG('l', 'i', 'g', 'a'), 0, 0, ~0u };
852         static const hb_feature_t no_clig = { HB_TAG('c', 'l', 'i', 'g'), 0, 0, ~0u };
853         features.push_back(no_liga);
854         features.push_back(no_clig);
855     }
856     addFeatures(ctx->paint.fontFeatureSettings, &features);
857 
858     double size = ctx->paint.size;
859     double scaleX = ctx->paint.scaleX;
860 
861     float x = mAdvance;
862     float y = 0;
863     for (int run_ix = isRtl ? items.size() - 1 : 0;
864             isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size());
865             isRtl ? --run_ix : ++run_ix) {
866         FontCollection::Run &run = items[run_ix];
867         if (run.fakedFont.font == NULL) {
868             ALOGE("no font for run starting u+%04x length %d", buf[run.start], run.end - run.start);
869             continue;
870         }
871         int font_ix = findFace(run.fakedFont, ctx);
872         ctx->paint.font = mFaces[font_ix].font;
873         ctx->paint.fakery = mFaces[font_ix].fakery;
874         hb_font_t* hbFont = ctx->hbFonts[font_ix];
875 #ifdef VERBOSE_DEBUG
876         ALOGD("Run %zu, font %d [%d:%d]", run_ix, font_ix, run.start, run.end);
877 #endif
878 
879         hb_font_set_ppem(hbFont, size * scaleX, size);
880         hb_font_set_scale(hbFont, HBFloatToFixed(size * scaleX), HBFloatToFixed(size));
881 
882         const bool is_color_bitmap_font = isColorBitmapFont(hbFont);
883 
884         // TODO: if there are multiple scripts within a font in an RTL run,
885         // we need to reorder those runs. This is unlikely with our current
886         // font stack, but should be done for correctness.
887 
888         // Note: scriptRunStart and scriptRunEnd, as well as run.start and run.end, run between 0
889         // and count.
890         ssize_t scriptRunEnd;
891         for (ssize_t scriptRunStart = run.start;
892                 scriptRunStart < run.end;
893                 scriptRunStart = scriptRunEnd) {
894             scriptRunEnd = scriptRunStart;
895             hb_script_t script = getScriptRun(buf + start, run.end, &scriptRunEnd /* iterator */);
896             // After the last line, scriptRunEnd is guaranteed to have increased, since the only
897             // time getScriptRun does not increase its iterator is when it has already reached the
898             // end of the buffer. But that can't happen, since if we have already reached the end
899             // of the buffer, we should have had (scriptRunEnd == run.end), which means
900             // (scriptRunStart == run.end) which is impossible due to the exit condition of the for
901             // loop. So we can be sure that scriptRunEnd > scriptRunStart.
902 
903             double letterSpace = 0.0;
904             double letterSpaceHalfLeft = 0.0;
905             double letterSpaceHalfRight = 0.0;
906 
907             if (ctx->paint.letterSpacing != 0.0 && isScriptOkForLetterspacing(script)) {
908                 letterSpace = ctx->paint.letterSpacing * size * scaleX;
909                 if ((ctx->paint.paintFlags & LinearTextFlag) == 0) {
910                     letterSpace = round(letterSpace);
911                     letterSpaceHalfLeft = floor(letterSpace * 0.5);
912                 } else {
913                     letterSpaceHalfLeft = letterSpace * 0.5;
914                 }
915                 letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
916             }
917 
918             hb_buffer_clear_contents(buffer);
919             hb_buffer_set_script(buffer, script);
920             hb_buffer_set_direction(buffer, isRtl? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
921             const FontLanguages& langList =
922                     FontLanguageListCache::getById(ctx->style.getLanguageListId());
923             if (langList.size() != 0) {
924                 const FontLanguage* hbLanguage = &langList[0];
925                 for (size_t i = 0; i < langList.size(); ++i) {
926                     if (langList[i].supportsHbScript(script)) {
927                         hbLanguage = &langList[i];
928                         break;
929                     }
930                 }
931                 hb_buffer_set_language(buffer, hbLanguage->getHbLanguage());
932             }
933 
934             const uint32_t clusterStart = addToHbBuffer(
935                 buffer,
936                 buf, start, count, bufSize,
937                 scriptRunStart, scriptRunEnd,
938                 ctx->paint.hyphenEdit, hbFont);
939 
940             hb_shape(hbFont, buffer, features.empty() ? NULL : &features[0], features.size());
941             unsigned int numGlyphs;
942             hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer, &numGlyphs);
943             hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer, NULL);
944 
945             // At this point in the code, the cluster values in the info buffer correspond to the
946             // input characters with some shift. The cluster value clusterStart corresponds to the
947             // first character passed to HarfBuzz, which is at buf[start + scriptRunStart] whose
948             // advance needs to be saved into mAdvances[scriptRunStart]. So cluster values need to
949             // be reduced by (clusterStart - scriptRunStart) to get converted to indices of
950             // mAdvances.
951             const ssize_t clusterOffset = clusterStart - scriptRunStart;
952 
953             if (numGlyphs)
954             {
955                 mAdvances[info[0].cluster - clusterOffset] += letterSpaceHalfLeft;
956                 x += letterSpaceHalfLeft;
957             }
958             for (unsigned int i = 0; i < numGlyphs; i++) {
959 #ifdef VERBOSE_DEBUG
960                 ALOGD("%d %d %d %d",
961                         positions[i].x_advance, positions[i].y_advance,
962                         positions[i].x_offset, positions[i].y_offset);
963                 ALOGD("DoLayout %u: %f; %d, %d",
964                         info[i].codepoint, HBFixedToFloat(positions[i].x_advance),
965                         positions[i].x_offset, positions[i].y_offset);
966 #endif
967                 if (i > 0 && info[i - 1].cluster != info[i].cluster) {
968                     mAdvances[info[i - 1].cluster - clusterOffset] += letterSpaceHalfRight;
969                     mAdvances[info[i].cluster - clusterOffset] += letterSpaceHalfLeft;
970                     x += letterSpace;
971                 }
972 
973                 hb_codepoint_t glyph_ix = info[i].codepoint;
974                 float xoff = HBFixedToFloat(positions[i].x_offset);
975                 float yoff = -HBFixedToFloat(positions[i].y_offset);
976                 xoff += yoff * ctx->paint.skewX;
977                 LayoutGlyph glyph = {font_ix, glyph_ix, x + xoff, y + yoff};
978                 mGlyphs.push_back(glyph);
979                 float xAdvance = HBFixedToFloat(positions[i].x_advance);
980                 if ((ctx->paint.paintFlags & LinearTextFlag) == 0) {
981                     xAdvance = roundf(xAdvance);
982                 }
983                 MinikinRect glyphBounds;
984                 hb_glyph_extents_t extents = {};
985                 if (is_color_bitmap_font && hb_font_get_glyph_extents(hbFont, glyph_ix, &extents)) {
986                     // Note that it is technically possible for a TrueType font to have outline and
987                     // embedded bitmap at the same time. We ignore modified bbox of hinted outline
988                     // glyphs in that case.
989                     glyphBounds.mLeft = roundf(HBFixedToFloat(extents.x_bearing));
990                     glyphBounds.mTop = roundf(HBFixedToFloat(-extents.y_bearing));
991                     glyphBounds.mRight = roundf(HBFixedToFloat(extents.x_bearing + extents.width));
992                     glyphBounds.mBottom =
993                             roundf(HBFixedToFloat(-extents.y_bearing - extents.height));
994                 } else {
995                     ctx->paint.font->GetBounds(&glyphBounds, glyph_ix, ctx->paint);
996                 }
997                 glyphBounds.offset(x + xoff, y + yoff);
998                 mBounds.join(glyphBounds);
999                 if (static_cast<size_t>(info[i].cluster - clusterOffset) < count) {
1000                     mAdvances[info[i].cluster - clusterOffset] += xAdvance;
1001                 } else {
1002                     ALOGE("cluster %zu (start %zu) out of bounds of count %zu",
1003                         info[i].cluster - clusterOffset, start, count);
1004                 }
1005                 x += xAdvance;
1006             }
1007             if (numGlyphs)
1008             {
1009                 mAdvances[info[numGlyphs - 1].cluster - clusterOffset] += letterSpaceHalfRight;
1010                 x += letterSpaceHalfRight;
1011             }
1012         }
1013     }
1014     mAdvance = x;
1015 }
1016 
appendLayout(Layout * src,size_t start,float extraAdvance)1017 void Layout::appendLayout(Layout* src, size_t start, float extraAdvance) {
1018     int fontMapStack[16];
1019     int* fontMap;
1020     if (src->mFaces.size() < sizeof(fontMapStack) / sizeof(fontMapStack[0])) {
1021         fontMap = fontMapStack;
1022     } else {
1023         fontMap = new int[src->mFaces.size()];
1024     }
1025     for (size_t i = 0; i < src->mFaces.size(); i++) {
1026         int font_ix = findFace(src->mFaces[i], NULL);
1027         fontMap[i] = font_ix;
1028     }
1029     int x0 = mAdvance;
1030     for (size_t i = 0; i < src->mGlyphs.size(); i++) {
1031         LayoutGlyph& srcGlyph = src->mGlyphs[i];
1032         int font_ix = fontMap[srcGlyph.font_ix];
1033         unsigned int glyph_id = srcGlyph.glyph_id;
1034         float x = x0 + srcGlyph.x;
1035         float y = srcGlyph.y;
1036         LayoutGlyph glyph = {font_ix, glyph_id, x, y};
1037         mGlyphs.push_back(glyph);
1038     }
1039     for (size_t i = 0; i < src->mAdvances.size(); i++) {
1040         mAdvances[i + start] = src->mAdvances[i];
1041         if (i == 0)
1042           mAdvances[i + start] += extraAdvance;
1043     }
1044     MinikinRect srcBounds(src->mBounds);
1045     srcBounds.offset(x0, 0);
1046     mBounds.join(srcBounds);
1047     mAdvance += src->mAdvance + extraAdvance;
1048 
1049     if (fontMap != fontMapStack) {
1050         delete[] fontMap;
1051     }
1052 }
1053 
nGlyphs() const1054 size_t Layout::nGlyphs() const {
1055     return mGlyphs.size();
1056 }
1057 
getFont(int i) const1058 const MinikinFont* Layout::getFont(int i) const {
1059     const LayoutGlyph& glyph = mGlyphs[i];
1060     return mFaces[glyph.font_ix].font;
1061 }
1062 
getFakery(int i) const1063 FontFakery Layout::getFakery(int i) const {
1064     const LayoutGlyph& glyph = mGlyphs[i];
1065     return mFaces[glyph.font_ix].fakery;
1066 }
1067 
getGlyphId(int i) const1068 unsigned int Layout::getGlyphId(int i) const {
1069     const LayoutGlyph& glyph = mGlyphs[i];
1070     return glyph.glyph_id;
1071 }
1072 
getX(int i) const1073 float Layout::getX(int i) const {
1074     const LayoutGlyph& glyph = mGlyphs[i];
1075     return glyph.x;
1076 }
1077 
getY(int i) const1078 float Layout::getY(int i) const {
1079     const LayoutGlyph& glyph = mGlyphs[i];
1080     return glyph.y;
1081 }
1082 
getAdvance() const1083 float Layout::getAdvance() const {
1084     return mAdvance;
1085 }
1086 
getAdvances(float * advances)1087 void Layout::getAdvances(float* advances) {
1088     memcpy(advances, &mAdvances[0], mAdvances.size() * sizeof(float));
1089 }
1090 
getBounds(MinikinRect * bounds) const1091 void Layout::getBounds(MinikinRect* bounds) const {
1092     bounds->set(mBounds);
1093 }
1094 
purgeCaches()1095 void Layout::purgeCaches() {
1096     android::AutoMutex _l(gMinikinLock);
1097     LayoutCache& layoutCache = LayoutEngine::getInstance().layoutCache;
1098     layoutCache.clear();
1099     purgeHbFontCacheLocked();
1100 }
1101 
1102 }  // namespace minikin
1103 
1104 // Unable to define the static data member outside of android.
1105 // TODO: introduce our own Singleton to drop android namespace.
1106 namespace android {
1107 ANDROID_SINGLETON_STATIC_INSTANCE(minikin::LayoutEngine);
1108 }  // namespace android
1109 
1110