Make fx_bidi sane.

Replace array of heterogenous ints with array of struct.
Create a class for traversing a string.
Flip array when R2L and process with forward iterator always.

R=thestig@chromium.org

Review URL: https://codereview.chromium.org/1682983002 .
diff --git a/core/include/fxcrt/fx_bidi.h b/core/include/fxcrt/fx_bidi.h
index a55ce6c..ecf888b3 100644
--- a/core/include/fxcrt/fx_bidi.h
+++ b/core/include/fxcrt/fx_bidi.h
@@ -7,15 +7,23 @@
 #ifndef CORE_INCLUDE_FXCRT_FX_BIDI_H_
 #define CORE_INCLUDE_FXCRT_FX_BIDI_H_
 
+#include <memory>
+#include <vector>
+
+#include "fx_string.h"
 #include "fx_system.h"
 
 // Processes characters and group them into segments based on text direction.
 class CFX_BidiChar {
  public:
   enum Direction { NEUTRAL, LEFT, RIGHT };
+  struct Segment {
+    int32_t start;        // Start position.
+    int32_t count;        // Character count.
+    Direction direction;  // Segment direction.
+  };
 
   CFX_BidiChar();
-  ~CFX_BidiChar();
 
   // Append a character and classify it as left, right, or neutral.
   // Returns true if the character has a different direction than the
@@ -27,33 +35,39 @@
   // Returns true if there is still a segment to process.
   bool EndChar();
 
-  // Get information about the segment to process.
-  // The segment's start position and character count is returned in |iStart|
-  // and |iCount|, respectively. Pass in null pointers if the information is
-  // not needed.
-  // Returns the segment direction.
-  Direction GetBidiInfo(int32_t* iStart, int32_t* iCount) const;
+  // Call after a change in direction is indicated by the above to get
+  // information about the segment to process.
+  Segment GetSegmentInfo() const { return m_LastSegment; }
 
  private:
-  void SaveCurrentStateToLastState();
+  void StartNewSegment(CFX_BidiChar::Direction direction);
 
-  // Position of the current segment.
-  int32_t m_iCurStart;
+  Segment m_CurrentSegment;
+  Segment m_LastSegment;
+};
 
-  // Number of characters in the current segment.
-  int32_t m_iCurCount;
+class CFX_BidiString {
+ public:
+  using const_iterator = std::vector<CFX_BidiChar::Segment>::const_iterator;
+  explicit CFX_BidiString(const CFX_WideString& str);
 
-  // Direction of the current segment.
-  Direction m_CurBidi;
+  // Overall direction is always LEFT or RIGHT, never NEUTRAL.
+  CFX_BidiChar::Direction OverallDirection() const {
+    return m_eOverallDirection;
+  }
 
-  // Number of characters in the last segment.
-  int32_t m_iLastStart;
+  // Force the overall direction to be R2L regardless of what was detected.
+  void SetOverallDirectionRight();
 
-  // Number of characters in the last segment.
-  int32_t m_iLastCount;
+  FX_WCHAR CharAt(size_t x) const { return m_Str[x]; }
+  const_iterator begin() const { return m_Order.begin(); }
+  const_iterator end() const { return m_Order.end(); }
 
-  // Direction of the last segment.
-  Direction m_LastBidi;
+ private:
+  const CFX_WideString m_Str;
+  std::unique_ptr<CFX_BidiChar> m_pBidiChar;
+  std::vector<CFX_BidiChar::Segment> m_Order;
+  CFX_BidiChar::Direction m_eOverallDirection;
 };
 
 #endif  // CORE_INCLUDE_FXCRT_FX_BIDI_H_
diff --git a/core/src/fpdftext/fpdf_text_int.cpp b/core/src/fpdftext/fpdf_text_int.cpp
index ede5f83..2259165 100644
--- a/core/src/fpdftext/fpdf_text_int.cpp
+++ b/core/src/fpdftext/fpdf_text_int.cpp
@@ -914,24 +914,21 @@
   return w;
 }
 void CPDF_TextPage::OnPiece(CFX_BidiChar* pBidi, CFX_WideString& str) {
-  int32_t start, count;
-  CFX_BidiChar::Direction ret = pBidi->GetBidiInfo(&start, &count);
-  if (ret == CFX_BidiChar::RIGHT) {
-    for (int i = start + count - 1; i >= start; i--) {
-      m_TextBuf.AppendChar(str.GetAt(i));
-      m_CharList.push_back(m_TempCharList[i]);
+  CFX_BidiChar::Segment seg = pBidi->GetSegmentInfo();
+  if (seg.direction == CFX_BidiChar::RIGHT) {
+    for (int i = seg.start + seg.count; i > seg.start; i--) {
+      m_TextBuf.AppendChar(str.GetAt(i - i));
+      m_CharList.push_back(m_TempCharList[i - 1]);
     }
   } else {
-    int end = start + count;
-    for (int i = start; i < end; i++) {
+    for (int i = seg.start; i < seg.start + seg.count; i++) {
       m_TextBuf.AppendChar(str.GetAt(i));
       m_CharList.push_back(m_TempCharList[i]);
     }
   }
 }
-void CPDF_TextPage::AddCharInfoByLRDirection(CFX_WideString& str, int i) {
-  PAGECHAR_INFO info = m_TempCharList[i];
-  FX_WCHAR wChar = str.GetAt(i);
+void CPDF_TextPage::AddCharInfoByLRDirection(FX_WCHAR wChar,
+                                             PAGECHAR_INFO info) {
   if (!IsControlChar(info)) {
     info.m_Index = m_TextBuf.GetLength();
     if (wChar >= 0xFB00 && wChar <= 0xFB06) {
@@ -957,11 +954,11 @@
   }
   m_CharList.push_back(info);
 }
-void CPDF_TextPage::AddCharInfoByRLDirection(CFX_WideString& str, int i) {
-  PAGECHAR_INFO info = m_TempCharList[i];
+void CPDF_TextPage::AddCharInfoByRLDirection(FX_WCHAR wChar,
+                                             PAGECHAR_INFO info) {
   if (!IsControlChar(info)) {
     info.m_Index = m_TextBuf.GetLength();
-    FX_WCHAR wChar = FX_GetMirrorChar(str.GetAt(i), TRUE, FALSE);
+    wChar = FX_GetMirrorChar(wChar, TRUE, FALSE);
     FX_WCHAR* pDst = NULL;
     FX_STRSIZE nCount = FX_Unicode_GetNormalization(wChar, pDst);
     if (nCount >= 1) {
@@ -984,140 +981,47 @@
   }
   m_CharList.push_back(info);
 }
+
 void CPDF_TextPage::CloseTempLine() {
-  if (m_TempCharList.empty()) {
+  if (m_TempCharList.empty())
     return;
-  }
-  std::unique_ptr<CFX_BidiChar> pBidiChar(new CFX_BidiChar);
+
   CFX_WideString str = m_TempTextBuf.GetWideString();
-  std::vector<FX_WORD> order;
-  FX_BOOL bR2L = FALSE;
-  int32_t start = 0, count = 0;
-  int nR2L = 0, nL2R = 0;
   FX_BOOL bPrevSpace = FALSE;
   for (int i = 0; i < str.GetLength(); i++) {
-    if (str.GetAt(i) == 32) {
-      if (bPrevSpace) {
-        m_TempTextBuf.Delete(i, 1);
-        m_TempCharList.erase(m_TempCharList.begin() + i);
-        str.Delete(i);
-        i--;
-        continue;
-      }
-      bPrevSpace = TRUE;
-    } else {
+    if (str.GetAt(i) != ' ') {
       bPrevSpace = FALSE;
+      continue;
     }
-    if (pBidiChar->AppendChar(str.GetAt(i))) {
-      CFX_BidiChar::Direction ret = pBidiChar->GetBidiInfo(&start, &count);
-      order.push_back(start);
-      order.push_back(count);
-      order.push_back(ret);
-      if (!bR2L) {
-        if (ret == CFX_BidiChar::RIGHT) {
-          nR2L++;
-        } else if (ret == CFX_BidiChar::LEFT) {
-          nL2R++;
-        }
-      }
+    if (bPrevSpace) {
+      m_TempTextBuf.Delete(i, 1);
+      m_TempCharList.erase(m_TempCharList.begin() + i);
+      str.Delete(i);
+      i--;
     }
+    bPrevSpace = TRUE;
   }
-  if (pBidiChar->EndChar()) {
-    CFX_BidiChar::Direction ret = pBidiChar->GetBidiInfo(&start, &count);
-    order.push_back(start);
-    order.push_back(count);
-    order.push_back(ret);
-    if (!bR2L) {
-      if (ret == CFX_BidiChar::RIGHT) {
-        nR2L++;
-      } else if (ret == CFX_BidiChar::LEFT) {
-        nL2R++;
-      }
-    }
-  }
-  if (nR2L > 0 && nR2L >= nL2R) {
-    bR2L = TRUE;
-  }
-  if (m_parserflag == FPDFTEXT_RLTB || bR2L) {
-    int count = pdfium::CollectionSize<int>(order);
-    for (int i = count - 1; i > 0; i -= 3) {
-      int ret = order[i];
-      int count1 = order[i - 1];
-      int start = order[i - 2];
-      if (ret == 2 || ret == 0) {
-        for (int j = start + count1 - 1; j >= start; j--) {
-          AddCharInfoByRLDirection(str, j);
-        }
-      } else {
-        int j = i;
-        FX_BOOL bSymbol = FALSE;
-        while (j > 0 && order[j] != 2) {
-          bSymbol = !order[j];
-          j -= 3;
-        }
-        int end = start + count1;
-        int n = 0;
-        if (bSymbol) {
-          n = j + 6;
-        } else {
-          n = j + 3;
-        }
-        if (n >= i) {
-          for (int m = start; m < end; m++) {
-            AddCharInfoByLRDirection(str, m);
-          }
-        } else {
-          j = i;
-          i = n;
-          for (; n <= j; n += 3) {
-            int start = order[n - 2];
-            int count1 = order[n - 1];
-            int end = start + count1;
-            for (int m = start; m < end; m++) {
-              AddCharInfoByLRDirection(str, m);
-            }
-          }
-        }
-      }
-    }
-  } else {
-    int count = pdfium::CollectionSize<int>(order);
-    FX_BOOL bL2R = FALSE;
-    for (int i = 0; i < count; i += 3) {
-      int start = order[i];
-      int count1 = order[i + 1];
-      int ret = order[i + 2];
-      if (ret == 2 || (i == 0 && ret == 0 && !bL2R)) {
-        int j = i + 3;
-        while (bR2L && j < count) {
-          if (order[j + 2] == 1)
-            break;
-          j += 3;
-        }
-        if (j == 3) {
-          i = -3;
-          bL2R = TRUE;
-          continue;
-        }
-        int end = pdfium::CollectionSize<int>(m_TempCharList) - 1;
-        if (j < count) {
-          end = order[j] - 1;
-        }
-        i = j - 3;
-        for (int n = end; n >= start; n--) {
-          AddCharInfoByRLDirection(str, n);
-        }
-      } else {
-        int end = start + count1;
-        for (int n = start; n < end; n++) {
-          AddCharInfoByLRDirection(str, n);
-        }
-      }
+  CFX_BidiString bidi(str);
+  if (m_parserflag == FPDFTEXT_RLTB)
+    bidi.SetOverallDirectionRight();
+  CFX_BidiChar::Direction eCurrentDirection = bidi.OverallDirection();
+  for (const auto& segment : bidi) {
+    if (segment.direction == CFX_BidiChar::RIGHT ||
+        (segment.direction == CFX_BidiChar::NEUTRAL &&
+         eCurrentDirection == CFX_BidiChar::RIGHT)) {
+      eCurrentDirection = CFX_BidiChar::RIGHT;
+      for (int m = segment.start + segment.count; m > segment.start; --m)
+        AddCharInfoByRLDirection(bidi.CharAt(m - 1), m_TempCharList[m - 1]);
+    } else {
+      eCurrentDirection = CFX_BidiChar::LEFT;
+      for (int m = segment.start; m < segment.start + segment.count; m++)
+        AddCharInfoByLRDirection(bidi.CharAt(m), m_TempCharList[m]);
     }
   }
   m_TempCharList.clear();
   m_TempTextBuf.Delete(0, m_TempTextBuf.GetLength());
 }
+
 void CPDF_TextPage::ProcessTextObject(CPDF_TextObject* pTextObj,
                                       const CFX_Matrix& formMatrix,
                                       FX_POSITION ObjPos) {
@@ -1360,15 +1264,13 @@
     std::swap(pTempBuffer[i], pTempBuffer[j]);
   }
 }
+
 FX_BOOL CPDF_TextPage::IsRightToLeft(const CPDF_TextObject* pTextObj,
                                      const CPDF_Font* pFont,
                                      int nItems) const {
-  std::unique_ptr<CFX_BidiChar> pBidiChar(new CFX_BidiChar);
-  int32_t nR2L = 0;
-  int32_t nL2R = 0;
-  int32_t start = 0, count = 0;
-  CPDF_TextObjectItem item;
+  CFX_WideString str;
   for (int32_t i = 0; i < nItems; i++) {
+    CPDF_TextObjectItem item;
     pTextObj->GetItemInfo(i, &item);
     if (item.m_CharCode == (FX_DWORD)-1) {
       continue;
@@ -1378,28 +1280,12 @@
     if ((wstrItem.IsEmpty() || wChar == 0) && item.m_CharCode) {
       wChar = (FX_WCHAR)item.m_CharCode;
     }
-    if (!wChar) {
-      continue;
-    }
-    if (pBidiChar->AppendChar(wChar)) {
-      CFX_BidiChar::Direction ret = pBidiChar->GetBidiInfo(&start, &count);
-      if (ret == CFX_BidiChar::RIGHT) {
-        nR2L++;
-      } else if (ret == CFX_BidiChar::LEFT) {
-        nL2R++;
-      }
-    }
+    if (wChar)
+      str += wChar;
   }
-  if (pBidiChar->EndChar()) {
-    CFX_BidiChar::Direction ret = pBidiChar->GetBidiInfo(&start, &count);
-    if (ret == CFX_BidiChar::RIGHT) {
-      nR2L++;
-    } else if (ret == CFX_BidiChar::LEFT) {
-      nL2R++;
-    }
-  }
-  return (nR2L > 0 && nR2L >= nL2R);
+  return CFX_BidiString(str).OverallDirection() == CFX_BidiChar::RIGHT;
 }
+
 void CPDF_TextPage::ProcessTextObject(PDFTEXT_Obj Obj) {
   CPDF_TextObject* pTextObj = Obj.m_pTextObj;
   if (FXSYS_fabs(pTextObj->m_Right - pTextObj->m_Left) < 0.01f) {
diff --git a/core/src/fpdftext/text_int.h b/core/src/fpdftext/text_int.h
index 93c2273..9131cdb 100644
--- a/core/src/fpdftext/text_int.h
+++ b/core/src/fpdftext/text_int.h
@@ -121,8 +121,8 @@
   void ProcessMarkedContent(PDFTEXT_Obj pObj);
   void CheckMarkedContentObject(int32_t& start, int32_t& nCount) const;
   void FindPreviousTextObject(void);
-  void AddCharInfoByLRDirection(CFX_WideString& str, int i);
-  void AddCharInfoByRLDirection(CFX_WideString& str, int i);
+  void AddCharInfoByLRDirection(FX_WCHAR wChar, PAGECHAR_INFO info);
+  void AddCharInfoByRLDirection(FX_WCHAR wChar, PAGECHAR_INFO info);
   int32_t GetTextObjectWritingMode(const CPDF_TextObject* pTextObj);
   int32_t FindTextlineFlowDirection();
 
diff --git a/core/src/fxcrt/fx_bidi.cpp b/core/src/fxcrt/fx_bidi.cpp
index d5d87a3..a7a3ecb 100644
--- a/core/src/fxcrt/fx_bidi.cpp
+++ b/core/src/fxcrt/fx_bidi.cpp
@@ -7,60 +7,75 @@
 #include "core/include/fxcrt/fx_bidi.h"
 #include "core/include/fxcrt/fx_ucd.h"
 
-CFX_BidiChar::CFX_BidiChar()
-    : m_iCurStart(0),
-      m_iCurCount(0),
-      m_CurBidi(NEUTRAL),
-      m_iLastStart(0),
-      m_iLastCount(0),
-      m_LastBidi(NEUTRAL) {
-}
+#include <algorithm>
 
-CFX_BidiChar::~CFX_BidiChar() {
-}
+CFX_BidiChar::CFX_BidiChar()
+    : m_CurrentSegment({0, 0, NEUTRAL}), m_LastSegment({0, 0, NEUTRAL}) {}
 
 bool CFX_BidiChar::AppendChar(FX_WCHAR wch) {
   FX_DWORD dwProps = FX_GetUnicodeProperties(wch);
   int32_t iBidiCls = (dwProps & FX_BIDICLASSBITSMASK) >> FX_BIDICLASSBITS;
-  Direction bidi = NEUTRAL;
+  Direction direction = NEUTRAL;
   switch (iBidiCls) {
     case FX_BIDICLASS_L:
     case FX_BIDICLASS_AN:
     case FX_BIDICLASS_EN:
-      bidi = LEFT;
+      direction = LEFT;
       break;
     case FX_BIDICLASS_R:
     case FX_BIDICLASS_AL:
-      bidi = RIGHT;
+      direction = RIGHT;
       break;
   }
 
-  bool bRet = (bidi != m_CurBidi);
-  if (bRet) {
-    SaveCurrentStateToLastState();
-    m_CurBidi = bidi;
-  }
-  m_iCurCount++;
-  return bRet;
+  bool bChangeDirection = (direction != m_CurrentSegment.direction);
+  if (bChangeDirection)
+    StartNewSegment(direction);
+
+  m_CurrentSegment.count++;
+  return bChangeDirection;
 }
 
 bool CFX_BidiChar::EndChar() {
-  SaveCurrentStateToLastState();
-  return m_iLastCount > 0;
+  StartNewSegment(NEUTRAL);
+  return m_LastSegment.count > 0;
 }
 
-CFX_BidiChar::Direction CFX_BidiChar::GetBidiInfo(int32_t* iStart,
-                                                  int32_t* iCount) const {
-  if (iStart)
-    *iStart = m_iLastStart;
-  if (iCount)
-    *iCount = m_iLastCount;
-  return m_LastBidi;
+void CFX_BidiChar::StartNewSegment(CFX_BidiChar::Direction direction) {
+  m_LastSegment = m_CurrentSegment;
+  m_CurrentSegment.start += m_CurrentSegment.count;
+  m_CurrentSegment.count = 0;
+  m_CurrentSegment.direction = direction;
 }
 
-void CFX_BidiChar::SaveCurrentStateToLastState() {
-  m_LastBidi = m_CurBidi;
-  m_iLastStart = m_iCurStart;
-  m_iCurStart = m_iCurCount;
-  m_iLastCount = m_iCurCount - m_iLastStart;
+CFX_BidiString::CFX_BidiString(const CFX_WideString& str)
+    : m_Str(str),
+      m_pBidiChar(new CFX_BidiChar),
+      m_eOverallDirection(CFX_BidiChar::LEFT) {
+  for (int i = 0; i < m_Str.GetLength(); ++i) {
+    if (m_pBidiChar->AppendChar(m_Str.GetAt(i)))
+      m_Order.push_back(m_pBidiChar->GetSegmentInfo());
+  }
+  if (m_pBidiChar->EndChar())
+    m_Order.push_back(m_pBidiChar->GetSegmentInfo());
+
+  size_t nR2L = std::count_if(m_Order.begin(), m_Order.end(),
+                              [](const CFX_BidiChar::Segment& seg) {
+                                return seg.direction == CFX_BidiChar::RIGHT;
+                              });
+
+  size_t nL2R = std::count_if(m_Order.begin(), m_Order.end(),
+                              [](const CFX_BidiChar::Segment& seg) {
+                                return seg.direction == CFX_BidiChar::LEFT;
+                              });
+
+  if (nR2L > 0 && nR2L >= nL2R)
+    SetOverallDirectionRight();
+}
+
+void CFX_BidiString::SetOverallDirectionRight() {
+  if (m_eOverallDirection != CFX_BidiChar::RIGHT) {
+    std::reverse(m_Order.begin(), m_Order.end());
+    m_eOverallDirection = CFX_BidiChar::RIGHT;
+  }
 }
diff --git a/core/src/fxcrt/fx_bidi_unittest.cpp b/core/src/fxcrt/fx_bidi_unittest.cpp
index 8b5b6df..a3c1487 100644
--- a/core/src/fxcrt/fx_bidi_unittest.cpp
+++ b/core/src/fxcrt/fx_bidi_unittest.cpp
@@ -14,123 +14,352 @@
 }  // namespace
 
 TEST(fxcrt, BidiCharEmpty) {
-  int32_t start = -1;
-  int32_t count = -1;
   CFX_BidiChar bidi;
-  CFX_BidiChar::Direction dir = bidi.GetBidiInfo(nullptr, nullptr);
-  EXPECT_EQ(CFX_BidiChar::NEUTRAL, dir);
+  CFX_BidiChar::Segment info;
 
-  dir = bidi.GetBidiInfo(&start, nullptr);
-  EXPECT_EQ(CFX_BidiChar::NEUTRAL, dir);
-  EXPECT_EQ(0, start);
-
-  dir = bidi.GetBidiInfo(nullptr, &count);
-  EXPECT_EQ(CFX_BidiChar::NEUTRAL, dir);
-  EXPECT_EQ(0, count);
-
-  start = -1;
-  count = -1;
-  dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(CFX_BidiChar::NEUTRAL, dir);
-  EXPECT_EQ(0, start);
-  EXPECT_EQ(0, count);
-
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(CFX_BidiChar::NEUTRAL, info.direction);
+  EXPECT_EQ(0, info.start);
+  EXPECT_EQ(0, info.count);
   EXPECT_FALSE(bidi.EndChar());
 }
 
 TEST(fxcrt, BidiCharLeft) {
-  int32_t start = -1;
-  int32_t count = -1;
   CFX_BidiChar bidi;
+  CFX_BidiChar::Segment info;
 
   EXPECT_TRUE(bidi.AppendChar(kLeftChar));
-  CFX_BidiChar::Direction dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(0, start);
-  EXPECT_EQ(0, count);
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(0, info.start);
+  EXPECT_EQ(0, info.count);
 
   EXPECT_FALSE(bidi.AppendChar(kLeftChar));
   EXPECT_FALSE(bidi.AppendChar(kLeftChar));
 
-  dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(CFX_BidiChar::NEUTRAL, dir);
-  EXPECT_EQ(0, start);
-  EXPECT_EQ(0, count);
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(CFX_BidiChar::NEUTRAL, info.direction);
+  EXPECT_EQ(0, info.start);
+  EXPECT_EQ(0, info.count);
 
   EXPECT_TRUE(bidi.EndChar());
-  dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(CFX_BidiChar::LEFT, dir);
-  EXPECT_EQ(0, start);
-  EXPECT_EQ(3, count);
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(CFX_BidiChar::LEFT, info.direction);
+  EXPECT_EQ(0, info.start);
+  EXPECT_EQ(3, info.count);
 
   EXPECT_FALSE(bidi.EndChar());
 }
 
 TEST(fxcrt, BidiCharLeftNeutralRight) {
-  int32_t start = -1;
-  int32_t count = -1;
   CFX_BidiChar bidi;
+  CFX_BidiChar::Segment info;
 
   EXPECT_TRUE(bidi.AppendChar(kLeftChar));
-  CFX_BidiChar::Direction dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(0, start);
-  EXPECT_EQ(0, count);
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(0, info.start);
+  EXPECT_EQ(0, info.count);
 
   EXPECT_FALSE(bidi.AppendChar(kLeftChar));
   EXPECT_FALSE(bidi.AppendChar(kLeftChar));
   EXPECT_TRUE(bidi.AppendChar(kNeutralChar));
-  dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(0, start);
-  EXPECT_EQ(3, count);
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(0, info.start);
+  EXPECT_EQ(3, info.count);
 
   EXPECT_FALSE(bidi.AppendChar(kNeutralChar));
   EXPECT_FALSE(bidi.AppendChar(kNeutralChar));
   EXPECT_FALSE(bidi.AppendChar(kNeutralChar));
   EXPECT_TRUE(bidi.AppendChar(kRightChar));
-  dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(CFX_BidiChar::NEUTRAL, dir);
-  EXPECT_EQ(3, start);
-  EXPECT_EQ(4, count);
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(CFX_BidiChar::NEUTRAL, info.direction);
+  EXPECT_EQ(3, info.start);
+  EXPECT_EQ(4, info.count);
 
   EXPECT_TRUE(bidi.EndChar());
-  dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(CFX_BidiChar::RIGHT, dir);
-  EXPECT_EQ(7, start);
-  EXPECT_EQ(1, count);
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(CFX_BidiChar::RIGHT, info.direction);
+  EXPECT_EQ(7, info.start);
+  EXPECT_EQ(1, info.count);
 
   EXPECT_FALSE(bidi.EndChar());
 }
 
 TEST(fxcrt, BidiCharLeftRightLeft) {
-  int32_t start = -1;
-  int32_t count = -1;
   CFX_BidiChar bidi;
+  CFX_BidiChar::Segment info;
 
   EXPECT_TRUE(bidi.AppendChar(kLeftChar));
-  CFX_BidiChar::Direction dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(0, start);
-  EXPECT_EQ(0, count);
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(0, info.start);
+  EXPECT_EQ(0, info.count);
 
   EXPECT_FALSE(bidi.AppendChar(kLeftChar));
   EXPECT_FALSE(bidi.AppendChar(kLeftChar));
   EXPECT_TRUE(bidi.AppendChar(kRightChar));
-  dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(0, start);
-  EXPECT_EQ(3, count);
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(0, info.start);
+  EXPECT_EQ(3, info.count);
 
   EXPECT_FALSE(bidi.AppendChar(kRightChar));
   EXPECT_FALSE(bidi.AppendChar(kRightChar));
   EXPECT_FALSE(bidi.AppendChar(kRightChar));
   EXPECT_TRUE(bidi.AppendChar(kLeftChar));
-  dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(CFX_BidiChar::RIGHT, dir);
-  EXPECT_EQ(3, start);
-  EXPECT_EQ(4, count);
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(CFX_BidiChar::RIGHT, info.direction);
+  EXPECT_EQ(3, info.start);
+  EXPECT_EQ(4, info.count);
 
   EXPECT_TRUE(bidi.EndChar());
-  dir = bidi.GetBidiInfo(&start, &count);
-  EXPECT_EQ(CFX_BidiChar::LEFT, dir);
-  EXPECT_EQ(7, start);
-  EXPECT_EQ(1, count);
+  info = bidi.GetSegmentInfo();
+  EXPECT_EQ(CFX_BidiChar::LEFT, info.direction);
+  EXPECT_EQ(7, info.start);
+  EXPECT_EQ(1, info.count);
 
   EXPECT_FALSE(bidi.EndChar());
 }
+
+TEST(fxcrt, BidiStringEmpty) {
+  CFX_BidiString bidi(L"");
+  EXPECT_EQ(CFX_BidiChar::LEFT, bidi.OverallDirection());
+  EXPECT_TRUE(bidi.begin() == bidi.end());
+}
+
+TEST(fxcrt, BidiStringAllNeutral) {
+  {
+    const FX_WCHAR str[] = {kNeutralChar, 0};
+    CFX_BidiString bidi(str);
+    EXPECT_EQ(CFX_BidiChar::LEFT, bidi.OverallDirection());
+
+    auto it = bidi.begin();
+    ASSERT_FALSE(it == bidi.end());
+    EXPECT_EQ(0, it->start);
+    EXPECT_EQ(1, it->count);
+    EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+    ++it;
+    EXPECT_TRUE(it == bidi.end());
+  }
+  {
+    const FX_WCHAR str[] = {kNeutralChar, kNeutralChar, kNeutralChar, 0};
+    CFX_BidiString bidi(str);
+    EXPECT_EQ(CFX_BidiChar::LEFT, bidi.OverallDirection());
+
+    auto it = bidi.begin();
+    ASSERT_FALSE(it == bidi.end());
+    EXPECT_EQ(0, it->start);
+    EXPECT_EQ(3, it->count);
+    EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+    ++it;
+    EXPECT_TRUE(it == bidi.end());
+  }
+}
+
+TEST(fxcrt, BidiStringAllLeft) {
+  {
+    const FX_WCHAR str[] = {kLeftChar, 0};
+    CFX_BidiString bidi(str);
+    EXPECT_EQ(CFX_BidiChar::LEFT, bidi.OverallDirection());
+
+    auto it = bidi.begin();
+    ASSERT_FALSE(it == bidi.end());
+    EXPECT_EQ(0, it->start);
+    EXPECT_EQ(0, it->count);
+    EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+    ASSERT_FALSE(it == bidi.end());
+
+    ++it;
+    EXPECT_EQ(0, it->start);
+    EXPECT_EQ(1, it->count);
+    EXPECT_EQ(CFX_BidiChar::LEFT, it->direction);
+    ASSERT_FALSE(it == bidi.end());
+
+    ++it;
+    EXPECT_TRUE(it == bidi.end());
+  }
+  {
+    const FX_WCHAR str[] = {kLeftChar, kLeftChar, kLeftChar, 0};
+    CFX_BidiString bidi(str);
+    EXPECT_EQ(CFX_BidiChar::LEFT, bidi.OverallDirection());
+
+    auto it = bidi.begin();
+    ASSERT_FALSE(it == bidi.end());
+    EXPECT_EQ(0, it->start);
+    EXPECT_EQ(0, it->count);
+    EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+    ASSERT_FALSE(it == bidi.end());
+
+    ++it;
+    EXPECT_EQ(0, it->start);
+    EXPECT_EQ(3, it->count);
+    EXPECT_EQ(CFX_BidiChar::LEFT, it->direction);
+    ASSERT_FALSE(it == bidi.end());
+
+    ++it;
+    EXPECT_TRUE(it == bidi.end());
+  }
+}
+
+TEST(fxcrt, BidiStringAllRight) {
+  {
+    const FX_WCHAR str[] = {kRightChar, 0};
+    CFX_BidiString bidi(str);
+    EXPECT_EQ(CFX_BidiChar::RIGHT, bidi.OverallDirection());
+
+    auto it = bidi.begin();
+    EXPECT_EQ(0, it->start);
+    EXPECT_EQ(1, it->count);
+    EXPECT_EQ(CFX_BidiChar::RIGHT, it->direction);
+    ASSERT_FALSE(it == bidi.end());
+
+    ++it;
+    ASSERT_FALSE(it == bidi.end());
+    EXPECT_EQ(0, it->start);
+    EXPECT_EQ(0, it->count);
+    EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+    ASSERT_FALSE(it == bidi.end());
+
+    ++it;
+    EXPECT_TRUE(it == bidi.end());
+  }
+  {
+    const FX_WCHAR str[] = {kRightChar, kRightChar, kRightChar, 0};
+    CFX_BidiString bidi(str);
+    EXPECT_EQ(CFX_BidiChar::RIGHT, bidi.OverallDirection());
+
+    auto it = bidi.begin();
+    EXPECT_EQ(0, it->start);
+    EXPECT_EQ(3, it->count);
+    EXPECT_EQ(CFX_BidiChar::RIGHT, it->direction);
+    ASSERT_FALSE(it == bidi.end());
+
+    ++it;
+    ASSERT_FALSE(it == bidi.end());
+    EXPECT_EQ(0, it->start);
+    EXPECT_EQ(0, it->count);
+    EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+    ASSERT_FALSE(it == bidi.end());
+
+    ++it;
+    EXPECT_TRUE(it == bidi.end());
+  }
+}
+
+TEST(fxcrt, BidiStringLeftNeutralLeftRight) {
+  const FX_WCHAR str[] = {kLeftChar, kNeutralChar, kLeftChar, kRightChar, 0};
+  CFX_BidiString bidi(str);
+  EXPECT_EQ(CFX_BidiChar::LEFT, bidi.OverallDirection());
+
+  auto it = bidi.begin();
+  ASSERT_FALSE(it == bidi.end());
+  EXPECT_EQ(0, it->start);
+  EXPECT_EQ(0, it->count);
+  EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+  ASSERT_FALSE(it == bidi.end());
+
+  ++it;
+  EXPECT_EQ(0, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::LEFT, it->direction);
+  ASSERT_FALSE(it == bidi.end());
+
+  ++it;
+  EXPECT_EQ(1, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+  ASSERT_FALSE(it == bidi.end());
+
+  ++it;
+  EXPECT_EQ(2, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::LEFT, it->direction);
+  ASSERT_FALSE(it == bidi.end());
+
+  ++it;
+  EXPECT_EQ(3, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::RIGHT, it->direction);
+  ASSERT_FALSE(it == bidi.end());
+
+  ++it;
+  EXPECT_TRUE(it == bidi.end());
+}
+
+TEST(fxcrt, BidiStringRightNeutralLeftRight) {
+  const FX_WCHAR str[] = {kRightChar, kNeutralChar, kLeftChar, kRightChar, 0};
+  CFX_BidiString bidi(str);
+  EXPECT_EQ(CFX_BidiChar::RIGHT, bidi.OverallDirection());
+
+  auto it = bidi.begin();
+  EXPECT_EQ(3, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::RIGHT, it->direction);
+  ASSERT_FALSE(it == bidi.end());
+
+  ++it;
+  EXPECT_EQ(2, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::LEFT, it->direction);
+  ASSERT_FALSE(it == bidi.end());
+
+  ++it;
+  EXPECT_EQ(1, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+  ASSERT_FALSE(it == bidi.end());
+
+  ++it;
+  EXPECT_EQ(0, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::RIGHT, it->direction);
+  ASSERT_FALSE(it == bidi.end());
+
+  ++it;
+  ASSERT_FALSE(it == bidi.end());
+  EXPECT_EQ(0, it->start);
+  EXPECT_EQ(0, it->count);
+  EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+  ASSERT_FALSE(it == bidi.end());
+
+  ++it;
+  EXPECT_TRUE(it == bidi.end());
+}
+
+TEST(fxcrt, BidiStringReverse) {
+  const FX_WCHAR str[] = {kLeftChar, kNeutralChar, kRightChar, kLeftChar, 0};
+  CFX_BidiString bidi(str);
+  EXPECT_EQ(CFX_BidiChar::LEFT, bidi.OverallDirection());
+  bidi.SetOverallDirectionRight();
+
+  auto it = bidi.begin();
+  ASSERT_FALSE(it == bidi.end());
+  EXPECT_EQ(3, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::LEFT, it->direction);
+
+  ++it;
+  ASSERT_FALSE(it == bidi.end());
+  EXPECT_EQ(2, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::RIGHT, it->direction);
+  ASSERT_FALSE(it == bidi.end());
+
+  ++it;
+  ASSERT_FALSE(it == bidi.end());
+  EXPECT_EQ(1, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+
+  ++it;
+  ASSERT_FALSE(it == bidi.end());
+  EXPECT_EQ(0, it->start);
+  EXPECT_EQ(1, it->count);
+  EXPECT_EQ(CFX_BidiChar::LEFT, it->direction);
+
+  ++it;
+  ASSERT_FALSE(it == bidi.end());
+  EXPECT_EQ(0, it->start);
+  EXPECT_EQ(0, it->count);
+  EXPECT_EQ(CFX_BidiChar::NEUTRAL, it->direction);
+
+  ++it;
+  EXPECT_TRUE(it == bidi.end());
+}