summaryrefslogtreecommitdiff
path: root/libs/minikin/GreedyLineBreaker.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/minikin/GreedyLineBreaker.cpp')
-rw-r--r--libs/minikin/GreedyLineBreaker.cpp227
1 files changed, 193 insertions, 34 deletions
diff --git a/libs/minikin/GreedyLineBreaker.cpp b/libs/minikin/GreedyLineBreaker.cpp
index bb2f91c..c4ceabe 100644
--- a/libs/minikin/GreedyLineBreaker.cpp
+++ b/libs/minikin/GreedyLineBreaker.cpp
@@ -16,17 +16,17 @@
#define LOG_TAG "GreedyLineBreak"
-#include "minikin/Characters.h"
-#include "minikin/LineBreaker.h"
-#include "minikin/MeasuredText.h"
-#include "minikin/Range.h"
-#include "minikin/U16StringPiece.h"
-
+#include "FeatureFlags.h"
#include "HyphenatorMap.h"
#include "LineBreakerUtil.h"
#include "Locale.h"
#include "LocaleListCache.h"
#include "WordBreaker.h"
+#include "minikin/Characters.h"
+#include "minikin/LineBreaker.h"
+#include "minikin/MeasuredText.h"
+#include "minikin/Range.h"
+#include "minikin/U16StringPiece.h"
namespace minikin {
@@ -40,18 +40,21 @@ public:
// destructed.
GreedyLineBreaker(const U16StringPiece& textBuf, const MeasuredText& measured,
const LineWidth& lineWidthLimits, const TabStops& tabStops,
- bool enableHyphenation)
+ bool enableHyphenation, bool useBoundsForWidth)
: mLineWidthLimit(lineWidthLimits.getAt(0)),
mTextBuf(textBuf),
mMeasuredText(measured),
mLineWidthLimits(lineWidthLimits),
mTabStops(tabStops),
- mEnableHyphenation(enableHyphenation) {}
+ mEnableHyphenation(enableHyphenation),
+ mUseBoundsForWidth(useBoundsForWidth) {}
- void process();
+ void process(bool forceWordStyleAutoToPhrase);
LineBreakResult getResult() const;
+ bool retryWithPhraseWordBreak = false;
+
private:
struct BreakPoint {
BreakPoint(uint32_t offset, float lineWidth, StartHyphenEdit startHyphen,
@@ -99,6 +102,9 @@ private:
// This method return true if there is no characters to be processed.
bool doLineBreakWithGraphemeBounds(const Range& range);
+ bool overhangExceedLineLimit(const Range& range);
+ bool doLineBreakWithFallback(const Range& range);
+
// Info about the line currently processing.
uint32_t mLineNum = 0;
double mLineWidth = 0;
@@ -121,6 +127,7 @@ private:
const LineWidth& mLineWidthLimits;
const TabStops& mTabStops;
bool mEnableHyphenation;
+ bool mUseBoundsForWidth;
// The result of line breaking.
std::vector<BreakPoint> mBreakPoints;
@@ -181,6 +188,10 @@ bool GreedyLineBreaker::tryLineBreakWithHyphenation(const Range& range, WordBrea
return false;
}
+ if (!targetRun->canHyphenate()) {
+ return false;
+ }
+
const std::vector<HyphenationType> hyphenResult =
hyphenate(mTextBuf.substr(targetRange), *mHyphenator);
Range contextRange = range;
@@ -263,7 +274,8 @@ bool GreedyLineBreaker::doLineBreakWithGraphemeBounds(const Range& range) {
if (w == 0) {
continue; // w == 0 means here is not a grapheme bounds. Don't break here.
}
- if (width + w > mLineWidthLimit) {
+ if (width + w > mLineWidthLimit ||
+ overhangExceedLineLimit(Range(range.getStart(), i + 1))) {
// Okay, here is the longest position.
breakLineAt(i, width, mLineWidth - width, mSumOfCharWidths - width,
EndHyphenEdit::NO_EDIT, StartHyphenEdit::NO_EDIT);
@@ -282,6 +294,72 @@ bool GreedyLineBreaker::doLineBreakWithGraphemeBounds(const Range& range) {
return true;
}
+bool GreedyLineBreaker::doLineBreakWithFallback(const Range& range) {
+ if (!features::phrase_strict_fallback()) {
+ return false;
+ }
+ Run* targetRun = nullptr;
+ for (const auto& run : mMeasuredText.runs) {
+ if (run->getRange().contains(range)) {
+ targetRun = run.get();
+ }
+ }
+
+ if (targetRun == nullptr) {
+ return false; // The target range may lay on multiple run. Unable to fallback.
+ }
+
+ if (targetRun->lineBreakWordStyle() == LineBreakWordStyle::None) {
+ return false; // If the line break word style is already none, nothing can be falled back.
+ }
+
+ WordBreaker wb;
+ wb.setText(mTextBuf.data(), mTextBuf.length());
+ ssize_t next = wb.followingWithLocale(getEffectiveLocale(targetRun->getLocaleListId()),
+ targetRun->lineBreakStyle(), LineBreakWordStyle::None,
+ range.getStart());
+
+ if (!range.contains(next)) {
+ return false; // No fallback break points.
+ }
+
+ int32_t prevBreak = -1;
+ float wordWidth = 0;
+ float preBreakWidth = 0;
+ for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
+ const float w = mMeasuredText.widths[i];
+ if (w == 0) {
+ continue; // w == 0 means here is not a grapheme bounds. Don't break here.
+ }
+ if (i == (uint32_t)next) {
+ if (preBreakWidth + wordWidth > mLineWidthLimit) {
+ if (prevBreak == -1) {
+ return false; // No candidate before this point. Give up.
+ }
+ breakLineAt(prevBreak, preBreakWidth, mLineWidth - preBreakWidth,
+ mSumOfCharWidths - preBreakWidth, EndHyphenEdit::NO_EDIT,
+ StartHyphenEdit::NO_EDIT);
+ return true;
+ }
+ prevBreak = i;
+ next = wb.next();
+ preBreakWidth += wordWidth;
+ wordWidth = w;
+ } else {
+ wordWidth += w;
+ }
+ }
+
+ if (preBreakWidth <= mLineWidthLimit) {
+ breakLineAt(prevBreak, preBreakWidth, mLineWidth - preBreakWidth,
+ mSumOfCharWidths - preBreakWidth, EndHyphenEdit::NO_EDIT,
+ StartHyphenEdit::NO_EDIT);
+ return true;
+ }
+
+ return false;
+}
+
void GreedyLineBreaker::updateLineWidth(uint16_t c, float width) {
if (c == CHAR_TAB) {
mSumOfCharWidths = mTabStops.nextTab(mSumOfCharWidths);
@@ -294,18 +372,48 @@ void GreedyLineBreaker::updateLineWidth(uint16_t c, float width) {
}
}
+bool GreedyLineBreaker::overhangExceedLineLimit(const Range& range) {
+ if (!mUseBoundsForWidth) {
+ return false;
+ }
+ if (!mMeasuredText.hasOverhang(range)) {
+ return false;
+ }
+
+ uint32_t i;
+ for (i = 0; i < range.getLength(); ++i) {
+ uint16_t ch = mTextBuf[range.getEnd() - i - 1];
+ if (!isLineEndSpace(ch)) {
+ break;
+ }
+ }
+ if (i == range.getLength()) {
+ return false;
+ }
+
+ return mMeasuredText.getBounds(mTextBuf, Range(range.getStart(), range.getEnd() - i)).width() >
+ mLineWidthLimit;
+}
+
void GreedyLineBreaker::processLineBreak(uint32_t offset, WordBreaker* breaker,
bool doHyphenation) {
- while (mLineWidth > mLineWidthLimit) {
- const Range lineRange(getPrevLineBreakOffset(), offset); // The range we need to address.
+ while (mLineWidth > mLineWidthLimit ||
+ overhangExceedLineLimit(Range(getPrevLineBreakOffset(), offset))) {
if (tryLineBreakWithWordBreak()) {
continue; // The word in the new line may still be too long for the line limit.
- } else if (doHyphenation && tryLineBreakWithHyphenation(lineRange, breaker)) {
+ }
+
+ if (doHyphenation &&
+ tryLineBreakWithHyphenation(Range(getPrevLineBreakOffset(), offset), breaker)) {
continue; // TODO: we may be able to return here.
- } else {
- if (doLineBreakWithGraphemeBounds(lineRange)) {
- return;
- }
+ }
+
+ if (doLineBreakWithFallback(Range(getPrevLineBreakOffset(), offset))) {
+ continue;
+ }
+
+ if (doLineBreakWithGraphemeBounds(Range(getPrevLineBreakOffset(), offset))) {
+ return;
}
}
@@ -319,24 +427,27 @@ void GreedyLineBreaker::processLineBreak(uint32_t offset, WordBreaker* breaker,
}
}
-void GreedyLineBreaker::process() {
+void GreedyLineBreaker::process(bool forceWordStyleAutoToPhrase) {
WordBreaker wordBreaker;
wordBreaker.setText(mTextBuf.data(), mTextBuf.size());
- // Following two will be initialized after the first iteration.
- uint32_t localeListId = LocaleListCache::kInvalidListId;
+ WordBreakerTransitionTracker wbTracker;
uint32_t nextWordBoundaryOffset = 0;
for (const auto& run : mMeasuredText.runs) {
const Range range = run->getRange();
// Update locale if necessary.
- uint32_t newLocaleListId = run->getLocaleListId();
- if (localeListId != newLocaleListId) {
- Locale locale = getEffectiveLocale(newLocaleListId);
- nextWordBoundaryOffset = wordBreaker.followingWithLocale(
- locale, run->lineBreakStyle(), run->lineBreakWordStyle(), range.getStart());
+ if (wbTracker.update(*run)) {
+ const LocaleList& localeList = wbTracker.getCurrentLocaleList();
+ const Locale locale = localeList.empty() ? Locale() : localeList[0];
+
+ LineBreakWordStyle lbWordStyle = wbTracker.getCurrentLineBreakWordStyle();
+ std::tie(lbWordStyle, retryWithPhraseWordBreak) =
+ resolveWordStyleAuto(lbWordStyle, localeList, forceWordStyleAutoToPhrase);
+
+ nextWordBoundaryOffset = wordBreaker.followingWithLocale(locale, run->lineBreakStyle(),
+ lbWordStyle, range.getStart());
mHyphenator = HyphenatorMap::lookup(locale);
- localeListId = newLocaleListId;
}
for (uint32_t i = range.getStart(); i < range.getEnd(); ++i) {
@@ -371,12 +482,32 @@ LineBreakResult GreedyLineBreaker::getResult() const {
hasTabChar |= mTextBuf[i] == CHAR_TAB;
}
- MinikinExtent extent =
- mMeasuredText.getExtent(mTextBuf, Range(prevBreakOffset, breakPoint.offset));
+ if (mUseBoundsForWidth) {
+ Range range = Range(prevBreakOffset, breakPoint.offset);
+ Range actualRange = trimTrailingLineEndSpaces(mTextBuf, range);
+ if (actualRange.isEmpty()) {
+ // No characters before the line-end-spaces.
+ MinikinExtent extent = mMeasuredText.getExtent(mTextBuf, range);
+ out.ascents.push_back(extent.ascent);
+ out.descents.push_back(extent.descent);
+ out.bounds.emplace_back(0, extent.ascent, breakPoint.lineWidth, extent.descent);
+ } else {
+ LineMetrics metrics = mMeasuredText.getLineMetrics(mTextBuf, actualRange);
+ out.ascents.push_back(metrics.extent.ascent);
+ out.descents.push_back(metrics.extent.descent);
+ out.bounds.emplace_back(metrics.bounds);
+ }
+ } else {
+ MinikinExtent extent =
+ mMeasuredText.getExtent(mTextBuf, Range(prevBreakOffset, breakPoint.offset));
+ out.ascents.push_back(extent.ascent);
+ out.descents.push_back(extent.descent);
+ // We don't have bounding box if mUseBoundsForWidth is false. Use line ascent/descent
+ // and linew width for the bounding box.
+ out.bounds.emplace_back(0, extent.ascent, breakPoint.lineWidth, extent.descent);
+ }
out.breakPoints.push_back(breakPoint.offset);
out.widths.push_back(breakPoint.lineWidth);
- out.ascents.push_back(extent.ascent);
- out.descents.push_back(extent.descent);
out.flags.push_back((hasTabChar ? TAB_BIT : 0) | static_cast<int>(breakPoint.hyphenEdit));
prevBreakOffset = breakPoint.offset;
@@ -388,13 +519,41 @@ LineBreakResult GreedyLineBreaker::getResult() const {
LineBreakResult breakLineGreedy(const U16StringPiece& textBuf, const MeasuredText& measured,
const LineWidth& lineWidthLimits, const TabStops& tabStops,
- bool enableHyphenation) {
+ bool enableHyphenation, bool useBoundsForWidth) {
if (textBuf.size() == 0) {
return LineBreakResult();
}
- GreedyLineBreaker lineBreaker(textBuf, measured, lineWidthLimits, tabStops, enableHyphenation);
- lineBreaker.process();
- return lineBreaker.getResult();
+ GreedyLineBreaker lineBreaker(textBuf, measured, lineWidthLimits, tabStops, enableHyphenation,
+ useBoundsForWidth);
+ lineBreaker.process(false);
+ LineBreakResult res = lineBreaker.getResult();
+
+ if (!features::word_style_auto()) {
+ return res;
+ }
+
+ // The line breaker says that retry with phrase based word break because of the auto option and
+ // given locales.
+ if (!lineBreaker.retryWithPhraseWordBreak) {
+ return res;
+ }
+
+ // If the line break result is more than heuristics threshold, don't try pharse based word
+ // break.
+ if (res.breakPoints.size() >= LBW_AUTO_HEURISTICS_LINE_COUNT) {
+ return res;
+ }
+
+ GreedyLineBreaker phLineBreaker(textBuf, measured, lineWidthLimits, tabStops, enableHyphenation,
+ useBoundsForWidth);
+ phLineBreaker.process(true);
+ LineBreakResult res2 = phLineBreaker.getResult();
+
+ if (res2.breakPoints.size() < LBW_AUTO_HEURISTICS_LINE_COUNT) {
+ return res2;
+ } else {
+ return res;
+ }
}
} // namespace minikin