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