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