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