diff options
Diffstat (limited to 'intl/components/gtest/TestBidi.cpp')
-rw-r--r-- | intl/components/gtest/TestBidi.cpp | 314 |
1 files changed, 314 insertions, 0 deletions
diff --git a/intl/components/gtest/TestBidi.cpp b/intl/components/gtest/TestBidi.cpp new file mode 100644 index 0000000000..f928f890a6 --- /dev/null +++ b/intl/components/gtest/TestBidi.cpp @@ -0,0 +1,314 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "gtest/gtest.h" + +#include "mozilla/intl/Bidi.h" +#include "mozilla/Span.h" +namespace mozilla::intl { + +struct VisualRun { + Span<const char16_t> string; + BidiDirection direction; +}; + +/** + * An iterator for visual runs in a paragraph. See Bug 1736597 for integrating + * this into the public API. + */ +class MOZ_STACK_CLASS VisualRunIter { + public: + VisualRunIter(Bidi& aBidi, Span<const char16_t> aParagraph, + BidiEmbeddingLevel aLevel) + : mBidi(aBidi), mParagraph(aParagraph) { + // Crash in case of errors by calling unwrap. If this were a real API, this + // would be a TryCreate call. + mBidi.SetParagraph(aParagraph, aLevel).unwrap(); + mRunCount = mBidi.CountRuns().unwrap(); + } + + Maybe<VisualRun> Next() { + if (mRunIndex >= mRunCount) { + return Nothing(); + } + + int32_t stringIndex = -1; + int32_t stringLength = -1; + + BidiDirection direction = + mBidi.GetVisualRun(mRunIndex, &stringIndex, &stringLength); + + Span<const char16_t> string(mParagraph.Elements() + stringIndex, + stringLength); + mRunIndex++; + return Some(VisualRun{string, direction}); + } + + private: + Bidi& mBidi; + Span<const char16_t> mParagraph = Span<const char16_t>(); + int32_t mRunIndex = 0; + int32_t mRunCount = 0; +}; + +struct LogicalRun { + Span<const char16_t> string; + BidiEmbeddingLevel embeddingLevel; +}; + +/** + * An iterator for logical runs in a paragraph. See Bug 1736597 for integrating + * this into the public API. + */ +class MOZ_STACK_CLASS LogicalRunIter { + public: + LogicalRunIter(Bidi& aBidi, Span<const char16_t> aParagraph, + BidiEmbeddingLevel aLevel) + : mBidi(aBidi), mParagraph(aParagraph) { + // Crash in case of errors by calling unwrap. If this were a real API, this + // would be a TryCreate call. + mBidi.SetParagraph(aParagraph, aLevel).unwrap(); + mBidi.CountRuns().unwrap(); + } + + Maybe<LogicalRun> Next() { + if (mRunIndex >= static_cast<int32_t>(mParagraph.Length())) { + return Nothing(); + } + + int32_t logicalLimit; + + BidiEmbeddingLevel embeddingLevel; + mBidi.GetLogicalRun(mRunIndex, &logicalLimit, &embeddingLevel); + + Span<const char16_t> string(mParagraph.Elements() + mRunIndex, + logicalLimit - mRunIndex); + + mRunIndex = logicalLimit; + return Some(LogicalRun{string, embeddingLevel}); + } + + private: + Bidi& mBidi; + Span<const char16_t> mParagraph = Span<const char16_t>(); + int32_t mRunIndex = 0; +}; + +TEST(IntlBidi, SimpleLTR) +{ + Bidi bidi{}; + LogicalRunIter logicalRunIter(bidi, MakeStringSpan(u"this is a paragraph"), + BidiEmbeddingLevel::DefaultLTR()); + ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 0); + ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::LTR); + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"this is a paragraph")); + ASSERT_EQ(logicalRun->embeddingLevel, 0); + ASSERT_EQ(logicalRun->embeddingLevel.Direction(), BidiDirection::LTR); + } + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isNothing()); + } +} + +TEST(IntlBidi, SimpleRTL) +{ + Bidi bidi{}; + LogicalRunIter logicalRunIter(bidi, MakeStringSpan(u"فايرفوكس رائع"), + BidiEmbeddingLevel::DefaultLTR()); + ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 1); + ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::RTL); + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"فايرفوكس رائع")); + ASSERT_EQ(logicalRun->embeddingLevel.Direction(), BidiDirection::RTL); + ASSERT_EQ(logicalRun->embeddingLevel, 1); + } + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isNothing()); + } +} + +TEST(IntlBidi, MultiLevel) +{ + Bidi bidi{}; + LogicalRunIter logicalRunIter( + bidi, MakeStringSpan(u"Firefox is awesome: رائع Firefox"), + BidiEmbeddingLevel::DefaultLTR()); + ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 0); + ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::Mixed); + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"Firefox is awesome: ")); + ASSERT_EQ(logicalRun->embeddingLevel, 0); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"رائع")); + ASSERT_EQ(logicalRun->embeddingLevel, 1); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u" Firefox")); + ASSERT_EQ(logicalRun->embeddingLevel, 0); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isNothing()); + } +} + +TEST(IntlBidi, RtlOverride) +{ + Bidi bidi{}; + // Set the paragraph using the RTL embedding mark U+202B, and the LTR + // embedding mark U+202A to increase the embedding level. This mark switches + // the weakly directional character "_". This demonstrates that embedding + // levels can be computed. + LogicalRunIter logicalRunIter( + bidi, MakeStringSpan(u"ltr\u202b___رائع___\u202a___ltr__"), + BidiEmbeddingLevel::DefaultLTR()); + ASSERT_EQ(bidi.GetParagraphEmbeddingLevel(), 0); + ASSERT_EQ(bidi.GetParagraphDirection(), Bidi::ParagraphDirection::Mixed); + + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"ltr")); + ASSERT_EQ(logicalRun->embeddingLevel, 0); + ASSERT_EQ(logicalRun->embeddingLevel.Direction(), BidiDirection::LTR); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"\u202b___رائع___")); + ASSERT_EQ(logicalRun->embeddingLevel, 1); + ASSERT_EQ(logicalRun->embeddingLevel.Direction(), BidiDirection::RTL); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isSome()); + ASSERT_EQ(logicalRun->string, MakeStringSpan(u"\u202a___ltr__")); + ASSERT_EQ(logicalRun->embeddingLevel, 2); + ASSERT_EQ(logicalRun->embeddingLevel.Direction(), BidiDirection::LTR); + } + { + auto logicalRun = logicalRunIter.Next(); + ASSERT_TRUE(logicalRun.isNothing()); + } +} + +TEST(IntlBidi, VisualRuns) +{ + Bidi bidi{}; + + VisualRunIter visualRunIter( + bidi, + MakeStringSpan( + u"first visual run التشغيل البصري الثاني third visual run"), + BidiEmbeddingLevel::DefaultLTR()); + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u"first visual run ")); + ASSERT_EQ(run->direction, BidiDirection::LTR); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u"التشغيل البصري الثاني")); + ASSERT_EQ(run->direction, BidiDirection::RTL); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u" third visual run")); + ASSERT_EQ(run->direction, BidiDirection::LTR); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isNothing()); + } +} + +TEST(IntlBidi, VisualRunsWithEmbeds) +{ + // Compare this test to the logical order test. + Bidi bidi{}; + VisualRunIter visualRunIter( + bidi, MakeStringSpan(u"ltr\u202b___رائع___\u202a___ltr___"), + BidiEmbeddingLevel::DefaultLTR()); + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u"ltr")); + ASSERT_EQ(run->direction, BidiDirection::LTR); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u"\u202a___ltr___")); + ASSERT_EQ(run->direction, BidiDirection::LTR); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isSome()); + ASSERT_EQ(run->string, MakeStringSpan(u"\u202b___رائع___")); + ASSERT_EQ(run->direction, BidiDirection::RTL); + } + { + Maybe<VisualRun> run = visualRunIter.Next(); + ASSERT_TRUE(run.isNothing()); + } +} + +// The full Bidi class can be found in [1]. +// +// [1]: https://www.unicode.org/Public/UNIDATA/extracted/DerivedBidiClass.txt +TEST(IntlBidi, GetBaseDirection) +{ + // Return Neutral as default if empty string is provided. + ASSERT_EQ(Bidi::GetBaseDirection(nullptr), Bidi::BaseDirection::Neutral); + + // White space(WS) is classified as Neutral. + ASSERT_EQ(Bidi::GetBaseDirection(MakeStringSpan(u" ")), + Bidi::BaseDirection::Neutral); + + // 000A and 000D are paragraph separators(BS), which are also classified as + // Neutral. + ASSERT_EQ(Bidi::GetBaseDirection(MakeStringSpan(u"\u000A")), + Bidi::BaseDirection::Neutral); + ASSERT_EQ(Bidi::GetBaseDirection(MakeStringSpan(u"\u000D")), + Bidi::BaseDirection::Neutral); + + // 0620..063f are Arabic letters, which is of type AL. + ASSERT_EQ(Bidi::GetBaseDirection(MakeStringSpan(u"\u0620\u0621\u0622")), + Bidi::BaseDirection::RTL); + ASSERT_EQ(Bidi::GetBaseDirection(MakeStringSpan(u" \u0620\u0621\u0622")), + Bidi::BaseDirection::RTL); + ASSERT_EQ(Bidi::GetBaseDirection(MakeStringSpan(u"\u0620\u0621\u0622ABC")), + Bidi::BaseDirection::RTL); + + // First strong character is of English letters. + ASSERT_EQ(Bidi::GetBaseDirection(MakeStringSpan(u"ABC")), + Bidi::BaseDirection::LTR); + ASSERT_EQ(Bidi::GetBaseDirection(MakeStringSpan(u" ABC")), + Bidi::BaseDirection::LTR); + ASSERT_EQ(Bidi::GetBaseDirection(MakeStringSpan(u"ABC\u0620")), + Bidi::BaseDirection::LTR); +} + +} // namespace mozilla::intl |