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 "minikin/Layout.h"
20 
21 #include <cmath>
22 #include <iostream>
23 #include <mutex>
24 #include <string>
25 #include <vector>
26 
27 #include <hb-icu.h>
28 #include <hb-ot.h>
29 #include <log/log.h>
30 #include <unicode/ubidi.h>
31 #include <unicode/utf16.h>
32 #include <utils/JenkinsHash.h>
33 #include <utils/LruCache.h>
34 
35 #include "minikin/Emoji.h"
36 #include "minikin/HbUtils.h"
37 #include "minikin/LayoutCache.h"
38 #include "minikin/LayoutPieces.h"
39 #include "minikin/Macros.h"
40 
41 #include "BidiUtils.h"
42 #include "LayoutUtils.h"
43 #include "LocaleListCache.h"
44 #include "MinikinInternal.h"
45 
46 namespace minikin {
47 
48 namespace {
49 
50 struct SkiaArguments {
51     const MinikinFont* font;
52     const MinikinPaint* paint;
53     FontFakery fakery;
54 };
55 
harfbuzzGetGlyphHorizontalAdvance(hb_font_t *,void * fontData,hb_codepoint_t glyph,void *)56 static hb_position_t harfbuzzGetGlyphHorizontalAdvance(hb_font_t* /* hbFont */, void* fontData,
57                                                        hb_codepoint_t glyph, void* /* userData */) {
58     SkiaArguments* args = reinterpret_cast<SkiaArguments*>(fontData);
59     float advance = args->font->GetHorizontalAdvance(glyph, *args->paint, args->fakery);
60     return 256 * advance + 0.5;
61 }
62 
harfbuzzGetGlyphHorizontalOrigin(hb_font_t *,void *,hb_codepoint_t,hb_position_t *,hb_position_t *,void *)63 static hb_bool_t harfbuzzGetGlyphHorizontalOrigin(hb_font_t* /* hbFont */, void* /* fontData */,
64                                                   hb_codepoint_t /* glyph */,
65                                                   hb_position_t* /* x */, hb_position_t* /* y */,
66                                                   void* /* userData */) {
67     // Just return true, following the way that Harfbuzz-FreeType implementation does.
68     return true;
69 }
70 
71 // TODO: Initialize in Zygote if it helps.
getUnicodeFunctions()72 hb_unicode_funcs_t* getUnicodeFunctions() {
73     static hb_unicode_funcs_t* unicodeFunctions = nullptr;
74     static std::once_flag once;
75     std::call_once(once, [&]() {
76         unicodeFunctions = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
77         /* Disable the function used for compatibility decomposition */
78         hb_unicode_funcs_set_decompose_compatibility_func(
79                 unicodeFunctions,
80                 [](hb_unicode_funcs_t*, hb_codepoint_t, hb_codepoint_t*, void*) -> unsigned int {
81                     return 0;
82                 },
83                 nullptr, nullptr);
84     });
85     return unicodeFunctions;
86 }
87 
88 // TODO: Initialize in Zygote if it helps.
getFontFuncs()89 hb_font_funcs_t* getFontFuncs() {
90     static hb_font_funcs_t* fontFuncs = nullptr;
91     static std::once_flag once;
92     std::call_once(once, [&]() {
93         fontFuncs = hb_font_funcs_create();
94         // Override the h_advance function since we can't use HarfBuzz's implemenation. It may
95         // return the wrong value if the font uses hinting aggressively.
96         hb_font_funcs_set_glyph_h_advance_func(fontFuncs, harfbuzzGetGlyphHorizontalAdvance, 0, 0);
97         hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
98         hb_font_funcs_make_immutable(fontFuncs);
99     });
100     return fontFuncs;
101 }
102 
103 // TODO: Initialize in Zygote if it helps.
getFontFuncsForEmoji()104 hb_font_funcs_t* getFontFuncsForEmoji() {
105     static hb_font_funcs_t* fontFuncs = nullptr;
106     static std::once_flag once;
107     std::call_once(once, [&]() {
108         fontFuncs = hb_font_funcs_create();
109         // Don't override the h_advance function since we use HarfBuzz's implementation for emoji
110         // for performance reasons.
111         // Note that it is technically possible for a TrueType font to have outline and embedded
112         // bitmap at the same time. We ignore modified advances of hinted outline glyphs in that
113         // case.
114         hb_font_funcs_set_glyph_h_origin_func(fontFuncs, harfbuzzGetGlyphHorizontalOrigin, 0, 0);
115         hb_font_funcs_make_immutable(fontFuncs);
116     });
117     return fontFuncs;
118 }
119 
120 }  // namespace
121 
join(const MinikinRect & r)122 void MinikinRect::join(const MinikinRect& r) {
123     if (isEmpty()) {
124         set(r);
125     } else if (!r.isEmpty()) {
126         mLeft = std::min(mLeft, r.mLeft);
127         mTop = std::min(mTop, r.mTop);
128         mRight = std::max(mRight, r.mRight);
129         mBottom = std::max(mBottom, r.mBottom);
130     }
131 }
132 
reset()133 void Layout::reset() {
134     mGlyphs.clear();
135     mFaces.clear();
136     mBounds.setEmpty();
137     mAdvances.clear();
138     mExtents.clear();
139     mAdvance = 0;
140 }
141 
isColorBitmapFont(const HbFontUniquePtr & font)142 static bool isColorBitmapFont(const HbFontUniquePtr& font) {
143     HbBlob cbdt(font, HB_TAG('C', 'B', 'D', 'T'));
144     return cbdt;
145 }
146 
HBFixedToFloat(hb_position_t v)147 static float HBFixedToFloat(hb_position_t v) {
148     return scalbnf(v, -8);
149 }
150 
HBFloatToFixed(float v)151 static hb_position_t HBFloatToFixed(float v) {
152     return scalbnf(v, +8);
153 }
154 
dump() const155 void Layout::dump() const {
156     for (size_t i = 0; i < mGlyphs.size(); i++) {
157         const LayoutGlyph& glyph = mGlyphs[i];
158         std::cout << glyph.glyph_id << ": " << glyph.x << ", " << glyph.y << std::endl;
159     }
160 }
161 
findOrPushBackFace(const FakedFont & face)162 uint8_t Layout::findOrPushBackFace(const FakedFont& face) {
163     MINIKIN_ASSERT(mFaces.size() < MAX_FAMILY_COUNT, "mFaces must not exceeds %d",
164                    MAX_FAMILY_COUNT);
165     uint8_t ix = 0;
166     for (; ix < mFaces.size(); ix++) {
167         if (mFaces[ix].font == face.font) {
168             return ix;
169         }
170     }
171     ix = mFaces.size();
172     mFaces.push_back(face);
173     return ix;
174 }
175 
decodeUtf16(const uint16_t * chars,size_t len,ssize_t * iter)176 static hb_codepoint_t decodeUtf16(const uint16_t* chars, size_t len, ssize_t* iter) {
177     UChar32 result;
178     U16_NEXT(chars, *iter, (ssize_t)len, result);
179     if (U_IS_SURROGATE(result)) {  // isolated surrogate
180         result = 0xFFFDu;          // U+FFFD REPLACEMENT CHARACTER
181     }
182     return (hb_codepoint_t)result;
183 }
184 
getScriptRun(const uint16_t * chars,size_t len,ssize_t * iter)185 static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter) {
186     if (size_t(*iter) == len) {
187         return HB_SCRIPT_UNKNOWN;
188     }
189     uint32_t cp = decodeUtf16(chars, len, iter);
190     hb_script_t current_script = hb_unicode_script(getUnicodeFunctions(), cp);
191     for (;;) {
192         if (size_t(*iter) == len) break;
193         const ssize_t prev_iter = *iter;
194         cp = decodeUtf16(chars, len, iter);
195         const hb_script_t script = hb_unicode_script(getUnicodeFunctions(), cp);
196         if (script != current_script) {
197             if (current_script == HB_SCRIPT_INHERITED || current_script == HB_SCRIPT_COMMON) {
198                 current_script = script;
199             } else if (script == HB_SCRIPT_INHERITED || script == HB_SCRIPT_COMMON) {
200                 continue;
201             } else {
202                 *iter = prev_iter;
203                 break;
204             }
205         }
206     }
207     if (current_script == HB_SCRIPT_INHERITED) {
208         current_script = HB_SCRIPT_COMMON;
209     }
210 
211     return current_script;
212 }
213 
214 /**
215  * Disable certain scripts (mostly those with cursive connection) from having letterspacing
216  * applied. See https://github.com/behdad/harfbuzz/issues/64 for more details.
217  */
isScriptOkForLetterspacing(hb_script_t script)218 static bool isScriptOkForLetterspacing(hb_script_t script) {
219     return !(script == HB_SCRIPT_ARABIC || script == HB_SCRIPT_NKO ||
220              script == HB_SCRIPT_PSALTER_PAHLAVI || script == HB_SCRIPT_MANDAIC ||
221              script == HB_SCRIPT_MONGOLIAN || script == HB_SCRIPT_PHAGS_PA ||
222              script == HB_SCRIPT_DEVANAGARI || script == HB_SCRIPT_BENGALI ||
223              script == HB_SCRIPT_GURMUKHI || script == HB_SCRIPT_MODI ||
224              script == HB_SCRIPT_SHARADA || script == HB_SCRIPT_SYLOTI_NAGRI ||
225              script == HB_SCRIPT_TIRHUTA || script == HB_SCRIPT_OGHAM);
226 }
227 
doLayout(const U16StringPiece & textBuf,const Range & range,Bidi bidiFlags,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen)228 void Layout::doLayout(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
229                       const MinikinPaint& paint, StartHyphenEdit startHyphen,
230                       EndHyphenEdit endHyphen) {
231     const uint32_t count = range.getLength();
232     reset();
233     mAdvances.resize(count, 0);
234     mExtents.resize(count);
235 
236     for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
237         doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, range.getStart(),
238                           startHyphen, endHyphen, nullptr, this, nullptr, nullptr, nullptr,
239                           nullptr);
240     }
241 }
242 
doLayoutWithPrecomputedPieces(const U16StringPiece & textBuf,const Range & range,Bidi bidiFlags,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,const LayoutPieces & lpIn)243 void Layout::doLayoutWithPrecomputedPieces(const U16StringPiece& textBuf, const Range& range,
244                                            Bidi bidiFlags, const MinikinPaint& paint,
245                                            StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
246                                            const LayoutPieces& lpIn) {
247     const uint32_t count = range.getLength();
248     reset();
249     mAdvances.resize(count, 0);
250     mExtents.resize(count);
251 
252     for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
253         doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, range.getStart(),
254                           startHyphen, endHyphen, &lpIn, this, nullptr, nullptr, nullptr, nullptr);
255     }
256 }
257 
getBoundsWithPrecomputedPieces(const U16StringPiece & textBuf,const Range & range,Bidi bidiFlags,const MinikinPaint & paint,const LayoutPieces & pieces)258 std::pair<float, MinikinRect> Layout::getBoundsWithPrecomputedPieces(const U16StringPiece& textBuf,
259                                                                      const Range& range,
260                                                                      Bidi bidiFlags,
261                                                                      const MinikinPaint& paint,
262                                                                      const LayoutPieces& pieces) {
263     MinikinRect rect;
264     float advance = 0;
265     for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
266         advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0,
267                                      StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, &pieces,
268                                      nullptr, nullptr, nullptr, &rect, nullptr);
269     }
270     return std::make_pair(advance, rect);
271 }
272 
measureText(const U16StringPiece & textBuf,const Range & range,Bidi bidiFlags,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,float * advances,MinikinExtent * extents,LayoutPieces * pieces)273 float Layout::measureText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags,
274                           const MinikinPaint& paint, StartHyphenEdit startHyphen,
275                           EndHyphenEdit endHyphen, float* advances, MinikinExtent* extents,
276                           LayoutPieces* pieces) {
277     float advance = 0;
278     for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) {
279         const size_t offset = range.toRangeOffset(runInfo.range.getStart());
280         float* advancesForRun = advances ? advances + offset : nullptr;
281         MinikinExtent* extentsForRun = extents ? extents + offset : nullptr;
282         advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, paint, 0, startHyphen,
283                                      endHyphen, nullptr, nullptr, advancesForRun, extentsForRun,
284                                      nullptr, pieces);
285     }
286     return advance;
287 }
288 
doLayoutRunCached(const U16StringPiece & textBuf,const Range & range,bool isRtl,const MinikinPaint & paint,size_t dstStart,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,const LayoutPieces * lpIn,Layout * layout,float * advances,MinikinExtent * extents,MinikinRect * bounds,LayoutPieces * lpOut)289 float Layout::doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl,
290                                 const MinikinPaint& paint, size_t dstStart,
291                                 StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
292                                 const LayoutPieces* lpIn, Layout* layout, float* advances,
293                                 MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut) {
294     if (!range.isValid()) {
295         return 0.0f;  // ICU failed to retrieve the bidi run?
296     }
297     const uint16_t* buf = textBuf.data();
298     const uint32_t bufSize = textBuf.size();
299     const uint32_t start = range.getStart();
300     const uint32_t end = range.getEnd();
301     float advance = 0;
302     if (!isRtl) {
303         // left to right
304         uint32_t wordstart =
305                 start == bufSize ? start : getPrevWordBreakForCache(buf, start + 1, bufSize);
306         uint32_t wordend;
307         for (size_t iter = start; iter < end; iter = wordend) {
308             wordend = getNextWordBreakForCache(buf, iter, bufSize);
309             const uint32_t wordcount = std::min(end, wordend) - iter;
310             const uint32_t offset = iter - start;
311             advance += doLayoutWord(buf + wordstart, iter - wordstart, wordcount,
312                                     wordend - wordstart, isRtl, paint, iter - dstStart,
313                                     // Only apply hyphen to the first or last word in the string.
314                                     iter == start ? startHyphen : StartHyphenEdit::NO_EDIT,
315                                     wordend >= end ? endHyphen : EndHyphenEdit::NO_EDIT, lpIn,
316                                     layout, advances ? advances + offset : nullptr,
317                                     extents ? extents + offset : nullptr, bounds, lpOut);
318             wordstart = wordend;
319         }
320     } else {
321         // right to left
322         uint32_t wordstart;
323         uint32_t wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize);
324         for (size_t iter = end; iter > start; iter = wordstart) {
325             wordstart = getPrevWordBreakForCache(buf, iter, bufSize);
326             uint32_t bufStart = std::max(start, wordstart);
327             const uint32_t offset = bufStart - start;
328             advance += doLayoutWord(buf + wordstart, bufStart - wordstart, iter - bufStart,
329                                     wordend - wordstart, isRtl, paint, bufStart - dstStart,
330                                     // Only apply hyphen to the first (rightmost) or last (leftmost)
331                                     // word in the string.
332                                     wordstart <= start ? startHyphen : StartHyphenEdit::NO_EDIT,
333                                     iter == end ? endHyphen : EndHyphenEdit::NO_EDIT, lpIn, layout,
334                                     advances ? advances + offset : nullptr,
335                                     extents ? extents + offset : nullptr, bounds, lpOut);
336             wordend = wordstart;
337         }
338     }
339     return advance;
340 }
341 
342 class LayoutAppendFunctor {
343 public:
LayoutAppendFunctor(const U16StringPiece & textBuf,const Range & range,const MinikinPaint & paint,bool dir,StartHyphenEdit startEdit,EndHyphenEdit endEdit,Layout * layout,float * advances,MinikinExtent * extents,LayoutPieces * pieces,float * totalAdvance,MinikinRect * bounds,uint32_t outOffset,float wordSpacing)344     LayoutAppendFunctor(const U16StringPiece& textBuf, const Range& range,
345                         const MinikinPaint& paint, bool dir, StartHyphenEdit startEdit,
346                         EndHyphenEdit endEdit, Layout* layout, float* advances,
347                         MinikinExtent* extents, LayoutPieces* pieces, float* totalAdvance,
348                         MinikinRect* bounds, uint32_t outOffset, float wordSpacing)
349             : mTextBuf(textBuf),
350               mRange(range),
351               mPaint(paint),
352               mDir(dir),
353               mStartEdit(startEdit),
354               mEndEdit(endEdit),
355               mLayout(layout),
356               mAdvances(advances),
357               mExtents(extents),
358               mPieces(pieces),
359               mTotalAdvance(totalAdvance),
360               mBounds(bounds),
361               mOutOffset(outOffset),
362               mWordSpacing(wordSpacing) {}
363 
operator ()(const Layout & layout)364     void operator()(const Layout& layout) {
365         if (mLayout) {
366             mLayout->appendLayout(layout, mOutOffset, mWordSpacing);
367         }
368         if (mAdvances) {
369             layout.getAdvances(mAdvances);
370         }
371         if (mTotalAdvance) {
372             *mTotalAdvance = layout.getAdvance();
373         }
374         if (mExtents) {
375             layout.getExtents(mExtents);
376         }
377         if (mBounds) {
378             mBounds->join(layout.getBounds());
379         }
380         if (mPieces) {
381             mPieces->insert(mTextBuf, mRange, mPaint, mDir, mStartEdit, mEndEdit, layout);
382         }
383     }
384 
385 private:
386     const U16StringPiece& mTextBuf;
387     const Range& mRange;
388     const MinikinPaint& mPaint;
389     bool mDir;
390     StartHyphenEdit mStartEdit;
391     EndHyphenEdit mEndEdit;
392     Layout* mLayout;
393     float* mAdvances;
394     MinikinExtent* mExtents;
395     LayoutPieces* mPieces;
396     float* mTotalAdvance;
397     MinikinRect* mBounds;
398     const uint32_t mOutOffset;
399     const float mWordSpacing;
400 };
401 
doLayoutWord(const uint16_t * buf,size_t start,size_t count,size_t bufSize,bool isRtl,const MinikinPaint & paint,size_t bufStart,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,const LayoutPieces * lpIn,Layout * layout,float * advances,MinikinExtent * extents,MinikinRect * bounds,LayoutPieces * lpOut)402 float Layout::doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
403                            bool isRtl, const MinikinPaint& paint, size_t bufStart,
404                            StartHyphenEdit startHyphen, EndHyphenEdit endHyphen,
405                            const LayoutPieces* lpIn, Layout* layout, float* advances,
406                            MinikinExtent* extents, MinikinRect* bounds, LayoutPieces* lpOut) {
407     float wordSpacing = count == 1 && isWordSpace(buf[start]) ? paint.wordSpacing : 0;
408     float totalAdvance;
409 
410     const U16StringPiece textBuf(buf, bufSize);
411     const Range range(start, start + count);
412     LayoutAppendFunctor f(textBuf, range, paint, isRtl, startHyphen, endHyphen, layout, advances,
413                           extents, lpOut, &totalAdvance, bounds, bufStart, wordSpacing);
414     if (lpIn != nullptr) {
415         lpIn->getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen, f);
416     } else {
417         LayoutCache::getInstance().getOrCreate(textBuf, range, paint, isRtl, startHyphen, endHyphen,
418                                                f);
419     }
420 
421     if (wordSpacing != 0) {
422         totalAdvance += wordSpacing;
423         if (advances) {
424             advances[0] += wordSpacing;
425         }
426     }
427     return totalAdvance;
428 }
429 
addFeatures(const std::string & str,std::vector<hb_feature_t> * features)430 static void addFeatures(const std::string& str, std::vector<hb_feature_t>* features) {
431     SplitIterator it(str, ',');
432     while (it.hasNext()) {
433         StringPiece featureStr = it.next();
434         static hb_feature_t feature;
435         /* We do not allow setting features on ranges.  As such, reject any
436          * setting that has non-universal range. */
437         if (hb_feature_from_string(featureStr.data(), featureStr.size(), &feature) &&
438             feature.start == 0 && feature.end == (unsigned int)-1) {
439             features->push_back(feature);
440         }
441     }
442 }
443 
determineHyphenChar(hb_codepoint_t preferredHyphen,hb_font_t * font)444 static inline hb_codepoint_t determineHyphenChar(hb_codepoint_t preferredHyphen, hb_font_t* font) {
445     hb_codepoint_t glyph;
446     if (preferredHyphen == 0x058A    /* ARMENIAN_HYPHEN */
447         || preferredHyphen == 0x05BE /* HEBREW PUNCTUATION MAQAF */
448         || preferredHyphen == 0x1400 /* CANADIAN SYLLABIC HYPHEN */) {
449         if (hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
450             return preferredHyphen;
451         } else {
452             // The original hyphen requested was not supported. Let's try and see if the
453             // Unicode hyphen is supported.
454             preferredHyphen = CHAR_HYPHEN;
455         }
456     }
457     if (preferredHyphen == CHAR_HYPHEN) { /* HYPHEN */
458         // Fallback to ASCII HYPHEN-MINUS if the font didn't have a glyph for the preferred hyphen.
459         // Note that we intentionally don't do anything special if the font doesn't have a
460         // HYPHEN-MINUS either, so a tofu could be shown, hinting towards something missing.
461         if (!hb_font_get_nominal_glyph(font, preferredHyphen, &glyph)) {
462             return 0x002D;  // HYPHEN-MINUS
463         }
464     }
465     return preferredHyphen;
466 }
467 
468 template <typename HyphenEdit>
addHyphenToHbBuffer(const HbBufferUniquePtr & buffer,const HbFontUniquePtr & font,HyphenEdit hyphen,uint32_t cluster)469 static inline void addHyphenToHbBuffer(const HbBufferUniquePtr& buffer, const HbFontUniquePtr& font,
470                                        HyphenEdit hyphen, uint32_t cluster) {
471     const uint32_t* chars;
472     size_t size;
473     std::tie(chars, size) = getHyphenString(hyphen);
474     for (size_t i = 0; i < size; i++) {
475         hb_buffer_add(buffer.get(), determineHyphenChar(chars[i], font.get()), cluster);
476     }
477 }
478 
479 // Returns the cluster value assigned to the first codepoint added to the buffer, which can be used
480 // to translate cluster values returned by HarfBuzz to input indices.
addToHbBuffer(const HbBufferUniquePtr & buffer,const uint16_t * buf,size_t start,size_t count,size_t bufSize,ssize_t scriptRunStart,ssize_t scriptRunEnd,StartHyphenEdit inStartHyphen,EndHyphenEdit inEndHyphen,const HbFontUniquePtr & hbFont)481 static inline uint32_t addToHbBuffer(const HbBufferUniquePtr& buffer, const uint16_t* buf,
482                                      size_t start, size_t count, size_t bufSize,
483                                      ssize_t scriptRunStart, ssize_t scriptRunEnd,
484                                      StartHyphenEdit inStartHyphen, EndHyphenEdit inEndHyphen,
485                                      const HbFontUniquePtr& hbFont) {
486     // Only hyphenate the very first script run for starting hyphens.
487     const StartHyphenEdit startHyphen =
488             (scriptRunStart == 0) ? inStartHyphen : StartHyphenEdit::NO_EDIT;
489     // Only hyphenate the very last script run for ending hyphens.
490     const EndHyphenEdit endHyphen =
491             (static_cast<size_t>(scriptRunEnd) == count) ? inEndHyphen : EndHyphenEdit::NO_EDIT;
492 
493     // In the following code, we drop the pre-context and/or post-context if there is a
494     // hyphen edit at that end. This is not absolutely necessary, since HarfBuzz uses
495     // contexts only for joining scripts at the moment, e.g. to determine if the first or
496     // last letter of a text range to shape should take a joining form based on an
497     // adjacent letter or joiner (that comes from the context).
498     //
499     // TODO: Revisit this for:
500     // 1. Desperate breaks for joining scripts like Arabic (where it may be better to keep
501     //    the context);
502     // 2. Special features like start-of-word font features (not implemented in HarfBuzz
503     //    yet).
504 
505     // We don't have any start-of-line replacement edit yet, so we don't need to check for
506     // those.
507     if (isInsertion(startHyphen)) {
508         // A cluster value of zero guarantees that the inserted hyphen will be in the same
509         // cluster with the next codepoint, since there is no pre-context.
510         addHyphenToHbBuffer(buffer, hbFont, startHyphen, 0 /* cluster */);
511     }
512 
513     const uint16_t* hbText;
514     int hbTextLength;
515     unsigned int hbItemOffset;
516     unsigned int hbItemLength = scriptRunEnd - scriptRunStart;  // This is >= 1.
517 
518     const bool hasEndInsertion = isInsertion(endHyphen);
519     const bool hasEndReplacement = isReplacement(endHyphen);
520     if (hasEndReplacement) {
521         // Skip the last code unit while copying the buffer for HarfBuzz if it's a replacement. We
522         // don't need to worry about non-BMP characters yet since replacements are only done for
523         // code units at the moment.
524         hbItemLength -= 1;
525     }
526 
527     if (startHyphen == StartHyphenEdit::NO_EDIT) {
528         // No edit at the beginning. Use the whole pre-context.
529         hbText = buf;
530         hbItemOffset = start + scriptRunStart;
531     } else {
532         // There's an edit at the beginning. Drop the pre-context and start the buffer at where we
533         // want to start shaping.
534         hbText = buf + start + scriptRunStart;
535         hbItemOffset = 0;
536     }
537 
538     if (endHyphen == EndHyphenEdit::NO_EDIT) {
539         // No edit at the end, use the whole post-context.
540         hbTextLength = (buf + bufSize) - hbText;
541     } else {
542         // There is an edit at the end. Drop the post-context.
543         hbTextLength = hbItemOffset + hbItemLength;
544     }
545 
546     hb_buffer_add_utf16(buffer.get(), hbText, hbTextLength, hbItemOffset, hbItemLength);
547 
548     unsigned int numCodepoints;
549     hb_glyph_info_t* cpInfo = hb_buffer_get_glyph_infos(buffer.get(), &numCodepoints);
550 
551     // Add the hyphen at the end, if there's any.
552     if (hasEndInsertion || hasEndReplacement) {
553         // When a hyphen is inserted, by assigning the added hyphen and the last
554         // codepoint added to the HarfBuzz buffer to the same cluster, we can make sure
555         // that they always remain in the same cluster, even if the last codepoint gets
556         // merged into another cluster (for example when it's a combining mark).
557         //
558         // When a replacement happens instead, we want it to get the cluster value of
559         // the character it's replacing, which is one "codepoint length" larger than
560         // the last cluster. But since the character replaced is always just one
561         // code unit, we can just add 1.
562         uint32_t hyphenCluster;
563         if (numCodepoints == 0) {
564             // Nothing was added to the HarfBuzz buffer. This can only happen if
565             // we have a replacement that is replacing a one-code unit script run.
566             hyphenCluster = 0;
567         } else {
568             hyphenCluster = cpInfo[numCodepoints - 1].cluster + (uint32_t)hasEndReplacement;
569         }
570         addHyphenToHbBuffer(buffer, hbFont, endHyphen, hyphenCluster);
571         // Since we have just added to the buffer, cpInfo no longer necessarily points to
572         // the right place. Refresh it.
573         cpInfo = hb_buffer_get_glyph_infos(buffer.get(), nullptr /* we don't need the size */);
574     }
575     return cpInfo[0].cluster;
576 }
577 
doLayoutRun(const uint16_t * buf,size_t start,size_t count,size_t bufSize,bool isRtl,const MinikinPaint & paint,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen)578 void Layout::doLayoutRun(const uint16_t* buf, size_t start, size_t count, size_t bufSize,
579                          bool isRtl, const MinikinPaint& paint, StartHyphenEdit startHyphen,
580                          EndHyphenEdit endHyphen) {
581     HbBufferUniquePtr buffer(hb_buffer_create());
582     hb_buffer_set_unicode_funcs(buffer.get(), getUnicodeFunctions());
583     std::vector<FontCollection::Run> items;
584     paint.font->itemize(buf + start, count, paint, &items);
585 
586     std::vector<hb_feature_t> features;
587     // Disable default-on non-required ligature features if letter-spacing
588     // See http://dev.w3.org/csswg/css-text-3/#letter-spacing-property
589     // "When the effective spacing between two characters is not zero (due to
590     // either justification or a non-zero value of letter-spacing), user agents
591     // should not apply optional ligatures."
592     if (fabs(paint.letterSpacing) > 0.03) {
593         static const hb_feature_t no_liga = {HB_TAG('l', 'i', 'g', 'a'), 0, 0, ~0u};
594         static const hb_feature_t no_clig = {HB_TAG('c', 'l', 'i', 'g'), 0, 0, ~0u};
595         features.push_back(no_liga);
596         features.push_back(no_clig);
597     }
598     addFeatures(paint.fontFeatureSettings, &features);
599 
600     std::vector<HbFontUniquePtr> hbFonts;
601     double size = paint.size;
602     double scaleX = paint.scaleX;
603 
604     float x = mAdvance;
605     float y = 0;
606     for (int run_ix = isRtl ? items.size() - 1 : 0;
607          isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size());
608          isRtl ? --run_ix : ++run_ix) {
609         FontCollection::Run& run = items[run_ix];
610         const FakedFont& fakedFont = run.fakedFont;
611         const uint8_t font_ix = findOrPushBackFace(fakedFont);
612         if (hbFonts.size() == font_ix) {  // findOrPushBackFace push backed the new face.
613             // We override some functions which are not thread safe.
614             HbFontUniquePtr font(hb_font_create_sub_font(fakedFont.font->baseFont().get()));
615             hb_font_set_funcs(
616                     font.get(), isColorBitmapFont(font) ? getFontFuncsForEmoji() : getFontFuncs(),
617                     new SkiaArguments({fakedFont.font->typeface().get(), &paint, fakedFont.fakery}),
618                     [](void* data) { delete reinterpret_cast<SkiaArguments*>(data); });
619             hbFonts.push_back(std::move(font));
620         }
621         const HbFontUniquePtr& hbFont = hbFonts[font_ix];
622 
623         MinikinExtent verticalExtent;
624         fakedFont.font->typeface()->GetFontExtent(&verticalExtent, paint, fakedFont.fakery);
625         std::fill(&mExtents[run.start], &mExtents[run.end], verticalExtent);
626 
627         hb_font_set_ppem(hbFont.get(), size * scaleX, size);
628         hb_font_set_scale(hbFont.get(), HBFloatToFixed(size * scaleX), HBFloatToFixed(size));
629 
630         const bool is_color_bitmap_font = isColorBitmapFont(hbFont);
631 
632         // TODO: if there are multiple scripts within a font in an RTL run,
633         // we need to reorder those runs. This is unlikely with our current
634         // font stack, but should be done for correctness.
635 
636         // Note: scriptRunStart and scriptRunEnd, as well as run.start and run.end, run between 0
637         // and count.
638         ssize_t scriptRunEnd;
639         for (ssize_t scriptRunStart = run.start; scriptRunStart < run.end;
640              scriptRunStart = scriptRunEnd) {
641             scriptRunEnd = scriptRunStart;
642             hb_script_t script = getScriptRun(buf + start, run.end, &scriptRunEnd /* iterator */);
643             // After the last line, scriptRunEnd is guaranteed to have increased, since the only
644             // time getScriptRun does not increase its iterator is when it has already reached the
645             // end of the buffer. But that can't happen, since if we have already reached the end
646             // of the buffer, we should have had (scriptRunEnd == run.end), which means
647             // (scriptRunStart == run.end) which is impossible due to the exit condition of the for
648             // loop. So we can be sure that scriptRunEnd > scriptRunStart.
649 
650             double letterSpace = 0.0;
651             double letterSpaceHalfLeft = 0.0;
652             double letterSpaceHalfRight = 0.0;
653 
654             if (paint.letterSpacing != 0.0 && isScriptOkForLetterspacing(script)) {
655                 letterSpace = paint.letterSpacing * size * scaleX;
656                 if ((paint.paintFlags & LinearTextFlag) == 0) {
657                     letterSpace = round(letterSpace);
658                     letterSpaceHalfLeft = floor(letterSpace * 0.5);
659                 } else {
660                     letterSpaceHalfLeft = letterSpace * 0.5;
661                 }
662                 letterSpaceHalfRight = letterSpace - letterSpaceHalfLeft;
663             }
664 
665             hb_buffer_clear_contents(buffer.get());
666             hb_buffer_set_script(buffer.get(), script);
667             hb_buffer_set_direction(buffer.get(), isRtl ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
668             const LocaleList& localeList = LocaleListCache::getById(paint.localeListId);
669             if (localeList.size() != 0) {
670                 hb_language_t hbLanguage = localeList.getHbLanguage(0);
671                 for (size_t i = 0; i < localeList.size(); ++i) {
672                     if (localeList[i].supportsHbScript(script)) {
673                         hbLanguage = localeList.getHbLanguage(i);
674                         break;
675                     }
676                 }
677                 hb_buffer_set_language(buffer.get(), hbLanguage);
678             }
679 
680             const uint32_t clusterStart =
681                     addToHbBuffer(buffer, buf, start, count, bufSize, scriptRunStart, scriptRunEnd,
682                                   startHyphen, endHyphen, hbFont);
683 
684             hb_shape(hbFont.get(), buffer.get(), features.empty() ? NULL : &features[0],
685                      features.size());
686             unsigned int numGlyphs;
687             hb_glyph_info_t* info = hb_buffer_get_glyph_infos(buffer.get(), &numGlyphs);
688             hb_glyph_position_t* positions = hb_buffer_get_glyph_positions(buffer.get(), NULL);
689 
690             // At this point in the code, the cluster values in the info buffer correspond to the
691             // input characters with some shift. The cluster value clusterStart corresponds to the
692             // first character passed to HarfBuzz, which is at buf[start + scriptRunStart] whose
693             // advance needs to be saved into mAdvances[scriptRunStart]. So cluster values need to
694             // be reduced by (clusterStart - scriptRunStart) to get converted to indices of
695             // mAdvances.
696             const ssize_t clusterOffset = clusterStart - scriptRunStart;
697 
698             if (numGlyphs) {
699                 mAdvances[info[0].cluster - clusterOffset] += letterSpaceHalfLeft;
700                 x += letterSpaceHalfLeft;
701             }
702             for (unsigned int i = 0; i < numGlyphs; i++) {
703                 const size_t clusterBaseIndex = info[i].cluster - clusterOffset;
704                 if (i > 0 && info[i - 1].cluster != info[i].cluster) {
705                     mAdvances[info[i - 1].cluster - clusterOffset] += letterSpaceHalfRight;
706                     mAdvances[clusterBaseIndex] += letterSpaceHalfLeft;
707                     x += letterSpace;
708                 }
709 
710                 hb_codepoint_t glyph_ix = info[i].codepoint;
711                 float xoff = HBFixedToFloat(positions[i].x_offset);
712                 float yoff = -HBFixedToFloat(positions[i].y_offset);
713                 xoff += yoff * paint.skewX;
714                 LayoutGlyph glyph = {font_ix, glyph_ix, x + xoff, y + yoff};
715                 mGlyphs.push_back(glyph);
716                 float xAdvance = HBFixedToFloat(positions[i].x_advance);
717                 if ((paint.paintFlags & LinearTextFlag) == 0) {
718                     xAdvance = roundf(xAdvance);
719                 }
720                 MinikinRect glyphBounds;
721                 hb_glyph_extents_t extents = {};
722                 if (is_color_bitmap_font &&
723                     hb_font_get_glyph_extents(hbFont.get(), glyph_ix, &extents)) {
724                     // Note that it is technically possible for a TrueType font to have outline and
725                     // embedded bitmap at the same time. We ignore modified bbox of hinted outline
726                     // glyphs in that case.
727                     glyphBounds.mLeft = roundf(HBFixedToFloat(extents.x_bearing));
728                     glyphBounds.mTop = roundf(HBFixedToFloat(-extents.y_bearing));
729                     glyphBounds.mRight = roundf(HBFixedToFloat(extents.x_bearing + extents.width));
730                     glyphBounds.mBottom =
731                             roundf(HBFixedToFloat(-extents.y_bearing - extents.height));
732                 } else {
733                     fakedFont.font->typeface()->GetBounds(&glyphBounds, glyph_ix, paint,
734                                                           fakedFont.fakery);
735                 }
736                 glyphBounds.offset(xoff, yoff);
737 
738                 if (clusterBaseIndex < count) {
739                     mAdvances[clusterBaseIndex] += xAdvance;
740                 } else {
741                     ALOGE("cluster %zu (start %zu) out of bounds of count %zu", clusterBaseIndex,
742                           start, count);
743                 }
744                 glyphBounds.offset(x, y);
745                 mBounds.join(glyphBounds);
746                 x += xAdvance;
747             }
748             if (numGlyphs) {
749                 mAdvances[info[numGlyphs - 1].cluster - clusterOffset] += letterSpaceHalfRight;
750                 x += letterSpaceHalfRight;
751             }
752         }
753     }
754     mAdvance = x;
755 }
756 
appendLayout(const Layout & src,size_t start,float extraAdvance)757 void Layout::appendLayout(const Layout& src, size_t start, float extraAdvance) {
758     int fontMapStack[16];
759     int* fontMap;
760     if (src.mFaces.size() < sizeof(fontMapStack) / sizeof(fontMapStack[0])) {
761         fontMap = fontMapStack;
762     } else {
763         fontMap = new int[src.mFaces.size()];
764     }
765     for (size_t i = 0; i < src.mFaces.size(); i++) {
766         uint8_t font_ix = findOrPushBackFace(src.mFaces[i]);
767         fontMap[i] = font_ix;
768     }
769     int x0 = mAdvance;
770     for (size_t i = 0; i < src.mGlyphs.size(); i++) {
771         const LayoutGlyph& srcGlyph = src.mGlyphs[i];
772         int font_ix = fontMap[srcGlyph.font_ix];
773         unsigned int glyph_id = srcGlyph.glyph_id;
774         float x = x0 + srcGlyph.x;
775         float y = srcGlyph.y;
776         LayoutGlyph glyph = {font_ix, glyph_id, x, y};
777         mGlyphs.push_back(glyph);
778     }
779     for (size_t i = 0; i < src.mAdvances.size(); i++) {
780         mAdvances[i + start] = src.mAdvances[i];
781         if (i == 0) {
782             mAdvances[start] += extraAdvance;
783         }
784         mExtents[i + start] = src.mExtents[i];
785     }
786     MinikinRect srcBounds(src.mBounds);
787     srcBounds.offset(x0, 0);
788     mBounds.join(srcBounds);
789     mAdvance += src.mAdvance + extraAdvance;
790 
791     if (fontMap != fontMapStack) {
792         delete[] fontMap;
793     }
794 }
795 
nGlyphs() const796 size_t Layout::nGlyphs() const {
797     return mGlyphs.size();
798 }
799 
getFont(int i) const800 const MinikinFont* Layout::getFont(int i) const {
801     const LayoutGlyph& glyph = mGlyphs[i];
802     return mFaces[glyph.font_ix].font->typeface().get();
803 }
804 
getFakery(int i) const805 FontFakery Layout::getFakery(int i) const {
806     const LayoutGlyph& glyph = mGlyphs[i];
807     return mFaces[glyph.font_ix].fakery;
808 }
809 
getGlyphId(int i) const810 unsigned int Layout::getGlyphId(int i) const {
811     const LayoutGlyph& glyph = mGlyphs[i];
812     return glyph.glyph_id;
813 }
814 
getX(int i) const815 float Layout::getX(int i) const {
816     const LayoutGlyph& glyph = mGlyphs[i];
817     return glyph.x;
818 }
819 
getY(int i) const820 float Layout::getY(int i) const {
821     const LayoutGlyph& glyph = mGlyphs[i];
822     return glyph.y;
823 }
824 
getAdvance() const825 float Layout::getAdvance() const {
826     return mAdvance;
827 }
828 
getAdvances(float * advances) const829 void Layout::getAdvances(float* advances) const {
830     memcpy(advances, &mAdvances[0], mAdvances.size() * sizeof(float));
831 }
832 
getExtents(MinikinExtent * extents) const833 void Layout::getExtents(MinikinExtent* extents) const {
834     memcpy(extents, &mExtents[0], mExtents.size() * sizeof(MinikinExtent));
835 }
836 
getBounds(MinikinRect * bounds) const837 void Layout::getBounds(MinikinRect* bounds) const {
838     bounds->set(mBounds);
839 }
840 
purgeCaches()841 void Layout::purgeCaches() {
842     LayoutCache::getInstance().clear();
843 }
844 
dumpMinikinStats(int fd)845 void Layout::dumpMinikinStats(int fd) {
846     LayoutCache::getInstance().dumpStats(fd);
847 }
848 
849 }  // namespace minikin
850