• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "OptimalLineBreaker.h"
18 
19 #include <algorithm>
20 #include <limits>
21 
22 #include "FeatureFlags.h"
23 #include "HyphenatorMap.h"
24 #include "LayoutUtils.h"
25 #include "LineBreakerUtil.h"
26 #include "Locale.h"
27 #include "LocaleListCache.h"
28 #include "MinikinInternal.h"
29 #include "WordBreaker.h"
30 #include "minikin/Characters.h"
31 #include "minikin/Layout.h"
32 #include "minikin/Range.h"
33 #include "minikin/U16StringPiece.h"
34 
35 namespace minikin {
36 
37 namespace {
38 
39 // Large scores in a hierarchy; we prefer desperate breaks to an overfull line. All these
40 // constants are larger than any reasonable actual width score.
41 constexpr float SCORE_INFTY = std::numeric_limits<float>::max();
42 constexpr float SCORE_OVERFULL = 1e12f;
43 constexpr float SCORE_DESPERATE = 1e10f;
44 constexpr float SCORE_FALLBACK = 1e6f;
45 
46 // Multiplier for hyphen penalty on last line.
47 constexpr float LAST_LINE_PENALTY_MULTIPLIER = 4.0f;
48 // Penalty assigned to each line break (to try to minimize number of lines)
49 // TODO: when we implement full justification (so spaces can shrink and stretch), this is
50 // probably not the most appropriate method.
51 constexpr float LINE_PENALTY_MULTIPLIER = 2.0f;
52 
53 // Penalty assigned to shrinking the whitepsace.
54 constexpr float SHRINK_PENALTY_MULTIPLIER = 4.0f;
55 
56 // Maximum amount that spaces can shrink, in justified text.
57 constexpr float SHRINKABILITY = 1.0 / 3.0;
58 
59 // A single candidate break
60 struct Candidate {
61     uint32_t offset;  // offset to text buffer, in code units
62 
63     ParaWidth preBreak;       // width of text until this point, if we decide to not break here:
64                               // preBreak is used as an optimized way to calculate the width
65                               // between two candidates. The line width between two line break
66                               // candidates i and j is calculated as postBreak(j) - preBreak(i).
67     ParaWidth postBreak;      // width of text until this point, if we decide to break here
68     float penalty;            // penalty of this break (for example, hyphen penalty)
69     uint32_t preSpaceCount;   // preceding space count before breaking
70     uint32_t postSpaceCount;  // preceding space count after breaking
71     HyphenationType hyphenType;
72     bool isRtl;  // The direction of the bidi run containing or ending in this candidate
73 
Candidateminikin::__anon05feea430111::Candidate74     Candidate(uint32_t offset, ParaWidth preBreak, ParaWidth postBreak, float penalty,
75               uint32_t preSpaceCount, uint32_t postSpaceCount, HyphenationType hyphenType,
76               bool isRtl)
77             : offset(offset),
78               preBreak(preBreak),
79               postBreak(postBreak),
80               penalty(penalty),
81               preSpaceCount(preSpaceCount),
82               postSpaceCount(postSpaceCount),
83               hyphenType(hyphenType),
84               isRtl(isRtl) {}
85 };
86 
87 // A context of line break optimization.
88 struct OptimizeContext {
89     // The break candidates.
90     std::vector<Candidate> candidates;
91 
92     // The penalty for the number of lines.
93     float linePenalty = 0.0f;
94 
95     // The width of a space. May be 0 if there are no spaces.
96     // Note: if there are multiple different widths for spaces (for example, because of mixing of
97     // fonts), it's only guaranteed to pick one.
98     float spaceWidth = 0.0f;
99 
100     bool retryWithPhraseWordBreak = false;
101 
102     float maxCharWidth = 0.0f;
103 
104     // Append desperate break point to the candidates.
pushDesperateminikin::__anon05feea430111::OptimizeContext105     inline void pushDesperate(uint32_t offset, ParaWidth sumOfCharWidths, float score,
106                               uint32_t spaceCount, bool isRtl, float letterSpacing) {
107         pushBreakCandidate(offset, sumOfCharWidths, sumOfCharWidths, score, spaceCount, spaceCount,
108                            HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, isRtl, letterSpacing);
109     }
110 
111     // Append hyphenation break point to the candidates.
pushHyphenationminikin::__anon05feea430111::OptimizeContext112     inline void pushHyphenation(uint32_t offset, ParaWidth preBreak, ParaWidth postBreak,
113                                 float penalty, uint32_t spaceCount, HyphenationType type,
114                                 bool isRtl, float letterSpacing) {
115         pushBreakCandidate(offset, preBreak, postBreak, penalty, spaceCount, spaceCount, type,
116                            isRtl, letterSpacing);
117     }
118 
119     // Append word break point to the candidates.
pushWordBreakminikin::__anon05feea430111::OptimizeContext120     inline void pushWordBreak(uint32_t offset, ParaWidth preBreak, ParaWidth postBreak,
121                               float penalty, uint32_t preSpaceCount, uint32_t postSpaceCount,
122                               bool isRtl, float letterSpacing) {
123         pushBreakCandidate(offset, preBreak, postBreak, penalty, preSpaceCount, postSpaceCount,
124                            HyphenationType::DONT_BREAK, isRtl, letterSpacing);
125     }
126 
OptimizeContextminikin::__anon05feea430111::OptimizeContext127     OptimizeContext(float firstLetterSpacing) {
128         pushWordBreak(0, 0, 0, 0, 0, 0, false, firstLetterSpacing);
129     }
130 
131 private:
pushBreakCandidateminikin::__anon05feea430111::OptimizeContext132     void pushBreakCandidate(uint32_t offset, ParaWidth preBreak, ParaWidth postBreak, float penalty,
133                             uint32_t preSpaceCount, uint32_t postSpaceCount, HyphenationType type,
134                             bool isRtl, float letterSpacing) {
135         // Adjust the letter spacing amount. To remove the letter spacing of left and right edge,
136         // adjust the preBreak and postBreak values. Adding half to preBreak and removing half from
137         // postBreak, the letter space amount is subtracted from the line.
138         //
139         // This calculation assumes the letter spacing of starting edge is the same to the line
140         // start offset and letter spacing of ending edge is the same to the line end offset.
141         // Ideally, we should do the BiDi reordering for identifying the run of the left edge and
142         // right edge but it makes the candidate population to O(n^2). To avoid performance
143         // regression, use the letter spacing of the line start offset and letter spacing of the
144         // line end offset.
145         const float letterSpacingHalf = letterSpacing * 0.5;
146         candidates.emplace_back(offset, preBreak + letterSpacingHalf, postBreak - letterSpacingHalf,
147                                 penalty, preSpaceCount, postSpaceCount, type, isRtl);
148     }
149 };
150 
151 // Compute the penalty for the run and returns penalty for hyphenation and number of lines.
computePenalties(const Run & run,const LineWidth & lineWidth,HyphenationFrequency frequency,bool justified)152 std::pair<float, float> computePenalties(const Run& run, const LineWidth& lineWidth,
153                                          HyphenationFrequency frequency, bool justified) {
154     float linePenalty = 0.0;
155     const MinikinPaint* paint = run.getPaint();
156     // a heuristic that seems to perform well
157     float hyphenPenalty = 0.5 * paint->size * paint->scaleX * lineWidth.getAt(0);
158     if (frequency == HyphenationFrequency::Normal) {
159         hyphenPenalty *= 4.0;  // TODO: Replace with a better value after some testing
160     }
161 
162     if (justified) {
163         // Make hyphenation more aggressive for fully justified text (so that "normal" in
164         // justified mode is the same as "full" in ragged-right).
165         hyphenPenalty *= 0.25;
166     } else {
167         // Line penalty is zero for justified text.
168         linePenalty = hyphenPenalty * LINE_PENALTY_MULTIPLIER;
169     }
170 
171     return std::make_pair(hyphenPenalty, linePenalty);
172 }
173 
174 // Represents a desperate break point.
175 struct DesperateBreak {
176     // The break offset.
177     uint32_t offset;
178 
179     // The sum of the character width from the beginning of the word.
180     ParaWidth sumOfChars;
181 
182     float score;
183 
DesperateBreakminikin::__anon05feea430111::DesperateBreak184     DesperateBreak(uint32_t offset, ParaWidth sumOfChars, float score)
185             : offset(offset), sumOfChars(sumOfChars), score(score){};
186 };
187 
188 // Retrieves desperate break points from a word.
populateDesperatePoints(const U16StringPiece & textBuf,const MeasuredText & measured,const Range & range,const Run & run)189 std::vector<DesperateBreak> populateDesperatePoints(const U16StringPiece& textBuf,
190                                                     const MeasuredText& measured,
191                                                     const Range& range, const Run& run) {
192     std::vector<DesperateBreak> out;
193 
194     if (!features::phrase_strict_fallback() ||
195         run.lineBreakWordStyle() == LineBreakWordStyle::None) {
196         ParaWidth width = measured.widths[range.getStart()];
197         for (uint32_t i = range.getStart() + 1; i < range.getEnd(); ++i) {
198             const float w = measured.widths[i];
199             if (w == 0) {
200                 continue;  // w == 0 means here is not a grapheme bounds. Don't break here.
201             }
202             out.emplace_back(i, width, SCORE_DESPERATE);
203             width += w;
204         }
205     } else {
206         WordBreaker wb;
207         wb.setText(textBuf.data(), textBuf.length());
208         ssize_t next = wb.followingWithLocale(getEffectiveLocale(run.getLocaleListId()),
209                                               run.lineBreakStyle(), LineBreakWordStyle::None,
210                                               range.getStart());
211 
212         const bool calculateFallback = range.contains(next);
213         ParaWidth width = measured.widths[range.getStart()];
214         for (uint32_t i = range.getStart() + 1; i < range.getEnd(); ++i) {
215             const float w = measured.widths[i];
216             if (w == 0) {
217                 continue;  // w == 0 means here is not a grapheme bounds. Don't break here.
218             }
219             if (calculateFallback && i == (uint32_t)next) {
220                 out.emplace_back(i, width, SCORE_FALLBACK);
221                 next = wb.next();
222                 if (!range.contains(next)) {
223                     break;
224                 }
225             } else {
226                 out.emplace_back(i, width, SCORE_DESPERATE);
227             }
228             width += w;
229         }
230     }
231 
232     return out;
233 }
234 
235 // Append hyphenation break points and desperate break points.
236 // If an offset is a both candidate for hyphenation and desperate break points, place desperate
237 // break candidate first and hyphenation break points second since the result width of the desperate
238 // break is shorter than hyphenation break.
239 // This is important since DP in computeBreaksOptimal assumes that the result line width is
240 // increased by break offset.
appendWithMerging(std::vector<HyphenBreak>::const_iterator hyIter,std::vector<HyphenBreak>::const_iterator endHyIter,const std::vector<DesperateBreak> & desperates,const CharProcessor & proc,float hyphenPenalty,bool isRtl,float letterSpacing,OptimizeContext * out)241 void appendWithMerging(std::vector<HyphenBreak>::const_iterator hyIter,
242                        std::vector<HyphenBreak>::const_iterator endHyIter,
243                        const std::vector<DesperateBreak>& desperates, const CharProcessor& proc,
244                        float hyphenPenalty, bool isRtl, float letterSpacing, OptimizeContext* out) {
245     auto d = desperates.begin();
246     while (hyIter != endHyIter || d != desperates.end()) {
247         // If both hyphen breaks and desperate breaks point to the same offset, push desperate
248         // breaks first.
249         if (d != desperates.end() && (hyIter == endHyIter || d->offset <= hyIter->offset)) {
250             out->pushDesperate(d->offset, proc.sumOfCharWidthsAtPrevWordBreak + d->sumOfChars,
251                                d->score, proc.effectiveSpaceCount, isRtl, letterSpacing);
252             d++;
253         } else {
254             out->pushHyphenation(hyIter->offset, proc.sumOfCharWidths - hyIter->second,
255                                  proc.sumOfCharWidthsAtPrevWordBreak + hyIter->first, hyphenPenalty,
256                                  proc.effectiveSpaceCount, hyIter->type, isRtl, letterSpacing);
257             hyIter++;
258         }
259     }
260 }
261 
262 // Enumerate all line break candidates.
populateCandidates(const U16StringPiece & textBuf,const MeasuredText & measured,const LineWidth & lineWidth,HyphenationFrequency frequency,bool isJustified,bool forceWordStyleAutoToPhrase)263 OptimizeContext populateCandidates(const U16StringPiece& textBuf, const MeasuredText& measured,
264                                    const LineWidth& lineWidth, HyphenationFrequency frequency,
265                                    bool isJustified, bool forceWordStyleAutoToPhrase) {
266     const ParaWidth minLineWidth = lineWidth.getMin();
267     CharProcessor proc(textBuf);
268 
269     float initialLetterSpacing;
270     if (features::letter_spacing_justification()) {
271         if (measured.runs.empty()) {
272             initialLetterSpacing = 0;
273         } else {
274             initialLetterSpacing = measured.runs[0]->getLetterSpacingInPx();
275         }
276     } else {
277         initialLetterSpacing = 0;
278     }
279     OptimizeContext result(initialLetterSpacing);
280 
281     const bool doHyphenation = frequency != HyphenationFrequency::None;
282     auto hyIter = std::begin(measured.hyphenBreaks);
283 
284     for (const auto& run : measured.runs) {
285         const bool isRtl = run->isRtl();
286         const Range& range = run->getRange();
287         const float letterSpacing =
288                 features::letter_spacing_justification() ? run->getLetterSpacingInPx() : 0;
289 
290         // Compute penalty parameters.
291         float hyphenPenalty = 0.0f;
292         if (run->canBreak()) {
293             auto penalties = computePenalties(*run, lineWidth, frequency, isJustified);
294             hyphenPenalty = penalties.first;
295             result.linePenalty = std::max(penalties.second, result.linePenalty);
296         }
297 
298         proc.updateLocaleIfNecessary(*run, forceWordStyleAutoToPhrase);
299 
300         for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
301             MINIKIN_ASSERT(textBuf[i] != CHAR_TAB, "TAB is not supported in optimal line breaker");
302             // Even if the run is not a candidate of line break, treat the end of run as the line
303             // break candidate.
304             const bool canBreak = run->canBreak() || (i + 1) == range.getEnd();
305             proc.feedChar(i, textBuf[i], measured.widths[i], canBreak);
306 
307             const uint32_t nextCharOffset = i + 1;
308             if (nextCharOffset != proc.nextWordBreak) {
309                 continue;  // Wait until word break point.
310             }
311 
312             // Add hyphenation and desperate break points.
313             std::vector<HyphenBreak> hyphenedBreaks;
314             std::vector<DesperateBreak> desperateBreaks;
315             const Range contextRange = proc.contextRange();
316 
317             auto beginHyIter = hyIter;
318             while (hyIter != std::end(measured.hyphenBreaks) &&
319                    hyIter->offset < contextRange.getEnd()) {
320                 hyIter++;
321             }
322             if (proc.widthFromLastWordBreak() > minLineWidth) {
323                 desperateBreaks = populateDesperatePoints(textBuf, measured, contextRange, *run);
324             }
325             const bool doHyphenationRun = doHyphenation && run->canHyphenate();
326 
327             appendWithMerging(beginHyIter, doHyphenationRun ? hyIter : beginHyIter, desperateBreaks,
328                               proc, hyphenPenalty, isRtl, letterSpacing, &result);
329 
330             // We skip breaks for zero-width characters inside replacement spans.
331             if (run->getPaint() != nullptr || nextCharOffset == range.getEnd() ||
332                 measured.widths[nextCharOffset] > 0) {
333                 const float penalty = hyphenPenalty * proc.wordBreakPenalty();
334                 result.pushWordBreak(nextCharOffset, proc.sumOfCharWidths, proc.effectiveWidth,
335                                      penalty, proc.rawSpaceCount, proc.effectiveSpaceCount, isRtl,
336                                      letterSpacing);
337             }
338         }
339     }
340     result.spaceWidth = proc.spaceWidth;
341     result.retryWithPhraseWordBreak = proc.retryWithPhraseWordBreak;
342     result.maxCharWidth = proc.maxCharWidth;
343     return result;
344 }
345 
346 class LineBreakOptimizer {
347 public:
LineBreakOptimizer()348     LineBreakOptimizer() {}
349 
350     LineBreakResult computeBreaks(const OptimizeContext& context, const U16StringPiece& textBuf,
351                                   const MeasuredText& measuredText, const LineWidth& lineWidth,
352                                   BreakStrategy strategy, bool justified, bool useBoundsForWidth);
353 
354 private:
355     // Data used to compute optimal line breaks
356     struct OptimalBreaksData {
357         float score;          // best score found for this break
358         uint32_t prev;        // index to previous break
359         uint32_t lineNumber;  // the computed line number of the candidate
360     };
361     LineBreakResult finishBreaksOptimal(const U16StringPiece& textBuf, const MeasuredText& measured,
362                                         const std::vector<OptimalBreaksData>& breaksData,
363                                         const std::vector<Candidate>& candidates,
364                                         bool useBoundsForWidth);
365 };
366 
367 // Follow "prev" links in candidates array, and copy to result arrays.
finishBreaksOptimal(const U16StringPiece & textBuf,const MeasuredText & measured,const std::vector<OptimalBreaksData> & breaksData,const std::vector<Candidate> & candidates,bool useBoundsForWidth)368 LineBreakResult LineBreakOptimizer::finishBreaksOptimal(
369         const U16StringPiece& textBuf, const MeasuredText& measured,
370         const std::vector<OptimalBreaksData>& breaksData, const std::vector<Candidate>& candidates,
371         bool useBoundsForWidth) {
372     LineBreakResult result;
373     const uint32_t nCand = candidates.size();
374     uint32_t prevIndex;
375     for (uint32_t i = nCand - 1; i > 0; i = prevIndex) {
376         prevIndex = breaksData[i].prev;
377         const Candidate& cand = candidates[i];
378         const Candidate& prev = candidates[prevIndex];
379 
380         result.breakPoints.push_back(cand.offset);
381         result.widths.push_back(cand.postBreak - prev.preBreak);
382         if (useBoundsForWidth) {
383             Range range = Range(prev.offset, cand.offset);
384             Range actualRange = trimTrailingLineEndSpaces(textBuf, range);
385             if (actualRange.isEmpty()) {
386                 MinikinExtent extent = measured.getExtent(textBuf, range);
387                 result.ascents.push_back(extent.ascent);
388                 result.descents.push_back(extent.descent);
389                 result.bounds.emplace_back(0, extent.ascent, cand.postBreak - prev.preBreak,
390                                            extent.descent);
391             } else {
392                 LineMetrics metrics = measured.getLineMetrics(textBuf, actualRange);
393                 result.ascents.push_back(metrics.extent.ascent);
394                 result.descents.push_back(metrics.extent.descent);
395                 result.bounds.emplace_back(metrics.bounds);
396             }
397         } else {
398             MinikinExtent extent = measured.getExtent(textBuf, Range(prev.offset, cand.offset));
399             result.ascents.push_back(extent.ascent);
400             result.descents.push_back(extent.descent);
401             result.bounds.emplace_back(0, extent.ascent, cand.postBreak - prev.preBreak,
402                                        extent.descent);
403         }
404 
405         const HyphenEdit edit =
406                 packHyphenEdit(editForNextLine(prev.hyphenType), editForThisLine(cand.hyphenType));
407         result.flags.push_back(static_cast<int>(edit));
408     }
409     result.reverse();
410     return result;
411 }
412 
computeBreaks(const OptimizeContext & context,const U16StringPiece & textBuf,const MeasuredText & measured,const LineWidth & lineWidth,BreakStrategy strategy,bool justified,bool useBoundsForWidth)413 LineBreakResult LineBreakOptimizer::computeBreaks(const OptimizeContext& context,
414                                                   const U16StringPiece& textBuf,
415                                                   const MeasuredText& measured,
416                                                   const LineWidth& lineWidth,
417                                                   BreakStrategy strategy, bool justified,
418                                                   bool useBoundsForWidth) {
419     const std::vector<Candidate>& candidates = context.candidates;
420     uint32_t active = 0;
421     const uint32_t nCand = candidates.size();
422     const float maxShrink = justified ? SHRINKABILITY * context.spaceWidth : 0.0f;
423 
424     std::vector<OptimalBreaksData> breaksData;
425     breaksData.reserve(nCand);
426     breaksData.push_back({0.0, 0, 0});  // The first candidate is always at the first line.
427 
428     const float deltaMax = context.maxCharWidth * 2;
429     // "i" iterates through candidates for the end of the line.
430     for (uint32_t i = 1; i < nCand; i++) {
431         const bool atEnd = i == nCand - 1;
432         float best = SCORE_INFTY;
433         uint32_t bestPrev = 0;
434 
435         uint32_t lineNumberLast = breaksData[active].lineNumber;
436         float width = lineWidth.getAt(lineNumberLast);
437 
438         ParaWidth leftEdge = candidates[i].postBreak - width;
439         float bestHope = 0;
440 
441         // "j" iterates through candidates for the beginning of the line.
442         for (uint32_t j = active; j < i; j++) {
443             const uint32_t lineNumber = breaksData[j].lineNumber;
444             if (lineNumber != lineNumberLast) {
445                 const float widthNew = lineWidth.getAt(lineNumber);
446                 if (widthNew != width) {
447                     leftEdge = candidates[i].postBreak - width;
448                     bestHope = 0;
449                     width = widthNew;
450                 }
451                 lineNumberLast = lineNumber;
452             }
453             const float jScore = breaksData[j].score;
454             if (jScore + bestHope >= best) continue;
455             float delta = candidates[j].preBreak - leftEdge;
456 
457             // The bounds calculation is for preventing horizontal clipping.
458             // So, if the delta is negative, i.e. overshoot is happening with advance width, we can
459             // skip the bounds calculation. Also we skip the bounds calculation if the delta is
460             // larger than twice of max character widdth. This is a heuristic that the twice of max
461             // character width should be good enough space for keeping overshoot.
462             if (useBoundsForWidth && 0 <= delta && delta < deltaMax) {
463                 // FIXME: Support bounds based line break for hyphenated break point.
464                 if (candidates[i].hyphenType == HyphenationType::DONT_BREAK &&
465                     candidates[j].hyphenType == HyphenationType::DONT_BREAK) {
466                     Range range = Range(candidates[j].offset, candidates[i].offset);
467                     Range actualRange = trimTrailingLineEndSpaces(textBuf, range);
468                     if (!actualRange.isEmpty() && measured.hasOverhang(range)) {
469                         float boundsDelta =
470                                 width - measured.getBounds(textBuf, actualRange).width();
471                         if (boundsDelta < 0) {
472                             delta = boundsDelta;
473                         }
474                     }
475                 }
476             }
477 
478             // compute width score for line
479 
480             // Note: the "bestHope" optimization makes the assumption that, when delta is
481             // non-negative, widthScore will increase monotonically as successive candidate
482             // breaks are considered.
483             float widthScore = 0.0f;
484             float additionalPenalty = 0.0f;
485             if ((atEnd || !justified) && delta < 0) {
486                 widthScore = SCORE_OVERFULL;
487             } else if (atEnd && strategy != BreakStrategy::Balanced) {
488                 // increase penalty for hyphen on last line
489                 additionalPenalty = LAST_LINE_PENALTY_MULTIPLIER * candidates[j].penalty;
490             } else {
491                 widthScore = delta * delta;
492                 if (delta < 0) {
493                     if (-delta <
494                         maxShrink * (candidates[i].postSpaceCount - candidates[j].preSpaceCount)) {
495                         widthScore *= SHRINK_PENALTY_MULTIPLIER;
496                     } else {
497                         widthScore = SCORE_OVERFULL;
498                     }
499                 }
500             }
501 
502             if (delta < 0) {
503                 active = j + 1;
504             } else {
505                 bestHope = widthScore;
506             }
507 
508             const float score = jScore + widthScore + additionalPenalty;
509             if (score <= best) {
510                 best = score;
511                 bestPrev = j;
512             }
513         }
514         breaksData.push_back({best + candidates[i].penalty + context.linePenalty,  // score
515                               bestPrev,                                            // prev
516                               breaksData[bestPrev].lineNumber + 1});               // lineNumber
517     }
518     return finishBreaksOptimal(textBuf, measured, breaksData, candidates, useBoundsForWidth);
519 }
520 
521 }  // namespace
522 
breakLineOptimal(const U16StringPiece & textBuf,const MeasuredText & measured,const LineWidth & lineWidth,BreakStrategy strategy,HyphenationFrequency frequency,bool justified,bool useBoundsForWidth)523 LineBreakResult breakLineOptimal(const U16StringPiece& textBuf, const MeasuredText& measured,
524                                  const LineWidth& lineWidth, BreakStrategy strategy,
525                                  HyphenationFrequency frequency, bool justified,
526                                  bool useBoundsForWidth) {
527     if (textBuf.size() == 0) {
528         return LineBreakResult();
529     }
530 
531     const OptimizeContext context =
532             populateCandidates(textBuf, measured, lineWidth, frequency, justified,
533                                false /* forceWordStyleAutoToPhrase */);
534     LineBreakOptimizer optimizer;
535     LineBreakResult res = optimizer.computeBreaks(context, textBuf, measured, lineWidth, strategy,
536                                                   justified, useBoundsForWidth);
537 
538     if (!features::word_style_auto()) {
539         return res;
540     }
541 
542     // The line breaker says that retry with phrase based word break because of the auto option and
543     // given locales.
544     if (!context.retryWithPhraseWordBreak) {
545         return res;
546     }
547 
548     // If the line break result is more than heuristics threshold, don't try pharse based word
549     // break.
550     if (res.breakPoints.size() >= LBW_AUTO_HEURISTICS_LINE_COUNT) {
551         return res;
552     }
553 
554     const OptimizeContext phContext =
555             populateCandidates(textBuf, measured, lineWidth, frequency, justified,
556                                true /* forceWordStyleAutoToPhrase */);
557     LineBreakResult res2 = optimizer.computeBreaks(phContext, textBuf, measured, lineWidth,
558                                                    strategy, justified, useBoundsForWidth);
559     if (res2.breakPoints.size() < LBW_AUTO_HEURISTICS_LINE_COUNT) {
560         return res2;
561     } else {
562         return res;
563     }
564 }
565 
566 }  // namespace minikin
567