forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			314 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			314 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* 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
 |