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 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 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. 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. 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. 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 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 133 void Layout::reset() { 134 mGlyphs.clear(); 135 mFaces.clear(); 136 mBounds.setEmpty(); 137 mAdvances.clear(); 138 mExtents.clear(); 139 mAdvance = 0; 140 } 141 142 static bool isColorBitmapFont(const HbFontUniquePtr& font) { 143 HbBlob cbdt(font, HB_TAG('C', 'B', 'D', 'T')); 144 return cbdt; 145 } 146 147 static float HBFixedToFloat(hb_position_t v) { 148 return scalbnf(v, -8); 149 } 150 151 static hb_position_t HBFloatToFixed(float v) { 152 return scalbnf(v, +8); 153 } 154 155 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 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 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 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 */ 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 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 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 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 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 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: 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 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 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 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 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> 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. 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 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 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 796 size_t Layout::nGlyphs() const { 797 return mGlyphs.size(); 798 } 799 800 const MinikinFont* Layout::getFont(int i) const { 801 const LayoutGlyph& glyph = mGlyphs[i]; 802 return mFaces[glyph.font_ix].font->typeface().get(); 803 } 804 805 FontFakery Layout::getFakery(int i) const { 806 const LayoutGlyph& glyph = mGlyphs[i]; 807 return mFaces[glyph.font_ix].fakery; 808 } 809 810 unsigned int Layout::getGlyphId(int i) const { 811 const LayoutGlyph& glyph = mGlyphs[i]; 812 return glyph.glyph_id; 813 } 814 815 float Layout::getX(int i) const { 816 const LayoutGlyph& glyph = mGlyphs[i]; 817 return glyph.x; 818 } 819 820 float Layout::getY(int i) const { 821 const LayoutGlyph& glyph = mGlyphs[i]; 822 return glyph.y; 823 } 824 825 float Layout::getAdvance() const { 826 return mAdvance; 827 } 828 829 void Layout::getAdvances(float* advances) const { 830 memcpy(advances, &mAdvances[0], mAdvances.size() * sizeof(float)); 831 } 832 833 void Layout::getExtents(MinikinExtent* extents) const { 834 memcpy(extents, &mExtents[0], mExtents.size() * sizeof(MinikinExtent)); 835 } 836 837 void Layout::getBounds(MinikinRect* bounds) const { 838 bounds->set(mBounds); 839 } 840 841 void Layout::purgeCaches() { 842 LayoutCache::getInstance().clear(); 843 } 844 845 void Layout::dumpMinikinStats(int fd) { 846 LayoutCache::getInstance().dumpStats(fd); 847 } 848 849 } // namespace minikin 850