summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSeigo Nonaka <nona@google.com>2023-09-12 01:01:21 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2023-09-12 01:01:21 +0000
commitcc22ab0d8d11526794360b640e3ed521c0810c3e (patch)
treec6fa208567e5ea873c0934d752972325f5cb1529
parent8c6ddf6866be462da81c72a8e26f3e8aa1e818b3 (diff)
parent2bbedec49d8d78d79c397f946a2a9f81837d176d (diff)
downloadminikin-cc22ab0d8d11526794360b640e3ed521c0810c3e.tar.gz
Merge "Add no hyphenation into style run" into main
-rw-r--r--include/minikin/MeasuredText.h14
-rw-r--r--libs/minikin/GreedyLineBreaker.cpp4
-rw-r--r--libs/minikin/OptimalLineBreaker.cpp3
-rw-r--r--tests/unittest/GreedyLineBreakerTest.cpp92
-rw-r--r--tests/unittest/LineBreakerTestHelper.h1
-rw-r--r--tests/unittest/MeasuredTextTest.cpp61
-rw-r--r--tests/unittest/OptimalLineBreakerTest.cpp81
7 files changed, 224 insertions, 32 deletions
diff --git a/include/minikin/MeasuredText.h b/include/minikin/MeasuredText.h
index 300d25f..a3b8bf1 100644
--- a/include/minikin/MeasuredText.h
+++ b/include/minikin/MeasuredText.h
@@ -63,6 +63,9 @@ public:
// Returns true if this run can be broken into multiple pieces for line breaking.
virtual bool canBreak() const = 0;
+ // Returns true if this run can be hyphenated.
+ virtual bool canHyphenate() const = 0;
+
// Return the line break style(lb) for this run.
virtual LineBreakStyle lineBreakStyle() const = 0;
@@ -118,11 +121,12 @@ protected:
class StyleRun : public Run {
public:
StyleRun(const Range& range, MinikinPaint&& paint, int lineBreakStyle, int lineBreakWordStyle,
- bool isRtl)
+ bool hyphenation, bool isRtl)
: Run(range),
mPaint(std::move(paint)),
mLineBreakStyle(lineBreakStyle),
mLineBreakWordStyle(lineBreakWordStyle),
+ mHyphenation(hyphenation),
mIsRtl(isRtl) {}
bool canBreak() const override { return true; }
@@ -132,6 +136,7 @@ public:
LineBreakWordStyle lineBreakWordStyle() const override {
return static_cast<LineBreakWordStyle>(mLineBreakWordStyle);
}
+ bool canHyphenate() const override { return mHyphenation; }
uint32_t getLocaleListId() const override { return mPaint.localeListId; }
bool isRtl() const override { return mIsRtl; }
@@ -164,6 +169,7 @@ private:
MinikinPaint mPaint;
int mLineBreakStyle;
int mLineBreakWordStyle;
+ const bool mHyphenation;
const bool mIsRtl;
};
@@ -174,6 +180,7 @@ public:
bool isRtl() const { return false; }
bool canBreak() const { return false; }
+ bool canHyphenate() const { return false; }
LineBreakStyle lineBreakStyle() const override { return LineBreakStyle::None; }
LineBreakWordStyle lineBreakWordStyle() const override { return LineBreakWordStyle::None; }
uint32_t getLocaleListId() const { return mLocaleListId; }
@@ -321,9 +328,10 @@ public:
MeasuredTextBuilder() {}
void addStyleRun(int32_t start, int32_t end, MinikinPaint&& paint, int lineBreakStyle,
- int lineBreakWordStyle, bool isRtl) {
+ int lineBreakWordStyle, bool hyphenation, bool isRtl) {
mRuns.emplace_back(std::make_unique<StyleRun>(Range(start, end), std::move(paint),
- lineBreakStyle, lineBreakWordStyle, isRtl));
+ lineBreakStyle, lineBreakWordStyle,
+ hyphenation, isRtl));
}
void addReplacementRun(int32_t start, int32_t end, float width, uint32_t localeListId) {
diff --git a/libs/minikin/GreedyLineBreaker.cpp b/libs/minikin/GreedyLineBreaker.cpp
index 7c5cf2f..5e11978 100644
--- a/libs/minikin/GreedyLineBreaker.cpp
+++ b/libs/minikin/GreedyLineBreaker.cpp
@@ -186,6 +186,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;
diff --git a/libs/minikin/OptimalLineBreaker.cpp b/libs/minikin/OptimalLineBreaker.cpp
index 50acae6..e170313 100644
--- a/libs/minikin/OptimalLineBreaker.cpp
+++ b/libs/minikin/OptimalLineBreaker.cpp
@@ -285,7 +285,8 @@ OptimizeContext populateCandidates(const U16StringPiece& textBuf, const Measured
if (proc.widthFromLastWordBreak() > minLineWidth) {
desperateBreaks = populateDesperatePoints(textBuf, measured, contextRange, *run);
}
- appendWithMerging(beginHyIter, doHyphenation ? hyIter : beginHyIter, desperateBreaks,
+ const bool doHyphenationRun = doHyphenation && run->canHyphenate();
+ appendWithMerging(beginHyIter, doHyphenationRun ? hyIter : beginHyIter, desperateBreaks,
proc, hyphenPenalty, isRtl, &result);
// We skip breaks for zero-width characters inside replacement spans.
diff --git a/tests/unittest/GreedyLineBreakerTest.cpp b/tests/unittest/GreedyLineBreakerTest.cpp
index 83ff2ae..79861f0 100644
--- a/tests/unittest/GreedyLineBreakerTest.cpp
+++ b/tests/unittest/GreedyLineBreakerTest.cpp
@@ -80,7 +80,7 @@ protected:
paint.size = 10.0f; // Make 1em=10px
paint.localeListId = LocaleListCache::getId(lang);
builder.addStyleRun(0, textBuffer.size(), std::move(paint), (int)LineBreakStyle::None,
- (int)LineBreakWordStyle::None, false);
+ (int)LineBreakWordStyle::None, true, false);
std::unique_ptr<MeasuredText> measuredText = builder.build(
textBuffer, false /* compute hyphenation */, false /* compute full layout */,
false /* computeBounds */, false /* ignore kerning */, nullptr /* no hint */);
@@ -90,6 +90,45 @@ protected:
doHyphenation, false);
}
+ LineBreakResult doLineBreakWithNoHyphenSpan(const U16StringPiece& textBuffer,
+ const Range& noHyphenRange, float lineWidth) {
+ MeasuredTextBuilder builder;
+ auto family1 = buildFontFamily("Ascii.ttf");
+ auto family2 = buildFontFamily("CustomExtent.ttf");
+ std::vector<std::shared_ptr<FontFamily>> families = {family1, family2};
+ auto fc = FontCollection::create(families);
+ if (noHyphenRange.getStart() != 0) {
+ MinikinPaint paint(fc);
+ paint.size = 10.0f; // Make 1em=10px
+ paint.localeListId = LocaleListCache::getId("en-US");
+ builder.addStyleRun(0, noHyphenRange.getStart(), std::move(paint),
+ (int)LineBreakStyle::None, (int)LineBreakWordStyle::None,
+ true /* hyphenation */, false);
+ }
+ MinikinPaint paint(fc);
+ paint.size = 10.0f; // Make 1em=10px
+ paint.localeListId = LocaleListCache::getId("en-US");
+ builder.addStyleRun(noHyphenRange.getStart(), noHyphenRange.getEnd(), std::move(paint),
+ (int)LineBreakStyle::None, (int)LineBreakWordStyle::None,
+ false /* hyphenation */, false);
+ if (noHyphenRange.getEnd() != textBuffer.size()) {
+ MinikinPaint paint(fc);
+ paint.size = 10.0f; // Make 1em=10px
+ paint.localeListId = LocaleListCache::getId("en-US");
+ builder.addStyleRun(noHyphenRange.getEnd(), textBuffer.size(), std::move(paint),
+ (int)LineBreakStyle::None, (int)LineBreakWordStyle::None,
+ true /* hyphenation */, false);
+ }
+
+ std::unique_ptr<MeasuredText> measuredText = builder.build(
+ textBuffer, false /* compute hyphenation */, false /* compute full layout */,
+ false /* computeBounds */, false /* ignore kerning */, nullptr /* no hint */);
+ RectangleLineWidth rectangleLineWidth(lineWidth);
+ TabStops tabStops(nullptr, 0, 10);
+ return breakLineGreedy(textBuffer, *measuredText, rectangleLineWidth, tabStops,
+ true /* doHyphenation */, false);
+ }
+
LineBreakResult doLineBreakForBounds(const U16StringPiece& textBuffer, bool doHyphenation,
float lineWidth) {
MeasuredTextBuilder builder;
@@ -101,7 +140,7 @@ protected:
paint.size = 10.0f; // Make 1em=10px
paint.localeListId = LocaleListCache::getId("en-US");
builder.addStyleRun(0, textBuffer.size(), std::move(paint), (int)LineBreakStyle::None,
- (int)LineBreakWordStyle::None, false);
+ (int)LineBreakWordStyle::None, true, false);
std::unique_ptr<MeasuredText> measuredText = builder.build(
textBuffer, false /* compute hyphenation */, false /* compute full layout */,
true /* computeBounds */, false /* ignore kerning */, nullptr /* no hint */);
@@ -132,7 +171,7 @@ TEST_F(GreedyLineBreakerTest, roundingError) {
StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, nullptr, nullptr);
builder.addStyleRun(0, textBuffer.size(), std::move(paint), (int)LineBreakStyle::None,
- (int)LineBreakWordStyle::None, false);
+ (int)LineBreakWordStyle::None, true, false);
std::unique_ptr<MeasuredText> measuredText = builder.build(
textBuffer, false /* compute hyphenation */, false /* compute full layout */,
false /* computeBounds */, false /* ignore kerning */, nullptr /* no hint */);
@@ -1755,5 +1794,52 @@ TEST_F(GreedyLineBreakerTest, testBreakWithoutBounds_preceding) {
EXPECT_EQ(MinikinRect(-15, 10, 40, 0), actual.bounds[3]);
}
}
+
+TEST_F(GreedyLineBreakerTest, testBreakWithHyphenation_NoHyphenationSpan) {
+ // "hyphenation" is hyphnated to "hy-phen-a-tion".
+ const std::vector<uint16_t> textBuf = utf8ToUtf16("This is Android. Here is hyphenation.");
+ const Range noHyphenRange(25, 37); // the range of the word "hyphenation".
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit END_HYPHEN = EndHyphenEdit::INSERT_HYPHEN;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+
+ // Note that disable clang-format everywhere since aligned expectation is more readable.
+ {
+ constexpr float LINE_WIDTH = 100;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "Android. " , 80, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "Here is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hyphena-" , 80, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tion." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreak(textBuf, true /* do hyphenation */, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 100;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "Android. " , 80, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "Here is " , 70, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ // Prevent hyphenation of "hyphenation". Fallback to desperate break.
+ { "hyphenatio" ,100, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "n." , 20, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ };
+ // clang-format on
+
+ const auto actual = doLineBreakWithNoHyphenSpan(textBuf, noHyphenRange, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuf, actual);
+ }
+}
} // namespace
} // namespace minikin
diff --git a/tests/unittest/LineBreakerTestHelper.h b/tests/unittest/LineBreakerTestHelper.h
index 80a2cd0..cb1d161 100644
--- a/tests/unittest/LineBreakerTestHelper.h
+++ b/tests/unittest/LineBreakerTestHelper.h
@@ -53,6 +53,7 @@ public:
virtual bool isRtl() const override { return false; }
virtual bool canBreak() const override { return true; }
+ virtual bool canHyphenate() const override { return true; }
virtual uint32_t getLocaleListId() const { return mLocaleListId; }
virtual void getMetrics(const U16StringPiece&, std::vector<float>* advances,
diff --git a/tests/unittest/MeasuredTextTest.cpp b/tests/unittest/MeasuredTextTest.cpp
index 486490c..b07ccf5 100644
--- a/tests/unittest/MeasuredTextTest.cpp
+++ b/tests/unittest/MeasuredTextTest.cpp
@@ -39,11 +39,13 @@ TEST(MeasuredTextTest, RunTests) {
MinikinPaint paint1(font);
paint1.size = 10.0f; // make 1em = 10px
- builder.addStyleRun(0, 2, std::move(paint1), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, 2, std::move(paint1), lbStyle, lbWordStyle, true /* can hyphenate */,
+ false /* is RTL */);
builder.addReplacementRun(2, 4, REPLACEMENT_WIDTH, 0 /* locale list id */);
MinikinPaint paint2(font);
paint2.size = 10.0f; // make 1em = 10px
- builder.addStyleRun(4, 6, std::move(paint2), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(4, 6, std::move(paint2), lbStyle, lbWordStyle, true /* can hyphenate */,
+ false /* is RTL */);
std::vector<uint16_t> text(CHAR_COUNT, 'a');
@@ -69,7 +71,8 @@ TEST(MeasuredTextTest, getBoundsTest) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle,
+ true /* can hyphenate */, false /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -90,7 +93,8 @@ TEST(MeasuredTextTest, getBoundsTest_LTR) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle,
+ true /* can hyphenate */, false /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -107,7 +111,8 @@ TEST(MeasuredTextTest, getBoundsTest_RTL) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, true /* is RTL */);
+ builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle,
+ true /* can hyphenate */, true /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -125,11 +130,12 @@ TEST(MeasuredTextTest, getBoundsTest_multiStyle) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle,
+ true /* can hyphenate */, false /* is RTL */);
MinikinPaint paint2(font);
paint2.size = 20.0f;
builder.addStyleRun(helloLength, text.size(), std::move(paint2), lbStyle, lbWordStyle,
- false /* is RTL */);
+ true /* can hyphenate */, false /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -153,7 +159,8 @@ TEST(MeasuredTextTest, getExtentTest) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle,
+ true /* can hyphenate */, false /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kernign */,
nullptr /* no hint */);
@@ -175,11 +182,13 @@ TEST(MeasuredTextTest, getExtentTest_multiStyle) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle,
+ true /* can hyphenate */, false /* is RTL */);
MinikinPaint paint2(font);
paint2.size = 20.0f;
builder.addStyleRun(helloLength, text.size(), std::move(paint2), 0 /* no line break */,
- 0 /* no line break word style */, false /* is RTL */);
+ 0 /* no line break word style */, true /* can hyphenate */,
+ false /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -204,7 +213,8 @@ TEST(MeasuredTextTest, buildLayoutTest) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle,
+ true /* can hyphenate */, false /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -287,11 +297,12 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle,
+ true /* can hyphenate */, false /* is RTL */);
MinikinPaint paint2(font);
paint2.size = 20.0f;
builder.addStyleRun(helloLength, text.size(), std::move(paint2), lbStyle, lbWordStyle,
- false /* is RTL */);
+ true /* can hyphenate */, false /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -376,7 +387,8 @@ TEST(MeasuredTextTest, buildLayoutTest_differentPaint) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle,
+ true /* can hyphenate */, false /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -459,11 +471,12 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, helloLength, std::move(paint), lbStyle, lbWordStyle,
+ true /* can hyphenate */, false /* is RTL */);
MinikinPaint paint2(font);
paint2.size = 20.0f;
builder.addStyleRun(helloLength, text.size(), std::move(paint2), lbStyle, lbWordStyle,
- false /* is RTL */);
+ true /* can hyphenate */, false /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -576,7 +589,8 @@ TEST(MeasuredTextTest, testLineBreakStyle_from_builder) {
MeasuredTextBuilder looseStyleBuilder;
MinikinPaint paint(font);
- looseStyleBuilder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false);
+ looseStyleBuilder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle,
+ true /* can hyphenate */, false);
auto mt = looseStyleBuilder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -589,7 +603,7 @@ TEST(MeasuredTextTest, testLineBreakStyle_from_builder) {
MeasuredTextBuilder normalStyleBuilder;
MinikinPaint normalStylePaint(font);
normalStyleBuilder.addStyleRun(0, text.size(), std::move(normalStylePaint), lbStyle,
- lbWordStyle, false);
+ lbWordStyle, true /* can hyphenate */, false);
mt = normalStyleBuilder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -603,7 +617,7 @@ TEST(MeasuredTextTest, testLineBreakStyle_from_builder) {
MeasuredTextBuilder strictStyleBuilder;
MinikinPaint strictStylePaint(font);
strictStyleBuilder.addStyleRun(0, text.size(), std::move(strictStylePaint), lbStyle,
- lbWordStyle, false);
+ lbWordStyle, true /* can hyphenate */, false);
mt = strictStyleBuilder.build(text, true /* hyphenation */, true /* full layout */,
false /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -621,7 +635,8 @@ TEST(MeasuredTextTest, testLineBreakStyle_from_run) {
Range range(0, text.size());
MinikinPaint paint(font);
- StyleRun styleRun(range, std::move(paint), lbStyle, lbWordStyle, false /* isRtl */);
+ StyleRun styleRun(range, std::move(paint), lbStyle, lbWordStyle, true /* can hyphenate */,
+ false /* isRtl */);
EXPECT_EQ(LineBreakStyle::Strict, styleRun.lineBreakStyle());
EXPECT_EQ(LineBreakWordStyle::Phrase, styleRun.lineBreakWordStyle());
@@ -639,7 +654,8 @@ TEST(MeasuredTextTest, hasOverhang_false) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle,
+ true /* hyphenation */, false /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
true /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
@@ -655,7 +671,8 @@ TEST(MeasuredTextTest, hasOverhang_true) {
MeasuredTextBuilder builder;
MinikinPaint paint(font);
paint.size = 10.0f;
- builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle, false /* is RTL */);
+ builder.addStyleRun(0, text.size(), std::move(paint), lbStyle, lbWordStyle,
+ true /* hyphenation */, false /* is RTL */);
auto mt = builder.build(text, true /* hyphenation */, true /* full layout */,
true /* computeBounds */, false /* ignore kerning */,
nullptr /* no hint */);
diff --git a/tests/unittest/OptimalLineBreakerTest.cpp b/tests/unittest/OptimalLineBreakerTest.cpp
index 4307874..045aad3 100644
--- a/tests/unittest/OptimalLineBreakerTest.cpp
+++ b/tests/unittest/OptimalLineBreakerTest.cpp
@@ -75,7 +75,7 @@ protected:
MinikinPaint paint(fc);
paint.size = 10.0f; // Make 1em=10px
paint.localeListId = LocaleListCache::getId(lang);
- builder.addStyleRun(0, textBuffer.size(), std::move(paint), 0, 0, false);
+ builder.addStyleRun(0, textBuffer.size(), std::move(paint), 0, 0, true, false);
bool computeHyphen = frequency != HyphenationFrequency::None;
std::unique_ptr<MeasuredText> measuredText =
builder.build(textBuffer, computeHyphen, false /* compute full layout */,
@@ -83,6 +83,39 @@ protected:
return doLineBreak(textBuffer, *measuredText, strategy, frequency, lineWidth);
}
+ LineBreakResult doLineBreakWithNoHyphenSpan(const U16StringPiece& textBuffer,
+ const Range& noHyphenRange, float lineWidth) {
+ MeasuredTextBuilder builder;
+ auto family1 = buildFontFamily("Ascii.ttf");
+ auto family2 = buildFontFamily("CustomExtent.ttf");
+ std::vector<std::shared_ptr<FontFamily>> families = {family1, family2};
+ auto fc = FontCollection::create(families);
+ if (noHyphenRange.getStart() != 0) {
+ MinikinPaint paint(fc);
+ paint.size = 10.0f; // Make 1em=10px
+ paint.localeListId = LocaleListCache::getId("en-US");
+ builder.addStyleRun(0, noHyphenRange.getStart(), std::move(paint), 0, 0,
+ true /* hyphenation */, false);
+ }
+ MinikinPaint paint(fc);
+ paint.size = 10.0f; // Make 1em=10px
+ paint.localeListId = LocaleListCache::getId("en-US");
+ builder.addStyleRun(noHyphenRange.getStart(), noHyphenRange.getEnd(), std::move(paint), 0,
+ 0, false /* no hyphenation */, false);
+ if (noHyphenRange.getEnd() != textBuffer.size()) {
+ MinikinPaint paint(fc);
+ paint.size = 10.0f; // Make 1em=10px
+ paint.localeListId = LocaleListCache::getId("en-US");
+ builder.addStyleRun(noHyphenRange.getEnd(), textBuffer.size(), std::move(paint), 0, 0,
+ true /* hyphenation */, false);
+ }
+ std::unique_ptr<MeasuredText> measuredText = builder.build(
+ textBuffer, true /* computeHyphen */, false /* compute full layout */,
+ false /* computeBounds */, false /* ignore kerning */, nullptr /* no hint */);
+ return doLineBreak(textBuffer, *measuredText, BreakStrategy::HighQuality,
+ HyphenationFrequency::Normal, lineWidth);
+ }
+
LineBreakResult doLineBreakForBounds(const U16StringPiece& textBuffer, BreakStrategy strategy,
HyphenationFrequency frequency, float lineWidth) {
MeasuredTextBuilder builder;
@@ -93,7 +126,7 @@ protected:
MinikinPaint paint(fc);
paint.size = 10.0f; // Make 1em=10px
paint.localeListId = LocaleListCache::getId("en-US");
- builder.addStyleRun(0, textBuffer.size(), std::move(paint), 0, 0, false);
+ builder.addStyleRun(0, textBuffer.size(), std::move(paint), 0, 0, true, false);
bool computeHyphen = frequency != HyphenationFrequency::None;
std::unique_ptr<MeasuredText> measuredText = builder.build(
textBuffer, computeHyphen, false /* compute full layout */,
@@ -1879,7 +1912,7 @@ TEST_F(OptimalLineBreakerTest, roundingError) {
StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, nullptr,
nullptr /* bounds */);
- builder.addStyleRun(0, textBuffer.size(), std::move(paint), 0, 0, false);
+ builder.addStyleRun(0, textBuffer.size(), std::move(paint), 0, 0, true, false);
std::unique_ptr<MeasuredText> measuredText = builder.build(
textBuffer, false /* compute hyphenation */, false /* compute full layout */,
false /* computeBounds */, false /* ignore kerning */, nullptr /* no hint */);
@@ -2033,5 +2066,47 @@ TEST_F(OptimalLineBreakerTest, testBreakWithoutBounds_preceding) {
}
}
+TEST_F(OptimalLineBreakerTest, testBreakWithHyphenation_NoHyphenSpan) {
+ const std::vector<uint16_t> textBuffer = utf8ToUtf16("This is Android. Here is hyphenation.");
+ const Range noHyphenRange(25, 37); // the range of the word "hyphenation".
+
+ constexpr StartHyphenEdit NO_START_HYPHEN = StartHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit NO_END_HYPHEN = EndHyphenEdit::NO_EDIT;
+ constexpr EndHyphenEdit END_HYPHEN = EndHyphenEdit::INSERT_HYPHEN;
+ // Note that disable clang-format everywhere since aligned expectation is more readable.
+ {
+ constexpr float LINE_WIDTH = 170;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is Android. " , 160, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "Here is hyphena-" , 160, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "tion." , 50, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ };
+ // clang-format on
+
+ auto actual =
+ doLineBreak(textBuffer, BreakStrategy::HighQuality, HyphenationFrequency::Normal,
+ "en-US", LINE_WIDTH, false /* ignore kerning */);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuffer, actual);
+ }
+ {
+ constexpr float LINE_WIDTH = 170;
+ // clang-format off
+ std::vector<LineBreakExpectation> expect = {
+ { "This is An-" , 110, NO_START_HYPHEN, END_HYPHEN, ASCENT, DESCENT },
+ { "droid. Here is ", 140, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ { "hyphenation." , 120, NO_START_HYPHEN, NO_END_HYPHEN, ASCENT, DESCENT },
+ };
+ // clang-format on
+
+ auto actual = doLineBreakWithNoHyphenSpan(textBuffer, noHyphenRange, LINE_WIDTH);
+ EXPECT_TRUE(sameLineBreak(expect, actual)) << toString(expect) << std::endl
+ << " vs " << std::endl
+ << toString(textBuffer, actual);
+ }
+}
+
} // namespace
} // namespace minikin