pdfium/testing/embedder_test.cpp
Rulong Chen(陈汝龙) 65db368888 Make embedder test delegates fixture-owned
Embedder tests often install stack-local delegates, but EmbedderTest can
invoke delegate callbacks while closing the document in TearDown(). By
then, a stack delegate may already have been destroyed.

Add SetOwnedDelegate() so the fixture owns the custom delegate through
document teardown, then restores the default delegate before releasing
it. Migrate all existing call sites, remove the raw SetDelegate() API,
and store the active delegate as an UnownedPtr.

Bug: 535697671
Change-Id: I8e8da028e9c563e82d40099637c63978122077eb
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/152110
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
2026-07-24 12:15:07 -07:00

1348 lines
46 KiB
C++

// Copyright 2015 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "testing/embedder_test.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "core/fdrm/fx_crypt.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/check_op.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/containers/contains.h"
#include "core/fxcrt/fx_memcpy_wrappers.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/notreached.h"
#include "core/fxcrt/numerics/checked_math.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/span_util.h"
#include "core/fxcrt/zip.h"
#include "core/fxge/cfx_gemodule.h"
#include "core/fxge/dib/fx_dib.h"
#include "fpdfsdk/cpdfsdk_helpers.h"
#include "public/cpp/fpdf_scopers.h"
#include "public/fpdf_dataavail.h"
#include "public/fpdf_edit.h"
#include "public/fpdf_text.h"
#include "public/fpdfview.h"
#include "testing/embedder_test_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/image_diff/image_diff_png.h"
#include "testing/test_loader.h"
#include "testing/utils/bitmap_saver.h"
#include "testing/utils/file_util.h"
#include "testing/utils/hash.h"
#include "testing/utils/path_service.h"
#include "testing/utils/pixel_diff_util.h"
#include "testing/utils/png_encode.h"
#include "third_party/simdutf/simdutf.h"
namespace {
int GetBitmapBytesPerPixel(FPDF_BITMAP bitmap) {
return EmbedderTest::BytesPerPixelForFormat(FPDFBitmap_GetFormat(bitmap));
}
#if BUILDFLAG(IS_WIN)
int CALLBACK GetRecordProc(HDC hdc,
HANDLETABLE* handle_table,
const ENHMETARECORD* record,
int objects_count,
LPARAM param) {
auto& records = *reinterpret_cast<std::vector<const ENHMETARECORD*>*>(param);
records.push_back(record);
return 1;
}
#endif // BUILDFLAG(IS_WIN)
// These "jump" into the delegate to do actual testing.
void UnsupportedHandlerTrampoline(UNSUPPORT_INFO* info, int type) {
auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
delegate->UnsupportedHandler(type);
}
int AlertTrampoline(IPDF_JSPLATFORM* platform,
FPDF_WIDESTRING message,
FPDF_WIDESTRING title,
int type,
int icon) {
auto* delegate = static_cast<EmbedderTest*>(platform)->GetDelegate();
return delegate->Alert(message, title, type, icon);
}
int SetTimerTrampoline(FPDF_FORMFILLINFO* info, int msecs, TimerCallback fn) {
auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
return delegate->SetTimer(msecs, fn);
}
void KillTimerTrampoline(FPDF_FORMFILLINFO* info, int id) {
auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
return delegate->KillTimer(id);
}
FPDF_PAGE GetPageTrampoline(FPDF_FORMFILLINFO* info,
FPDF_DOCUMENT document,
int page_index) {
auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
return delegate->GetPage(info, document, page_index);
}
void DoURIActionTrampoline(FPDF_FORMFILLINFO* info, FPDF_BYTESTRING uri) {
auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
return delegate->DoURIAction(uri);
}
void DoGoToActionTrampoline(FPDF_FORMFILLINFO* info,
int page_index,
int zoom_mode,
float* pos_array,
int array_size) {
auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
return delegate->DoGoToAction(info, page_index, zoom_mode, pos_array,
array_size);
}
void OnFocusChangeTrampoline(FPDF_FORMFILLINFO* info,
FPDF_ANNOTATION annot,
int page_index) {
auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
return delegate->OnFocusChange(info, annot, page_index);
}
void DoURIActionWithKeyboardModifierTrampoline(FPDF_FORMFILLINFO* info,
FPDF_BYTESTRING uri,
int modifiers) {
auto* delegate = static_cast<EmbedderTest*>(info)->GetDelegate();
return delegate->DoURIActionWithKeyboardModifier(info, uri, modifiers);
}
// These do nothing (but must return a reasonable default value).
void InvalidateStub(FPDF_FORMFILLINFO* pThis,
FPDF_PAGE page,
double left,
double top,
double right,
double bottom) {}
void OutputSelectedRectStub(FPDF_FORMFILLINFO* pThis,
FPDF_PAGE page,
double left,
double top,
double right,
double bottom) {}
void SetCursorStub(FPDF_FORMFILLINFO* pThis, int nCursorType) {}
FPDF_SYSTEMTIME GetLocalTimeStub(FPDF_FORMFILLINFO* pThis) {
return {122, 11, 6, 28, 12, 59, 59, 500};
}
void OnChangeStub(FPDF_FORMFILLINFO* pThis) {}
FPDF_PAGE GetCurrentPageStub(FPDF_FORMFILLINFO* pThis, FPDF_DOCUMENT document) {
return GetPageTrampoline(pThis, document, 0);
}
int GetRotationStub(FPDF_FORMFILLINFO* pThis, FPDF_PAGE page) {
return 0;
}
void ExecuteNamedActionStub(FPDF_FORMFILLINFO* pThis, FPDF_BYTESTRING name) {}
void SetTextFieldFocusStub(FPDF_FORMFILLINFO* pThis,
FPDF_WIDESTRING value,
FPDF_DWORD valueLen,
FPDF_BOOL is_focus) {}
#ifdef PDF_ENABLE_XFA
void DisplayCaretStub(FPDF_FORMFILLINFO* pThis,
FPDF_PAGE page,
FPDF_BOOL bVisible,
double left,
double top,
double right,
double bottom) {}
int GetCurrentPageIndexStub(FPDF_FORMFILLINFO* pThis, FPDF_DOCUMENT document) {
return 0;
}
void SetCurrentPageStub(FPDF_FORMFILLINFO* pThis,
FPDF_DOCUMENT document,
int iCurPage) {}
void GotoURLStub(FPDF_FORMFILLINFO* pThis,
FPDF_DOCUMENT document,
FPDF_WIDESTRING wsURL) {}
void GetPageViewRectStub(FPDF_FORMFILLINFO* pThis,
FPDF_PAGE page,
double* left,
double* top,
double* right,
double* bottom) {
*left = 0.0;
*top = 0.0;
*right = 512.0;
*bottom = 512.0;
}
void PageEventStub(FPDF_FORMFILLINFO* pThis,
int page_count,
FPDF_DWORD event_type) {}
FPDF_BOOL PopupMenuStub(FPDF_FORMFILLINFO* pThis,
FPDF_PAGE page,
FPDF_WIDGET hWidget,
int menuFlag,
float x,
float y) {
return true;
}
FPDF_FILEHANDLER* OpenFileStub(FPDF_FORMFILLINFO* pThis,
int fileFlag,
FPDF_WIDESTRING wsURL,
const char* mode) {
return nullptr;
}
void EmailToStub(FPDF_FORMFILLINFO* pThis,
FPDF_FILEHANDLER* fileHandler,
FPDF_WIDESTRING pTo,
FPDF_WIDESTRING pSubject,
FPDF_WIDESTRING pCC,
FPDF_WIDESTRING pBcc,
FPDF_WIDESTRING pMsg) {}
void UploadToStub(FPDF_FORMFILLINFO* pThis,
FPDF_FILEHANDLER* fileHandler,
int fileFlag,
FPDF_WIDESTRING uploadTo) {}
int GetPlatformStub(FPDF_FORMFILLINFO* pThis, void* platform, int length) {
return 0;
}
int GetLanguageStub(FPDF_FORMFILLINFO* pThis, void* language, int length) {
return 0;
}
FPDF_FILEHANDLER* DownloadFromURLStub(FPDF_FORMFILLINFO* pThis,
FPDF_WIDESTRING URL) {
static const char kString[] = "<body>secrets</body>";
static FPDF_FILEHANDLER kFakeFileHandler = {
nullptr,
[](void*) -> void {},
[](void*) -> FPDF_DWORD { return sizeof(kString) - 1; },
[](void*, FPDF_DWORD off, void* buffer, FPDF_DWORD size) -> FPDF_RESULT {
UNSAFE_TODO(FXSYS_memcpy(buffer, kString,
std::min<size_t>(size, sizeof(kString) - 1)));
return 0;
},
[](void*, FPDF_DWORD, const void*, FPDF_DWORD) -> FPDF_RESULT {
return -1;
},
[](void*) -> FPDF_RESULT { return 0; },
[](void*, FPDF_DWORD) -> FPDF_RESULT { return 0; }};
return &kFakeFileHandler;
}
FPDF_BOOL PostRequestURLStub(FPDF_FORMFILLINFO* pThis,
FPDF_WIDESTRING wsURL,
FPDF_WIDESTRING wsData,
FPDF_WIDESTRING wsContentType,
FPDF_WIDESTRING wsEncode,
FPDF_WIDESTRING wsHeader,
FPDF_BSTR* response) {
const char kString[] = "p\0o\0s\0t\0e\0d\0";
FPDF_BStr_Set(response, kString, sizeof(kString) - 1);
return true;
}
FPDF_BOOL PutRequestURLStub(FPDF_FORMFILLINFO* pThis,
FPDF_WIDESTRING wsURL,
FPDF_WIDESTRING wsData,
FPDF_WIDESTRING wsEncode) {
return true;
}
#endif // PDF_ENABLE_XFA
std::string_view GetPlatformNameSuffix() {
#if BUILDFLAG(IS_WIN)
return "_win";
#elif BUILDFLAG(IS_APPLE)
return "_mac";
#else
return "_linux";
#endif
}
std::string_view GetCpuArchSuffix() {
#if BUILDFLAG(IS_APPLE) && !defined(ARCH_CPU_ARM64)
return "_x86";
#else
return "";
#endif // BUILDFLAG(IS_APPLE) && !defined(ARCH_CPU_ARM64)
}
int GetPlatformMaxPixelDelta() {
#if BUILDFLAG(IS_APPLE)
return 1;
#else
return 0;
#endif // BUILDFLAG(IS_APPLE)
}
std::string GetEmbedderTestExpectationPath(
std::string_view expectation_png_name) {
std::string path = PathService::GetTestFilePath("embedder_tests");
if (path.empty()) {
return std::string();
}
path.push_back(PATH_SEPARATOR);
path.append(expectation_png_name);
path.append(".png");
return path;
}
std::vector<std::string> GetEmbedderTestExpectationsWithSuffixPath(
std::string_view expectation_png_name) {
const std::string basename(expectation_png_name);
const std::string platform_suffix(GetPlatformNameSuffix());
const std::string cpu_arch_suffix(GetCpuArchSuffix());
const bool has_cpu_arch_suffix = !cpu_arch_suffix.empty();
std::string renderer = "_agg";
#if defined(PDF_USE_SKIA)
if (CFX_GEModule::Get()->UseSkiaRenderer()) {
renderer = "_skia";
}
#endif
std::vector<std::string> expectation_names;
expectation_names.reserve(has_cpu_arch_suffix ? 6 : 4);
if (has_cpu_arch_suffix) {
expectation_names.push_back(basename + renderer + platform_suffix +
cpu_arch_suffix);
}
expectation_names.push_back(basename + renderer + platform_suffix);
expectation_names.push_back(basename + renderer);
if (has_cpu_arch_suffix) {
expectation_names.push_back(basename + platform_suffix + cpu_arch_suffix);
}
expectation_names.push_back(basename + platform_suffix);
expectation_names.push_back(basename);
std::vector<std::string> results;
for (const auto& name : expectation_names) {
results.push_back(GetEmbedderTestExpectationPath(name));
if (results.back().empty()) {
return {};
}
}
return results;
}
struct DecodedPng {
int width = -1;
int height = -1;
std::vector<uint8_t> pixel_data; // BGRA
};
DecodedPng DecodePngData(pdfium::span<const uint8_t> png_data) {
DecodedPng results;
int width = -1;
int height = -1;
std::vector<uint8_t> decoded_png = image_diff_png::DecodePNG(
png_data, /*reverse_byte_order=*/true, &width, &height);
if (width > 0 && height > 0 && !decoded_png.empty()) {
results.width = width;
results.height = height;
results.pixel_data = std::move(decoded_png);
}
return results;
}
int CompareBGRxBitmapToPng(pdfium::span<const uint8_t> bitmap_span,
size_t bitmap_stride,
const DecodedPng& decoded_png,
int max_pixel_per_channel_delta) {
const size_t unsigned_width = static_cast<size_t>(decoded_png.width);
auto decoded_png_span32 = fxcrt::reinterpret_span<const uint32_t>(
pdfium::span(decoded_png.pixel_data));
int pixels_different = 0;
for (int h = 0; h < decoded_png.height; ++h) {
auto decoded_png_row = decoded_png_span32.first(unsigned_width);
decoded_png_span32 = decoded_png_span32.subspan(unsigned_width);
auto bitmap_row = fxcrt::reinterpret_span<const uint32_t>(
bitmap_span.first(bitmap_stride));
bitmap_span = bitmap_span.subspan(bitmap_stride);
for (int w = 0; w < decoded_png.width; ++w) {
uint32_t png_pixel = decoded_png_row[w];
uint32_t bitmap_pixel = bitmap_row[w];
if (png_pixel == bitmap_pixel) {
continue;
}
if (max_pixel_per_channel_delta == 0 ||
MaxPixelPerChannelDelta(png_pixel, bitmap_pixel) >
max_pixel_per_channel_delta) {
++pixels_different;
}
}
}
return pixels_different;
}
int CompareGrayBitmapToPng(pdfium::span<const uint8_t> bitmap_span,
size_t bitmap_stride,
const DecodedPng& decoded_png,
int max_pixel_per_channel_delta) {
const int width = decoded_png.width;
const size_t dest_row_width = static_cast<size_t>(width);
const int height = decoded_png.height;
const size_t bgrx_stride = width * sizeof(FX_BGRA_STRUCT<uint8_t>);
std::vector<uint8_t> bgrx_buffer(bgrx_stride * height);
auto bgrx_span = fxcrt::reinterpret_span<FX_BGRA_STRUCT<uint8_t>>(
pdfium::span<uint8_t>(bgrx_buffer));
for (int h = 0; h < height; ++h) {
auto src_row = bitmap_span.subspan(h * bitmap_stride, bitmap_stride);
auto dest_row = bgrx_span.take_first(dest_row_width);
for (auto [src_pixel, dest_pixel] : fxcrt::Zip(src_row, dest_row)) {
dest_pixel.blue = src_pixel;
dest_pixel.green = src_pixel;
dest_pixel.red = src_pixel;
dest_pixel.alpha = 255;
}
}
return CompareBGRxBitmapToPng(bgrx_buffer, bgrx_stride, decoded_png,
max_pixel_per_channel_delta);
}
#ifdef PDF_USE_SKIA
int CompareBGRxPremultBitmapToPng(pdfium::span<const uint8_t> bitmap_span,
size_t bitmap_stride,
const DecodedPng& decoded_png,
int max_pixel_per_channel_delta) {
std::vector<uint8_t> bitmap_data(bitmap_span.begin(), bitmap_span.end());
pdfium::span<uint8_t> converted_bitmap_span{bitmap_data};
for (int h = 0; h < decoded_png.height; ++h) {
auto bitmap_row = fxcrt::reinterpret_span<FX_BGRA_STRUCT<uint8_t>>(
converted_bitmap_span.first(bitmap_stride));
converted_bitmap_span = converted_bitmap_span.subspan(bitmap_stride);
for (int w = 0; w < decoded_png.width; ++w) {
bitmap_row[w] = UnPreMultiplyColor(bitmap_row[w]);
}
}
return CompareBGRxBitmapToPng(bitmap_span, bitmap_stride, decoded_png,
max_pixel_per_channel_delta);
}
#endif // PDF_USE_SKIA
int CompareBGRBitmapToPng(pdfium::span<const uint8_t> bitmap_span,
size_t bitmap_stride,
const DecodedPng& decoded_png,
int max_pixel_per_channel_delta) {
const int width = decoded_png.width;
const size_t dest_row_width = static_cast<size_t>(width);
const int height = decoded_png.height;
const size_t bgrx_stride = width * sizeof(FX_BGRA_STRUCT<uint8_t>);
std::vector<uint8_t> bgrx_buffer(bgrx_stride * height);
auto bgrx_span = fxcrt::reinterpret_span<FX_BGRA_STRUCT<uint8_t>>(
pdfium::span<uint8_t>(bgrx_buffer));
for (int h = 0; h < height; ++h) {
auto src_row = fxcrt::reinterpret_span<const FX_BGR_STRUCT<uint8_t>>(
bitmap_span.subspan(h * bitmap_stride, bitmap_stride));
auto dest_row = bgrx_span.take_first(dest_row_width);
for (int w = 0; w < width; ++w) {
dest_row[w].blue = src_row[w].blue;
dest_row[w].green = src_row[w].green;
dest_row[w].red = src_row[w].red;
dest_row[w].alpha = 255;
}
}
return CompareBGRxBitmapToPng(bgrx_buffer, bgrx_stride, decoded_png,
max_pixel_per_channel_delta);
}
std::string EncodeBase64(pdfium::span<const uint8_t> png) {
std::string base64_png(simdutf::base64_length_from_binary(png.size()), '\0');
size_t base64_len = simdutf::binary_to_base64(png, base64_png);
CHECK_EQ(base64_len, base64_png.size());
return "data:image/png;base64," + base64_png;
}
std::string EncodeBase64Png(FPDF_BITMAP bitmap) {
return EncodeBase64(EncodePng(bitmap));
}
void ReportMissingExpectation(FPDF_BITMAP bitmap) {
ADD_FAILURE() << "No matching expectation file!\n"
<< "Actual pixels (open in browser):\n"
<< EncodeBase64Png(bitmap);
}
void CompareBitmapToPngFile(FPDF_BITMAP bitmap,
const std::string& png_path,
int max_pixel_per_channel_delta) {
std::vector<uint8_t> png_data = GetFileContents(png_path.c_str());
if (png_data.empty()) {
ReportMissingExpectation(bitmap);
return;
}
DecodedPng decoded_png = DecodePngData(png_data);
ASSERT_GT(decoded_png.width, 0);
ASSERT_GT(decoded_png.height, 0);
ASSERT_FALSE(decoded_png.pixel_data.empty());
const int stride = FPDFBitmap_GetStride(bitmap);
const int width = FPDFBitmap_GetWidth(bitmap);
const int height = FPDFBitmap_GetHeight(bitmap);
ASSERT_GT(stride, 0);
ASSERT_EQ(width, decoded_png.width);
ASSERT_EQ(height, decoded_png.height);
FX_SAFE_SIZE_T size = stride;
size *= height;
auto bitmap_span = UNSAFE_TODO(
pdfium::span(static_cast<const uint8_t*>(FPDFBitmap_GetBuffer(bitmap)),
size.ValueOrDie()));
int pixels_different;
switch (FPDFBitmap_GetFormat(bitmap)) {
case FPDFBitmap_Gray:
pixels_different = CompareGrayBitmapToPng(
bitmap_span, stride, decoded_png, max_pixel_per_channel_delta);
break;
case FPDFBitmap_BGR:
pixels_different = CompareBGRBitmapToPng(bitmap_span, stride, decoded_png,
max_pixel_per_channel_delta);
break;
case FPDFBitmap_BGRx:
case FPDFBitmap_BGRA: {
pixels_different = CompareBGRxBitmapToPng(
bitmap_span, stride, decoded_png, max_pixel_per_channel_delta);
break;
}
#ifdef PDF_USE_SKIA
case FPDFBitmap_BGRA_Premul:
pixels_different = CompareBGRxPremultBitmapToPng(
bitmap_span, stride, decoded_png, max_pixel_per_channel_delta);
break;
#endif // PDF_USE_SKIA
default:
// Support other formats as-needed.
NOTREACHED();
}
EXPECT_EQ(pixels_different, 0) << "Pixels do not match!\n"
<< "Actual pixels (open in browser):\n"
<< EncodeBase64Png(bitmap) << "\n"
<< "Expected pixels (open in browser):\n"
<< EncodeBase64(png_data) << "\n";
}
} // namespace
EmbedderTest::EmbedderTest()
: default_delegate_(std::make_unique<EmbedderTest::Delegate>()),
delegate_(default_delegate_.get()) {
FPDF_FILEWRITE::version = 1;
FPDF_FILEWRITE::WriteBlock = WriteBlockCallback;
}
EmbedderTest::~EmbedderTest() = default;
void EmbedderTest::SetUp() {
UNSUPPORT_INFO* info = static_cast<UNSUPPORT_INFO*>(this);
*info = {
.version = 1,
.FSDK_UnSupport_Handler = UnsupportedHandlerTrampoline,
};
FSDK_SetUnSpObjProcessHandler(info);
}
void EmbedderTest::TearDown() {
// Use an EXPECT_EQ() here and continue to let TearDown() finish as cleanly as
// possible. This can fail when an DCHECK test fails in a test case.
EXPECT_EQ(0U, page_map_.size());
EXPECT_EQ(0U, saved_page_map_.size());
// Document teardown can invoke delegate callbacks, so keep the current
// delegate installed until CloseDocument() completes. Point `delegate_` back
// to the default delegate before destroying the owned delegate.
if (document()) {
CloseDocument();
}
delegate_ = default_delegate_.get();
owned_delegate_.reset();
}
void EmbedderTest::CreateEmptyDocument() {
CreateEmptyDocumentWithoutFormFillEnvironment();
form_handle_.reset(SetupFormFillEnvironment(
document(), JavaScriptOption::kEnableJavaScript));
}
void EmbedderTest::CreateEmptyDocumentWithoutFormFillEnvironment() {
document_.reset(FPDF_CreateNewDocument());
CHECK(document_);
}
bool EmbedderTest::OpenDocument(const std::string& filename) {
return OpenDocumentWithOptions(filename, "",
LinearizeOption::kDefaultLinearize,
JavaScriptOption::kEnableJavaScript);
}
bool EmbedderTest::OpenDocumentLinearized(const std::string& filename) {
return OpenDocumentWithOptions(filename, "", LinearizeOption::kMustLinearize,
JavaScriptOption::kEnableJavaScript);
}
bool EmbedderTest::OpenDocumentWithPassword(const std::string& filename,
const ByteString& password) {
return OpenDocumentWithOptions(filename, password,
LinearizeOption::kDefaultLinearize,
JavaScriptOption::kEnableJavaScript);
}
bool EmbedderTest::OpenDocumentWithoutJavaScript(const std::string& filename) {
return OpenDocumentWithOptions(filename, "",
LinearizeOption::kDefaultLinearize,
JavaScriptOption::kDisableJavaScript);
}
bool EmbedderTest::OpenDocumentWithOptions(const std::string& filename,
const ByteString& password,
LinearizeOption linearize_option,
JavaScriptOption javascript_option) {
std::string file_path = PathService::GetTestFilePath(filename);
if (file_path.empty()) {
return false;
}
file_contents_ = GetFileContents(file_path.c_str());
if (file_contents_.empty()) {
return false;
}
EXPECT_TRUE(!loader_);
loader_ = std::make_unique<TestLoader>(file_contents_);
file_access_ = {
.m_FileLen = pdfium::checked_cast<unsigned long>(file_contents_.size()),
.m_GetBlock = TestLoader::GetBlock,
.m_Param = loader_.get(),
};
fake_file_access_ = std::make_unique<FakeFileAccess>(&file_access_);
return OpenDocumentHelper(password, linearize_option, javascript_option,
fake_file_access_.get(), &document_, &avail_,
&form_handle_);
}
bool EmbedderTest::OpenDocumentHelper(const ByteString& password,
LinearizeOption linearize_option,
JavaScriptOption javascript_option,
FakeFileAccess* network_simulator,
ScopedFPDFDocument* document,
ScopedFPDFAvail* avail,
ScopedFPDFFormHandle* form_handle) {
network_simulator->AddSegment(0, 1024);
network_simulator->SetRequestedDataAvailable();
avail->reset(FPDFAvail_Create(network_simulator->GetFileAvail(),
network_simulator->GetFileAccess()));
FPDF_AVAIL avail_ptr = avail->get();
FPDF_DOCUMENT document_ptr = nullptr;
if (FPDFAvail_IsLinearized(avail_ptr) == PDF_LINEARIZED) {
int32_t nRet = PDF_DATA_NOTAVAIL;
while (nRet == PDF_DATA_NOTAVAIL) {
network_simulator->SetRequestedDataAvailable();
nRet = FPDFAvail_IsDocAvail(avail_ptr,
network_simulator->GetDownloadHints());
}
if (nRet == PDF_DATA_ERROR) {
return false;
}
document->reset(FPDFAvail_GetDocument(avail_ptr, password.c_str()));
document_ptr = document->get();
if (!document_ptr) {
return false;
}
nRet = PDF_DATA_NOTAVAIL;
while (nRet == PDF_DATA_NOTAVAIL) {
network_simulator->SetRequestedDataAvailable();
nRet = FPDFAvail_IsFormAvail(avail_ptr,
network_simulator->GetDownloadHints());
}
if (nRet == PDF_FORM_ERROR) {
return false;
}
int page_count = FPDF_GetPageCount(document_ptr);
for (int i = 0; i < page_count; ++i) {
nRet = PDF_DATA_NOTAVAIL;
while (nRet == PDF_DATA_NOTAVAIL) {
network_simulator->SetRequestedDataAvailable();
nRet = FPDFAvail_IsPageAvail(avail_ptr, i,
network_simulator->GetDownloadHints());
}
if (nRet == PDF_DATA_ERROR) {
return false;
}
}
} else {
if (linearize_option == LinearizeOption::kMustLinearize) {
return false;
}
network_simulator->SetWholeFileAvailable();
document->reset(FPDF_LoadCustomDocument(network_simulator->GetFileAccess(),
password.c_str()));
document_ptr = document->get();
if (!document_ptr) {
return false;
}
}
form_handle->reset(SetupFormFillEnvironment(document_ptr, javascript_option));
int doc_type = FPDF_GetFormType(document_ptr);
if (doc_type == FORMTYPE_XFA_FULL || doc_type == FORMTYPE_XFA_FOREGROUND) {
FPDF_LoadXFA(document_ptr);
}
return true;
}
void EmbedderTest::CloseDocument() {
FORM_DoDocumentAAction(form_handle(), FPDFDOC_AACTION_WC);
form_handle_.reset();
document_.reset();
avail_.reset();
fake_file_access_.reset();
file_access_ = {};
loader_.reset();
file_contents_ = {};
}
FPDF_FORMHANDLE EmbedderTest::SetupFormFillEnvironment(
FPDF_DOCUMENT doc,
JavaScriptOption javascript_option) {
IPDF_JSPLATFORM* platform = static_cast<IPDF_JSPLATFORM*>(this);
*platform = {};
platform->version = 3;
platform->app_alert = AlertTrampoline;
FPDF_FORMFILLINFO* formfillinfo = static_cast<FPDF_FORMFILLINFO*>(this);
*formfillinfo = {};
formfillinfo->version = form_fill_info_version_;
formfillinfo->FFI_Invalidate = InvalidateStub;
formfillinfo->FFI_OutputSelectedRect = OutputSelectedRectStub;
formfillinfo->FFI_SetCursor = SetCursorStub;
formfillinfo->FFI_SetTimer = SetTimerTrampoline;
formfillinfo->FFI_KillTimer = KillTimerTrampoline;
formfillinfo->FFI_GetLocalTime = GetLocalTimeStub;
formfillinfo->FFI_OnChange = OnChangeStub;
formfillinfo->FFI_GetPage = GetPageTrampoline;
formfillinfo->FFI_GetCurrentPage = GetCurrentPageStub;
formfillinfo->FFI_GetRotation = GetRotationStub;
formfillinfo->FFI_ExecuteNamedAction = ExecuteNamedActionStub;
formfillinfo->FFI_SetTextFieldFocus = SetTextFieldFocusStub;
formfillinfo->FFI_DoURIAction = DoURIActionTrampoline;
formfillinfo->FFI_DoGoToAction = DoGoToActionTrampoline;
#ifdef PDF_ENABLE_XFA
formfillinfo->FFI_DisplayCaret = DisplayCaretStub;
formfillinfo->FFI_GetCurrentPageIndex = GetCurrentPageIndexStub;
formfillinfo->FFI_SetCurrentPage = SetCurrentPageStub;
formfillinfo->FFI_GotoURL = GotoURLStub;
formfillinfo->FFI_GetPageViewRect = GetPageViewRectStub;
formfillinfo->FFI_PageEvent = PageEventStub;
formfillinfo->FFI_PopupMenu = PopupMenuStub;
formfillinfo->FFI_OpenFile = OpenFileStub;
formfillinfo->FFI_EmailTo = EmailToStub;
formfillinfo->FFI_UploadTo = UploadToStub;
formfillinfo->FFI_GetPlatform = GetPlatformStub;
formfillinfo->FFI_GetLanguage = GetLanguageStub;
formfillinfo->FFI_DownloadFromURL = DownloadFromURLStub;
formfillinfo->FFI_PostRequestURL = PostRequestURLStub;
formfillinfo->FFI_PutRequestURL = PutRequestURLStub;
#endif // PDF_ENABLE_XFA
formfillinfo->FFI_OnFocusChange = OnFocusChangeTrampoline;
formfillinfo->FFI_DoURIActionWithKeyboardModifier =
DoURIActionWithKeyboardModifierTrampoline;
if (javascript_option == JavaScriptOption::kEnableJavaScript) {
formfillinfo->m_pJsPlatform = platform;
}
FPDF_FORMHANDLE form_handle =
FPDFDOC_InitFormFillEnvironment(doc, formfillinfo);
SetInitialFormFieldHighlight(form_handle);
return form_handle;
}
void EmbedderTest::DoOpenActions() {
CHECK(form_handle());
FORM_DoDocumentJSAction(form_handle());
FORM_DoDocumentOpenAction(form_handle());
}
int EmbedderTest::GetFirstPageNum() {
int first_page = FPDFAvail_GetFirstPageNum(document());
(void)FPDFAvail_IsPageAvail(avail(), first_page,
fake_file_access_->GetDownloadHints());
return first_page;
}
int EmbedderTest::GetPageCount() {
int page_count = FPDF_GetPageCount(document());
for (int i = 0; i < page_count; ++i) {
(void)FPDFAvail_IsPageAvail(avail(), i,
fake_file_access_->GetDownloadHints());
}
return page_count;
}
EmbedderTest::ScopedPage EmbedderTest::LoadScopedPage(int page_index) {
return ScopedPage(this, page_index);
}
FPDF_PAGE EmbedderTest::LoadPage(int page_index) {
return LoadPageCommon(page_index, /*do_events=*/true);
}
FPDF_PAGE EmbedderTest::LoadPageNoEvents(int page_index) {
return LoadPageCommon(page_index, /*do_events=*/false);
}
FPDF_PAGE EmbedderTest::LoadPageCommon(int page_index, bool do_events) {
CHECK(form_handle());
CHECK_GE(page_index, 0);
CHECK(!pdfium::Contains(page_map_, page_index));
FPDF_PAGE page = FPDF_LoadPage(document(), page_index);
if (!page) {
return nullptr;
}
if (do_events) {
FORM_OnAfterLoadPage(page, form_handle());
FORM_DoPageAAction(page, form_handle(), FPDFPAGE_AACTION_OPEN);
}
page_map_[page_index] = page;
return page;
}
void EmbedderTest::UnloadPage(FPDF_PAGE page) {
UnloadPageCommon(page, true);
}
void EmbedderTest::UnloadPageNoEvents(FPDF_PAGE page) {
UnloadPageCommon(page, false);
}
void EmbedderTest::UnloadPageCommon(FPDF_PAGE page, bool do_events) {
CHECK(form_handle());
int page_index = GetPageNumberForLoadedPage(page);
CHECK_GE(page_index, 0);
if (do_events) {
FORM_DoPageAAction(page, form_handle(), FPDFPAGE_AACTION_CLOSE);
FORM_OnBeforeClosePage(page, form_handle());
}
FPDF_ClosePage(page);
page_map_.erase(page_index);
}
void EmbedderTest::SetInitialFormFieldHighlight(FPDF_FORMHANDLE form) {
FPDF_SetFormFieldHighlightColor(form, FPDF_FORMFIELD_UNKNOWN, 0xFFE4DD);
FPDF_SetFormFieldHighlightAlpha(form, 100);
}
ScopedFPDFBitmap EmbedderTest::RenderLoadedPage(FPDF_PAGE page) {
return RenderLoadedPageWithFlags(page, 0);
}
ScopedFPDFBitmap EmbedderTest::RenderLoadedPageWithFlags(FPDF_PAGE page,
int flags) {
int page_index = GetPageNumberForLoadedPage(page);
CHECK_GE(page_index, 0);
return RenderPageWithFlags(page, form_handle(), flags);
}
ScopedFPDFBitmap EmbedderTest::RenderSavedPage(FPDF_PAGE page) {
return RenderSavedPageWithFlags(page, 0);
}
ScopedFPDFBitmap EmbedderTest::RenderSavedPageWithFlags(FPDF_PAGE page,
int flags) {
int page_index = GetPageNumberForSavedPage(page);
CHECK_GE(page_index, 0);
return RenderPageWithFlags(page, saved_form_handle(), flags);
}
// static
ScopedFPDFBitmap EmbedderTest::RenderPageWithFlags(FPDF_PAGE page,
FPDF_FORMHANDLE handle,
int flags) {
int width = static_cast<int>(FPDF_GetPageWidthF(page));
int height = static_cast<int>(FPDF_GetPageHeightF(page));
int alpha = FPDFPage_HasTransparency(page) ? 1 : 0;
ScopedFPDFBitmap bitmap(FPDFBitmap_Create(width, height, alpha));
FPDF_DWORD fill_color = alpha ? 0x00000000 : 0xFFFFFFFF;
if (!FPDFBitmap_FillRect(bitmap.get(), 0, 0, width, height, fill_color)) {
return nullptr;
}
FPDF_RenderPageBitmap(bitmap.get(), page, 0, 0, width, height, 0, flags);
FPDF_FFLDraw(handle, bitmap.get(), page, 0, 0, width, height, 0, flags);
return bitmap;
}
// static
ScopedFPDFBitmap EmbedderTest::RenderPage(FPDF_PAGE page) {
return RenderPageWithFlags(page, nullptr, 0);
}
#if BUILDFLAG(IS_WIN)
// static
std::vector<uint8_t> EmbedderTest::RenderPageWithFlagsToEmf(FPDF_PAGE page,
int flags) {
HDC dc = CreateEnhMetaFileA(nullptr, nullptr, nullptr, nullptr);
int width = static_cast<int>(FPDF_GetPageWidthF(page));
int height = static_cast<int>(FPDF_GetPageHeightF(page));
HRGN rgn = CreateRectRgn(0, 0, width, height);
SelectClipRgn(dc, rgn);
DeleteObject(rgn);
SelectObject(dc, GetStockObject(NULL_PEN));
SelectObject(dc, GetStockObject(WHITE_BRUSH));
// If a PS_NULL pen is used, the dimensions of the rectangle are 1 pixel less.
Rectangle(dc, 0, 0, width + 1, height + 1);
CHECK(FPDF_RenderPage(dc, page, 0, 0, width, height, 0, flags));
HENHMETAFILE emf = CloseEnhMetaFile(dc);
UINT size_in_bytes = GetEnhMetaFileBits(emf, 0, nullptr);
std::vector<uint8_t> buffer(size_in_bytes);
GetEnhMetaFileBits(emf, size_in_bytes, buffer.data());
DeleteEnhMetaFile(emf);
return buffer;
}
// static
std::string EmbedderTest::GetPostScriptFromEmf(
pdfium::span<const uint8_t> emf_data) {
// This comes from Emf::InitFromData() in Chromium.
HENHMETAFILE emf = SetEnhMetaFileBits(
pdfium::checked_cast<UINT>(emf_data.size()), emf_data.data());
if (!emf) {
return std::string();
}
// This comes from Emf::Enumerator::Enumerator() in Chromium.
std::vector<const ENHMETARECORD*> records;
if (!EnumEnhMetaFile(nullptr, emf, &GetRecordProc, &records, nullptr)) {
DeleteEnhMetaFile(emf);
return std::string();
}
// This comes from PostScriptMetaFile::SafePlayback() in Chromium.
std::string ps_data;
for (const auto* record : records) {
if (record->iType != EMR_GDICOMMENT) {
continue;
}
// PostScript data is encapsulated inside EMF comment records.
// The first two bytes of the comment indicate the string length. The rest
// is the actual string data.
const auto* comment = reinterpret_cast<const EMRGDICOMMENT*>(record);
const char* data = reinterpret_cast<const char*>(comment->Data);
uint16_t size = *reinterpret_cast<const uint16_t*>(data);
UNSAFE_TODO(data += 2);
ps_data.append(data, size);
}
DeleteEnhMetaFile(emf);
return ps_data;
}
#endif // BUILDFLAG(IS_WIN)
EmbedderTest::ScopedSavedDoc EmbedderTest::OpenScopedSavedDocument() {
return ScopedSavedDoc(this);
}
EmbedderTest::ScopedSavedDoc EmbedderTest::OpenScopedSavedDocumentWithPassword(
const ByteString& password) {
return ScopedSavedDoc(this, password);
}
FPDF_DOCUMENT EmbedderTest::OpenSavedDocument() {
return OpenSavedDocumentWithPassword("");
}
// static
int EmbedderTest::BytesPerPixelForFormat(int format) {
FXDIB_Format fx_format = FXDIBFormatFromFPDFFormat(format);
CHECK_NE(fx_format, FXDIB_Format::kInvalid);
return GetCompsFromFormat(fx_format);
}
FPDF_DOCUMENT EmbedderTest::OpenSavedDocumentWithPassword(
const ByteString& password) {
// Copy data to prevent clearing it before saved document close.
saved_document_file_data_ = data_string_;
saved_file_access_ = {
.m_FileLen = pdfium::checked_cast<unsigned long>(data_string_.size()),
.m_GetBlock = GetBlockFromString,
.m_Param = &saved_document_file_data_,
};
saved_fake_file_access_ =
std::make_unique<FakeFileAccess>(&saved_file_access_);
EXPECT_TRUE(OpenDocumentHelper(
password, LinearizeOption::kDefaultLinearize,
JavaScriptOption::kEnableJavaScript, saved_fake_file_access_.get(),
&saved_document_, &saved_avail_, &saved_form_handle_));
return saved_document();
}
void EmbedderTest::CloseSavedDocument() {
CHECK(saved_document());
saved_form_handle_.reset();
saved_document_.reset();
saved_avail_.reset();
}
EmbedderTest::ScopedSavedPage EmbedderTest::LoadScopedSavedPage(
int page_index) {
return ScopedSavedPage(this, page_index);
}
FPDF_PAGE EmbedderTest::LoadSavedPage(int page_index) {
CHECK(saved_form_handle());
CHECK_GE(page_index, 0);
CHECK(!pdfium::Contains(saved_page_map_, page_index));
FPDF_PAGE page = FPDF_LoadPage(saved_document(), page_index);
if (!page) {
return nullptr;
}
FORM_OnAfterLoadPage(page, saved_form_handle());
FORM_DoPageAAction(page, saved_form_handle(), FPDFPAGE_AACTION_OPEN);
saved_page_map_[page_index] = page;
return page;
}
void EmbedderTest::CloseSavedPage(FPDF_PAGE page) {
CHECK(saved_form_handle());
int page_index = GetPageNumberForSavedPage(page);
CHECK_GE(page_index, 0);
FORM_DoPageAAction(page, saved_form_handle(), FPDFPAGE_AACTION_CLOSE);
FORM_OnBeforeClosePage(page, saved_form_handle());
FPDF_ClosePage(page);
saved_page_map_.erase(page_index);
}
void EmbedderTest::VerifySavedRendering(FPDF_PAGE page,
std::string_view expectation_png_name) {
ScopedFPDFBitmap bitmap = VerifySavedRenderingCommon(page);
CompareBitmap(bitmap.get(), expectation_png_name);
}
void EmbedderTest::VerifySavedRenderingWithExpectationSuffix(
FPDF_PAGE page,
std::string_view expectation_png_name) {
ScopedFPDFBitmap bitmap = VerifySavedRenderingCommon(page);
CompareBitmapWithExpectationSuffix(bitmap.get(), expectation_png_name);
}
void EmbedderTest::VerifySavedRenderingWithFuzzyExpectationSuffix(
FPDF_PAGE page,
std::string_view expectation_png_name) {
ScopedFPDFBitmap bitmap = VerifySavedRenderingCommon(page);
CompareBitmapWithFuzzyExpectationSuffix(bitmap.get(), expectation_png_name);
}
ScopedFPDFBitmap EmbedderTest::VerifySavedRenderingCommon(FPDF_PAGE page) {
CHECK(page);
CHECK(saved_document());
return RenderSavedPageWithFlags(page, FPDF_ANNOT);
}
void EmbedderTest::VerifySavedDocument(std::string_view expectation_png_name) {
ScopedFPDFBitmap bitmap = VerifySavedDocumentCommon();
CompareBitmap(bitmap.get(), expectation_png_name);
}
void EmbedderTest::VerifySavedDocumentWithExpectationSuffix(
std::string_view expectation_png_name) {
ScopedFPDFBitmap bitmap = VerifySavedDocumentCommon();
CompareBitmapWithExpectationSuffix(bitmap.get(), expectation_png_name);
}
void EmbedderTest::VerifySavedDocumentWithFuzzyExpectationSuffix(
std::string_view expectation_png_name) {
ScopedFPDFBitmap bitmap = VerifySavedDocumentCommon();
CompareBitmapWithFuzzyExpectationSuffix(bitmap.get(), expectation_png_name);
}
ScopedFPDFBitmap EmbedderTest::VerifySavedDocumentCommon() {
ScopedSavedDoc saved_doc = OpenScopedSavedDocument();
if (!saved_doc) {
return nullptr;
}
ScopedSavedPage page = LoadScopedSavedPage(0);
return VerifySavedRenderingCommon(page.get());
}
void EmbedderTest::SetWholeFileAvailable() {
CHECK(fake_file_access_);
fake_file_access_->SetWholeFileAvailable();
}
void EmbedderTest::SetDocumentFromAvail() {
document_.reset(FPDFAvail_GetDocument(avail(), nullptr));
}
void EmbedderTest::CreateAvail(FX_FILEAVAIL* file_avail,
FPDF_FILEACCESS* file) {
avail_.reset(FPDFAvail_Create(file_avail, file));
}
FPDF_PAGE EmbedderTest::Delegate::GetPage(FPDF_FORMFILLINFO* info,
FPDF_DOCUMENT document,
int page_index) {
EmbedderTest* test = static_cast<EmbedderTest*>(info);
auto it = test->page_map_.find(page_index);
return it != test->page_map_.end() ? it->second : nullptr;
}
// static
std::string EmbedderTest::HashBitmap(FPDF_BITMAP bitmap) {
int stride = FPDFBitmap_GetStride(bitmap);
int usable_bytes_per_row =
GetBitmapBytesPerPixel(bitmap) * FPDFBitmap_GetWidth(bitmap);
int height = FPDFBitmap_GetHeight(bitmap);
auto span = UNSAFE_TODO(
pdfium::span(static_cast<uint8_t*>(FPDFBitmap_GetBuffer(bitmap)),
static_cast<size_t>(stride) * height));
CryptMd5Context context = CryptMd5Start();
for (int i = 0; i < height; ++i) {
CryptMd5Update(&context,
span.subspan(static_cast<size_t>(i * stride),
static_cast<size_t>(usable_bytes_per_row)));
}
uint8_t digest[16];
CryptMd5Finish(&context, digest);
return CryptToBase16(digest);
}
// static
void EmbedderTest::WriteBitmapToPng(FPDF_BITMAP bitmap,
const std::string& filename) {
BitmapSaver::WriteBitmapToPng(bitmap, filename);
}
// static
void EmbedderTest::CompareBitmap(FPDF_BITMAP bitmap,
std::string_view expectation_png_name) {
std::string png_path = GetEmbedderTestExpectationPath(expectation_png_name);
SCOPED_TRACE(testing::Message() << "CompareBitmap() with " << png_path);
CompareBitmapToPngFile(bitmap, png_path, /*max_pixel_per_channel_delta=*/0);
if (EmbedderTestEnvironment::GetInstance()->write_pngs()) {
WriteBitmapToPng(bitmap, png_path);
}
}
// static
void EmbedderTest::CompareBitmapWithExpectationSuffix(
FPDF_BITMAP bitmap,
std::string_view expectation_png_name,
int max_pixel_per_channel_delta) {
std::vector<std::string> candidate_png_path =
GetEmbedderTestExpectationsWithSuffixPath(expectation_png_name);
for (const std::string& png_path : candidate_png_path) {
if (!CanReadFile(png_path.c_str())) {
continue;
}
SCOPED_TRACE(testing::Message()
<< "CompareBitmapWithExpectationSuffix() with " << png_path);
CompareBitmapToPngFile(bitmap, png_path, max_pixel_per_channel_delta);
if (EmbedderTestEnvironment::GetInstance()->write_pngs()) {
WriteBitmapToPng(bitmap, png_path);
}
return;
}
ReportMissingExpectation(bitmap);
}
// static
void EmbedderTest::CompareBitmapWithFuzzyExpectationSuffix(
FPDF_BITMAP bitmap,
std::string_view expectation_png_name) {
CompareBitmapWithExpectationSuffix(bitmap, expectation_png_name,
GetPlatformMaxPixelDelta());
}
// static
int EmbedderTest::WriteBlockCallback(FPDF_FILEWRITE* pFileWrite,
const void* data,
unsigned long size) {
EmbedderTest* pThis = static_cast<EmbedderTest*>(pFileWrite);
pThis->data_string_.append(static_cast<const char*>(data), size);
if (pThis->filestream_.is_open()) {
pThis->filestream_.write(static_cast<const char*>(data), size);
}
return 1;
}
// static
int EmbedderTest::GetBlockFromString(void* param,
unsigned long pos,
unsigned char* buf,
unsigned long size) {
std::string* new_file = static_cast<std::string*>(param);
CHECK(new_file);
pdfium::CheckedNumeric<size_t> end = pos;
end += size;
CHECK_LE(end.ValueOrDie(), new_file->size());
UNSAFE_TODO(FXSYS_memcpy(buf, new_file->data() + pos, size));
return 1;
}
// static
int EmbedderTest::GetPageNumberForPage(const PageNumberToHandleMap& page_map,
FPDF_PAGE page) {
for (const auto& it : page_map) {
if (it.second == page) {
int page_index = it.first;
CHECK_GE(page_index, 0);
return page_index;
}
}
return -1;
}
int EmbedderTest::GetPageNumberForLoadedPage(FPDF_PAGE page) const {
return GetPageNumberForPage(page_map_, page);
}
int EmbedderTest::GetPageNumberForSavedPage(FPDF_PAGE page) const {
return GetPageNumberForPage(saved_page_map_, page);
}
#ifndef NDEBUG
void EmbedderTest::OpenPDFFileForWrite(const std::string& filename) {
filestream_.open(filename, std::ios_base::binary);
}
void EmbedderTest::ClosePDFFileForWrite() {
filestream_.close();
}
#endif
EmbedderTest::ScopedSavedDoc::ScopedSavedDoc()
: test_(nullptr), doc_(nullptr) {}
EmbedderTest::ScopedSavedDoc::ScopedSavedDoc(EmbedderTest* test)
: test_(test), doc_(test->OpenSavedDocument()) {}
EmbedderTest::ScopedSavedDoc::ScopedSavedDoc(EmbedderTest* test,
const ByteString& password)
: test_(test), doc_(test->OpenSavedDocumentWithPassword(password)) {}
EmbedderTest::ScopedSavedDoc::ScopedSavedDoc(ScopedSavedDoc&& that) noexcept
: test_(std::move(that.test_)), doc_(std::exchange(that.doc_, nullptr)) {}
EmbedderTest::ScopedSavedDoc& EmbedderTest::ScopedSavedDoc::operator=(
ScopedSavedDoc&& that) noexcept {
test_ = std::move(that.test_);
doc_ = std::exchange(that.doc_, nullptr);
return *this;
}
EmbedderTest::ScopedSavedDoc::~ScopedSavedDoc() {
if (doc_) {
test_->CloseSavedDocument();
}
}
EmbedderTest::ScopedPageBase::ScopedPageBase(EmbedderTest* test, FPDF_PAGE page)
: test_(test), page_(page) {}
EmbedderTest::ScopedPageBase::~ScopedPageBase() = default;
EmbedderTest::ScopedPage::ScopedPage() : ScopedPageBase(nullptr, nullptr) {}
EmbedderTest::ScopedPage::ScopedPage(EmbedderTest* test, int page_index)
: ScopedPageBase(test, test->LoadPage(page_index)) {}
EmbedderTest::ScopedPage::ScopedPage(EmbedderTest::ScopedPage&& that) noexcept
: ScopedPageBase(std::move(that.test_),
std::exchange(that.page_, nullptr)) {}
EmbedderTest::ScopedPage& EmbedderTest::ScopedPage::operator=(
EmbedderTest::ScopedPage&& that) noexcept {
test_ = std::move(that.test_);
page_ = std::exchange(that.page_, nullptr);
return *this;
}
EmbedderTest::ScopedPage::~ScopedPage() {
if (page_) {
test_->UnloadPage(page_);
}
}
EmbedderTest::ScopedSavedPage::ScopedSavedPage()
: ScopedPageBase(nullptr, nullptr) {}
EmbedderTest::ScopedSavedPage::ScopedSavedPage(EmbedderTest* test,
int page_index)
: ScopedPageBase(test, test->LoadSavedPage(page_index)) {}
EmbedderTest::ScopedSavedPage::ScopedSavedPage(
EmbedderTest::ScopedSavedPage&& that) noexcept
: ScopedPageBase(std::move(that.test_),
std::exchange(that.page_, nullptr)) {}
EmbedderTest::ScopedSavedPage& EmbedderTest::ScopedSavedPage::operator=(
EmbedderTest::ScopedSavedPage&& that) noexcept {
test_ = std::move(that.test_);
page_ = std::exchange(that.page_, nullptr);
return *this;
}
EmbedderTest::ScopedSavedPage::~ScopedSavedPage() {
if (page_) {
test_->CloseSavedPage(page_);
}
}