diff options
author | Seigo Nonaka <nona@google.com> | 2023-09-12 01:01:21 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2023-09-12 01:01:21 +0000 |
commit | cc22ab0d8d11526794360b640e3ed521c0810c3e (patch) | |
tree | c6fa208567e5ea873c0934d752972325f5cb1529 | |
parent | 8c6ddf6866be462da81c72a8e26f3e8aa1e818b3 (diff) | |
parent | 2bbedec49d8d78d79c397f946a2a9f81837d176d (diff) | |
download | minikin-cc22ab0d8d11526794360b640e3ed521c0810c3e.tar.gz |
Merge "Add no hyphenation into style run" into main
-rw-r--r-- | include/minikin/MeasuredText.h | 14 | ||||
-rw-r--r-- | libs/minikin/GreedyLineBreaker.cpp | 4 | ||||
-rw-r--r-- | libs/minikin/OptimalLineBreaker.cpp | 3 | ||||
-rw-r--r-- | tests/unittest/GreedyLineBreakerTest.cpp | 92 | ||||
-rw-r--r-- | tests/unittest/LineBreakerTestHelper.h | 1 | ||||
-rw-r--r-- | tests/unittest/MeasuredTextTest.cpp | 61 | ||||
-rw-r--r-- | tests/unittest/OptimalLineBreakerTest.cpp | 81 |
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 |