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