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::__anonb48b3e650111::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::__anonb48b3e650111::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::__anonb48b3e650111::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::__anonb48b3e650111::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::__anonb48b3e650111::OptimizeContext127 OptimizeContext(float firstLetterSpacing) {
128 pushWordBreak(0, 0, 0, 0, 0, 0, false, firstLetterSpacing);
129 }
130
131 private:
pushBreakCandidateminikin::__anonb48b3e650111::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::__anonb48b3e650111::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 WordBreaker wb;
195 wb.setText(textBuf.data(), textBuf.length());
196 ssize_t next =
197 wb.followingWithLocale(getEffectiveLocale(run.getLocaleListId()), run.lineBreakStyle(),
198 LineBreakWordStyle::None, range.getStart());
199
200 const bool calculateFallback = range.contains(next);
201 ParaWidth width = measured.widths[range.getStart()];
202 for (uint32_t i = range.getStart() + 1; i < range.getEnd(); ++i) {
203 const float w = measured.widths[i];
204 if (w == 0) {
205 continue; // w == 0 means here is not a grapheme bounds. Don't break here.
206 }
207 if (calculateFallback && i == (uint32_t)next) {
208 out.emplace_back(i, width, SCORE_FALLBACK);
209 next = wb.next();
210 if (!range.contains(next)) {
211 break;
212 }
213 } else {
214 out.emplace_back(i, width, SCORE_DESPERATE);
215 }
216 width += w;
217 }
218
219 return out;
220 }
221
222 // Append hyphenation break points and desperate break points.
223 // If an offset is a both candidate for hyphenation and desperate break points, place desperate
224 // break candidate first and hyphenation break points second since the result width of the desperate
225 // break is shorter than hyphenation break.
226 // This is important since DP in computeBreaksOptimal assumes that the result line width is
227 // 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)228 void appendWithMerging(std::vector<HyphenBreak>::const_iterator hyIter,
229 std::vector<HyphenBreak>::const_iterator endHyIter,
230 const std::vector<DesperateBreak>& desperates, const CharProcessor& proc,
231 float hyphenPenalty, bool isRtl, float letterSpacing, OptimizeContext* out) {
232 auto d = desperates.begin();
233 while (hyIter != endHyIter || d != desperates.end()) {
234 // If both hyphen breaks and desperate breaks point to the same offset, push desperate
235 // breaks first.
236 if (d != desperates.end() && (hyIter == endHyIter || d->offset <= hyIter->offset)) {
237 out->pushDesperate(d->offset, proc.sumOfCharWidthsAtPrevWordBreak + d->sumOfChars,
238 d->score, proc.effectiveSpaceCount, isRtl, letterSpacing);
239 d++;
240 } else {
241 out->pushHyphenation(hyIter->offset, proc.sumOfCharWidths - hyIter->second,
242 proc.sumOfCharWidthsAtPrevWordBreak + hyIter->first, hyphenPenalty,
243 proc.effectiveSpaceCount, hyIter->type, isRtl, letterSpacing);
244 hyIter++;
245 }
246 }
247 }
248
249 // Enumerate all line break candidates.
populateCandidates(const U16StringPiece & textBuf,const MeasuredText & measured,const LineWidth & lineWidth,HyphenationFrequency frequency,bool isJustified,bool forceWordStyleAutoToPhrase)250 OptimizeContext populateCandidates(const U16StringPiece& textBuf, const MeasuredText& measured,
251 const LineWidth& lineWidth, HyphenationFrequency frequency,
252 bool isJustified, bool forceWordStyleAutoToPhrase) {
253 const ParaWidth minLineWidth = lineWidth.getMin();
254 CharProcessor proc(textBuf);
255
256 float initialLetterSpacing;
257 if (measured.runs.empty()) {
258 initialLetterSpacing = 0;
259 } else {
260 initialLetterSpacing = measured.runs[0]->getLetterSpacingInPx();
261 }
262 OptimizeContext result(initialLetterSpacing);
263
264 const bool doHyphenation = frequency != HyphenationFrequency::None;
265 auto hyIter = std::begin(measured.hyphenBreaks);
266
267 for (const auto& run : measured.runs) {
268 const bool isRtl = run->isRtl();
269 const Range& range = run->getRange();
270 const float letterSpacing = run->getLetterSpacingInPx();
271
272 // Compute penalty parameters.
273 float hyphenPenalty = 0.0f;
274 if (run->canBreak()) {
275 auto penalties = computePenalties(*run, lineWidth, frequency, isJustified);
276 hyphenPenalty = penalties.first;
277 result.linePenalty = std::max(penalties.second, result.linePenalty);
278 }
279
280 proc.updateLocaleIfNecessary(*run, forceWordStyleAutoToPhrase);
281
282 for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
283 MINIKIN_ASSERT(textBuf[i] != CHAR_TAB, "TAB is not supported in optimal line breaker");
284 // Even if the run is not a candidate of line break, treat the end of run as the line
285 // break candidate.
286 const bool canBreak = run->canBreak() || (i + 1) == range.getEnd();
287 proc.feedChar(i, textBuf[i], measured.widths[i], canBreak);
288
289 const uint32_t nextCharOffset = i + 1;
290 if (nextCharOffset != proc.nextWordBreak) {
291 continue; // Wait until word break point.
292 }
293
294 // Add hyphenation and desperate break points.
295 std::vector<HyphenBreak> hyphenedBreaks;
296 std::vector<DesperateBreak> desperateBreaks;
297 const Range contextRange = proc.contextRange();
298
299 auto beginHyIter = hyIter;
300 while (hyIter != std::end(measured.hyphenBreaks) &&
301 hyIter->offset < contextRange.getEnd()) {
302 hyIter++;
303 }
304 if (proc.widthFromLastWordBreak() > minLineWidth) {
305 desperateBreaks = populateDesperatePoints(textBuf, measured, contextRange, *run);
306 }
307 const bool doHyphenationRun = doHyphenation && run->canHyphenate();
308
309 appendWithMerging(beginHyIter, doHyphenationRun ? hyIter : beginHyIter, desperateBreaks,
310 proc, hyphenPenalty, isRtl, letterSpacing, &result);
311
312 // We skip breaks for zero-width characters inside replacement spans.
313 if (run->getPaint() != nullptr || nextCharOffset == range.getEnd() ||
314 measured.widths[nextCharOffset] > 0) {
315 const float penalty = hyphenPenalty * proc.wordBreakPenalty();
316 result.pushWordBreak(nextCharOffset, proc.sumOfCharWidths, proc.effectiveWidth,
317 penalty, proc.rawSpaceCount, proc.effectiveSpaceCount, isRtl,
318 letterSpacing);
319 }
320 }
321 }
322 result.spaceWidth = proc.spaceWidth;
323 result.retryWithPhraseWordBreak = proc.retryWithPhraseWordBreak;
324 result.maxCharWidth = proc.maxCharWidth;
325 return result;
326 }
327
328 class LineBreakOptimizer {
329 public:
LineBreakOptimizer()330 LineBreakOptimizer() {}
331
332 LineBreakResult computeBreaks(const OptimizeContext& context, const U16StringPiece& textBuf,
333 const MeasuredText& measuredText, const LineWidth& lineWidth,
334 BreakStrategy strategy, bool justified, bool useBoundsForWidth);
335
336 private:
337 // Data used to compute optimal line breaks
338 struct OptimalBreaksData {
339 float score; // best score found for this break
340 uint32_t prev; // index to previous break
341 uint32_t lineNumber; // the computed line number of the candidate
342 };
343 LineBreakResult finishBreaksOptimal(const U16StringPiece& textBuf, const MeasuredText& measured,
344 const std::vector<OptimalBreaksData>& breaksData,
345 const std::vector<Candidate>& candidates,
346 bool useBoundsForWidth);
347 };
348
349 // 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)350 LineBreakResult LineBreakOptimizer::finishBreaksOptimal(
351 const U16StringPiece& textBuf, const MeasuredText& measured,
352 const std::vector<OptimalBreaksData>& breaksData, const std::vector<Candidate>& candidates,
353 bool useBoundsForWidth) {
354 LineBreakResult result;
355 const uint32_t nCand = candidates.size();
356 uint32_t prevIndex;
357 for (uint32_t i = nCand - 1; i > 0; i = prevIndex) {
358 prevIndex = breaksData[i].prev;
359 const Candidate& cand = candidates[i];
360 const Candidate& prev = candidates[prevIndex];
361
362 result.breakPoints.push_back(cand.offset);
363 result.widths.push_back(cand.postBreak - prev.preBreak);
364 if (useBoundsForWidth) {
365 Range range = Range(prev.offset, cand.offset);
366 Range actualRange = trimTrailingLineEndSpaces(textBuf, range);
367 if (actualRange.isEmpty()) {
368 MinikinExtent extent = measured.getExtent(textBuf, range);
369 result.ascents.push_back(extent.ascent);
370 result.descents.push_back(extent.descent);
371 result.bounds.emplace_back(0, extent.ascent, cand.postBreak - prev.preBreak,
372 extent.descent);
373 } else {
374 LineMetrics metrics = measured.getLineMetrics(textBuf, actualRange);
375 result.ascents.push_back(metrics.extent.ascent);
376 result.descents.push_back(metrics.extent.descent);
377 result.bounds.emplace_back(metrics.bounds);
378 }
379 } else {
380 MinikinExtent extent = measured.getExtent(textBuf, Range(prev.offset, cand.offset));
381 result.ascents.push_back(extent.ascent);
382 result.descents.push_back(extent.descent);
383 result.bounds.emplace_back(0, extent.ascent, cand.postBreak - prev.preBreak,
384 extent.descent);
385 }
386
387 const HyphenEdit edit =
388 packHyphenEdit(editForNextLine(prev.hyphenType), editForThisLine(cand.hyphenType));
389 result.flags.push_back(static_cast<int>(edit));
390 }
391 result.reverse();
392 return result;
393 }
394
computeBreaks(const OptimizeContext & context,const U16StringPiece & textBuf,const MeasuredText & measured,const LineWidth & lineWidth,BreakStrategy strategy,bool justified,bool useBoundsForWidth)395 LineBreakResult LineBreakOptimizer::computeBreaks(const OptimizeContext& context,
396 const U16StringPiece& textBuf,
397 const MeasuredText& measured,
398 const LineWidth& lineWidth,
399 BreakStrategy strategy, bool justified,
400 bool useBoundsForWidth) {
401 const std::vector<Candidate>& candidates = context.candidates;
402 uint32_t active = 0;
403 const uint32_t nCand = candidates.size();
404 const float maxShrink = justified ? SHRINKABILITY * context.spaceWidth : 0.0f;
405
406 std::vector<OptimalBreaksData> breaksData;
407 breaksData.reserve(nCand);
408 breaksData.push_back({0.0, 0, 0}); // The first candidate is always at the first line.
409
410 const float deltaMax = context.maxCharWidth * 2;
411 // "i" iterates through candidates for the end of the line.
412 for (uint32_t i = 1; i < nCand; i++) {
413 const bool atEnd = i == nCand - 1;
414 float best = SCORE_INFTY;
415 uint32_t bestPrev = 0;
416
417 uint32_t lineNumberLast = breaksData[active].lineNumber;
418 float width = lineWidth.getAt(lineNumberLast);
419
420 ParaWidth leftEdge = candidates[i].postBreak - width;
421 float bestHope = 0;
422
423 // "j" iterates through candidates for the beginning of the line.
424 for (uint32_t j = active; j < i; j++) {
425 const uint32_t lineNumber = breaksData[j].lineNumber;
426 if (lineNumber != lineNumberLast) {
427 const float widthNew = lineWidth.getAt(lineNumber);
428 if (widthNew != width) {
429 leftEdge = candidates[i].postBreak - width;
430 bestHope = 0;
431 width = widthNew;
432 }
433 lineNumberLast = lineNumber;
434 }
435 const float jScore = breaksData[j].score;
436 if (jScore + bestHope >= best) continue;
437 float delta = candidates[j].preBreak - leftEdge;
438
439 // The bounds calculation is for preventing horizontal clipping.
440 // So, if the delta is negative, i.e. overshoot is happening with advance width, we can
441 // skip the bounds calculation. Also we skip the bounds calculation if the delta is
442 // larger than twice of max character widdth. This is a heuristic that the twice of max
443 // character width should be good enough space for keeping overshoot.
444 if (useBoundsForWidth && 0 <= delta && delta < deltaMax) {
445 // FIXME: Support bounds based line break for hyphenated break point.
446 if (candidates[i].hyphenType == HyphenationType::DONT_BREAK &&
447 candidates[j].hyphenType == HyphenationType::DONT_BREAK) {
448 Range range = Range(candidates[j].offset, candidates[i].offset);
449 Range actualRange = trimTrailingLineEndSpaces(textBuf, range);
450 if (!actualRange.isEmpty() && measured.hasOverhang(range)) {
451 float boundsDelta =
452 width - measured.getBounds(textBuf, actualRange).width();
453 if (boundsDelta < 0) {
454 delta = boundsDelta;
455 }
456 }
457 }
458 }
459
460 // compute width score for line
461
462 // Note: the "bestHope" optimization makes the assumption that, when delta is
463 // non-negative, widthScore will increase monotonically as successive candidate
464 // breaks are considered.
465 float widthScore = 0.0f;
466 float additionalPenalty = 0.0f;
467 if ((atEnd || !justified) && delta < 0) {
468 widthScore = SCORE_OVERFULL;
469 } else if (atEnd && strategy != BreakStrategy::Balanced) {
470 // increase penalty for hyphen on last line
471 additionalPenalty = LAST_LINE_PENALTY_MULTIPLIER * candidates[j].penalty;
472 } else {
473 widthScore = delta * delta;
474 if (delta < 0) {
475 if (-delta <
476 maxShrink * (candidates[i].postSpaceCount - candidates[j].preSpaceCount)) {
477 widthScore *= SHRINK_PENALTY_MULTIPLIER;
478 } else {
479 widthScore = SCORE_OVERFULL;
480 }
481 }
482 }
483
484 if (delta < 0) {
485 active = j + 1;
486 } else {
487 bestHope = widthScore;
488 }
489
490 const float score = jScore + widthScore + additionalPenalty;
491 if (score <= best) {
492 best = score;
493 bestPrev = j;
494 }
495 }
496 breaksData.push_back({best + candidates[i].penalty + context.linePenalty, // score
497 bestPrev, // prev
498 breaksData[bestPrev].lineNumber + 1}); // lineNumber
499 }
500 return finishBreaksOptimal(textBuf, measured, breaksData, candidates, useBoundsForWidth);
501 }
502
503 } // namespace
504
breakLineOptimal(const U16StringPiece & textBuf,const MeasuredText & measured,const LineWidth & lineWidth,BreakStrategy strategy,HyphenationFrequency frequency,bool justified,bool useBoundsForWidth)505 LineBreakResult breakLineOptimal(const U16StringPiece& textBuf, const MeasuredText& measured,
506 const LineWidth& lineWidth, BreakStrategy strategy,
507 HyphenationFrequency frequency, bool justified,
508 bool useBoundsForWidth) {
509 if (textBuf.size() == 0) {
510 return LineBreakResult();
511 }
512
513 const OptimizeContext context =
514 populateCandidates(textBuf, measured, lineWidth, frequency, justified,
515 false /* forceWordStyleAutoToPhrase */);
516 LineBreakOptimizer optimizer;
517 LineBreakResult res = optimizer.computeBreaks(context, textBuf, measured, lineWidth, strategy,
518 justified, useBoundsForWidth);
519
520 // The line breaker says that retry with phrase based word break because of the auto option and
521 // given locales.
522 if (!context.retryWithPhraseWordBreak) {
523 return res;
524 }
525
526 // If the line break result is more than heuristics threshold, don't try pharse based word
527 // break.
528 if (res.breakPoints.size() >= LBW_AUTO_HEURISTICS_LINE_COUNT) {
529 return res;
530 }
531
532 const OptimizeContext phContext =
533 populateCandidates(textBuf, measured, lineWidth, frequency, justified,
534 true /* forceWordStyleAutoToPhrase */);
535 LineBreakResult res2 = optimizer.computeBreaks(phContext, textBuf, measured, lineWidth,
536 strategy, justified, useBoundsForWidth);
537 if (res2.breakPoints.size() < LBW_AUTO_HEURISTICS_LINE_COUNT) {
538 return res2;
539 } else {
540 return res;
541 }
542 }
543
544 } // namespace minikin
545