blob: 7103b8ff8601af638143d600e38521bcf8f4a2c9 [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 "build/build_config.h"
#include "core/fxcrt/fx_coordinates.h"
#include "core/fxcrt/fx_coordinates_test_support.h"
#include "core/fxcrt/retain_ptr.h"
#include "core/fxge/cfx_fillrenderoptions.h"
#include "core/fxge/cfx_graphstatedata.h"
#include "core/fxge/cfx_path.h"
#include "core/fxge/cfx_renderdevice.h"
#include "core/fxge/dib/cfx_dibitmap.h"
#include "core/fxge/dib/fx_dib.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#include <memory>
#include <type_traits>
#include "core/fxge/cfx_gemodule.h"
#include "core/fxge/win32/cfx_psfonttracker.h"
#endif
TEST(CFXRenderDeviceTest, GetClipBoxDefault) {
std::unique_ptr<CFX_RenderDevice> device =
CFX_RenderDevice::CreateForNewBitmap(16, 16, FXDIB_Format::kBgra);
ASSERT_TRUE(device);
EXPECT_EQ(FX_RECT(0, 0, 16, 16), device->GetClipBox());
}
TEST(CFXRenderDeviceTest, GetClipBoxPathFill) {
// Matrix that transposes and translates by 1 unit on each axis.
const CFX_Matrix object_to_device(0, 1, 1, 0, 1, -1);
// Fill type cannot be none.
const CFX_FillRenderOptions fill_options(
CFX_FillRenderOptions::FillType::kEvenOdd);
std::unique_ptr<CFX_RenderDevice> device =
CFX_RenderDevice::CreateForNewBitmap(16, 16, FXDIB_Format::kBgra);
ASSERT_TRUE(device);
CFX_Path path;
path.AppendRect(2, 4, 14, 12);
EXPECT_TRUE(device->SetClip_PathFill(path, &object_to_device, fill_options));
EXPECT_EQ(FX_RECT(5, 1, 13, 13), device->GetClipBox());
}
TEST(CFXRenderDeviceTest, GetClipBoxPathStroke) {
// Matrix that transposes and translates by 1 unit on each axis.
const CFX_Matrix object_to_device(0, 1, 1, 0, 1, -1);
// Default line width is 1.
const CFX_GraphStateData graphics_state;
std::unique_ptr<CFX_RenderDevice> device =
CFX_RenderDevice::CreateForNewBitmap(16, 16, FXDIB_Format::kBgra);
ASSERT_TRUE(device);
CFX_Path path;
path.AppendRect(2, 4, 14, 12);
EXPECT_TRUE(
device->SetClip_PathStroke(path, &object_to_device, &graphics_state));
EXPECT_EQ(FX_RECT(4, 0, 14, 14), device->GetClipBox());
}
TEST(CFXRenderDeviceTest, GetClipBoxRect) {
std::unique_ptr<CFX_RenderDevice> device =
CFX_RenderDevice::CreateForNewBitmap(16, 16, FXDIB_Format::kBgra);
ASSERT_TRUE(device);
EXPECT_TRUE(device->SetClip_Rect({2, 4, 14, 12}));
EXPECT_EQ(FX_RECT(2, 4, 14, 12), device->GetClipBox());
}
TEST(CFXRenderDeviceTest, GetClipBoxEmpty) {
std::unique_ptr<CFX_RenderDevice> device =
CFX_RenderDevice::CreateForNewBitmap(16, 16, FXDIB_Format::kBgra);
ASSERT_TRUE(device);
EXPECT_TRUE(device->SetClip_Rect({2, 8, 14, 8}));
EXPECT_TRUE(device->GetClipBox().IsEmpty());
}
#if BUILDFLAG(IS_WIN)
namespace {
constexpr CFX_Matrix kIdentityMatrix;
class ScopedPrintMode {
public:
explicit ScopedPrintMode(WindowsPrintMode mode)
: old_mode_(CFX_GEModule::GetPrintMode()) {
CFX_GEModule::SetPrintMode(mode);
}
~ScopedPrintMode() { CFX_GEModule::SetPrintMode(old_mode_); }
private:
const WindowsPrintMode old_mode_;
};
struct EnhMetaFileDCDeleter {
void operator()(HDC dc) const { ::DeleteEnhMetaFile(::CloseEnhMetaFile(dc)); }
};
using ScopedEnhMetaFileDC =
std::unique_ptr<std::remove_pointer_t<HDC>, EnhMetaFileDCDeleter>;
struct GdiObjectDeleter {
void operator()(HGDIOBJ object) const { ::DeleteObject(object); }
};
template <typename T>
using ScopedGdiObject =
std::unique_ptr<std::remove_pointer_t<T>, GdiObjectDeleter>;
} // namespace
class CFXWindowsRenderDeviceTest : public testing::Test {
public:
void SetUp() override {
testing::Test::SetUp();
// Get a device context with Windows GDI.
dc_handle_ = CreateCompatibleDC(nullptr);
ASSERT_TRUE(dc_handle_);
device_ =
CFX_RenderDevice::CreateForWindowsDC(dc_handle_, &psfont_tracker_);
ASSERT_TRUE(device_);
device_->SaveState();
}
void TearDown() override {
device_->RestoreState(false);
device_.reset();
DeleteDC(dc_handle_);
testing::Test::TearDown();
}
protected:
HDC dc_handle_;
CFX_PSFontTracker psfont_tracker_;
std::unique_ptr<CFX_RenderDevice> device_;
};
TEST_F(CFXWindowsRenderDeviceTest, SimpleClipTriangle) {
CFX_Path path_data;
CFX_PointF p1(0.0f, 0.0f);
CFX_PointF p2(0.0f, 100.0f);
CFX_PointF p3(100.0f, 100.0f);
path_data.AppendLine(p1, p2);
path_data.AppendLine(p2, p3);
path_data.AppendLine(p3, p1);
path_data.ClosePath();
EXPECT_TRUE(device_->SetClip_PathFill(
path_data, &kIdentityMatrix, CFX_FillRenderOptions::WindingOptions()));
}
TEST_F(CFXWindowsRenderDeviceTest, SimpleClipRect) {
CFX_Path path_data;
path_data.AppendRect(0.0f, 100.0f, 200.0f, 0.0f);
path_data.ClosePath();
EXPECT_TRUE(device_->SetClip_PathFill(
path_data, &kIdentityMatrix, CFX_FillRenderOptions::WindingOptions()));
}
TEST_F(CFXWindowsRenderDeviceTest, GargantuanClipRect) {
CFX_Path path_data;
path_data.AppendRect(-257698020.0f, -257697252.0f, 257698044.0f,
257698812.0f);
path_data.ClosePath();
// These coordinates for a clip path are valid, just very large. Using these
// for a clip path should allow IntersectClipRect() to return success;
// however they do not because the GDI API IntersectClipRect() errors out and
// affect subsequent imaging. crbug.com/1019026
EXPECT_FALSE(device_->SetClip_PathFill(
path_data, &kIdentityMatrix, CFX_FillRenderOptions::WindingOptions()));
}
TEST_F(CFXWindowsRenderDeviceTest, GargantuanClipRectWithBaseClip) {
CFX_Path path_data;
const FX_RECT kBaseClip(0, 0, 5100, 6600);
device_->SetBaseClip(kBaseClip);
path_data.AppendRect(-257698020.0f, -257697252.0f, 257698044.0f,
257698812.0f);
path_data.ClosePath();
// Use of a reasonable base clip ensures that we avoid getting an error back
// from GDI API IntersectClipRect().
EXPECT_TRUE(device_->SetClip_PathFill(
path_data, &kIdentityMatrix, CFX_FillRenderOptions::WindingOptions()));
}
TEST(CFXRenderDeviceTest, WindowsEmfPostScriptClipBox) {
ScopedPrintMode scoped_print_mode(WindowsPrintMode::kPostScript2);
ScopedEnhMetaFileDC dc_handle(
::CreateEnhMetaFile(nullptr, nullptr, nullptr, nullptr));
ASSERT_TRUE(dc_handle);
const int horz_res = ::GetDeviceCaps(dc_handle.get(), HORZRES);
const int vert_res = ::GetDeviceCaps(dc_handle.get(), VERTRES);
const FX_RECT large_clip_rect(0, 0, horz_res * 2, vert_res * 2);
CFX_PSFontTracker font_tracker;
std::unique_ptr<CFX_RenderDevice> device =
CFX_RenderDevice::CreateForWindowsDC(dc_handle.get(), &font_tracker);
ASSERT_TRUE(device);
EXPECT_TRUE(device->SetClip_Rect(large_clip_rect));
// TODO(crbug.com/553140224): Clip box should be `large_clip_rect`.
EXPECT_EQ(FX_RECT(0, 2 * vert_res, 2 * horz_res, 0), device->GetClipBox());
}
TEST(CFXRenderDeviceTest, WindowsEmfPostScriptClipRgn) {
ScopedPrintMode scoped_print_mode(WindowsPrintMode::kPostScript2);
ScopedEnhMetaFileDC dc_handle(
::CreateEnhMetaFile(nullptr, nullptr, nullptr, nullptr));
ASSERT_TRUE(dc_handle);
const int horz_res = ::GetDeviceCaps(dc_handle.get(), HORZRES);
const int vert_res = ::GetDeviceCaps(dc_handle.get(), VERTRES);
const FX_RECT large_clip_rect(0, 0, horz_res * 2, vert_res * 2);
ScopedGdiObject<HRGN> clip_rgn(
::CreateRectRgn(large_clip_rect.left, large_clip_rect.top,
large_clip_rect.right, large_clip_rect.bottom));
ASSERT_TRUE(clip_rgn);
EXPECT_NE(ERROR, ::SelectClipRgn(dc_handle.get(), clip_rgn.get()));
CFX_PSFontTracker font_tracker;
std::unique_ptr<CFX_RenderDevice> device =
CFX_RenderDevice::CreateForWindowsDC(dc_handle.get(), &font_tracker);
ASSERT_TRUE(device);
// TODO(crbug.com/553140224): Clip box should be `large_clip_rect`.
EXPECT_EQ(FX_RECT(0, 2 * vert_res, 2 * horz_res, 0), device->GetClipBox());
}
#endif // BUILDFLAG(IS_WIN)