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