// 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 #include #include #include #include #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*>(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(info)->GetDelegate(); delegate->UnsupportedHandler(type); } int AlertTrampoline(IPDF_JSPLATFORM* platform, FPDF_WIDESTRING message, FPDF_WIDESTRING title, int type, int icon) { auto* delegate = static_cast(platform)->GetDelegate(); return delegate->Alert(message, title, type, icon); } int SetTimerTrampoline(FPDF_FORMFILLINFO* info, int msecs, TimerCallback fn) { auto* delegate = static_cast(info)->GetDelegate(); return delegate->SetTimer(msecs, fn); } void KillTimerTrampoline(FPDF_FORMFILLINFO* info, int id) { auto* delegate = static_cast(info)->GetDelegate(); return delegate->KillTimer(id); } FPDF_PAGE GetPageTrampoline(FPDF_FORMFILLINFO* info, FPDF_DOCUMENT document, int page_index) { auto* delegate = static_cast(info)->GetDelegate(); return delegate->GetPage(info, document, page_index); } void DoURIActionTrampoline(FPDF_FORMFILLINFO* info, FPDF_BYTESTRING uri) { auto* delegate = static_cast(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(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(info)->GetDelegate(); return delegate->OnFocusChange(info, annot, page_index); } void DoURIActionWithKeyboardModifierTrampoline(FPDF_FORMFILLINFO* info, FPDF_BYTESTRING uri, int modifiers) { auto* delegate = static_cast(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[] = "secrets"; 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, 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 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 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 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 pixel_data; // BGRA }; DecodedPng DecodePngData(pdfium::span png_data) { DecodedPng results; int width = -1; int height = -1; std::vector 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 bitmap_span, size_t bitmap_stride, const DecodedPng& decoded_png, int max_pixel_per_channel_delta) { const size_t unsigned_width = static_cast(decoded_png.width); auto decoded_png_span32 = fxcrt::reinterpret_span( 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( 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 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(width); const int height = decoded_png.height; const size_t bgrx_stride = width * sizeof(FX_BGRA_STRUCT); std::vector bgrx_buffer(bgrx_stride * height); auto bgrx_span = fxcrt::reinterpret_span>( pdfium::span(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 bitmap_span, size_t bitmap_stride, const DecodedPng& decoded_png, int max_pixel_per_channel_delta) { std::vector bitmap_data(bitmap_span.begin(), bitmap_span.end()); pdfium::span converted_bitmap_span{bitmap_data}; for (int h = 0; h < decoded_png.height; ++h) { auto bitmap_row = fxcrt::reinterpret_span>( 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 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(width); const int height = decoded_png.height; const size_t bgrx_stride = width * sizeof(FX_BGRA_STRUCT); std::vector bgrx_buffer(bgrx_stride * height); auto bgrx_span = fxcrt::reinterpret_span>( pdfium::span(bgrx_buffer)); for (int h = 0; h < height; ++h) { auto src_row = fxcrt::reinterpret_span>( 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 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 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(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()), delegate_(default_delegate_.get()) { FPDF_FILEWRITE::version = 1; FPDF_FILEWRITE::WriteBlock = WriteBlockCallback; } EmbedderTest::~EmbedderTest() = default; void EmbedderTest::SetUp() { UNSUPPORT_INFO* info = static_cast(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(file_contents_); file_access_ = { .m_FileLen = pdfium::checked_cast(file_contents_.size()), .m_GetBlock = TestLoader::GetBlock, .m_Param = loader_.get(), }; fake_file_access_ = std::make_unique(&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(this); *platform = {}; platform->version = 3; platform->app_alert = AlertTrampoline; FPDF_FORMFILLINFO* formfillinfo = static_cast(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(FPDF_GetPageWidthF(page)); int height = static_cast(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 EmbedderTest::RenderPageWithFlagsToEmf(FPDF_PAGE page, int flags) { HDC dc = CreateEnhMetaFileA(nullptr, nullptr, nullptr, nullptr); int width = static_cast(FPDF_GetPageWidthF(page)); int height = static_cast(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 buffer(size_in_bytes); GetEnhMetaFileBits(emf, size_in_bytes, buffer.data()); DeleteEnhMetaFile(emf); return buffer; } // static std::string EmbedderTest::GetPostScriptFromEmf( pdfium::span emf_data) { // This comes from Emf::InitFromData() in Chromium. HENHMETAFILE emf = SetEnhMetaFileBits( pdfium::checked_cast(emf_data.size()), emf_data.data()); if (!emf) { return std::string(); } // This comes from Emf::Enumerator::Enumerator() in Chromium. std::vector 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(record); const char* data = reinterpret_cast(comment->Data); uint16_t size = *reinterpret_cast(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(data_string_.size()), .m_GetBlock = GetBlockFromString, .m_Param = &saved_document_file_data_, }; saved_fake_file_access_ = std::make_unique(&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(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(FPDFBitmap_GetBuffer(bitmap)), static_cast(stride) * height)); CryptMd5Context context = CryptMd5Start(); for (int i = 0; i < height; ++i) { CryptMd5Update(&context, span.subspan(static_cast(i * stride), static_cast(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 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(pFileWrite); pThis->data_string_.append(static_cast(data), size); if (pThis->filestream_.is_open()) { pThis->filestream_.write(static_cast(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(param); CHECK(new_file); pdfium::CheckedNumeric 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_); } }