From d56fd77ef0b2e2a14ceb127283ac0e7cf7ca090b Mon Sep 17 00:00:00 2001 From: Dan Sinclair Date: Wed, 20 Sep 2017 11:17:52 -0400 Subject: Implement CFDE_TextEditEngine::GetIndex* methods. This CL adds unittests and implementations for the text edit engine methods to get various indexes based on cursor position. The |RebuildPieces| method was fixed to correctly keep track of character position when dealing with BIDI characters. Change-Id: Ie3c5ee5d63bfd00f6f0cdcb1c6fcfe6e05bba50e Reviewed-on: https://pdfium-review.googlesource.com/14430 Commit-Queue: dsinclair Reviewed-by: Ryan Harrison --- xfa/fde/cfde_texteditengine.cpp | 140 ++++++++++++++++++++++++++++++- xfa/fde/cfde_texteditengine.h | 16 ++-- xfa/fde/cfde_texteditengine_unittest.cpp | 123 +++++++++++++++++++++++++++ 3 files changed, 268 insertions(+), 11 deletions(-) diff --git a/xfa/fde/cfde_texteditengine.cpp b/xfa/fde/cfde_texteditengine.cpp index c619a984d2..143bac49f9 100644 --- a/xfa/fde/cfde_texteditengine.cpp +++ b/xfa/fde/cfde_texteditengine.cpp @@ -394,10 +394,145 @@ void CFDE_TextEditEngine::ClearOperationRecords() { next_operation_index_to_insert_ = 0; } +size_t CFDE_TextEditEngine::GetIndexBefore(size_t pos) { + int32_t bidi_level; + CFX_RectF rect; + // Possible |Layout| triggered by |GetCharacterInfo|. + std::tie(bidi_level, rect) = GetCharacterInfo(pos); + return FX_IsOdd(bidi_level) ? GetIndexRight(pos) : GetIndexLeft(pos); +} + +size_t CFDE_TextEditEngine::GetIndexLeft(size_t pos) const { + if (pos == 0) + return 0; + --pos; + + wchar_t ch = GetChar(pos); + while (pos != 0) { + // We want to be on the location just before the \r or \n + ch = GetChar(pos - 1); + if (ch != '\r' && ch != '\n') + break; + + --pos; + } + return pos; +} + +size_t CFDE_TextEditEngine::GetIndexRight(size_t pos) const { + if (pos >= text_length_) + return text_length_; + ++pos; + + wchar_t ch = GetChar(pos); + // We want to be on the location after the \r\n. + while (pos < text_length_ && (ch == '\r' || ch == '\n')) { + ++pos; + ch = GetChar(pos); + } + + return pos; +} + +size_t CFDE_TextEditEngine::GetIndexUp(size_t pos) const { + size_t line_start = GetIndexAtStartOfLine(pos); + if (line_start == 0) + return pos; + + // Determine how far along the line we were. + size_t dist = pos - line_start; + + // Move to the end of the preceding line. + wchar_t ch; + do { + --line_start; + ch = GetChar(line_start); + } while (line_start != 0 && (ch == '\r' || ch == '\n')); + + if (line_start == 0) + return dist; + + // Get the start of the line prior to the current line. + size_t prior_start = GetIndexAtStartOfLine(line_start); + + // Prior line is shorter then next line, and we're past the end of that line + // return the end of line. + if (prior_start + dist > line_start) + return GetIndexAtEndOfLine(line_start); + + return prior_start + dist; +} + +size_t CFDE_TextEditEngine::GetIndexDown(size_t pos) const { + size_t line_end = GetIndexAtEndOfLine(pos); + if (line_end == text_length_) + return pos; + + wchar_t ch; + do { + ++line_end; + ch = GetChar(line_end); + } while (line_end < text_length_ && (ch == '\r' || ch == '\n')); + + if (line_end == text_length_) + return line_end; + + // Determine how far along the line we are. + size_t dist = pos - GetIndexAtStartOfLine(pos); + + // Check if next line is shorter then current line. If so, return end + // of next line. + size_t next_line_end = GetIndexAtEndOfLine(line_end); + if (line_end + dist > next_line_end) + return next_line_end; + + return line_end + dist; +} + +size_t CFDE_TextEditEngine::GetIndexAtStartOfLine(size_t pos) const { + if (pos == 0) + return 0; + + wchar_t ch = GetChar(pos); + // What to do. + if (ch == '\r' || ch == '\n') + return pos; + + do { + // We want to be on the location just after the \r\n + ch = GetChar(pos - 1); + if (ch == '\r' || ch == '\n') + break; + + --pos; + } while (pos > 0); + + return pos; +} + +size_t CFDE_TextEditEngine::GetIndexAtEndOfLine(size_t pos) const { + if (pos >= text_length_) + return text_length_; + + wchar_t ch = GetChar(pos); + // Not quite sure which way to go here? + if (ch == '\r' || ch == '\n') + return pos; + + // We want to be on the location of the first \r or \n. + do { + ++pos; + ch = GetChar(pos); + } while (pos < text_length_ && (ch != '\r' && ch != '\n')); + + return pos; +} + void CFDE_TextEditEngine::LimitHorizontalScroll(bool val) { ClearOperationRecords(); limit_horizontal_area_ = val; } + void CFDE_TextEditEngine::LimitVerticalScroll(bool val) { ClearOperationRecords(); limit_vertical_area_ = val; @@ -838,6 +973,8 @@ void CFDE_TextEditEngine::RebuildPieces() { bool initialized_bounding_box = false; contents_bounding_box_ = CFX_RectF(); + size_t current_piece_start = 0; + float current_line_start = 0; auto iter = pdfium::MakeUnique(this); while (!iter->IsEOF(false)) { @@ -850,9 +987,6 @@ void CFDE_TextEditEngine::RebuildPieces() { if (CFX_BreakTypeNoneOrPiece(break_status)) continue; - - size_t current_piece_start = 0; - float current_line_start = 0; int32_t piece_count = text_break_.CountBreakPieces(); for (int32_t i = 0; i < piece_count; ++i) { const CFX_BreakPiece* piece = text_break_.GetBreakPieceUnstable(i); diff --git a/xfa/fde/cfde_texteditengine.h b/xfa/fde/cfde_texteditengine.h index e26487aaf6..dbb12f3953 100644 --- a/xfa/fde/cfde_texteditengine.h +++ b/xfa/fde/cfde_texteditengine.h @@ -129,14 +129,14 @@ class CFDE_TextEditEngine { bool Undo(); void ClearOperationRecords(); - // TODO(dsinclair): Implement .... - size_t GetIndexBefore(size_t pos) { return 0; } - size_t GetIndexLeft(size_t pos) { return 0; } - size_t GetIndexRight(size_t pos) { return 0; } - size_t GetIndexUp(size_t pos) { return 0; } - size_t GetIndexDown(size_t pos) { return 0; } - size_t GetIndexAtStartOfLine(size_t pos) { return 0; } - size_t GetIndexAtEndOfLine(size_t pos) { return 0; } + // This is not const it can trigger a |Layout|. + size_t GetIndexBefore(size_t pos); + size_t GetIndexLeft(size_t pos) const; + size_t GetIndexRight(size_t pos) const; + size_t GetIndexUp(size_t pos) const; + size_t GetIndexDown(size_t pos) const; + size_t GetIndexAtStartOfLine(size_t pos) const; + size_t GetIndexAtEndOfLine(size_t pos) const; void SelectAll(); void SetSelection(size_t start_idx, size_t count); diff --git a/xfa/fde/cfde_texteditengine_unittest.cpp b/xfa/fde/cfde_texteditengine_unittest.cpp index 51940f21a8..b0a27f331c 100644 --- a/xfa/fde/cfde_texteditengine_unittest.cpp +++ b/xfa/fde/cfde_texteditengine_unittest.cpp @@ -571,3 +571,126 @@ TEST_F(CFDE_TextEditEngineTest, BoundsForWordAt) { } } } + +TEST_F(CFDE_TextEditEngineTest, CursorMovement) { + engine()->Clear(); + engine()->Insert(0, L"Hello"); + + EXPECT_EQ(0U, engine()->GetIndexLeft(0)); + EXPECT_EQ(5U, engine()->GetIndexRight(5)); + EXPECT_EQ(2U, engine()->GetIndexUp(2)); + EXPECT_EQ(2U, engine()->GetIndexDown(2)); + EXPECT_EQ(1U, engine()->GetIndexLeft(2)); + EXPECT_EQ(1U, engine()->GetIndexBefore(2)); + EXPECT_EQ(3U, engine()->GetIndexRight(2)); + EXPECT_EQ(0U, engine()->GetIndexAtStartOfLine(2)); + EXPECT_EQ(5U, engine()->GetIndexAtEndOfLine(2)); + + engine()->Clear(); + engine()->Insert(0, L"The book is \"مدخل إلى C++\""); + EXPECT_EQ(2U, engine()->GetIndexBefore(3)); // Before is to left. + EXPECT_EQ(16U, engine()->GetIndexBefore(15)); // Before is to right. + EXPECT_EQ(22U, engine()->GetIndexBefore(23)); // Before is to left. + + engine()->Clear(); + engine()->Insert(0, L"Hello\r\nWorld\r\nTest"); + // Move to end of Hello from start of World. + engine()->SetSelection(engine()->GetIndexBefore(7U), 7); + EXPECT_STREQ(L"\r\nWorld", engine()->GetSelectedText().c_str()); + + // Second letter in Hello from second letter in World. + engine()->SetSelection(engine()->GetIndexUp(8U), 2); + EXPECT_STREQ(L"el", engine()->GetSelectedText().c_str()); + + // Second letter in World from second letter in Test. + engine()->SetSelection(engine()->GetIndexUp(15U), 2); + EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str()); + + // Second letter in World from second letter in Hello. + engine()->SetSelection(engine()->GetIndexDown(1U), 2); + EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str()); + + // Second letter in Test from second letter in World. + engine()->SetSelection(engine()->GetIndexDown(8U), 2); + EXPECT_STREQ(L"es", engine()->GetSelectedText().c_str()); + + size_t start_idx = engine()->GetIndexAtStartOfLine(8U); + size_t end_idx = engine()->GetIndexAtEndOfLine(8U); + engine()->SetSelection(start_idx, end_idx - start_idx); + EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str()); + + // Move past \r\n to before W. + engine()->SetSelection(engine()->GetIndexRight(5U), 5); + EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str()); + + engine()->Clear(); + engine()->Insert(0, L"Short\nAnd a very long line"); + engine()->SetSelection(engine()->GetIndexUp(14U), 11); + EXPECT_STREQ(L"\nAnd a very", engine()->GetSelectedText().c_str()); + + engine()->Clear(); + engine()->Insert(0, L"A Very long line\nShort"); + EXPECT_EQ(engine()->GetLength(), engine()->GetIndexDown(8U)); + + engine()->Clear(); + engine()->Insert(0, L"Hello\rWorld\rTest"); + // Move to end of Hello from start of World. + engine()->SetSelection(engine()->GetIndexBefore(6U), 6); + EXPECT_STREQ(L"\rWorld", engine()->GetSelectedText().c_str()); + + // Second letter in Hello from second letter in World. + engine()->SetSelection(engine()->GetIndexUp(7U), 2); + EXPECT_STREQ(L"el", engine()->GetSelectedText().c_str()); + + // Second letter in World from second letter in Test. + engine()->SetSelection(engine()->GetIndexUp(13U), 2); + EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str()); + + // Second letter in World from second letter in Hello. + engine()->SetSelection(engine()->GetIndexDown(1U), 2); + EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str()); + + // Second letter in Test from second letter in World. + engine()->SetSelection(engine()->GetIndexDown(7U), 2); + EXPECT_STREQ(L"es", engine()->GetSelectedText().c_str()); + + start_idx = engine()->GetIndexAtStartOfLine(7U); + end_idx = engine()->GetIndexAtEndOfLine(7U); + engine()->SetSelection(start_idx, end_idx - start_idx); + EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str()); + + // Move past \r to before W. + engine()->SetSelection(engine()->GetIndexRight(5U), 5); + EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str()); + + engine()->Clear(); + engine()->Insert(0, L"Hello\nWorld\nTest"); + // Move to end of Hello from start of World. + engine()->SetSelection(engine()->GetIndexBefore(6U), 6); + EXPECT_STREQ(L"\nWorld", engine()->GetSelectedText().c_str()); + + // Second letter in Hello from second letter in World. + engine()->SetSelection(engine()->GetIndexUp(7U), 2); + EXPECT_STREQ(L"el", engine()->GetSelectedText().c_str()); + + // Second letter in World from second letter in Test. + engine()->SetSelection(engine()->GetIndexUp(13U), 2); + EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str()); + + // Second letter in World from second letter in Hello. + engine()->SetSelection(engine()->GetIndexDown(1U), 2); + EXPECT_STREQ(L"or", engine()->GetSelectedText().c_str()); + + // Second letter in Test from second letter in World. + engine()->SetSelection(engine()->GetIndexDown(7U), 2); + EXPECT_STREQ(L"es", engine()->GetSelectedText().c_str()); + + start_idx = engine()->GetIndexAtStartOfLine(7U); + end_idx = engine()->GetIndexAtEndOfLine(7U); + engine()->SetSelection(start_idx, end_idx - start_idx); + EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str()); + + // Move past \r to before W. + engine()->SetSelection(engine()->GetIndexRight(5U), 5); + EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str()); +} -- cgit v1.2.3