846 lines
28 KiB
C++
846 lines
28 KiB
C++
// Copyright 2017 The PDFium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "xfa/fde/cfde_texteditengine.h"
|
|
|
|
#include "core/fxcrt/fx_codepage.h"
|
|
#include "core/fxcrt/fx_extension.h"
|
|
#include "core/fxge/text_char_pos.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "testing/xfa_test_environment.h"
|
|
#include "xfa/fgas/font/cfgas_gefont.h"
|
|
|
|
class CFDE_TextEditEngineTest : public testing::Test {
|
|
public:
|
|
class Delegate final : public CFDE_TextEditEngine::Delegate {
|
|
public:
|
|
void Reset() {
|
|
text_is_full = false;
|
|
fail_validation = false;
|
|
}
|
|
|
|
void NotifyTextFull() override { text_is_full = true; }
|
|
|
|
void OnCaretChanged() override {}
|
|
void OnTextWillChange(CFDE_TextEditEngine::TextChange* change) override {}
|
|
void OnTextChanged() override {}
|
|
void OnSelChanged() override {}
|
|
bool OnValidate(const WideString& wsText) override {
|
|
return !fail_validation;
|
|
}
|
|
void SetScrollOffset(float fScrollOffset) override {}
|
|
|
|
bool fail_validation = false;
|
|
bool text_is_full = false;
|
|
};
|
|
|
|
CFDE_TextEditEngineTest() = default;
|
|
~CFDE_TextEditEngineTest() override = default;
|
|
|
|
void SetUp() override {
|
|
const wchar_t kFontFamily[] = L"Arimo Bold";
|
|
font_ = CFGAS_GEFont::LoadFont(kFontFamily, 0, FX_CodePage::kDefANSI);
|
|
ASSERT_TRUE(font_);
|
|
|
|
engine_ = std::make_unique<CFDE_TextEditEngine>();
|
|
engine_->SetFont(font_);
|
|
engine_->SetFontSize(12.0f);
|
|
}
|
|
|
|
void TearDown() override {
|
|
engine_.reset();
|
|
font_.Reset();
|
|
}
|
|
|
|
CFDE_TextEditEngine* engine() const { return engine_.get(); }
|
|
|
|
private:
|
|
RetainPtr<CFGAS_GEFont> font_;
|
|
std::unique_ptr<CFDE_TextEditEngine> engine_;
|
|
};
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, Insert) {
|
|
EXPECT_STREQ(L"", engine()->GetText().c_str());
|
|
|
|
engine()->Insert(0, L"");
|
|
EXPECT_STREQ(L"", engine()->GetText().c_str());
|
|
EXPECT_EQ(0U, engine()->GetLength());
|
|
|
|
engine()->Insert(0, L"Hello");
|
|
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
|
|
EXPECT_EQ(5U, engine()->GetLength());
|
|
|
|
engine()->Insert(5, L" World");
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
EXPECT_EQ(11U, engine()->GetLength());
|
|
|
|
engine()->Insert(5, L" New");
|
|
EXPECT_STREQ(L"Hello New World", engine()->GetText().c_str());
|
|
|
|
engine()->Insert(100, L" Cat");
|
|
EXPECT_STREQ(L"Hello New World Cat", engine()->GetText().c_str());
|
|
|
|
engine()->Clear();
|
|
|
|
engine()->SetHasCharacterLimit(true);
|
|
engine()->SetCharacterLimit(5);
|
|
engine()->Insert(0, L"Hello");
|
|
|
|
// No delegate
|
|
engine()->Insert(5, L" World");
|
|
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
|
|
|
|
engine()->SetCharacterLimit(8);
|
|
engine()->Insert(5, L" World");
|
|
EXPECT_STREQ(L"Hello Wo", engine()->GetText().c_str());
|
|
|
|
engine()->Clear();
|
|
|
|
// With Delegate
|
|
auto delegate = std::make_unique<CFDE_TextEditEngineTest::Delegate>();
|
|
engine()->SetDelegate(delegate.get());
|
|
|
|
engine()->SetCharacterLimit(5);
|
|
engine()->Insert(0, L"Hello");
|
|
|
|
// Insert when full.
|
|
engine()->Insert(5, L" World");
|
|
EXPECT_TRUE(delegate->text_is_full);
|
|
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
|
|
delegate->Reset();
|
|
|
|
engine()->SetCharacterLimit(8);
|
|
engine()->Insert(5, L" World");
|
|
EXPECT_TRUE(delegate->text_is_full);
|
|
EXPECT_STREQ(L"Hello Wo", engine()->GetText().c_str());
|
|
delegate->Reset();
|
|
engine()->SetHasCharacterLimit(false);
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"Hello");
|
|
|
|
// Insert Invalid text
|
|
delegate->fail_validation = true;
|
|
engine()->EnableValidation(true);
|
|
engine()->Insert(5, L" World");
|
|
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
|
|
|
|
delegate->fail_validation = false;
|
|
engine()->Insert(5, L" World");
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
engine()->EnableValidation(false);
|
|
|
|
engine()->Clear();
|
|
|
|
engine()->Insert(0, L"Hello\nWorld");
|
|
EXPECT_FALSE(delegate->text_is_full);
|
|
EXPECT_STREQ(L"Hello\nWorld", engine()->GetText().c_str());
|
|
delegate->Reset();
|
|
engine()->Clear();
|
|
|
|
// Insert with limited area and over-fill
|
|
engine()->LimitHorizontalScroll(true);
|
|
engine()->SetAvailableWidth(52.0f); // Fits 'Hello Wo'.
|
|
engine()->Insert(0, L"Hello");
|
|
EXPECT_FALSE(delegate->text_is_full);
|
|
engine()->Insert(5, L" World");
|
|
EXPECT_TRUE(delegate->text_is_full);
|
|
EXPECT_STREQ(L"Hello Wo", engine()->GetText().c_str());
|
|
engine()->LimitHorizontalScroll(false);
|
|
|
|
delegate->Reset();
|
|
engine()->Clear();
|
|
|
|
engine()->SetLineSpace(12.0f);
|
|
engine()->LimitVerticalScroll(true);
|
|
// Default is one line of text.
|
|
engine()->Insert(0, L"Hello");
|
|
EXPECT_FALSE(delegate->text_is_full);
|
|
engine()->Insert(5, L" Wo\nrld");
|
|
EXPECT_TRUE(delegate->text_is_full);
|
|
EXPECT_STREQ(L"Hello Wo\n", engine()->GetText().c_str());
|
|
engine()->LimitVerticalScroll(false);
|
|
|
|
engine()->SetDelegate(nullptr);
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, InsertToggleLimit) {
|
|
engine()->SetHasCharacterLimit(true);
|
|
engine()->Insert(0, L"Hello World");
|
|
engine()->SetCharacterLimit(5);
|
|
engine()->Insert(0, L"Not Inserted before ");
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
|
|
engine()->SetHasCharacterLimit(false);
|
|
engine()->Insert(0, L"Inserted before ");
|
|
engine()->SetHasCharacterLimit(true);
|
|
engine()->Insert(0, L"Not Inserted before ");
|
|
EXPECT_STREQ(L"Inserted before Hello World", engine()->GetText().c_str());
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, InsertSkipNotify) {
|
|
engine()->SetHasCharacterLimit(true);
|
|
engine()->SetCharacterLimit(8);
|
|
engine()->Insert(0, L"Hello");
|
|
engine()->Insert(5, L" World",
|
|
CFDE_TextEditEngine::RecordOperation::kSkipNotify);
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
|
|
engine()->Insert(0, L"Not inserted");
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
|
|
engine()->Delete(5, 1);
|
|
EXPECT_STREQ(L"HelloWorld", engine()->GetText().c_str());
|
|
|
|
engine()->Insert(0, L"****");
|
|
EXPECT_STREQ(L"*HelloWorld", engine()->GetText().c_str());
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, InsertGrowGap) {
|
|
engine()->Insert(0, L"||");
|
|
for (size_t i = 1; i < 1023; ++i) {
|
|
engine()->Insert(i, L"a");
|
|
}
|
|
WideString result = engine()->GetText();
|
|
ASSERT_EQ(result.GetLength(), 1024u);
|
|
EXPECT_EQ(result[0], L'|');
|
|
EXPECT_EQ(result[1], L'a');
|
|
EXPECT_EQ(result[2], L'a');
|
|
// ...
|
|
EXPECT_EQ(result[1022], L'a');
|
|
EXPECT_EQ(result[1023], L'|');
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, Delete) {
|
|
EXPECT_STREQ(L"", engine()->Delete(0, 50).c_str());
|
|
EXPECT_STREQ(L"", engine()->GetText().c_str());
|
|
|
|
engine()->Insert(0, L"Hello World");
|
|
EXPECT_STREQ(L" World", engine()->Delete(5, 6).c_str());
|
|
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"Hello World");
|
|
EXPECT_STREQ(L" ", engine()->Delete(5, 1).c_str());
|
|
EXPECT_STREQ(L"HelloWorld", engine()->GetText().c_str());
|
|
|
|
EXPECT_STREQ(L"elloWorld", engine()->Delete(1, 50).c_str());
|
|
EXPECT_STREQ(L"H", engine()->GetText().c_str());
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, Clear) {
|
|
EXPECT_STREQ(L"", engine()->GetText().c_str());
|
|
|
|
engine()->Clear();
|
|
EXPECT_STREQ(L"", engine()->GetText().c_str());
|
|
|
|
engine()->Insert(0, L"Hello World");
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
|
|
engine()->Clear();
|
|
EXPECT_STREQ(L"", engine()->GetText().c_str());
|
|
EXPECT_EQ(0U, engine()->GetLength());
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, GetChar) {
|
|
// Out of bounds.
|
|
EXPECT_EQ(L'\0', engine()->GetChar(0));
|
|
|
|
engine()->Insert(0, L"Hello World");
|
|
EXPECT_EQ(L'H', engine()->GetChar(0));
|
|
EXPECT_EQ(L'd', engine()->GetChar(engine()->GetLength() - 1));
|
|
EXPECT_EQ(L' ', engine()->GetChar(5));
|
|
|
|
engine()->Insert(5, L" A");
|
|
EXPECT_STREQ(L"Hello A World", engine()->GetText().c_str());
|
|
EXPECT_EQ(L'W', engine()->GetChar(8));
|
|
|
|
engine()->EnablePasswordMode(true);
|
|
EXPECT_EQ(L'*', engine()->GetChar(8));
|
|
|
|
engine()->SetAliasChar(L'+');
|
|
EXPECT_EQ(L'+', engine()->GetChar(8));
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, GetWidthOfChar) {
|
|
// Out of Bounds.
|
|
EXPECT_EQ(0, engine()->GetWidthOfChar(0));
|
|
|
|
engine()->Insert(0, L"Hello World");
|
|
EXPECT_EQ(173280, engine()->GetWidthOfChar(0));
|
|
EXPECT_EQ(133440, engine()->GetWidthOfChar(1));
|
|
|
|
engine()->Insert(0, L"\t");
|
|
EXPECT_EQ(0, engine()->GetWidthOfChar(0));
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, GetDisplayPos) {
|
|
EXPECT_EQ(0U, engine()->GetDisplayPos(FDE_TEXTEDITPIECE()).size());
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, Selection) {
|
|
EXPECT_FALSE(engine()->HasSelection());
|
|
engine()->SelectAll();
|
|
EXPECT_FALSE(engine()->HasSelection());
|
|
|
|
engine()->Insert(0, L"Hello World");
|
|
EXPECT_STREQ(L"", engine()->DeleteSelectedText().c_str());
|
|
|
|
EXPECT_FALSE(engine()->HasSelection());
|
|
engine()->SelectAll();
|
|
EXPECT_TRUE(engine()->HasSelection());
|
|
EXPECT_STREQ(L"Hello World", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->ClearSelection();
|
|
EXPECT_FALSE(engine()->HasSelection());
|
|
EXPECT_STREQ(L"", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->SelectAll();
|
|
size_t start_idx;
|
|
size_t count;
|
|
std::tie(start_idx, count) = engine()->GetSelection();
|
|
EXPECT_EQ(0U, start_idx);
|
|
EXPECT_EQ(11U, count);
|
|
|
|
// Selection before gap.
|
|
EXPECT_STREQ(L"Hello World", engine()->GetSelectedText().c_str());
|
|
EXPECT_TRUE(engine()->HasSelection());
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
|
|
engine()->Insert(5, L" A");
|
|
EXPECT_FALSE(engine()->HasSelection());
|
|
EXPECT_STREQ(L"", engine()->GetSelectedText().c_str());
|
|
|
|
// Selection over the gap.
|
|
engine()->SelectAll();
|
|
EXPECT_TRUE(engine()->HasSelection());
|
|
EXPECT_STREQ(L"Hello A World", engine()->GetSelectedText().c_str());
|
|
engine()->Clear();
|
|
|
|
engine()->Insert(0, L"Hello World");
|
|
engine()->SelectAll();
|
|
|
|
EXPECT_STREQ(L"Hello World", engine()->DeleteSelectedText().c_str());
|
|
EXPECT_FALSE(engine()->HasSelection());
|
|
EXPECT_STREQ(L"", engine()->GetText().c_str());
|
|
|
|
engine()->Insert(0, L"Hello World");
|
|
engine()->SetSelection(5, 5);
|
|
EXPECT_STREQ(L" Worl", engine()->DeleteSelectedText().c_str());
|
|
EXPECT_FALSE(engine()->HasSelection());
|
|
EXPECT_STREQ(L"Hellod", engine()->GetText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"Hello World");
|
|
engine()->SelectAll();
|
|
engine()->ReplaceSelectedText(L"Goodbye Everybody");
|
|
EXPECT_FALSE(engine()->HasSelection());
|
|
EXPECT_STREQ(L"Goodbye Everybody", engine()->GetText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"Hello World");
|
|
engine()->SetSelection(1, 4);
|
|
engine()->ReplaceSelectedText(L"i,");
|
|
EXPECT_FALSE(engine()->HasSelection());
|
|
EXPECT_STREQ(L"Hi, World", engine()->GetText().c_str());
|
|
|
|
// Selection fully after gap.
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"Hello");
|
|
engine()->Insert(0, L"A ");
|
|
engine()->SetSelection(3, 6);
|
|
EXPECT_STREQ(L"ello", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"Hello World");
|
|
engine()->ClearSelection();
|
|
engine()->DeleteSelectedText();
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, UndoRedo) {
|
|
EXPECT_FALSE(engine()->CanUndo());
|
|
EXPECT_FALSE(engine()->CanRedo());
|
|
EXPECT_FALSE(engine()->Undo());
|
|
EXPECT_FALSE(engine()->Redo());
|
|
|
|
engine()->Insert(0, L"Hello");
|
|
EXPECT_TRUE(engine()->CanUndo());
|
|
EXPECT_FALSE(engine()->CanRedo());
|
|
EXPECT_TRUE(engine()->Undo());
|
|
EXPECT_STREQ(L"", engine()->GetText().c_str());
|
|
EXPECT_FALSE(engine()->CanUndo());
|
|
EXPECT_TRUE(engine()->CanRedo());
|
|
EXPECT_TRUE(engine()->Redo());
|
|
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->CanUndo());
|
|
EXPECT_FALSE(engine()->CanRedo());
|
|
|
|
engine()->Clear();
|
|
EXPECT_FALSE(engine()->CanUndo());
|
|
EXPECT_FALSE(engine()->CanRedo());
|
|
|
|
engine()->Insert(0, L"Hello World");
|
|
engine()->SelectAll();
|
|
engine()->DeleteSelectedText();
|
|
EXPECT_STREQ(L"", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->CanUndo());
|
|
EXPECT_TRUE(engine()->Undo());
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->CanRedo());
|
|
EXPECT_TRUE(engine()->Redo());
|
|
EXPECT_STREQ(L"", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->CanUndo());
|
|
EXPECT_FALSE(engine()->CanRedo());
|
|
|
|
engine()->Insert(0, L"Hello World");
|
|
engine()->SelectAll();
|
|
engine()->ReplaceSelectedText(L"Goodbye Friend");
|
|
EXPECT_STREQ(L"Goodbye Friend", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->CanUndo());
|
|
EXPECT_TRUE(engine()->Undo());
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->CanRedo());
|
|
EXPECT_TRUE(engine()->Redo());
|
|
EXPECT_STREQ(L"Goodbye Friend", engine()->GetText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->SetMaxEditOperationsForTesting(3);
|
|
engine()->Insert(0, L"First ");
|
|
engine()->Insert(engine()->GetLength(), L"Second ");
|
|
engine()->Insert(engine()->GetLength(), L"Third");
|
|
|
|
EXPECT_TRUE(engine()->CanUndo());
|
|
EXPECT_TRUE(engine()->Undo());
|
|
EXPECT_STREQ(L"First Second ", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->CanUndo());
|
|
EXPECT_TRUE(engine()->Undo());
|
|
EXPECT_FALSE(
|
|
engine()->CanUndo()); // Can't undo First; undo buffer too small.
|
|
EXPECT_STREQ(L"First ", engine()->GetText().c_str());
|
|
|
|
EXPECT_TRUE(engine()->CanRedo());
|
|
EXPECT_TRUE(engine()->Redo());
|
|
EXPECT_TRUE(engine()->CanRedo());
|
|
EXPECT_TRUE(engine()->Redo());
|
|
EXPECT_FALSE(engine()->CanRedo());
|
|
EXPECT_STREQ(L"First Second Third", engine()->GetText().c_str());
|
|
|
|
engine()->Clear();
|
|
|
|
engine()->SetMaxEditOperationsForTesting(4);
|
|
|
|
// Go beyond the max operations limit.
|
|
engine()->Insert(0, L"H");
|
|
engine()->Insert(1, L"e");
|
|
engine()->Insert(2, L"l");
|
|
engine()->Insert(3, L"l");
|
|
engine()->Insert(4, L"o");
|
|
engine()->Insert(5, L" World");
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
|
|
// Do A, undo. Do B, undo. Redo should cause B.
|
|
engine()->Delete(4, 3);
|
|
EXPECT_STREQ(L"Hellorld", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->Undo());
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
engine()->Delete(5, 6);
|
|
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->Undo());
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->Redo());
|
|
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
|
|
|
|
// Undo down to the limit.
|
|
EXPECT_TRUE(engine()->Undo());
|
|
EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->Undo());
|
|
EXPECT_STREQ(L"Hello", engine()->GetText().c_str());
|
|
EXPECT_TRUE(engine()->Undo());
|
|
EXPECT_STREQ(L"Hell", engine()->GetText().c_str());
|
|
EXPECT_FALSE(engine()->Undo());
|
|
EXPECT_STREQ(L"Hell", engine()->GetText().c_str());
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, GetIndexForPoint) {
|
|
engine()->SetFontSize(10.0f);
|
|
engine()->Insert(0, L"Hello World");
|
|
EXPECT_EQ(0U, engine()->GetIndexForPoint({0.0f, 0.0f}));
|
|
EXPECT_EQ(11U, engine()->GetIndexForPoint({999999.0f, 9999999.0f}));
|
|
EXPECT_EQ(11U, engine()->GetIndexForPoint({999999.0f, 0.0f}));
|
|
EXPECT_EQ(1U, engine()->GetIndexForPoint({5.0f, 5.0f}));
|
|
EXPECT_EQ(2U, engine()->GetIndexForPoint({10.0f, 5.0f}));
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, GetIndexForPointLineWrap) {
|
|
engine()->SetFontSize(10.0f);
|
|
engine()->Insert(0,
|
|
L"A text long enough to span multiple lines and test "
|
|
L"getting indexes on multi-line edits.");
|
|
EXPECT_EQ(0U, engine()->GetIndexForPoint({0.0f, 0.0f}));
|
|
EXPECT_EQ(87U, engine()->GetIndexForPoint({999999.0f, 9999999.0f}));
|
|
EXPECT_EQ(18U, engine()->GetIndexForPoint({999999.0f, 0.0f}));
|
|
EXPECT_EQ(19U, engine()->GetIndexForPoint({1.0f, 10.0f}));
|
|
EXPECT_EQ(1U, engine()->GetIndexForPoint({5.0f, 5.0f}));
|
|
EXPECT_EQ(2U, engine()->GetIndexForPoint({10.0f, 5.0f}));
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, GetIndexForPointSpaceAtEnd) {
|
|
engine()->SetFontSize(10.0f);
|
|
engine()->Insert(0, L"Hello World ");
|
|
EXPECT_EQ(0U, engine()->GetIndexForPoint({0.0f, 0.0f}));
|
|
EXPECT_EQ(12U, engine()->GetIndexForPoint({999999.0f, 9999999.0f}));
|
|
EXPECT_EQ(12U, engine()->GetIndexForPoint({999999.0f, 0.0f}));
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, GetIndexForPointLineBreaks) {
|
|
engine()->SetFontSize(10.0f);
|
|
engine()->Insert(0, L"Hello\nWorld");
|
|
EXPECT_EQ(0U, engine()->GetIndexForPoint({0.0f, 0.0f}));
|
|
EXPECT_EQ(5U, engine()->GetIndexForPoint({999999.0f, 0.0f}));
|
|
EXPECT_EQ(6U, engine()->GetIndexForPoint({0.0f, 10.0f}));
|
|
EXPECT_EQ(11U, engine()->GetIndexForPoint({999999.0f, 9999999.0f}));
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, CanGenerateCharacterInfo) {
|
|
RetainPtr<CFGAS_GEFont> font = engine()->GetFont();
|
|
ASSERT_TRUE(font);
|
|
|
|
// Has font but no text.
|
|
EXPECT_FALSE(engine()->CanGenerateCharacterInfo());
|
|
|
|
// Has font and text.
|
|
engine()->Insert(0, L"Hi!");
|
|
EXPECT_TRUE(engine()->CanGenerateCharacterInfo());
|
|
|
|
// Has text but no font.
|
|
engine()->SetFont(nullptr);
|
|
EXPECT_FALSE(engine()->CanGenerateCharacterInfo());
|
|
|
|
// Has no text and no font.
|
|
engine()->Clear();
|
|
EXPECT_FALSE(engine()->CanGenerateCharacterInfo());
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, GetCharacterInfo) {
|
|
std::pair<int32_t, CFX_RectF> char_info;
|
|
|
|
engine()->Insert(0, L"Hi!");
|
|
ASSERT_EQ(3U, engine()->GetLength());
|
|
|
|
char_info = engine()->GetCharacterInfo(0);
|
|
EXPECT_EQ(0, char_info.first);
|
|
EXPECT_FLOAT_EQ(0.0f, char_info.second.Left());
|
|
EXPECT_FLOAT_EQ(0.0f, char_info.second.Top());
|
|
EXPECT_FLOAT_EQ(8.664f, char_info.second.Width());
|
|
EXPECT_FLOAT_EQ(12.0f, char_info.second.Height());
|
|
|
|
char_info = engine()->GetCharacterInfo(1);
|
|
EXPECT_EQ(0, char_info.first);
|
|
EXPECT_FLOAT_EQ(8.664f, char_info.second.Left());
|
|
EXPECT_FLOAT_EQ(0.0f, char_info.second.Top());
|
|
EXPECT_FLOAT_EQ(3.324f, char_info.second.Width());
|
|
EXPECT_FLOAT_EQ(12.0f, char_info.second.Height());
|
|
|
|
char_info = engine()->GetCharacterInfo(2);
|
|
EXPECT_EQ(0, char_info.first);
|
|
EXPECT_FLOAT_EQ(11.988f, char_info.second.Left());
|
|
EXPECT_FLOAT_EQ(0.0f, char_info.second.Top());
|
|
EXPECT_FLOAT_EQ(3.996f, char_info.second.Width());
|
|
EXPECT_FLOAT_EQ(12.0f, char_info.second.Height());
|
|
|
|
// Allow retrieving the character info for the end of the text, as that
|
|
// information can be used to determine where to draw a cursor positioned at
|
|
// the end.
|
|
char_info = engine()->GetCharacterInfo(3);
|
|
EXPECT_EQ(0, char_info.first);
|
|
EXPECT_FLOAT_EQ(15.984, char_info.second.Left());
|
|
EXPECT_FLOAT_EQ(0.0f, char_info.second.Top());
|
|
EXPECT_FLOAT_EQ(0.0f, char_info.second.Width());
|
|
EXPECT_FLOAT_EQ(12.0f, char_info.second.Height());
|
|
}
|
|
|
|
TEST_F(CFDE_TextEditEngineTest, BoundsForWordAt) {
|
|
size_t start_idx;
|
|
size_t count;
|
|
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(100);
|
|
EXPECT_EQ(0U, start_idx);
|
|
EXPECT_EQ(0U, count);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"Hello");
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(0);
|
|
EXPECT_EQ(0U, start_idx);
|
|
EXPECT_EQ(5U, count);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"Hello", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"Hello World");
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(100);
|
|
EXPECT_EQ(0U, start_idx);
|
|
EXPECT_EQ(0U, count);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"", engine()->GetSelectedText().c_str());
|
|
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(0);
|
|
EXPECT_EQ(0U, start_idx);
|
|
EXPECT_EQ(5U, count);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"Hello", engine()->GetSelectedText().c_str());
|
|
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(1);
|
|
EXPECT_EQ(0U, start_idx);
|
|
EXPECT_EQ(5U, count);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"Hello", engine()->GetSelectedText().c_str());
|
|
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(4);
|
|
EXPECT_EQ(0U, start_idx);
|
|
EXPECT_EQ(5U, count);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"Hello", engine()->GetSelectedText().c_str());
|
|
|
|
// Select the space
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(5);
|
|
EXPECT_EQ(5U, start_idx);
|
|
EXPECT_EQ(1U, count);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L" ", engine()->GetSelectedText().c_str());
|
|
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(6);
|
|
EXPECT_EQ(6U, start_idx);
|
|
EXPECT_EQ(5U, count);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"World", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"123 456 789");
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(5);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"456", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"123def789");
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(5);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"123def789", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"abc456ghi");
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(5);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"abc456ghi", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"hello, world");
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(0);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"hello", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"hello, world");
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(5);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L",", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"np-complete");
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(6);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"complete", engine()->GetSelectedText().c_str());
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"(123) 456-7890");
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(0);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"(", engine()->GetSelectedText().c_str());
|
|
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(1);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"123", engine()->GetSelectedText().c_str());
|
|
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(7);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"456", engine()->GetSelectedText().c_str());
|
|
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(11);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(L"7890", engine()->GetSelectedText().c_str());
|
|
|
|
// Tests from:
|
|
// http://unicode.org/Public/UNIDATA/auxiliary/WordBreakTest.html#samples
|
|
struct bounds {
|
|
size_t start;
|
|
size_t end;
|
|
};
|
|
struct {
|
|
const wchar_t* str;
|
|
std::vector<const wchar_t*> results;
|
|
} tests[] = {
|
|
// {L"\r\na\n\u0308", {L"\r\n", L"a", L"\n", L"\u0308"}},
|
|
// {L"a\u0308", {L"a\u0308"}},
|
|
// {L" \u200d\u0646", {L" \u200d", L"\u0646"}},
|
|
// {L"\u0646\u200d ", {L"\u0646\u200d", L" "}},
|
|
{L"AAA", {L"AAA"}},
|
|
{L"A:A", {L"A:A"}},
|
|
{L"A::A", {L"A", L":", L":", L"A"}},
|
|
// {L"\u05d0'", {L"\u05d0'"}},
|
|
// {L"\u05d0\"\u05d0", {L"\u05d0\"\u05d0"}},
|
|
{L"A00A", {L"A00A"}},
|
|
{L"0,0", {L"0,0"}},
|
|
{L"0,,0", {L"0", L",", L",", L"0"}},
|
|
{L"\u3031\u3031", {L"\u3031\u3031"}},
|
|
{L"A_0_\u3031_", {L"A_0_\u3031_"}},
|
|
{L"A__A", {L"A__A"}},
|
|
// {L"\u200d\u2640", {L"\u200d\u2640"}},
|
|
// {L"a\u0308\u200b\u0308b", {L"a\u0308\u200b\u0308b"}},
|
|
};
|
|
|
|
for (auto t : tests) {
|
|
engine()->Clear();
|
|
engine()->Insert(0, t.str);
|
|
|
|
size_t idx = 0;
|
|
for (const auto* res : t.results) {
|
|
std::tie(start_idx, count) = engine()->BoundsForWordAt(idx);
|
|
engine()->SetSelection(start_idx, count);
|
|
EXPECT_STREQ(res, engine()->GetSelectedText().c_str())
|
|
<< "Input: '" << t.str << "'";
|
|
idx += count;
|
|
}
|
|
}
|
|
}
|
|
|
|
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(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_FALSE(FX_IsOdd(engine()->GetCharacterInfo(3).first));
|
|
EXPECT_EQ(2U, engine()->GetIndexLeft(3));
|
|
EXPECT_EQ(4U, engine()->GetIndexRight(3));
|
|
EXPECT_TRUE(FX_IsOdd(engine()->GetCharacterInfo(15).first));
|
|
EXPECT_EQ(14U, engine()->GetIndexLeft(15));
|
|
EXPECT_EQ(16U, engine()->GetIndexRight(15));
|
|
EXPECT_FALSE(FX_IsOdd(engine()->GetCharacterInfo(23).first));
|
|
EXPECT_EQ(22U, engine()->GetIndexLeft(23));
|
|
EXPECT_EQ(24U, engine()->GetIndexRight(23));
|
|
|
|
engine()->Clear();
|
|
engine()->Insert(0, L"Hello\r\nWorld\r\nTest");
|
|
// Move to end of Hello from start of World.
|
|
engine()->SetSelection(engine()->GetIndexLeft(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()->GetIndexLeft(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()->GetIndexLeft(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());
|
|
}
|