LineBreaker.cpp revision 4cf0e3763c5087db642757933de1fcae3074c76c
1/* 2 * Copyright (C) 2015 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 VERBOSE_DEBUG 0 18 19#define LOG_TAG "Minikin" 20 21#include <limits> 22 23#include <log/log.h> 24 25#include "LayoutUtils.h" 26#include "WordBreaker.h" 27#include <minikin/Layout.h> 28#include <minikin/LineBreaker.h> 29 30using std::vector; 31 32namespace minikin { 33 34constexpr uint16_t CHAR_TAB = 0x0009; 35constexpr uint16_t CHAR_NBSP = 0x00A0; 36 37// Large scores in a hierarchy; we prefer desperate breaks to an overfull line. All these 38// constants are larger than any reasonable actual width score. 39const float SCORE_INFTY = std::numeric_limits<float>::max(); 40const float SCORE_OVERFULL = 1e12f; 41const float SCORE_DESPERATE = 1e10f; 42 43// Multiplier for hyphen penalty on last line. 44const float LAST_LINE_PENALTY_MULTIPLIER = 4.0f; 45// Penalty assigned to each line break (to try to minimize number of lines) 46// TODO: when we implement full justification (so spaces can shrink and stretch), this is 47// probably not the most appropriate method. 48const float LINE_PENALTY_MULTIPLIER = 2.0f; 49 50// Penalty assigned to shrinking the whitepsace. 51const float SHRINK_PENALTY_MULTIPLIER = 4.0f; 52 53// Very long words trigger O(n^2) behavior in hyphenation, so we disable hyphenation for 54// unreasonably long words. This is somewhat of a heuristic because extremely long words 55// are possible in some languages. This does mean that very long real words can get 56// broken by desperate breaks, with no hyphens. 57const size_t LONGEST_HYPHENATED_WORD = 45; 58 59// When the text buffer is within this limit, capacity of vectors is retained at finish(), 60// to avoid allocation. 61const size_t MAX_TEXT_BUF_RETAIN = 32678; 62 63// Maximum amount that spaces can shrink, in justified text. 64const float SHRINKABILITY = 1.0 / 3.0; 65 66LineBreaker::LineBreaker() : mWordBreaker(std::make_unique<WordBreaker>()) { } 67 68LineBreaker::LineBreaker(std::unique_ptr<WordBreaker>&& breaker) 69 : mWordBreaker(std::move(breaker)) { } 70 71LineBreaker::~LineBreaker() {} 72 73void LineBreaker::setLocales(const char* locales, const std::vector<Hyphenator*>& hyphenators, 74 size_t restartFrom) { 75 bool goodLocaleFound = false; 76 const ssize_t numLocales = hyphenators.size(); 77 // For now, we ignore all locales except the first valid one. 78 // TODO: Support selecting the locale based on the script of the text. 79 const char* localeStart = locales; 80 icu::Locale locale; 81 for (ssize_t i = 0; i < numLocales - 1; i++) { // Loop over all locales, except the last one. 82 const char* localeEnd = strchr(localeStart, ','); 83 const size_t localeNameLength = localeEnd - localeStart; 84 char localeName[localeNameLength + 1]; 85 strncpy(localeName, localeStart, localeNameLength); 86 localeName[localeNameLength] = '\0'; 87 locale = icu::Locale::createFromName(localeName); 88 goodLocaleFound = !locale.isBogus(); 89 if (goodLocaleFound) { 90 mHyphenator = hyphenators[i]; 91 break; 92 } else { 93 localeStart = localeEnd + 1; 94 } 95 } 96 if (!goodLocaleFound) { // Try the last locale. 97 locale = icu::Locale::createFromName(localeStart); 98 if (locale.isBogus()) { 99 // No good locale. 100 locale = icu::Locale::getRoot(); 101 mHyphenator = nullptr; 102 } else { 103 mHyphenator = numLocales == 0 ? nullptr : hyphenators[numLocales - 1]; 104 } 105 } 106 mWordBreaker->followingWithLocale(locale, restartFrom); 107} 108 109void LineBreaker::setText() { 110 mWordBreaker->setText(mTextBuf.data(), mTextBuf.size()); 111 112 // handle initial break here because addStyleRun may never be called 113 mCandidates.clear(); 114 Candidate cand = { 115 0, 0, 0.0, 0.0, 0.0, 0.0, 0, 0, {0.0, 0.0, 0.0}, HyphenationType::DONT_BREAK}; 116 mCandidates.push_back(cand); 117 118 // reset greedy breaker state 119 mBreaks.clear(); 120 mWidths.clear(); 121 mAscents.clear(); 122 mDescents.clear(); 123 mFlags.clear(); 124 mLastBreak = 0; 125 mBestBreak = 0; 126 mBestScore = SCORE_INFTY; 127 mPreBreak = 0; 128 mLastHyphenation = HyphenEdit::NO_EDIT; 129 mFirstTabIndex = INT_MAX; 130 mSpaceCount = 0; 131} 132 133// This function determines whether a character is a space that disappears at end of line. 134// It is the Unicode set: [[:General_Category=Space_Separator:]-[:Line_Break=Glue:]], 135// plus '\n'. 136// Note: all such characters are in the BMP, so it's ok to use code units for this. 137static bool isLineEndSpace(uint16_t c) { 138 return c == '\n' || c == ' ' || c == 0x1680 || (0x2000 <= c && c <= 0x200A && c != 0x2007) || 139 c == 0x205F || c == 0x3000; 140} 141 142// Hyphenates a string potentially containing non-breaking spaces. 143std::vector<HyphenationType> LineBreaker::hyphenate(const uint16_t* str, size_t len) { 144 std::vector<HyphenationType> out; 145 out.reserve(len); 146 147 // A word here is any consecutive string of non-NBSP characters. 148 bool inWord = false; 149 size_t wordStart = 0; // The initial value will never be accessed, but just in case. 150 for (size_t i = 0; i <= len; i++) { 151 if (i == len || str[i] == CHAR_NBSP) { 152 if (inWord) { 153 // A word just ended. Hyphenate it. 154 const size_t wordLen = i - wordStart; 155 if (wordLen <= LONGEST_HYPHENATED_WORD) { 156 if (wordStart == 0) { 157 // The string starts with a word. Use out directly. 158 mHyphenator->hyphenate(&out, str, wordLen); 159 } else { 160 std::vector<HyphenationType> wordVec; 161 mHyphenator->hyphenate(&wordVec, str + wordStart, wordLen); 162 out.insert(out.end(), wordVec.cbegin(), wordVec.cend()); 163 } 164 } else { // Word is too long. Inefficient to hyphenate. 165 out.insert(out.end(), wordLen, HyphenationType::DONT_BREAK); 166 } 167 inWord = false; 168 } 169 if (i < len) { 170 // Insert one DONT_BREAK for the NBSP. 171 out.push_back(HyphenationType::DONT_BREAK); 172 } 173 } else if (!inWord) { 174 inWord = true; 175 wordStart = i; 176 } 177 } 178 return out; 179} 180 181// Compute the total overhang of text based on per-cluster advances and overhangs. 182// The two input vectors are expected to be of the same size. 183/* static */ LayoutOverhang LineBreaker::computeOverhang(float totalAdvance, 184 const std::vector<float>& advances, const std::vector<LayoutOverhang>& overhangs, 185 bool isRtl) { 186 ParaWidth left = 0.0; 187 ParaWidth right = 0.0; 188 ParaWidth seenAdvance = 0.0; 189 const size_t len = advances.size(); 190 if (isRtl) { 191 for (size_t i = 0; i < len; i++) { 192 right = std::max(right, overhangs[i].right - seenAdvance); 193 seenAdvance += advances[i]; 194 left = std::max(left, overhangs[i].left - (totalAdvance - seenAdvance)); 195 } 196 } else { 197 for (size_t i = 0; i < len; i++) { 198 left = std::max(left, overhangs[i].left - seenAdvance); 199 seenAdvance += advances[i]; 200 right = std::max(right, overhangs[i].right - (totalAdvance - seenAdvance)); 201 } 202 } 203 return {static_cast<float>(left), static_cast<float>(right)}; 204} 205 206// This adds all the hyphenation candidates for a given word by first finding all the hyphenation 207// points and then calling addWordBreak for each. 208// 209// paint and typeface will be used for measuring the text and paint must not be null. 210// runStart is the beginning of the whole run, and is used for making sure we don't hyphenated words 211// that go outside the run. 212// afterWord is the index of last code unit seen that's not a line-ending space, plus one. In other 213// words, the index of the first code unit after a word. 214// lastBreak is the index of the previous break point and lastBreakWidth is the width seen until 215// that point. 216// extent is a pointer to the variable that keeps track of the maximum vertical extents seen since 217// the last time addWordBreak() was called. It will be reset to 0 after every call. 218// bidiFlags keep the bidi flags to determine the direction of text for layout and other 219// calculations. It may only be kBidi_Force_RTL or kBidi_Force_LTR. 220// 221// The following parameters are needed to be passed to addWordBreak: 222// postBreak is the width that would be seen if we decide to break at the end of the word (so it 223// doesn't count any line-ending space after the word). 224// postSpaceCount is the number of spaces that would be seen if we decide to break at the end of the 225// word (and like postBreak it doesn't count any line-ending space after the word). 226// hyphenPenalty is the amount of penalty for hyphenation. 227void LineBreaker::addHyphenationCandidates(MinikinPaint* paint, 228 const std::shared_ptr<FontCollection>& typeface, FontStyle style, size_t runStart, 229 size_t afterWord, size_t lastBreak, ParaWidth lastBreakWidth, ParaWidth postBreak, 230 size_t postSpaceCount, MinikinExtent* extent, float hyphenPenalty, int bidiFlags) { 231 const bool isRtl = (bidiFlags == kBidi_Force_RTL); 232 const size_t wordStart = mWordBreaker->wordStart(); 233 const size_t wordEnd = mWordBreaker->wordEnd(); 234 if (wordStart < runStart || wordEnd <= wordStart) { 235 return; 236 } 237 std::vector<HyphenationType> hyphenResult = 238 hyphenate(&mTextBuf[wordStart], wordEnd - wordStart); 239 240 std::vector<float> advances; 241 std::vector<LayoutOverhang> overhangs; 242 const size_t bufferSize = std::max(wordEnd - 1 - lastBreak, afterWord - wordStart); 243 advances.reserve(bufferSize); 244 overhangs.reserve(bufferSize); 245 // measure hyphenated substrings 246 for (size_t j = wordStart; j < wordEnd; j++) { 247 HyphenationType hyph = hyphenResult[j - wordStart]; 248 if (hyph == HyphenationType::DONT_BREAK) { 249 continue; 250 } 251 252 paint->hyphenEdit = HyphenEdit::editForThisLine(hyph); 253 const size_t firstPartLen = j - lastBreak; 254 advances.resize(firstPartLen); 255 overhangs.resize(firstPartLen); 256 const float firstPartWidth = Layout::measureText(mTextBuf.data(), lastBreak, firstPartLen, 257 mTextBuf.size(), bidiFlags, style, *paint, typeface, advances.data(), 258 nullptr /* extent */, overhangs.data()); 259 const LayoutOverhang overhang = computeOverhang(firstPartWidth, advances, overhangs, isRtl); 260 const ParaWidth hyphPostBreak = lastBreakWidth + firstPartWidth 261 + (isRtl ? overhang.left : overhang.right); 262 263 paint->hyphenEdit = HyphenEdit::editForNextLine(hyph); // XXX may need to be NO_EDIT. 264 const size_t secondPartLen = afterWord - j; 265 const float secondPartWidth = Layout::measureText(mTextBuf.data(), j, secondPartLen, 266 mTextBuf.size(), bidiFlags, style, *paint, typeface, nullptr /* advances */, 267 nullptr /* extent */, nullptr /* overhangs */); 268 const ParaWidth hyphPreBreak = postBreak - secondPartWidth; 269 270 addWordBreak(j, hyphPreBreak, hyphPostBreak, postSpaceCount, 271 postSpaceCount, *extent, hyphenPenalty, hyph); 272 extent->reset(); 273 274 paint->hyphenEdit = HyphenEdit::NO_EDIT; 275 } 276} 277 278// Ordinarily, this method measures the text in the range given. However, when paint is nullptr, it 279// assumes the character widths, extents, and overhangs have already been calculated and stored in 280// the mCharWidths, mCharExtents, and mCharOverhangs buffers. 281// 282// This method finds the candidate word breaks (using the ICU break iterator) and sends them 283// to addCandidate. 284float LineBreaker::addStyleRun(MinikinPaint* paint, const std::shared_ptr<FontCollection>& typeface, 285 FontStyle style, size_t start, size_t end, bool isRtl, const char* langTags, 286 const std::vector<Hyphenator*>& hyphenators) { 287 float width = 0.0f; 288 const int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR; 289 290 float hyphenPenalty = 0.0; 291 if (paint != nullptr) { 292 width = Layout::measureText(mTextBuf.data(), start, end - start, mTextBuf.size(), bidiFlags, 293 style, *paint, typeface, mCharWidths.data() + start, mCharExtents.data() + start, 294 mCharOverhangs.data() + start); 295 296 // a heuristic that seems to perform well 297 hyphenPenalty = 0.5 * paint->size * paint->scaleX * mLineWidthDelegate->getLineWidth(0); 298 if (mHyphenationFrequency == kHyphenationFrequency_Normal) { 299 hyphenPenalty *= 4.0; // TODO: Replace with a better value after some testing 300 } 301 302 if (mJustified) { 303 // Make hyphenation more aggressive for fully justified text (so that "normal" in 304 // justified mode is the same as "full" in ragged-right). 305 hyphenPenalty *= 0.25; 306 } else { 307 // Line penalty is zero for justified text. 308 mLinePenalty = std::max(mLinePenalty, hyphenPenalty * LINE_PENALTY_MULTIPLIER); 309 } 310 } 311 312 // Caller passes nullptr for langTag if language is not changed. 313 if (langTags != nullptr) { 314 setLocales(langTags, hyphenators, start); 315 } 316 size_t current = (size_t) mWordBreaker->current(); 317 // This will keep the index of last code unit seen that's not a line-ending space, plus one. 318 // In other words, the index of the first code unit after a word. 319 size_t afterWord = start; 320 321 size_t lastBreak = start; // The index of the previous break point. 322 ParaWidth lastBreakWidth = mWidth; // The width of the text as of the previous break point. 323 ParaWidth postBreak = mWidth; // The width of text seen if we decide to break here 324 size_t postSpaceCount = mSpaceCount; 325 MinikinExtent extent = {0.0, 0.0, 0.0}; 326 for (size_t i = start; i < end; i++) { 327 const uint16_t c = mTextBuf[i]; 328 if (c == CHAR_TAB) { 329 mWidth = mPreBreak + mTabStops.nextTab(mWidth - mPreBreak); 330 if (mFirstTabIndex == INT_MAX) { 331 mFirstTabIndex = (int)i; 332 } 333 // fall back to greedy; other modes don't know how to deal with tabs 334 mStrategy = kBreakStrategy_Greedy; 335 } else { 336 if (isWordSpace(c)) { 337 mSpaceCount += 1; 338 } 339 mWidth += mCharWidths[i]; 340 extent.extendBy(mCharExtents[i]); 341 if (isLineEndSpace(c)) { 342 // If we break a line on a line-ending space, that space goes away. So postBreak 343 // and postSpaceCount, which keep the width and number of spaces if we decide to 344 // break at this point, don't need to get adjusted. 345 } else { 346 postBreak = mWidth; 347 postSpaceCount = mSpaceCount; 348 afterWord = i + 1; 349 } 350 } 351 if (i + 1 == current) { // We are at the end of a word. 352 if (paint != nullptr && mHyphenator != nullptr && 353 mHyphenationFrequency != kHyphenationFrequency_None) { 354 addHyphenationCandidates(paint, typeface, style, start, afterWord, lastBreak, 355 lastBreakWidth, postBreak, postSpaceCount, &extent, hyphenPenalty, 356 bidiFlags); 357 } 358 359 // Skip break for zero-width characters inside replacement span 360 if (paint != nullptr || current == end || mCharWidths[current] > 0) { 361 const float penalty = hyphenPenalty * mWordBreaker->breakBadness(); 362 addWordBreak(current, mWidth, postBreak, mSpaceCount, postSpaceCount, extent, 363 penalty, HyphenationType::DONT_BREAK); 364 extent.reset(); 365 } 366 lastBreak = current; 367 lastBreakWidth = mWidth; 368 current = (size_t)mWordBreaker->next(); 369 } 370 } 371 372 return width; 373} 374 375// add a word break (possibly for a hyphenated fragment), and add desperate breaks if 376// needed (ie when word exceeds current line width) 377void LineBreaker::addWordBreak(size_t offset, ParaWidth preBreak, ParaWidth postBreak, 378 size_t preSpaceCount, size_t postSpaceCount, MinikinExtent extent, 379 float penalty, HyphenationType hyph) { 380 Candidate cand; 381 ParaWidth width = mCandidates.back().preBreak; 382 if (postBreak - width > currentLineWidth()) { 383 // Add desperate breaks. 384 // Note: these breaks are based on the shaping of the (non-broken) original text; they 385 // are imprecise especially in the presence of kerning, ligatures, and Arabic shaping. 386 size_t i = mCandidates.back().offset; 387 width += mCharWidths[i++]; 388 for (; i < offset; i++) { 389 float w = mCharWidths[i]; 390 if (w > 0) { 391 cand.offset = i; 392 cand.preBreak = width; 393 cand.postBreak = width; 394 // postSpaceCount doesn't include trailing spaces 395 cand.preSpaceCount = postSpaceCount; 396 cand.postSpaceCount = postSpaceCount; 397 cand.extent = mCharExtents[i]; 398 cand.penalty = SCORE_DESPERATE; 399 cand.hyphenType = HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN; 400#if VERBOSE_DEBUG 401 ALOGD("desperate cand: %zd %g:%g", 402 mCandidates.size(), cand.postBreak, cand.preBreak); 403#endif 404 addCandidate(cand); 405 width += w; 406 } 407 } 408 } 409 410 cand.offset = offset; 411 cand.preBreak = preBreak; 412 cand.postBreak = postBreak; 413 cand.penalty = penalty; 414 cand.preSpaceCount = preSpaceCount; 415 cand.postSpaceCount = postSpaceCount; 416 cand.extent = extent; 417 cand.hyphenType = hyph; 418#if VERBOSE_DEBUG 419 ALOGD("cand: %zd %g:%g", mCandidates.size(), cand.postBreak, cand.preBreak); 420#endif 421 addCandidate(cand); 422} 423 424// Find the needed extent between the start and end ranges. start and end are inclusive. 425MinikinExtent LineBreaker::computeMaxExtent(size_t start, size_t end) const { 426 MinikinExtent res = mCandidates[end].extent; 427 for (size_t j = start; j < end; j++) { 428 res.extendBy(mCandidates[j].extent); 429 } 430 return res; 431} 432 433// Helper method for addCandidate() 434void LineBreaker::pushGreedyBreak() { 435 const Candidate& bestCandidate = mCandidates[mBestBreak]; 436 pushBreak(bestCandidate.offset, bestCandidate.postBreak - mPreBreak, 437 computeMaxExtent(mLastBreak + 1, mBestBreak), 438 mLastHyphenation | HyphenEdit::editForThisLine(bestCandidate.hyphenType)); 439 mBestScore = SCORE_INFTY; 440#if VERBOSE_DEBUG 441 ALOGD("break: %d %g", mBreaks.back(), mWidths.back()); 442#endif 443 mLastBreak = mBestBreak; 444 mPreBreak = bestCandidate.preBreak; 445 mLastHyphenation = HyphenEdit::editForNextLine(bestCandidate.hyphenType); 446} 447 448// TODO performance: could avoid populating mCandidates if greedy only 449void LineBreaker::addCandidate(Candidate cand) { 450 const size_t candIndex = mCandidates.size(); 451 mCandidates.push_back(cand); 452 453 // mLastBreak is the index of the last line break we decided to do in mCandidates, 454 // and mPreBreak is its preBreak value. mBestBreak is the index of the best line breaking 455 // candidate we have found since then, and mBestScore is its penalty. 456 if (cand.postBreak - mPreBreak > currentLineWidth()) { 457 // This break would create an overfull line, pick the best break and break there (greedy) 458 if (mBestBreak == mLastBreak) { 459 // No good break has been found since last break. Break here. 460 mBestBreak = candIndex; 461 } 462 pushGreedyBreak(); 463 } 464 465 while (mLastBreak != candIndex && cand.postBreak - mPreBreak > currentLineWidth()) { 466 // We should rarely come here. But if we are here, we have broken the line, but the 467 // remaining part still doesn't fit. We now need to break at the second best place after the 468 // last break, but we have not kept that information, so we need to go back and find it. 469 // 470 // In some really rare cases, postBreak - preBreak of a candidate itself may be over the 471 // current line width. We protect ourselves against an infinite loop in that case by 472 // checking that we have not broken the line at this candidate already. 473 for (size_t i = mLastBreak + 1; i < candIndex; i++) { 474 const float penalty = mCandidates[i].penalty; 475 if (penalty <= mBestScore) { 476 mBestBreak = i; 477 mBestScore = penalty; 478 } 479 } 480 if (mBestBreak == mLastBreak) { 481 // We didn't find anything good. Break here. 482 mBestBreak = candIndex; 483 } 484 pushGreedyBreak(); 485 } 486 487 if (cand.penalty <= mBestScore) { 488 mBestBreak = candIndex; 489 mBestScore = cand.penalty; 490 } 491} 492 493void LineBreaker::pushBreak(int offset, float width, MinikinExtent extent, uint8_t hyphenEdit) { 494 mBreaks.push_back(offset); 495 mWidths.push_back(width); 496 mAscents.push_back(extent.ascent); 497 mDescents.push_back(extent.descent); 498 int flags = (mFirstTabIndex < mBreaks.back()) << kTab_Shift; 499 flags |= hyphenEdit; 500 mFlags.push_back(flags); 501 mFirstTabIndex = INT_MAX; 502} 503 504void LineBreaker::addReplacement(size_t start, size_t end, float width, const char* langTags, 505 const std::vector<Hyphenator*>& hyphenators) { 506 mCharWidths[start] = width; 507 std::fill(&mCharWidths[start + 1], &mCharWidths[end], 0.0f); 508 // TODO: Get the extents information from the caller. 509 std::fill(&mCharExtents[start], &mCharExtents[end], (MinikinExtent) {0.0f, 0.0f, 0.0f}); 510 addStyleRun(nullptr, nullptr, FontStyle(), start, end, false, langTags, hyphenators); 511} 512 513// Get the width of a space. May return 0 if there are no spaces. 514// Note: if there are multiple different widths for spaces (for example, because of mixing of 515// fonts), it's only guaranteed to pick one. 516float LineBreaker::getSpaceWidth() const { 517 for (size_t i = 0; i < mTextBuf.size(); i++) { 518 if (isWordSpace(mTextBuf[i])) { 519 return mCharWidths[i]; 520 } 521 } 522 return 0.0f; 523} 524 525float LineBreaker::currentLineWidth() const { 526 return mLineWidthDelegate->getLineWidth(mBreaks.size()); 527} 528 529void LineBreaker::computeBreaksGreedy() { 530 // All breaks but the last have been added in addCandidate already. 531 size_t nCand = mCandidates.size(); 532 if (nCand == 1 || mLastBreak != nCand - 1) { 533 pushBreak(mCandidates[nCand - 1].offset, mCandidates[nCand - 1].postBreak - mPreBreak, 534 computeMaxExtent(mLastBreak + 1, nCand - 1), 535 mLastHyphenation); 536 // don't need to update mBestScore, because we're done 537#if VERBOSE_DEBUG 538 ALOGD("final break: %d %g", mBreaks.back(), mWidths.back()); 539#endif 540 } 541} 542 543// Follow "prev" links in mCandidates array, and copy to result arrays. 544void LineBreaker::finishBreaksOptimal() { 545 // clear existing greedy break result 546 mBreaks.clear(); 547 mWidths.clear(); 548 mAscents.clear(); 549 mDescents.clear(); 550 mFlags.clear(); 551 552 size_t nCand = mCandidates.size(); 553 size_t prev; 554 for (size_t i = nCand - 1; i > 0; i = prev) { 555 prev = mCandidates[i].prev; 556 mBreaks.push_back(mCandidates[i].offset); 557 mWidths.push_back(mCandidates[i].postBreak - mCandidates[prev].preBreak); 558 MinikinExtent extent = computeMaxExtent(prev + 1, i); 559 mAscents.push_back(extent.ascent); 560 mDescents.push_back(extent.descent); 561 int flags = HyphenEdit::editForThisLine(mCandidates[i].hyphenType); 562 if (prev > 0) { 563 flags |= HyphenEdit::editForNextLine(mCandidates[prev].hyphenType); 564 } 565 mFlags.push_back(flags); 566 } 567 std::reverse(mBreaks.begin(), mBreaks.end()); 568 std::reverse(mWidths.begin(), mWidths.end()); 569 std::reverse(mFlags.begin(), mFlags.end()); 570} 571 572void LineBreaker::computeBreaksOptimal() { 573 size_t active = 0; 574 size_t nCand = mCandidates.size(); 575 float width = mLineWidthDelegate->getLineWidth(0); 576 float maxShrink = mJustified ? SHRINKABILITY * getSpaceWidth() : 0.0f; 577 std::vector<size_t> lineNumbers; 578 lineNumbers.reserve(nCand); 579 lineNumbers.push_back(0); // The first candidate is always at the first line. 580 581 // "i" iterates through candidates for the end of the line. 582 for (size_t i = 1; i < nCand; i++) { 583 bool atEnd = i == nCand - 1; 584 float best = SCORE_INFTY; 585 size_t bestPrev = 0; 586 587 size_t lineNumberLast = lineNumbers[active]; 588 width = mLineWidthDelegate->getLineWidth(lineNumberLast); 589 590 ParaWidth leftEdge = mCandidates[i].postBreak - width; 591 float bestHope = 0; 592 593 // "j" iterates through candidates for the beginning of the line. 594 for (size_t j = active; j < i; j++) { 595 size_t lineNumber = lineNumbers[j]; 596 if (lineNumber != lineNumberLast) { 597 float widthNew = mLineWidthDelegate->getLineWidth(lineNumber); 598 if (widthNew != width) { 599 leftEdge = mCandidates[i].postBreak - width; 600 bestHope = 0; 601 width = widthNew; 602 } 603 lineNumberLast = lineNumber; 604 } 605 float jScore = mCandidates[j].score; 606 if (jScore + bestHope >= best) continue; 607 float delta = mCandidates[j].preBreak - leftEdge; 608 609 // compute width score for line 610 611 // Note: the "bestHope" optimization makes the assumption that, when delta is 612 // non-negative, widthScore will increase monotonically as successive candidate 613 // breaks are considered. 614 float widthScore = 0.0f; 615 float additionalPenalty = 0.0f; 616 if ((atEnd || !mJustified) && delta < 0) { 617 widthScore = SCORE_OVERFULL; 618 } else if (atEnd && mStrategy != kBreakStrategy_Balanced) { 619 // increase penalty for hyphen on last line 620 additionalPenalty = LAST_LINE_PENALTY_MULTIPLIER * mCandidates[j].penalty; 621 } else { 622 widthScore = delta * delta; 623 if (delta < 0) { 624 if (-delta < maxShrink * 625 (mCandidates[i].postSpaceCount - mCandidates[j].preSpaceCount)) { 626 widthScore *= SHRINK_PENALTY_MULTIPLIER; 627 } else { 628 widthScore = SCORE_OVERFULL; 629 } 630 } 631 } 632 633 if (delta < 0) { 634 active = j + 1; 635 } else { 636 bestHope = widthScore; 637 } 638 639 float score = jScore + widthScore + additionalPenalty; 640 if (score <= best) { 641 best = score; 642 bestPrev = j; 643 } 644 } 645 mCandidates[i].score = best + mCandidates[i].penalty + mLinePenalty; 646 mCandidates[i].prev = bestPrev; 647 lineNumbers.push_back(lineNumbers[bestPrev] + 1); 648#if VERBOSE_DEBUG 649 ALOGD("break %zd: score=%g, prev=%zd", i, mCandidates[i].score, mCandidates[i].prev); 650#endif 651 } 652 finishBreaksOptimal(); 653} 654 655size_t LineBreaker::computeBreaks() { 656 if (mStrategy == kBreakStrategy_Greedy) { 657 computeBreaksGreedy(); 658 } else { 659 computeBreaksOptimal(); 660 } 661 return mBreaks.size(); 662} 663 664void LineBreaker::finish() { 665 mWordBreaker->finish(); 666 mWidth = 0; 667 mCandidates.clear(); 668 mBreaks.clear(); 669 mWidths.clear(); 670 mAscents.clear(); 671 mDescents.clear(); 672 mFlags.clear(); 673 if (mTextBuf.size() > MAX_TEXT_BUF_RETAIN) { 674 mTextBuf.clear(); 675 mTextBuf.shrink_to_fit(); 676 mCharWidths.clear(); 677 mCharWidths.shrink_to_fit(); 678 mCharExtents.clear(); 679 mCharExtents.shrink_to_fit(); 680 mCandidates.shrink_to_fit(); 681 mBreaks.shrink_to_fit(); 682 mWidths.shrink_to_fit(); 683 mAscents.shrink_to_fit(); 684 mDescents.shrink_to_fit(); 685 mFlags.shrink_to_fit(); 686 } 687 mStrategy = kBreakStrategy_Greedy; 688 mHyphenationFrequency = kHyphenationFrequency_Normal; 689 mLinePenalty = 0.0f; 690 mJustified = false; 691 mLineWidthDelegate.reset(); 692} 693 694} // namespace minikin 695