1 /*
2 * Copyright (C) 2017 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 "GreedyLineBreak"
18
19 #include "FeatureFlags.h"
20 #include "HyphenatorMap.h"
21 #include "LineBreakerUtil.h"
22 #include "Locale.h"
23 #include "LocaleListCache.h"
24 #include "WordBreaker.h"
25 #include "minikin/Characters.h"
26 #include "minikin/LineBreaker.h"
27 #include "minikin/MeasuredText.h"
28 #include "minikin/Range.h"
29 #include "minikin/U16StringPiece.h"
30
31 namespace minikin {
32
33 namespace {
34
35 constexpr uint32_t NOWHERE = 0xFFFFFFFF;
36
37 class GreedyLineBreaker {
38 public:
39 // User of this class must keep measured, lineWidthLimit, tabStop alive until the instance is
40 // destructed.
GreedyLineBreaker(const U16StringPiece & textBuf,const MeasuredText & measured,const LineWidth & lineWidthLimits,const TabStops & tabStops,bool enableHyphenation,bool useBoundsForWidth)41 GreedyLineBreaker(const U16StringPiece& textBuf, const MeasuredText& measured,
42 const LineWidth& lineWidthLimits, const TabStops& tabStops,
43 bool enableHyphenation, bool useBoundsForWidth)
44 : mLineWidthLimit(lineWidthLimits.getAt(0)),
45 mTextBuf(textBuf),
46 mMeasuredText(measured),
47 mLineWidthLimits(lineWidthLimits),
48 mTabStops(tabStops),
49 mEnableHyphenation(enableHyphenation),
50 mUseBoundsForWidth(useBoundsForWidth) {}
51
52 void process(bool forceWordStyleAutoToPhrase);
53
54 LineBreakResult getResult() const;
55
56 bool retryWithPhraseWordBreak = false;
57
58 private:
59 struct BreakPoint {
BreakPointminikin::__anon7b4d708d0111::GreedyLineBreaker::BreakPoint60 BreakPoint(uint32_t offset, float lineWidth, StartHyphenEdit startHyphen,
61 EndHyphenEdit endHyphen)
62 : offset(offset),
63 lineWidth(lineWidth),
64 hyphenEdit(packHyphenEdit(startHyphen, endHyphen)) {}
65
66 uint32_t offset;
67 float lineWidth;
68 HyphenEdit hyphenEdit;
69 };
70
getPrevLineBreakOffset()71 inline uint32_t getPrevLineBreakOffset() {
72 return mBreakPoints.empty() ? 0 : mBreakPoints.back().offset;
73 }
74
75 // Registers the break point and prepares for next line computation.
76 void breakLineAt(uint32_t offset, float lineWidth, float remainingNextLineWidth,
77 float remainingNextSumOfCharWidths, EndHyphenEdit thisLineEndHyphen,
78 StartHyphenEdit nextLineStartHyphen);
79
80 // Update current line width.
81 void updateLineWidth(uint16_t c, float width);
82
83 // Break line if current line exceeds the line limit.
84 void processLineBreak(uint32_t offset, WordBreaker* breaker, bool doHyphenation);
85
86 // Try to break with previous word boundary.
87 // Returns false if unable to break by word boundary.
88 bool tryLineBreakWithWordBreak();
89
90 // Try to break with hyphenation.
91 // Returns false if unable to hyphenate.
92 //
93 // This method keeps hyphenation until the line width after line break meets the line width
94 // limit.
95 bool tryLineBreakWithHyphenation(const Range& range, WordBreaker* breaker);
96
97 // Do line break with each characters.
98 //
99 // This method only breaks at the first offset which has the longest width for the line width
100 // limit. This method don't keep line breaking even if the rest of the word exceeds the line
101 // width limit.
102 // This method return true if there is no characters to be processed.
103 bool doLineBreakWithGraphemeBounds(const Range& range);
104
105 bool overhangExceedLineLimit(const Range& range);
106 bool doLineBreakWithFallback(const Range& range);
107
108 // Returns true if the current break point exceeds the width constraint.
109 bool isWidthExceeded() const;
110
111 // Info about the line currently processing.
112 uint32_t mLineNum = 0;
113 double mLineWidth = 0;
114 double mSumOfCharWidths = 0;
115 double mLineWidthLimit;
116 StartHyphenEdit mStartHyphenEdit = StartHyphenEdit::NO_EDIT;
117
118 // Previous word break point info.
119 uint32_t mPrevWordBoundsOffset = NOWHERE;
120 double mLineWidthAtPrevWordBoundary = 0;
121 double mSumOfCharWidthsAtPrevWordBoundary = 0;
122 bool mIsPrevWordBreakIsInEmailOrUrl = false;
123 float mLineStartLetterSpacing = 0; // initialized in the first loop of the run.
124 float mCurrentLetterSpacing = 0;
125
126 // The hyphenator currently used.
127 const Hyphenator* mHyphenator = nullptr;
128
129 // Input parameters.
130 const U16StringPiece& mTextBuf;
131 const MeasuredText& mMeasuredText;
132 const LineWidth& mLineWidthLimits;
133 const TabStops& mTabStops;
134 bool mEnableHyphenation;
135 bool mUseBoundsForWidth;
136
137 // The result of line breaking.
138 std::vector<BreakPoint> mBreakPoints;
139
140 MINIKIN_PREVENT_COPY_ASSIGN_AND_MOVE(GreedyLineBreaker);
141 };
142
breakLineAt(uint32_t offset,float lineWidth,float remainingNextLineWidth,float remainingNextSumOfCharWidths,EndHyphenEdit thisLineEndHyphen,StartHyphenEdit nextLineStartHyphen)143 void GreedyLineBreaker::breakLineAt(uint32_t offset, float lineWidth, float remainingNextLineWidth,
144 float remainingNextSumOfCharWidths,
145 EndHyphenEdit thisLineEndHyphen,
146 StartHyphenEdit nextLineStartHyphen) {
147 float edgeLetterSpacing = (mLineStartLetterSpacing + mCurrentLetterSpacing) / 2.0f;
148 // First, push the break to result.
149 mBreakPoints.emplace_back(offset, lineWidth - edgeLetterSpacing, mStartHyphenEdit,
150 thisLineEndHyphen);
151
152 // Update the current line info.
153 mLineWidthLimit = mLineWidthLimits.getAt(++mLineNum);
154 mLineWidth = remainingNextLineWidth;
155 mSumOfCharWidths = remainingNextSumOfCharWidths;
156 mStartHyphenEdit = nextLineStartHyphen;
157 mPrevWordBoundsOffset = NOWHERE;
158 mLineWidthAtPrevWordBoundary = 0;
159 mSumOfCharWidthsAtPrevWordBoundary = 0;
160 mIsPrevWordBreakIsInEmailOrUrl = false;
161 mLineStartLetterSpacing = mCurrentLetterSpacing;
162 }
163
tryLineBreakWithWordBreak()164 bool GreedyLineBreaker::tryLineBreakWithWordBreak() {
165 if (mPrevWordBoundsOffset == NOWHERE) {
166 return false; // No word break point before..
167 }
168
169 breakLineAt(mPrevWordBoundsOffset, // break offset
170 mLineWidthAtPrevWordBoundary, // line width
171 mLineWidth - mSumOfCharWidthsAtPrevWordBoundary, // remaining next line width
172 // remaining next sum of char widths.
173 mSumOfCharWidths - mSumOfCharWidthsAtPrevWordBoundary, EndHyphenEdit::NO_EDIT,
174 StartHyphenEdit::NO_EDIT); // No hyphen modification.
175 return true;
176 }
177
tryLineBreakWithHyphenation(const Range & range,WordBreaker * breaker)178 bool GreedyLineBreaker::tryLineBreakWithHyphenation(const Range& range, WordBreaker* breaker) {
179 if (!mEnableHyphenation || mHyphenator == nullptr) {
180 return false;
181 }
182
183 Run* targetRun = nullptr;
184 for (const auto& run : mMeasuredText.runs) {
185 if (run->getRange().contains(range)) {
186 targetRun = run.get();
187 }
188 }
189
190 if (targetRun == nullptr) {
191 return false; // The target range may lay on multiple run. Unable to hyphenate.
192 }
193
194 const Range targetRange = breaker->wordRange();
195 if (!range.contains(targetRange)) {
196 return false;
197 }
198
199 if (!targetRun->canHyphenate()) {
200 return false;
201 }
202
203 const std::vector<HyphenationType> hyphenResult =
204 hyphenate(mTextBuf.substr(targetRange), *mHyphenator);
205 Range contextRange = range;
206 uint32_t prevOffset = NOWHERE;
207 float prevWidth = 0;
208
209 // Look up the hyphenation point from the begining.
210 for (uint32_t i = targetRange.getStart(); i < targetRange.getEnd(); ++i) {
211 const HyphenationType hyph = hyphenResult[targetRange.toRangeOffset(i)];
212 if (hyph == HyphenationType::DONT_BREAK) {
213 continue; // Not a hyphenation point.
214 }
215
216 const float width =
217 targetRun->measureHyphenPiece(mTextBuf, contextRange.split(i).first,
218 mStartHyphenEdit, editForThisLine(hyph), nullptr);
219
220 if (width <= mLineWidthLimit) {
221 // There are still space, remember current offset and look up next hyphenation point.
222 prevOffset = i;
223 prevWidth = width;
224 continue;
225 }
226
227 if (prevOffset == NOWHERE) {
228 // Even with hyphenation, the piece is too long for line. Give up and break in
229 // character bounds.
230 doLineBreakWithGraphemeBounds(contextRange);
231 } else {
232 // Previous offset is the longest hyphenation piece. Break with it.
233 const HyphenationType hyph = hyphenResult[targetRange.toRangeOffset(prevOffset)];
234 const StartHyphenEdit nextLineStartHyphenEdit = editForNextLine(hyph);
235 const float remainingCharWidths = targetRun->measureHyphenPiece(
236 mTextBuf, contextRange.split(prevOffset).second, nextLineStartHyphenEdit,
237 EndHyphenEdit::NO_EDIT, nullptr);
238 breakLineAt(prevOffset, prevWidth,
239 remainingCharWidths - (mSumOfCharWidths - mLineWidth), remainingCharWidths,
240 editForThisLine(hyph), nextLineStartHyphenEdit);
241 }
242
243 if (mLineWidth <= mLineWidthLimit) {
244 // The remaining hyphenation piece is less than line width. No more hyphenation is
245 // needed. Go to next word.
246 return true;
247 }
248
249 // Even after line break, the remaining hyphenation piece is still too long for the limit.
250 // Keep hyphenating for the rest.
251 i = getPrevLineBreakOffset();
252 contextRange.setStart(i); // Update the hyphenation start point.
253 prevOffset = NOWHERE;
254 }
255
256 // Do the same line break at the end of text.
257 // TODO: Remove code duplication. This is the same as in the for loop but extracting function
258 // may not clear.
259 if (prevOffset == NOWHERE) {
260 doLineBreakWithGraphemeBounds(contextRange);
261 } else {
262 const HyphenationType hyph = hyphenResult[targetRange.toRangeOffset(prevOffset)];
263 const StartHyphenEdit nextLineStartHyphenEdit = editForNextLine(hyph);
264 const float remainingCharWidths = targetRun->measureHyphenPiece(
265 mTextBuf, contextRange.split(prevOffset).second, nextLineStartHyphenEdit,
266 EndHyphenEdit::NO_EDIT, nullptr);
267
268 breakLineAt(prevOffset, prevWidth, remainingCharWidths - (mSumOfCharWidths - mLineWidth),
269 remainingCharWidths, editForThisLine(hyph), nextLineStartHyphenEdit);
270 }
271
272 return true;
273 }
274
275 // TODO: Respect trailing line end spaces.
doLineBreakWithGraphemeBounds(const Range & range)276 bool GreedyLineBreaker::doLineBreakWithGraphemeBounds(const Range& range) {
277 float width = mMeasuredText.widths[range.getStart()];
278
279 const float estimatedLetterSpacing = (mLineStartLetterSpacing + mCurrentLetterSpacing) * 0.5;
280 // Starting from + 1 since at least one character needs to be assigned to a line.
281 for (uint32_t i = range.getStart() + 1; i < range.getEnd(); ++i) {
282 const float w = mMeasuredText.widths[i];
283 if (w == 0) {
284 continue; // w == 0 means here is not a grapheme bounds. Don't break here.
285 }
286 if (width + w - estimatedLetterSpacing > mLineWidthLimit ||
287 overhangExceedLineLimit(Range(range.getStart(), i + 1))) {
288 // Okay, here is the longest position.
289 breakLineAt(i, width, mLineWidth - width, mSumOfCharWidths - width,
290 EndHyphenEdit::NO_EDIT, StartHyphenEdit::NO_EDIT);
291
292 // This method only breaks at the first longest offset, since we may want to hyphenate
293 // the rest of the word.
294 return false;
295 } else {
296 width += w;
297 }
298 }
299
300 // Reaching here means even one character (or cluster) doesn't fit the line.
301 // Give up and break at the end of this range.
302 breakLineAt(range.getEnd(), mLineWidth, 0, 0, EndHyphenEdit::NO_EDIT, StartHyphenEdit::NO_EDIT);
303 return true;
304 }
305
doLineBreakWithFallback(const Range & range)306 bool GreedyLineBreaker::doLineBreakWithFallback(const Range& range) {
307 if (!features::phrase_strict_fallback()) {
308 return false;
309 }
310 Run* targetRun = nullptr;
311 for (const auto& run : mMeasuredText.runs) {
312 if (run->getRange().contains(range)) {
313 targetRun = run.get();
314 }
315 }
316
317 if (targetRun == nullptr) {
318 return false; // The target range may lay on multiple run. Unable to fallback.
319 }
320
321 if (targetRun->lineBreakWordStyle() == LineBreakWordStyle::None) {
322 return false; // If the line break word style is already none, nothing can be falled back.
323 }
324
325 WordBreaker wb;
326 wb.setText(mTextBuf.data(), mTextBuf.length());
327 ssize_t next = wb.followingWithLocale(getEffectiveLocale(targetRun->getLocaleListId()),
328 targetRun->lineBreakStyle(), LineBreakWordStyle::None,
329 range.getStart());
330
331 if (!range.contains(next)) {
332 return false; // No fallback break points.
333 }
334
335 int32_t prevBreak = -1;
336 float wordWidth = 0;
337 float preBreakWidth = 0;
338 for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
339 const float w = mMeasuredText.widths[i];
340 if (w == 0) {
341 continue; // w == 0 means here is not a grapheme bounds. Don't break here.
342 }
343 if (i == (uint32_t)next) {
344 if (preBreakWidth + wordWidth > mLineWidthLimit) {
345 if (prevBreak == -1) {
346 return false; // No candidate before this point. Give up.
347 }
348 breakLineAt(prevBreak, preBreakWidth, mLineWidth - preBreakWidth,
349 mSumOfCharWidths - preBreakWidth, EndHyphenEdit::NO_EDIT,
350 StartHyphenEdit::NO_EDIT);
351 return true;
352 }
353 prevBreak = i;
354 next = wb.next();
355 preBreakWidth += wordWidth;
356 wordWidth = w;
357 } else {
358 wordWidth += w;
359 }
360 }
361
362 if (preBreakWidth <= mLineWidthLimit) {
363 breakLineAt(prevBreak, preBreakWidth, mLineWidth - preBreakWidth,
364 mSumOfCharWidths - preBreakWidth, EndHyphenEdit::NO_EDIT,
365 StartHyphenEdit::NO_EDIT);
366 return true;
367 }
368
369 return false;
370 }
371
updateLineWidth(uint16_t c,float width)372 void GreedyLineBreaker::updateLineWidth(uint16_t c, float width) {
373 if (c == CHAR_TAB) {
374 mSumOfCharWidths = mTabStops.nextTab(mSumOfCharWidths);
375 mLineWidth = mSumOfCharWidths;
376 } else {
377 mSumOfCharWidths += width;
378 if (!isLineEndSpace(c)) {
379 mLineWidth = mSumOfCharWidths;
380 }
381 }
382 }
383
overhangExceedLineLimit(const Range & range)384 bool GreedyLineBreaker::overhangExceedLineLimit(const Range& range) {
385 if (!mUseBoundsForWidth) {
386 return false;
387 }
388 if (!mMeasuredText.hasOverhang(range)) {
389 return false;
390 }
391
392 uint32_t i;
393 for (i = 0; i < range.getLength(); ++i) {
394 uint16_t ch = mTextBuf[range.getEnd() - i - 1];
395 if (!isLineEndSpace(ch)) {
396 break;
397 }
398 }
399 if (i == range.getLength()) {
400 return false;
401 }
402
403 return mMeasuredText.getBounds(mTextBuf, Range(range.getStart(), range.getEnd() - i)).width() >
404 mLineWidthLimit;
405 }
406
isWidthExceeded() const407 bool GreedyLineBreaker::isWidthExceeded() const {
408 // The text layout adds letter spacing to the all characters, but the spaces at left and
409 // right edge are removed. Here, we use the accumulated character widths as a text widths, but
410 // it includes the letter spacing at the left and right edge. Thus, we need to remove a letter
411 // spacing amount for one character. However, it is hard to get letter spacing of the left and
412 // right edge and it makes greey line breaker O(n^2): n for line break and n for perforimng
413 // BiDi run resolution for getting left and right edge for every break opportunity. To avoid
414 // this performance regression, use the letter spacing of the previous break point and letter
415 // spacing of current break opportunity instead.
416 const float estimatedLetterSpacing = (mLineStartLetterSpacing + mCurrentLetterSpacing) * 0.5;
417 return (mLineWidth - estimatedLetterSpacing) > mLineWidthLimit;
418 }
419
processLineBreak(uint32_t offset,WordBreaker * breaker,bool doHyphenation)420 void GreedyLineBreaker::processLineBreak(uint32_t offset, WordBreaker* breaker,
421 bool doHyphenation) {
422 while (isWidthExceeded() || overhangExceedLineLimit(Range(getPrevLineBreakOffset(), offset))) {
423 if (tryLineBreakWithWordBreak()) {
424 continue; // The word in the new line may still be too long for the line limit.
425 }
426
427 if (doHyphenation &&
428 tryLineBreakWithHyphenation(Range(getPrevLineBreakOffset(), offset), breaker)) {
429 continue; // TODO: we may be able to return here.
430 }
431
432 if (doLineBreakWithFallback(Range(getPrevLineBreakOffset(), offset))) {
433 continue;
434 }
435
436 if (doLineBreakWithGraphemeBounds(Range(getPrevLineBreakOffset(), offset))) {
437 return;
438 }
439 }
440
441 // There is still spaces, remember current word break point as a candidate and wait next word.
442 const bool isInEmailOrUrl = breaker->breakBadness() != 0;
443 if (mPrevWordBoundsOffset == NOWHERE || mIsPrevWordBreakIsInEmailOrUrl | !isInEmailOrUrl) {
444 mPrevWordBoundsOffset = offset;
445 mLineWidthAtPrevWordBoundary = mLineWidth;
446 mSumOfCharWidthsAtPrevWordBoundary = mSumOfCharWidths;
447 mIsPrevWordBreakIsInEmailOrUrl = isInEmailOrUrl;
448 }
449 }
450
process(bool forceWordStyleAutoToPhrase)451 void GreedyLineBreaker::process(bool forceWordStyleAutoToPhrase) {
452 WordBreaker wordBreaker;
453 wordBreaker.setText(mTextBuf.data(), mTextBuf.size());
454
455 WordBreakerTransitionTracker wbTracker;
456 uint32_t nextWordBoundaryOffset = 0;
457 for (uint32_t runIndex = 0; runIndex < mMeasuredText.runs.size(); ++runIndex) {
458 const std::unique_ptr<Run>& run = mMeasuredText.runs[runIndex];
459 if (features::letter_spacing_justification()) {
460 mCurrentLetterSpacing = run->getLetterSpacingInPx();
461 if (runIndex == 0) {
462 mLineStartLetterSpacing = mCurrentLetterSpacing;
463 }
464 } else {
465 mCurrentLetterSpacing = 0;
466 mLineStartLetterSpacing = 0;
467 }
468 const Range range = run->getRange();
469
470 // Update locale if necessary.
471 if (wbTracker.update(*run)) {
472 const LocaleList& localeList = wbTracker.getCurrentLocaleList();
473 const Locale locale = localeList.empty() ? Locale() : localeList[0];
474
475 LineBreakWordStyle lbWordStyle = wbTracker.getCurrentLineBreakWordStyle();
476 std::tie(lbWordStyle, retryWithPhraseWordBreak) =
477 resolveWordStyleAuto(lbWordStyle, localeList, forceWordStyleAutoToPhrase);
478
479 nextWordBoundaryOffset = wordBreaker.followingWithLocale(locale, run->lineBreakStyle(),
480 lbWordStyle, range.getStart());
481 mHyphenator = HyphenatorMap::lookup(locale);
482 }
483
484 for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
485 updateLineWidth(mTextBuf[i], mMeasuredText.widths[i]);
486
487 if ((i + 1) == nextWordBoundaryOffset) {
488 // Only process line break at word boundary and the run can break into some pieces.
489 if (run->canBreak() || nextWordBoundaryOffset == range.getEnd()) {
490 processLineBreak(i + 1, &wordBreaker, run->canBreak());
491 }
492 nextWordBoundaryOffset = wordBreaker.next();
493 }
494 }
495 }
496
497 if (getPrevLineBreakOffset() != mTextBuf.size() && mPrevWordBoundsOffset != NOWHERE) {
498 // The remaining words in the last line.
499 breakLineAt(mPrevWordBoundsOffset, mLineWidth, 0, 0, EndHyphenEdit::NO_EDIT,
500 StartHyphenEdit::NO_EDIT);
501 }
502 }
503
getResult() const504 LineBreakResult GreedyLineBreaker::getResult() const {
505 constexpr int TAB_BIT = 1 << 29; // Must be the same in StaticLayout.java
506
507 LineBreakResult out;
508 uint32_t prevBreakOffset = 0;
509 for (const auto& breakPoint : mBreakPoints) {
510 // TODO: compute these during line breaking if these takes longer time.
511 bool hasTabChar = false;
512 for (uint32_t i = prevBreakOffset; i < breakPoint.offset; ++i) {
513 hasTabChar |= mTextBuf[i] == CHAR_TAB;
514 }
515
516 if (mUseBoundsForWidth) {
517 Range range = Range(prevBreakOffset, breakPoint.offset);
518 Range actualRange = trimTrailingLineEndSpaces(mTextBuf, range);
519 if (actualRange.isEmpty()) {
520 // No characters before the line-end-spaces.
521 MinikinExtent extent = mMeasuredText.getExtent(mTextBuf, range);
522 out.ascents.push_back(extent.ascent);
523 out.descents.push_back(extent.descent);
524 out.bounds.emplace_back(0, extent.ascent, breakPoint.lineWidth, extent.descent);
525 } else {
526 LineMetrics metrics = mMeasuredText.getLineMetrics(mTextBuf, actualRange);
527 out.ascents.push_back(metrics.extent.ascent);
528 out.descents.push_back(metrics.extent.descent);
529 out.bounds.emplace_back(metrics.bounds);
530 }
531 } else {
532 MinikinExtent extent =
533 mMeasuredText.getExtent(mTextBuf, Range(prevBreakOffset, breakPoint.offset));
534 out.ascents.push_back(extent.ascent);
535 out.descents.push_back(extent.descent);
536 // We don't have bounding box if mUseBoundsForWidth is false. Use line ascent/descent
537 // and linew width for the bounding box.
538 out.bounds.emplace_back(0, extent.ascent, breakPoint.lineWidth, extent.descent);
539 }
540 out.breakPoints.push_back(breakPoint.offset);
541 out.widths.push_back(breakPoint.lineWidth);
542 out.flags.push_back((hasTabChar ? TAB_BIT : 0) | static_cast<int>(breakPoint.hyphenEdit));
543
544 prevBreakOffset = breakPoint.offset;
545 }
546 return out;
547 }
548
549 } // namespace
550
breakLineGreedy(const U16StringPiece & textBuf,const MeasuredText & measured,const LineWidth & lineWidthLimits,const TabStops & tabStops,bool enableHyphenation,bool useBoundsForWidth)551 LineBreakResult breakLineGreedy(const U16StringPiece& textBuf, const MeasuredText& measured,
552 const LineWidth& lineWidthLimits, const TabStops& tabStops,
553 bool enableHyphenation, bool useBoundsForWidth) {
554 if (textBuf.size() == 0) {
555 return LineBreakResult();
556 }
557 GreedyLineBreaker lineBreaker(textBuf, measured, lineWidthLimits, tabStops, enableHyphenation,
558 useBoundsForWidth);
559 lineBreaker.process(false);
560 LineBreakResult res = lineBreaker.getResult();
561
562 if (!features::word_style_auto()) {
563 return res;
564 }
565
566 // The line breaker says that retry with phrase based word break because of the auto option and
567 // given locales.
568 if (!lineBreaker.retryWithPhraseWordBreak) {
569 return res;
570 }
571
572 // If the line break result is more than heuristics threshold, don't try pharse based word
573 // break.
574 if (res.breakPoints.size() >= LBW_AUTO_HEURISTICS_LINE_COUNT) {
575 return res;
576 }
577
578 GreedyLineBreaker phLineBreaker(textBuf, measured, lineWidthLimits, tabStops, enableHyphenation,
579 useBoundsForWidth);
580 phLineBreaker.process(true);
581 LineBreakResult res2 = phLineBreaker.getResult();
582
583 if (res2.breakPoints.size() < LBW_AUTO_HEURISTICS_LINE_COUNT) {
584 return res2;
585 } else {
586 return res;
587 }
588 }
589
590 } // namespace minikin
591