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