• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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