blob: fbf9a5d712bebc07cc2eddb4a229bbfb78878633 [file]
// Copyright 2026 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/fpdfdoc/cpvt_variabletext.h"
#include <memory>
#include "core/fpdfapi/font/cpdf_font.h"
#include "core/fpdfapi/page/cpdf_pagemodule.h"
#include "core/fpdfapi/parser/cpdf_test_document.h"
#include "core/fpdfdoc/cpvt_fontmap.h"
#include "core/fpdfdoc/cpvt_word.h"
#include "core/fxcrt/retain_ptr.h"
#include "testing/gtest/include/gtest/gtest.h"
// A stub that provides fixed font metrics (like a character width of 10)
// for testing text layout.
class StubProvider : public CPVT_VariableText::Provider {
public:
explicit StubProvider(IPVT_FontMap* font_map)
: CPVT_VariableText::Provider(font_map) {}
~StubProvider() override = default;
// CPVT_VariableText::Provider:
int GetCharWidth(int32_t nFontIndex, uint16_t word) override { return 10; }
int32_t GetTypeAscent(int32_t nFontIndex) override { return 10; }
int32_t GetTypeDescent(int32_t nFontIndex) override { return -2; }
int32_t GetWordFontIndex(uint16_t word,
FX_Charset charset,
int32_t nFontIndex) override {
return 0;
}
int32_t GetDefaultFontIndex() override { return 0; }
};
class CPVT_VariableTextTest : public testing::Test {
public:
void SetUp() override {
pdfium::InitializePageModule();
test_doc_ = std::make_unique<CPDF_TestDocument>();
font_ = CPDF_Font::GetStockFont(test_doc_.get(), "Helvetica");
font_map_ = std::make_unique<CPVT_FontMap>(test_doc_.get(), nullptr, font_,
"Helvetica");
provider_ = std::make_unique<StubProvider>(font_map_.get());
}
void TearDown() override {
provider_.reset();
font_map_.reset();
font_.Reset();
test_doc_.reset();
pdfium::DestroyPageModule();
}
protected:
std::unique_ptr<CPDF_TestDocument> test_doc_;
RetainPtr<CPDF_Font> font_;
std::unique_ptr<CPVT_FontMap> font_map_;
std::unique_ptr<StubProvider> provider_;
};
TEST_F(CPVT_VariableTextTest, LTRTextLayout) {
CPVT_VariableText vt(provider_.get());
vt.SetPlateRect(CFX_FloatRect(0, 0, 100, 100));
vt.SetFontSize(10.0f);
vt.SetMultiLine(false);
vt.SetAutoReturn(false);
vt.Initialize();
// "hello" in English (Left-to-Right layout)
vt.SetText(L"hello");
vt.RearrangeAll();
CPVT_VariableText::Iterator* it = vt.GetIterator();
it->SetAt(1); // Set to first word index (Place(0, 0, 0))
CPVT_Word word;
ASSERT_TRUE(it->GetWord(word));
EXPECT_EQ('h', word.Word);
float first_x = word.ptWord.x;
ASSERT_TRUE(it->NextWord());
ASSERT_TRUE(it->GetWord(word));
EXPECT_EQ('e', word.Word);
float second_x = word.ptWord.x;
ASSERT_TRUE(it->NextWord());
ASSERT_TRUE(it->GetWord(word));
EXPECT_EQ('l', word.Word);
float third_x = word.ptWord.x;
ASSERT_TRUE(it->NextWord());
ASSERT_TRUE(it->GetWord(word));
EXPECT_EQ('l', word.Word);
float fourth_x = word.ptWord.x;
ASSERT_TRUE(it->NextWord());
ASSERT_TRUE(it->GetWord(word));
EXPECT_EQ('o', word.Word);
float fifth_x = word.ptWord.x;
EXPECT_FALSE(it->NextWord());
// In LTR, coordinates go left-to-right (0, 10, 20, 30, 40).
EXPECT_LT(first_x, second_x);
EXPECT_LT(second_x, third_x);
EXPECT_LT(third_x, fourth_x);
EXPECT_LT(fourth_x, fifth_x);
}
TEST_F(CPVT_VariableTextTest, RTLTextLayout) {
CPVT_VariableText vt(provider_.get());
vt.SetPlateRect(CFX_FloatRect(0, 0, 100, 100));
vt.SetFontSize(10.0f);
vt.SetMultiLine(false);
vt.SetAutoReturn(false);
vt.Initialize();
// Shalom in Hebrew (U+05E9, U+05DC, U+05D5, U+05DD)
// Logically ordered but should be displayed from right to left.
vt.SetText(L"\x05E9\x05DC\x05D5\x05DD");
vt.RearrangeAll();
CPVT_VariableText::Iterator* it = vt.GetIterator();
it->SetAt(1); // Set to first word index (Place(0, 0, 0))
CPVT_Word word;
ASSERT_TRUE(it->GetWord(word));
EXPECT_EQ(0x05E9, word.Word);
float first_x = word.ptWord.x;
ASSERT_TRUE(it->NextWord());
ASSERT_TRUE(it->GetWord(word));
EXPECT_EQ(0x05DC, word.Word);
float second_x = word.ptWord.x;
ASSERT_TRUE(it->NextWord());
ASSERT_TRUE(it->GetWord(word));
EXPECT_EQ(0x05D5, word.Word);
float third_x = word.ptWord.x;
ASSERT_TRUE(it->NextWord());
ASSERT_TRUE(it->GetWord(word));
EXPECT_EQ(0x05DD, word.Word);
float fourth_x = word.ptWord.x;
EXPECT_FALSE(it->NextWord());
// TODO(crbug.com/40115028): This should be in RTL order. Currently, it falls
// back to LTR, so coordinates increase from left to right. Once RTL is
// supported, the first logical character should have the largest X
// coordinate, and this test case should use EXPECT_GT instead.
EXPECT_LT(first_x, second_x);
EXPECT_LT(second_x, third_x);
EXPECT_LT(third_x, fourth_x);
}