| // 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) | |
| } | |
| 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, | |
| double max_mean_squared_error) { | |
| 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; | |
| uint64_t total_squared_error = 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; | |
| } | |
| total_squared_error += PixelSquaredError(png_pixel, bitmap_pixel); | |
| } | |
| } | |
| if (max_mean_squared_error > 0.0) { | |
| double mse = static_cast<double>(total_squared_error) / | |
| (3.0 * decoded_png.width * decoded_png.height); | |
| if (mse > max_mean_squared_error) { | |
| ++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, | |
| double max_mean_squared_error) { | |
| 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, | |
| max_mean_squared_error); | |
| } | |
| #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, | |
| double max_mean_squared_error) { | |
| 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, | |
| max_mean_squared_error); | |
| } | |
| #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, | |
| double max_mean_squared_error) { | |
| 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, | |
| max_mean_squared_error); | |
| } | |
| 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, | |
| double max_mean_squared_error) { | |
| 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, | |
| max_mean_squared_error); | |
| break; | |
| case FPDFBitmap_BGR: | |
| pixels_different = CompareBGRBitmapToPng(bitmap_span, stride, decoded_png, | |
| max_pixel_per_channel_delta, | |
| max_mean_squared_error); | |
| break; | |
| case FPDFBitmap_BGRx: | |
| case FPDFBitmap_BGRA: { | |
| pixels_different = CompareBGRxBitmapToPng( | |
| bitmap_span, stride, decoded_png, max_pixel_per_channel_delta, | |
| max_mean_squared_error); | |
| break; | |
| } | |
| #ifdef PDF_USE_SKIA | |
| case FPDFBitmap_BGRA_Premul: | |
| pixels_different = CompareBGRxPremultBitmapToPng( | |
| bitmap_span, stride, decoded_png, max_pixel_per_channel_delta, | |
| max_mean_squared_error); | |
| 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, | |
| /*max_mean_squared_error=*/0.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, | |
| double max_mean_squared_error) { | |
| 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, | |
| max_mean_squared_error); | |
| 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, | |
| kMaxFuzzyPixelDelta, | |
| kMaxFuzzyMeanSquaredError); | |
| } | |
| // 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_); | |
| } | |
| } |