diff --git a/.github/workflows/release-libpdfium.yml b/.github/workflows/release-libpdfium.yml index bb690e1c30..ec2720c702 100644 --- a/.github/workflows/release-libpdfium.yml +++ b/.github/workflows/release-libpdfium.yml @@ -162,6 +162,9 @@ jobs: throw "rc.exe not found in $rcPath" } $rcPath | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + # build/ pins a Windows SDK the runner may not have; build with the + # newest one it does have (patches/embedpdf-runtime/win/build.patch). + "EMBEDPDF_WIN_SDK_VERSION=$($sdk.Name)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Build target shell: bash run: scripts/embedpdf-runtime/build-target.sh "${{ matrix.target }}" diff --git a/.vpython3 b/.vpython3 deleted file mode 100644 index a24de03452..0000000000 --- a/.vpython3 +++ /dev/null @@ -1,53 +0,0 @@ -# This is a vpython "spec" file. -# -# It describes patterns for python wheel dependencies of the python scripts in -# the chromium repo, particularly for dependencies that have compiled components -# (since pure-python dependencies can be easily vendored into third_party). -# -# When vpython is invoked, it finds this file and builds a python VirtualEnv, -# containing all of the dependencies described in this file, fetching them from -# CIPD (the "Chrome Infrastructure Package Deployer" service). Unlike `pip`, -# this never requires the end-user machine to have a working python extension -# compilation environment. All of these packages are built using: -# https://chromium.googlesource.com/infra/infra/+/main/infra/tools/dockerbuild/ -# -# All python scripts in the repo share this same spec, to avoid dependency -# fragmentation. -# -# If you have depot_tools installed in your $PATH, you can invoke python scripts -# in this repo by running them as you normally would run them, except -# substituting `vpython` instead of `python` on the command line, e.g.: -# vpython path/to/script.py some --arguments -# -# Read more about `vpython` and how to modify this file here: -# https://chromium.googlesource.com/infra/infra/+/main/doc/users/vpython.md - -python_version: "3.11" - -# Used by build/util/lib/results/result_sink.py -wheel: < - name: "infra/python/wheels/certifi-py3" - version: "version:2023.7.22" -> -wheel: < - name: "infra/python/wheels/charset_normalizer-py3" - version: "version:3.1.0" -> -wheel: < - name: "infra/python/wheels/idna-py3" - version: "version:3.4" -> -wheel: < - name: "infra/python/wheels/requests-py3" - version: "version:2.31.0" -> -wheel: < - name: "infra/python/wheels/urllib3-py3" - version: "version:2.0.3" -> - -# Used by //testing/scripts/rust unit tests. -wheel: < - name: "infra/python/wheels/pyfakefs-py2_py3" - version: "version:3.7.2" -> diff --git a/AUTHORS b/AUTHORS index 9fa8a89ac0..c8141c9379 100644 --- a/AUTHORS +++ b/AUTHORS @@ -31,6 +31,7 @@ Huy Ngo Ivan Odulo Jeroen Bobbeldijk Jiang Jiang +Jimin Lee Justin Pierce Ke Liu Luật Nguyễn @@ -55,6 +56,7 @@ Tyler Denniston Wang Chuan Wang Qing Wang Yongjie +Xonaman Zhuo Qingliang # END individuals section. diff --git a/BUILD.gn b/BUILD.gn index 0cce62ee43..d9790cd2fe 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -29,6 +29,7 @@ config("pdfium_common_config") { cflags_cc = [] ldflags = [] include_dirs = [ "." ] + inputs = [ "unsafe_buffers_paths.txt" ] defines = [] if (!use_system_libopenjpeg2) { @@ -39,19 +40,15 @@ config("pdfium_common_config") { defines += [ "PDF_ENABLE_CLICK_LOGGING" ] } - if (pdf_use_skia && pdf_enable_fontations) { + if (pdf_enable_fontations) { defines += [ "PDF_ENABLE_FONTATIONS" ] } - if (pdf_use_skia && pdf_enable_skia_typeface_checks) { - defines += [ "PDF_ENABLE_SKIA_TYPEFACE_CHECKS" ] - } - if (pdf_enable_rust_bmp) { defines += [ "PDF_ENABLE_RUST_BMP" ] } - if (pdf_use_skia && pdf_enable_rust_jpeg) { + if (pdf_enable_rust_jpeg) { defines += [ "PDF_ENABLE_RUST_JPEG" ] } diff --git a/DEPS b/DEPS index 811613d299..081f4bd49e 100644 --- a/DEPS +++ b/DEPS @@ -90,7 +90,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling android_ndk # and whatever else without interference from each other. - 'android_ndk_version': Str('2@30.0.15729638'), + 'android_ndk_version': Str('2@30.0.16138531'), # Three lines of non-changing comments so that # the commit queue can handle CLs rolling brotli # and whatever else without interference from each other. @@ -118,7 +118,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling code_coverage # and whatever else without interference from each other. - 'code_coverage_revision': 'e1878fbb1ff79de75d54b6b57caa19a92b90079b', + 'code_coverage_revision': 'a81032f8d6f57774af865e502c5661cf24780ca2', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling cpu_features # and whatever else without interference from each other. @@ -130,7 +130,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling depot_tools # and whatever else without interference from each other. - 'depot_tools_revision': '732902ab86f8bd629bee00af102d5aa2d9336166', + 'depot_tools_revision': '36a8df4ad006eaa0572fb446edb8145fe5403592', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling dragonbox # and whatever else without interference from each other. @@ -154,7 +154,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling goldctl CIPD package version # and whatever else without interference from each other. - 'goldctl_version': 'git_revision:9379dad302e027bb42a09c353bfbac0166c8c845', + 'goldctl_version': 'git_revision:63df9e9df52e8edfd3cd86b642ba79f440e96ecd', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling gtest # and whatever else without interference from each other. @@ -174,7 +174,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling instrumented_lib # and whatever else without interference from each other. - 'instrumented_lib_revision': 'd15c278eed5d38d9acf2d8054cf37baba93cef8e', + 'instrumented_lib_revision': '423262e4438b032c0ba5ea61f78a85ac3690298c', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling jinja2 # and whatever else without interference from each other. @@ -196,7 +196,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libpng # and whatever else without interference from each other. - 'libpng_revision': '6d5341764ef4e38cbc07d35514a8aa73de50de8a', + 'libpng_revision': '0179a807421cf96731cdfa10008bba04fda243c1', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libunwind # and whatever else without interference from each other. @@ -224,7 +224,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling pdfium_tests # and whatever else without interference from each other. - 'pdfium_tests_revision': 'd4ec3e1987a094b1eae612943b57782d3b0038ff', + 'pdfium_tests_revision': '6a4136071608f159f45e274fed1b7d5f4272ca8f', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling result_adapter_revision # and whatever else without interference from each other. @@ -240,7 +240,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling siso # and whatever else without interference from each other. - 'siso_version': 'git_revision:bc45e8f67ae0f37d337190ad64aa8bb440c791eb', + 'siso_version': 'git_revision:efbbe7f1892211b5e9512576843a3c247b6a6d7c', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling skia # and whatever else without interference from each other. @@ -256,7 +256,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling tools_memory # and whatever else without interference from each other. - 'tools_memory_revision': '8d3182bb3c86948e9e27fbfe11d2134c7e29c898', + 'tools_memory_revision': '0732f7097d066a81e673f90862994f76886fc638', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling tools_rust # and whatever else without interference from each other. @@ -272,7 +272,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling zlib # and whatever else without interference from each other. - 'zlib_revision': 'c5cc9edf8992ff36dfca3c2c4f6c8327a66b6782', + 'zlib_revision': '285e94b8fa95ad3b7d16b80798ec8dce6febb8c8', } # Only these hosts are allowed for dependencies in this DEPS file. @@ -361,11 +361,13 @@ deps = { 'third_party/android_toolchain/ndk': { 'packages': [ { - 'package': 'chromium/third_party/android_toolchain/android_toolchain', + 'package': 'chromium/third_party/android_toolchain/android_toolchain/${{platform}}', 'version': 'version:' + Var('android_ndk_version'), }, ], - 'condition': 'checkout_android_native_support', + 'condition': 'checkout_android_native_support and ' + '((host_os == "linux" and host_cpu == "x64") or ' + '(host_os == "mac" and host_cpu == "arm64"))', 'dep_type': 'cipd', }, diff --git a/OWNERS b/OWNERS index 12ecd8e1af..7b813bdcc4 100644 --- a/OWNERS +++ b/OWNERS @@ -1,4 +1,3 @@ -npm@chromium.org thestig@chromium.org tsepez@chromium.org diff --git a/build_overrides/build.gni b/build_overrides/build.gni index dfad9304ec..b11047feb8 100644 --- a/build_overrides/build.gni +++ b/build_overrides/build.gni @@ -26,6 +26,11 @@ asan_suppressions_file = "//build/sanitizers/asan_suppressions.cc" lsan_suppressions_file = "//build/sanitizers/lsan_suppressions.cc" tsan_suppressions_file = "//build/sanitizers/tsan_suppressions.cc" +vpython_spec_files = [ + "//vpython.toml", + "//vpython.toml.uv.lock", +] + declare_args() { # Android 32-bit non-component, non-clang builds cannot have symbol_level=2 # due to 4GiB file size limit, see https://crbug.com/648948. diff --git a/core/fpdfapi/edit/BUILD.gn b/core/fpdfapi/edit/BUILD.gn index 55a6f3b621..3a0d7e8927 100644 --- a/core/fpdfapi/edit/BUILD.gn +++ b/core/fpdfapi/edit/BUILD.gn @@ -51,7 +51,6 @@ source_set("edit") { "../../../:pdfium_noshorten_config", ] deps = [ - ":contentstream_write_utils", "../../../constants", "../../fxcrt", "../font", @@ -67,24 +66,8 @@ source_set("edit") { visibility = [ "../../../*" ] } -source_set("contentstream_write_utils") { - sources = [ - "cpdf_contentstream_write_utils.cpp", - "cpdf_contentstream_write_utils.h", - ] - configs += [ - "../../../:pdfium_strict_config", - "../../../:pdfium_noshorten_config", - ] - deps = [ - "../../fxcrt", - "//third_party/dragonbox", - ] -} - pdfium_unittest_source_set("unittests") { sources = [ - "cpdf_contentstream_write_utils_unittest.cpp", "cpdf_creator_unittest.cpp", "cpdf_npagetooneexporter_unittest.cpp", "cpdf_pagecontentgenerator_unittest.cpp", @@ -95,8 +78,8 @@ pdfium_unittest_source_set("unittests") { "cpdf_tounicodecmap_unittest.cpp", ] deps = [ - ":contentstream_write_utils", ":edit", + "../../fxcrt", "../../fxge", "../font", "../page", diff --git a/core/fpdfapi/edit/DEPS b/core/fpdfapi/edit/DEPS deleted file mode 100644 index 3cd317f3f0..0000000000 --- a/core/fpdfapi/edit/DEPS +++ /dev/null @@ -1,5 +0,0 @@ -specific_include_rules = { - 'cpdf_contentstream_write_utils.cpp': [ - '+third_party/dragonbox/src/include/dragonbox/dragonbox.h', - ], -} diff --git a/core/fpdfapi/edit/cpdf_npagetooneexporter.cpp b/core/fpdfapi/edit/cpdf_npagetooneexporter.cpp index b20813726c..b67708a971 100644 --- a/core/fpdfapi/edit/cpdf_npagetooneexporter.cpp +++ b/core/fpdfapi/edit/cpdf_npagetooneexporter.cpp @@ -12,7 +12,6 @@ #include #include "constants/page_object.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" #include "core/fpdfapi/page/cpdf_page.h" #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" @@ -27,6 +26,7 @@ #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/fx_string_wrappers.h" #include "core/fxcrt/span.h" +#include "core/fxcrt/stream_write_utils.h" namespace { diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp index d72c720484..c9a6744342 100644 --- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp +++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp @@ -17,7 +17,6 @@ #include #include "constants/page_object.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" #include "core/fpdfapi/edit/cpdf_pagecontentmanager.h" #include "core/fpdfapi/edit/cpdf_resourceuse.h" #include "core/fpdfapi/edit/cpdf_stringarchivestream.h" @@ -57,6 +56,7 @@ #include "core/fxcrt/notreached.h" #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/span.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fxcrt/zip.h" #include "core/fxge/cfx_graphstate.h" diff --git a/core/fpdfapi/edit/cpdf_text_redactor.cpp b/core/fpdfapi/edit/cpdf_text_redactor.cpp index 13c892a0bb..c0af3fd147 100644 --- a/core/fpdfapi/edit/cpdf_text_redactor.cpp +++ b/core/fpdfapi/edit/cpdf_text_redactor.cpp @@ -9,7 +9,7 @@ #include #include -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfapi/edit/cpdf_image_redactor.h" #include "core/fpdfapi/edit/cpdf_path_redactor.h" #include "core/fpdfapi/edit/cpdf_pagecontentgenerator.h" diff --git a/core/fpdfapi/font/cpdf_cmap.cpp b/core/fpdfapi/font/cpdf_cmap.cpp index 81f35c7d67..ecbca3d556 100644 --- a/core/fpdfapi/font/cpdf_cmap.cpp +++ b/core/fpdfapi/font/cpdf_cmap.cpp @@ -16,6 +16,7 @@ #include "core/fpdfapi/font/cpdf_fontglobals.h" #include "core/fpdfapi/parser/cpdf_simple_parser.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/notreached.h" namespace { @@ -421,7 +422,7 @@ size_t CPDF_CMap::CountChar(ByteStringView pString) const { case OneByte: return pString.GetLength(); case TwoBytes: - return (pString.GetLength() + 1) / 2; + return fxcrt::CeilDiv(pString.GetLength(), 2); case MixedTwoBytes: { size_t count = 0; for (size_t i = 0; i < pString.GetLength(); i++) { diff --git a/core/fpdfapi/font/cpdf_tounicodemap.cpp b/core/fpdfapi/font/cpdf_tounicodemap.cpp index 5a4d342d0e..b2e53b0f4d 100644 --- a/core/fpdfapi/font/cpdf_tounicodemap.cpp +++ b/core/fpdfapi/font/cpdf_tounicodemap.cpp @@ -373,13 +373,32 @@ void CPDF_ToUnicodeMap::SetCode(uint32_t srccode, WideString destcode) { } void CPDF_ToUnicodeMap::InsertIntoMaps(uint32_t code, uint32_t destcode) { - auto [it, inserted] = map_.insert({code, destcode}); - if (!inserted) { - it->second = std::min(it->second, destcode); - } - - auto [reverse_it, reverse_inserted] = reverse_map_.insert({destcode, code}); - if (!reverse_inserted) { - reverse_it->second = std::min(reverse_it->second, code); - } + // Adobe TN #5014, "Adobe CMap and CIDFont Files Specification": "Code + // mappings (unlike codespace ranges) may overlap, but succeeding maps + // superceded preceding maps." A later bfchar or bfrange entry therefore + // replaces an earlier mapping for the same code, which subset fonts rely on + // when a catch-all bfrange is followed by overrides for individual codes. + map_[code] = destcode; + reverse_map_[destcode] = code; + + // TODO(crbug.com/554790604): Two rough edges remain here. + // + // A superseded mapping leaves an orphaned entry behind in `reverse_map_`: + // after InsertIntoMaps(1, 2) and then InsertIntoMaps(1, 3), `map_` holds + // {1: 3} but `reverse_map_` holds {2: 1, 3: 1}, so ReverseLookup(2) still + // answers 1 for a mapping that no longer exists. Rebuilding `reverse_map_` + // from the finished `map_` removes those, but it changes which charcode + // font subsetting picks, so it needs its own change. + // + // Also, last-wins is only meaningful for a CMap that a producer authored. + // LoadCompositeFont() collects a font's cmap into a multimap keyed by glyph, + // so a glyph that several unicodes reach contributes several entries, and + // LoadUnicode() then emits overlapping bfranges for it. Which of those wins + // comes down to the order they were emitted in rather than to the font, and + // no ordering is right for all of them. Among the pairs + // FPDFSaveWithFontSubsetEmbedderTest covers, last-wins picks U+4E00 over + // U+2F00, which is the better answer, but also U+F906 over U+53E5 and + // U+00A0 over U+0020, which are worse; lowest-wins gets those same three + // the other way round. Making that CMap map each charcode once is the real + // fix. } diff --git a/core/fpdfapi/font/cpdf_tounicodemap.h b/core/fpdfapi/font/cpdf_tounicodemap.h index 5e9dddc200..1b270ccf21 100644 --- a/core/fpdfapi/font/cpdf_tounicodemap.h +++ b/core/fpdfapi/font/cpdf_tounicodemap.h @@ -53,16 +53,14 @@ class CPDF_ToUnicodeMap { // Key: charcode // Value: unicode - // If there are multiple entries with the same key, this stores the lowest - // value. + // If a charcode is mapped more than once, this stores the last mapping, as + // later map entries supersede earlier ones. std::map map_; // Key: unicode // Value: charcode - // Since `map_` may encounter entries with the same key but different values, - // that situation does not cause a conflict in `reverse_map_`. Thus - // `reverse_map_` may have a different number of entries compared to `map_`. - // Similar to `map_`, if there is a key collision, then this stores the lowest - // value. + // Several charcodes may map to one unicode, so `reverse_map_` may have fewer + // entries than `map_`. On such a collision this stores the last charcode, + // matching how `map_` resolves a charcode mapped more than once. std::map reverse_map_; UnownedPtr base_map_; diff --git a/core/fpdfapi/font/cpdf_tounicodemap_unittest.cpp b/core/fpdfapi/font/cpdf_tounicodemap_unittest.cpp index 5b4da257b6..73cad9924d 100644 --- a/core/fpdfapi/font/cpdf_tounicodemap_unittest.cpp +++ b/core/fpdfapi/font/cpdf_tounicodemap_unittest.cpp @@ -403,11 +403,9 @@ TEST(CPDFToUnicodeMapTest, SucceedingMapsSupersedePreceding) { "1 beginbfrange<00><0000>endbfrange\n" "3 beginbfchar<41><00F3><42><00BA><43><20AC>endbfchar"; CPDF_ToUnicodeMap map(pdfium::MakeRetain(kInput1)); - // Should be U+00F3, U+00BA and U+20AC. The lowest value wins instead, so - // each charcode keeps the identity mapping from the earlier bfrange. - EXPECT_EQ(L"A", map.Lookup(0x41)); - EXPECT_EQ(L"B", map.Lookup(0x42)); - EXPECT_EQ(L"C", map.Lookup(0x43)); + EXPECT_EQ(L"\u00f3", map.Lookup(0x41)); + EXPECT_EQ(L"\u00ba", map.Lookup(0x42)); + EXPECT_EQ(L"\u20ac", map.Lookup(0x43)); // A charcode no later entry mentions keeps the range's mapping. EXPECT_EQ(L"D", map.Lookup(0x44)); } @@ -417,14 +415,12 @@ TEST(CPDFToUnicodeMapTest, SucceedingMapsSupersedePreceding) { "1 beginbfrange<00><0000>endbfrange\n" "1 beginbfrange<70><71><00E9>endbfrange"; CPDF_ToUnicodeMap map(pdfium::MakeRetain(kInput2)); - // Should be U+00E9 and U+00EA. - EXPECT_EQ(L"p", map.Lookup(0x70)); - EXPECT_EQ(L"q", map.Lookup(0x71)); + EXPECT_EQ(L"\u00e9", map.Lookup(0x70)); + EXPECT_EQ(L"\u00ea", map.Lookup(0x71)); EXPECT_EQ(L"r", map.Lookup(0x72)); } { - // A mapping to a lower unicode happens to win today as well, since it is - // also the lowest. This case is unaffected by the bug. + // A mapping to a lower unicode also wins when it comes later. static constexpr uint8_t kInput3[] = "1 beginbfchar<41><00F3>endbfchar\n" "1 beginbfchar<41><0041>endbfchar"; diff --git a/core/fpdfapi/font/cpdf_type1font.cpp b/core/fpdfapi/font/cpdf_type1font.cpp index 31fa1e2ac4..ff0bda7edb 100644 --- a/core/fpdfapi/font/cpdf_type1font.cpp +++ b/core/fpdfapi/font/cpdf_type1font.cpp @@ -51,6 +51,13 @@ const char* GlyphNameRemap(const char* pStrAdobe) { #endif // BUILDFLAG(IS_APPLE) bool UseType1Charmap(const RetainPtr& face) { +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + // TODO(crbug.com/42271123): properly support charmaps. + return face->SelectCharMap(fxge::FontEncoding::kAdobeCustom); + } +#endif + size_t num_charmaps = face->GetCharMapCount(); if (num_charmaps == 0) { return false; diff --git a/core/fpdfapi/page/cpdf_dib.cpp b/core/fpdfapi/page/cpdf_dib.cpp index ae78b46b8f..5968217f51 100644 --- a/core/fpdfapi/page/cpdf_dib.cpp +++ b/core/fpdfapi/page/cpdf_dib.cpp @@ -16,6 +16,7 @@ #include "core/fpdfapi/page/cpdf_colorspace.h" #include "core/fpdfapi/page/cpdf_docpagedata.h" +#include "core/fpdfapi/page/cpdf_iccprofile.h" #include "core/fpdfapi/page/cpdf_image.h" #include "core/fpdfapi/page/cpdf_imageobject.h" #include "core/fpdfapi/page/cpdf_indexedcs.h" @@ -520,6 +521,41 @@ CPDF_DIB::LoadState CPDF_DIB::CreateDecoder(uint8_t resolution_levels_to_skip) { return LoadState::kSuccess; } +bool CPDF_DIB::ComponentCountMatchesColorSpace() const { + return color_space_ && components_ == color_space_->ComponentCount(); +} + +// Whether the JPEG decoder should be asked to emit BGR-ordered scanlines. +// Only true when GetScanline()'s only transformation of the decoded RGB +// scanline would be TranslateScanline24bppDefaultDecode()'s per-pixel +// RGB-to-BGR swap, which a BGR decode makes a pass-through: +// - a default Decode array (no per-component decode arithmetic), +// - no color key mask (its comparisons read components in R,G,B order), +// - a color space whose whole translation is the bare swap: DeviceRGB, +// CalRGB, or ICCBased with an sRGB profile (whose TranslateImageLine() +// is exactly fxcodec::ReverseRGB()); every other case goes through a +// real conversion, +// - 3 components at 8 bpc (the swap case). +bool CPDF_DIB::ShouldDecodeJpegToBgr() const { + if (!default_decode_ || color_key_ || components_ != 3 || bpc_ != 8) { + return false; + } + if (family_ == CPDF_ColorSpace::Family::kDeviceRGB || + family_ == CPDF_ColorSpace::Family::kCalRGB) { + return true; + } + // For an sRGB profile, CPDF_ICCBasedCS::TranslateImageLine() is exactly + // fxcodec::ReverseRGB(), so the whole translation is the swap. Any other + // profile converts through the alternate color space instead, which is not + // a swap, so those keep the translate path. + if (family_ == CPDF_ColorSpace::Family::kICCBased && + ComponentCountMatchesColorSpace()) { + RetainPtr profile = color_space_->GetIccProfile(); + return profile && profile->IsSRGB(); + } + return false; +} + bool CPDF_DIB::CreateDCTDecoder(pdfium::span src_span, const CPDF_Dictionary* pParams, uint8_t resolution_levels_to_skip) { @@ -530,7 +566,8 @@ bool CPDF_DIB::CreateDCTDecoder(pdfium::span src_span, << std::min(resolution_levels_to_skip, 3); decoder_ = JpegModule::CreateDecoder( src_span, GetWidth(), GetHeight(), components_, - !pParams || pParams->GetIntegerFor("ColorTransform", 1), scale_denom); + !pParams || pParams->GetIntegerFor("ColorTransform", 1), scale_denom, + ShouldDecodeJpegToBgr()); if (decoder_) { // Adopt the decoder's dimensions. libjpeg may have decoded at a reduced // size (scale_denom above), and its dimensions are authoritative in any @@ -561,7 +598,7 @@ bool CPDF_DIB::CreateDCTDecoder(pdfium::span src_span, bpc_ = info.bits_per_components; decoder_ = JpegModule::CreateDecoder(src_span, GetWidth(), GetHeight(), components_, info.color_transform, - scale_denom); + scale_denom, ShouldDecodeJpegToBgr()); if (decoder_) { SetWidth(decoder_->GetWidth()); SetHeight(decoder_->GetHeight()); @@ -616,9 +653,9 @@ bool CPDF_DIB::CreateDCTDecoder(pdfium::span src_span, } bpc_ = info.bits_per_components; - decoder_ = - JpegModule::CreateDecoder(src_span, GetWidth(), GetHeight(), components_, - info.color_transform, scale_denom); + decoder_ = JpegModule::CreateDecoder(src_span, GetWidth(), GetHeight(), + components_, info.color_transform, + scale_denom, ShouldDecodeJpegToBgr()); if (decoder_) { SetWidth(decoder_->GetWidth()); SetHeight(decoder_->GetHeight()); @@ -1084,7 +1121,7 @@ bool CPDF_DIB::TranslateScanline24bppDefaultDecode( return false; } - if (components_ == color_space_->ComponentCount()) { + if (ComponentCountMatchesColorSpace()) { color_space_->TranslateImageLine(dest_scan, src_scan, GetWidth(), GetWidth(), GetHeight(), TransMask()); } @@ -1288,9 +1325,21 @@ pdfium::span CPDF_DIB::GetScanline(int line) const { } } if (color_space_) { - TranslateScanline24bpp(line_buf_, src_line); src_pitch_value = 3 * GetWidth(); - src_line = pdfium::span(line_buf_).first(src_pitch_value); + // Ask the decoder what it did rather than re-evaluating + // ShouldDecodeJpegToBgr(): that predicate reads `bpc_` and `components_`, + // which CreateDCTDecoder() updates between its attempts at creating a + // decoder, so it does not necessarily still describe the decoder that + // ended up being used. A decoder may also decline the request outright. + // When the decoder did emit B,G,R, the translation below would have been + // the default-decode RGB-to-BGR swap and nothing else, so the decoded + // scanline is already the result. + if (decoder_ && decoder_->ScanlinesAreBgr()) { + src_line = src_line.first(src_pitch_value); + } else { + TranslateScanline24bpp(line_buf_, src_line); + src_line = pdfium::span(line_buf_).first(src_pitch_value); + } } if (!color_key_) { return src_line; diff --git a/core/fpdfapi/page/cpdf_dib.h b/core/fpdfapi/page/cpdf_dib.h index f365f399b9..39b47bcd96 100644 --- a/core/fpdfapi/page/cpdf_dib.h +++ b/core/fpdfapi/page/cpdf_dib.h @@ -115,6 +115,13 @@ class CPDF_DIB final : public CFX_DIBBase { bool CreateDCTDecoder(pdfium::span src_span, const CPDF_Dictionary* pParams, uint8_t resolution_levels_to_skip); + // Whether `components_` matches the color space's own component count, which + // is the condition under which a scanline gets translated at all. The two + // can disagree because LoadColorInfo() overrides `components_` for an + // ICCBased color space named DeviceGray, DeviceRGB or DeviceCMYK. Returns + // false when there is no color space. + bool ComponentCountMatchesColorSpace() const; + bool ShouldDecodeJpegToBgr() const; void TranslateScanline24bpp(pdfium::span dest_scan, pdfium::span src_scan) const; bool TranslateScanline24bppDefaultDecode( diff --git a/core/fpdfapi/page/cpdf_image.cpp b/core/fpdfapi/page/cpdf_image.cpp index 4c120f9e16..4aef6e7dc3 100644 --- a/core/fpdfapi/page/cpdf_image.cpp +++ b/core/fpdfapi/page/cpdf_image.cpp @@ -34,6 +34,7 @@ #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/data_vector.h" #include "core/fxcrt/fx_2d_size.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_stream.h" #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/span_util.h" @@ -358,7 +359,7 @@ void CPDF_Image::SetImage(const RetainPtr& pBitmap) { pCS->AppendNew(ct, CPDF_String::DataType::kIsHex); } dict->SetNewFor("BitsPerComponent", 1); - dest_pitch = (BitmapWidth + 7) / 8; + dest_pitch = fxcrt::CeilDiv(BitmapWidth, 8); } else if (bpp == 8) { size_t palette_size = pBitmap->GetRequiredPaletteSize(); if (palette_size > 0) { diff --git a/core/fpdfapi/page/cpdf_indexedcs.cpp b/core/fpdfapi/page/cpdf_indexedcs.cpp index 54426e73dd..ffff901a27 100644 --- a/core/fpdfapi/page/cpdf_indexedcs.cpp +++ b/core/fpdfapi/page/cpdf_indexedcs.cpp @@ -76,14 +76,12 @@ uint32_t CPDF_IndexedCS::v_Load(CPDF_Document* doc, if (const CPDF_String* str_obj = pTableObj->AsString()) { ByteString str_data = str_obj->GetString(); - pdfium::span str_span = str_data.unsigned_span(); - lookup_table_ = DataVector(str_span.begin(), str_span.end()); + lookup_table_ = ToDataVector(str_data.unsigned_span()); } else if (const CPDF_Stream* stream_obj = pTableObj->AsStream()) { auto acc = pdfium::MakeRetain(pdfium::WrapRetain(stream_obj)); acc->LoadAllDataFiltered(); - pdfium::span str_span = acc->GetSpan(); - lookup_table_ = DataVector(str_span.begin(), str_span.end()); + lookup_table_ = ToDataVector(acc->GetSpan()); } return 1; } diff --git a/core/fpdfapi/page/cpdf_pageimagecache.cpp b/core/fpdfapi/page/cpdf_pageimagecache.cpp index 7ca8baf147..5a59e69c9b 100644 --- a/core/fpdfapi/page/cpdf_pageimagecache.cpp +++ b/core/fpdfapi/page/cpdf_pageimagecache.cpp @@ -57,8 +57,7 @@ class CachedImage final : public CFX_DIBBase { SetPitch(image_->GetPitch()); if (image_->HasPalette()) { - pdfium::span palette = image_->GetPaletteSpan(); - palette_ = DataVector(palette.begin(), palette.end()); + palette_ = ToDataVector(image_->GetPaletteSpan()); } } @@ -102,7 +101,7 @@ class CachedImage final : public CFX_DIBBase { RetainPtr MakeCachedImage(RetainPtr image, bool realize_hint) { #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { // Ignore `realize_hint`, as `RealizeSkImage()` doesn't benefit from it. return pdfium::MakeRetain(std::move(image)); } diff --git a/core/fpdfapi/page/cpdf_sampledfunc.cpp b/core/fpdfapi/page/cpdf_sampledfunc.cpp index d235344d1b..3cf145d038 100644 --- a/core/fpdfapi/page/cpdf_sampledfunc.cpp +++ b/core/fpdfapi/page/cpdf_sampledfunc.cpp @@ -15,6 +15,7 @@ #include "core/fpdfapi/parser/cpdf_stream_acc.h" #include "core/fxcrt/cfx_bitstream.h" #include "core/fxcrt/compiler_specific.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_memory_wrappers.h" #include "core/fxcrt/fx_safe_types.h" #include "third_party/abseil-cpp/absl/container/inlined_vector.h" @@ -82,7 +83,7 @@ bool CPDF_SampledFunc::v_Init(const CPDF_Object* pObj, VisitedSet* pVisited) { encode_info_[i].sizes == 1 ? 1 : encode_info_[i].sizes - 1; } } - FX_SAFE_UINT32 nTotalSampleBytes = (nTotalSampleBits + 7) / 8; + FX_SAFE_UINT32 nTotalSampleBytes = fxcrt::CeilDiv(nTotalSampleBits, 8); if (!nTotalSampleBytes.IsValid() || nTotalSampleBytes.ValueOrDie() == 0) { return false; } diff --git a/core/fpdfapi/page/cpdf_streamparser.cpp b/core/fpdfapi/page/cpdf_streamparser.cpp index 4f8f6ccd4c..89cd925105 100644 --- a/core/fpdfapi/page/cpdf_streamparser.cpp +++ b/core/fpdfapi/page/cpdf_streamparser.cpp @@ -115,7 +115,7 @@ uint32_t DecodeInlineStream(pdfium::span src_span, std::unique_ptr pDecoder = JpegModule::CreateDecoder( src_span, width, height, 0, !pParam || pParam->GetIntegerFor("ColorTransform", 1), - /*scale_denom=*/1); + /*scale_denom=*/1, /*prefer_bgr_output=*/false); return DecodeAllScanlines(std::move(pDecoder)); } if (decoder == "CCITTFaxDecode") { @@ -135,7 +135,7 @@ uint32_t DecodeInlineStream(pdfium::span src_span, } #if defined(PDF_ENABLE_BROTLI) if (decoder == "BrotliDecode" && BrotliDecoder::GetBrotliEnabled()) { - return BrotliDecoder::Decode(src_span, orig_size).bytes_consumed; + return BrotliDecode(src_span, pParam.Get(), orig_size).bytes_consumed; } #endif @@ -214,8 +214,7 @@ RetainPtr CPDF_StreamParser::ReadInlineStream( uint32_t actual_stream_size; if (decoder.IsEmpty()) { original_size = std::min(original_size, stream_span.size()); - auto src_span = stream_span.first(original_size); - data = DataVector(src_span.begin(), src_span.end()); + data = ToDataVector(stream_span.first(original_size)); actual_stream_size = original_size; pos_ += original_size; } else { @@ -243,8 +242,7 @@ RetainPtr CPDF_StreamParser::ReadInlineStream( actual_stream_size += pos_ - saved_iteration_position; } } - auto src_span = stream_span.first(actual_stream_size); - data = DataVector(src_span.begin(), src_span.end()); + data = ToDataVector(stream_span.first(actual_stream_size)); pos_ += actual_stream_size; } dict->SetNewFor("Length", static_cast(actual_stream_size)); @@ -432,7 +430,8 @@ RetainPtr CPDF_StreamParser::ReadLeafObject() { return nullptr; } -// TODO(npm): the following methods are almost identical in cpdf_syntaxparser +// TODO(crbug.com/567627774): the following methods are almost identical in +// cpdf_syntaxparser bool CPDF_StreamParser::GetNextWord() { word_size_ = 0; if (!PositionIsInBounds()) { diff --git a/core/fpdfapi/page/jpx_decode_conversion.cpp b/core/fpdfapi/page/jpx_decode_conversion.cpp index ca4aa91554..7dc4f12c2f 100644 --- a/core/fpdfapi/page/jpx_decode_conversion.cpp +++ b/core/fpdfapi/page/jpx_decode_conversion.cpp @@ -9,6 +9,7 @@ #include "core/fpdfapi/page/cpdf_colorspace.h" #include "core/fxcodec/jpx/cjpx_decoder.h" #include "core/fxcrt/check_op.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/notreached.h" #include "core/fxcrt/retain_ptr.h" #include "core/fxge/dib/fx_dib.h" @@ -214,7 +215,7 @@ std::optional JpxDecodeConversion::Create( conversion.width_ = jpx_info.width; } else { conversion.format_ = FXDIB_Format::kBgr; - conversion.width_ = (jpx_info.width * jpx_info.channels + 2) / 3; + conversion.width_ = fxcrt::CeilDiv(jpx_info.width * jpx_info.channels, 3); } return conversion; diff --git a/core/fpdfapi/parser/BUILD.gn b/core/fpdfapi/parser/BUILD.gn index e174b66c34..d73c1e737b 100644 --- a/core/fpdfapi/parser/BUILD.gn +++ b/core/fpdfapi/parser/BUILD.gn @@ -105,7 +105,6 @@ source_set("parser") { "../../../constants", "../../fdrm", "../../fxcodec", - "../edit:contentstream_write_utils", ] public_deps = [ "../../fxcrt" ] allow_circular_includes_from = [] diff --git a/core/fpdfapi/parser/cpdf_crypto_handler.cpp b/core/fpdfapi/parser/cpdf_crypto_handler.cpp index ab9921c594..8f0d3acc75 100644 --- a/core/fpdfapi/parser/cpdf_crypto_handler.cpp +++ b/core/fpdfapi/parser/cpdf_crypto_handler.cpp @@ -69,7 +69,7 @@ DataVector CPDF_CryptoHandler::EncryptContent( uint32_t gennum, pdfium::span source) const { if (cipher_ == Cipher::kNone) { - return DataVector(source.begin(), source.end()); + return ToDataVector(source); } std::array realkey; size_t realkeylen = realkey.size(); diff --git a/core/fpdfapi/parser/cpdf_number.cpp b/core/fpdfapi/parser/cpdf_number.cpp index 05cd66a01c..1b368bbe80 100644 --- a/core/fpdfapi/parser/cpdf_number.cpp +++ b/core/fpdfapi/parser/cpdf_number.cpp @@ -8,11 +8,11 @@ #include -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" #include "core/fpdfapi/parser/cpdf_write_generation.h" #include "core/fxcrt/check.h" #include "core/fxcrt/fx_stream.h" #include "core/fxcrt/fx_string_wrappers.h" +#include "core/fxcrt/stream_write_utils.h" namespace { diff --git a/core/fpdfapi/parser/cpdf_object_stream_unittest.cpp b/core/fpdfapi/parser/cpdf_object_stream_unittest.cpp index 5360ddf141..4cb2a51f55 100644 --- a/core/fpdfapi/parser/cpdf_object_stream_unittest.cpp +++ b/core/fpdfapi/parser/cpdf_object_stream_unittest.cpp @@ -4,7 +4,6 @@ #include "core/fpdfapi/parser/cpdf_object_stream.h" -#include #include #include "core/fpdfapi/parser/cpdf_dictionary.h" @@ -39,7 +38,7 @@ TEST(ObjectStreamTest, StreamDictNormal) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); auto obj_stream = CPDF_ObjectStream::Create(std::move(stream)); ASSERT_TRUE(obj_stream); @@ -82,9 +81,9 @@ TEST(ObjectStreamTest, StreamDictNormal) { TEST(ObjectStreamTest, StreamEmptyDict) { ByteStringView contents_view(kNormalStreamContent); - auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), - pdfium::MakeRetain()); + auto stream = + pdfium::MakeRetain(ToDataVector(contents_view), + pdfium::MakeRetain()); EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream))); } @@ -95,7 +94,7 @@ TEST(ObjectStreamTest, StreamDictNoType) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream))); } @@ -107,7 +106,7 @@ TEST(ObjectStreamTest, StreamDictWrongType) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream))); } @@ -119,7 +118,7 @@ TEST(ObjectStreamTest, StreamDictWrongTypeValue) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream))); } @@ -130,7 +129,7 @@ TEST(ObjectStreamTest, StreamDictNoCount) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream))); } @@ -142,7 +141,7 @@ TEST(ObjectStreamTest, StreamDictFloatCount) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream))); } @@ -154,7 +153,7 @@ TEST(ObjectStreamTest, StreamDictNegativeCount) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream))); } @@ -166,7 +165,7 @@ TEST(ObjectStreamTest, StreamDictCountTooBig) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream))); } @@ -177,7 +176,7 @@ TEST(ObjectStreamTest, StreamDictNoOffset) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream))); } @@ -189,7 +188,7 @@ TEST(ObjectStreamTest, StreamDictFloatOffset) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream))); } @@ -201,7 +200,7 @@ TEST(ObjectStreamTest, StreamDictNegativeOffset) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); EXPECT_FALSE(CPDF_ObjectStream::Create(std::move(stream))); } @@ -214,7 +213,7 @@ TEST(ObjectStreamTest, StreamDictOffsetTooBig) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); auto obj_stream = CPDF_ObjectStream::Create(std::move(stream)); ASSERT_TRUE(obj_stream); @@ -237,7 +236,7 @@ TEST(ObjectStreamTest, StreamDictTooFewCount) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); auto obj_stream = CPDF_ObjectStream::Create(std::move(stream)); ASSERT_TRUE(obj_stream); @@ -269,7 +268,7 @@ TEST(ObjectStreamTest, StreamDictTooManyObject) { ByteStringView contents_view(kNormalStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); auto obj_stream = CPDF_ObjectStream::Create(std::move(stream)); ASSERT_TRUE(obj_stream); @@ -297,7 +296,7 @@ TEST(ObjectStreamTest, StreamDictGarbageObjNum) { const char kStreamContent[] = "10 0 hi 14 12 21<>[1 2 3]4"; ByteStringView contents_view(kStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); auto obj_stream = CPDF_ObjectStream::Create(std::move(stream)); ASSERT_TRUE(obj_stream); @@ -315,7 +314,7 @@ TEST(ObjectStreamTest, StreamDictGarbageObjectOffset) { const char kStreamContent[] = "10 0 11 hi 12 21<>[1 2 3]4"; ByteStringView contents_view(kStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); auto obj_stream = CPDF_ObjectStream::Create(std::move(stream)); ASSERT_TRUE(obj_stream); @@ -348,7 +347,7 @@ TEST(ObjectStreamTest, StreamDictNegativeObjectOffset) { const char kStreamContent[] = "10 0 11 -1 12 21<>[1 2 3]4"; ByteStringView contents_view(kStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); auto obj_stream = CPDF_ObjectStream::Create(std::move(stream)); ASSERT_TRUE(obj_stream); @@ -371,7 +370,7 @@ TEST(ObjectStreamTest, StreamDictObjectOffsetTooBig) { const char kStreamContent[] = "10 0 11 999 12 21<>[1 2 3]4"; ByteStringView contents_view(kStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); auto obj_stream = CPDF_ObjectStream::Create(std::move(stream)); ASSERT_TRUE(obj_stream); @@ -394,7 +393,7 @@ TEST(ObjectStreamTest, StreamDictDuplicateObjNum) { const char kStreamContent[] = "10 0 10 14 12 21<>[1 2 3]4"; ByteStringView contents_view(kStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); auto obj_stream = CPDF_ObjectStream::Create(std::move(stream)); ASSERT_TRUE(obj_stream); @@ -437,7 +436,7 @@ TEST(ObjectStreamTest, StreamDictUnorderedObjectNumbers) { const char kStreamContent[] = "11 0 12 14 10 21<>[1 2 3]4"; ByteStringView contents_view(kStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); auto obj_stream = CPDF_ObjectStream::Create(std::move(stream)); ASSERT_TRUE(obj_stream); @@ -479,7 +478,7 @@ TEST(ObjectStreamTest, StreamDictUnorderedObjectOffsets) { const char kStreamContent[] = "10 21 11 0 12 14<>[1 2 3]4"; ByteStringView contents_view(kStreamContent); auto stream = pdfium::MakeRetain( - DataVector(contents_view.begin(), contents_view.end()), dict); + ToDataVector(contents_view), dict); auto obj_stream = CPDF_ObjectStream::Create(std::move(stream)); ASSERT_TRUE(obj_stream); diff --git a/core/fpdfapi/parser/cpdf_stream_acc.cpp b/core/fpdfapi/parser/cpdf_stream_acc.cpp index 0f175e0186..78c5652433 100644 --- a/core/fpdfapi/parser/cpdf_stream_acc.cpp +++ b/core/fpdfapi/parser/cpdf_stream_acc.cpp @@ -174,9 +174,7 @@ DataVector CPDF_StreamAcc::DetachData() { if (is_owned()) { return std::move(std::get>(data_)); } - - auto span = std::get>(data_); - return DataVector(span.begin(), span.end()); + return ToDataVector(std::get>(data_)); } void CPDF_StreamAcc::ProcessRawData() { diff --git a/core/fpdfapi/parser/fpdf_parser_decode.cpp b/core/fpdfapi/parser/fpdf_parser_decode.cpp index 962f9d78ac..fa47584f7e 100644 --- a/core/fpdfapi/parser/fpdf_parser_decode.cpp +++ b/core/fpdfapi/parser/fpdf_parser_decode.cpp @@ -40,21 +40,6 @@ namespace { const uint32_t kMaxStreamSize = 20 * 1024 * 1024; -bool CheckFlateDecodeParams(int Colors, int BitsPerComponent, int Columns) { - if (Colors < 0 || BitsPerComponent < 0 || Columns < 0) { - return false; - } - - FX_SAFE_INT32 check = Columns; - check *= Colors; - check *= BitsPerComponent; - if (!check.IsValid()) { - return false; - } - - return check.ValueOrDie() <= INT_MAX - 7; -} - uint8_t GetA85Result(uint32_t res, size_t i) { return static_cast(res >> (3 - i) * 8); } @@ -121,6 +106,36 @@ bool ValidateDecoderPipeline(const CPDF_Array* pDecoders) { return true; } +std::optional GetAndCheckDecodeParams( + const CPDF_Dictionary* params) { + fxcodec::DecodeParams ret; + if (!params) { + return ret; + } + ret.predictor = params->GetIntegerFor("Predictor"); + ret.early_change = !!params->GetIntegerFor("EarlyChange", 1); + ret.colors = params->GetIntegerFor("Colors", 1); + ret.bits_per_component = params->GetIntegerFor("BitsPerComponent", 8); + ret.columns = params->GetIntegerFor("Columns", 1); + + if (ret.colors < 0 || ret.bits_per_component < 0 || ret.columns < 0) { + return std::nullopt; + } + + FX_SAFE_INT32 check = ret.columns; + check *= ret.colors; + check *= ret.bits_per_component; + if (!check.IsValid()) { + return std::nullopt; + } + + if (check.ValueOrDie() > INT_MAX - 7) { + return std::nullopt; + } + + return ret; +} + DataAndBytesConsumed A85Decode(pdfium::span src_span) { if (src_span.empty()) { return {DataVector(), 0u}; @@ -348,46 +363,47 @@ std::unique_ptr CreateFlateDecoder( int nComps, int bpc, const CPDF_Dictionary* pParams) { - int predictor = 0; - int Colors = 0; - int BitsPerComponent = 0; - int Columns = 0; - if (pParams) { - predictor = pParams->GetIntegerFor("Predictor"); - Colors = pParams->GetIntegerFor("Colors", 1); - BitsPerComponent = pParams->GetIntegerFor("BitsPerComponent", 8); - Columns = pParams->GetIntegerFor("Columns", 1); - if (!CheckFlateDecodeParams(Colors, BitsPerComponent, Columns)) { - return nullptr; - } + std::optional decode_params = + GetAndCheckDecodeParams(pParams); + if (!decode_params.has_value()) { + return nullptr; + } + const fxcodec::DecodeParams& decode_vars = decode_params.value(); + return FlateModule::CreateDecoder( + src_span, width, height, nComps, bpc, decode_vars.predictor, + decode_vars.colors, decode_vars.bits_per_component, decode_vars.columns); +} + +#if defined(PDF_ENABLE_BROTLI) +DataAndBytesConsumed BrotliDecode(pdfium::span src_span, + const CPDF_Dictionary* params, + uint32_t estimated_size) { + std::optional decode_params = + GetAndCheckDecodeParams(params); + if (!decode_params.has_value()) { + return {DataVector(), FX_INVALID_OFFSET}; } - return FlateModule::CreateDecoder(src_span, width, height, nComps, bpc, - predictor, Colors, BitsPerComponent, - Columns); + const fxcodec::DecodeParams& decode_vars = decode_params.value(); + return BrotliDecoder::Decode( + src_span, decode_vars.predictor, decode_vars.colors, + decode_vars.bits_per_component, decode_vars.columns, estimated_size); } +#endif DataAndBytesConsumed FlateOrLZWDecode(bool use_lzw, pdfium::span src_span, const CPDF_Dictionary* pParams, uint32_t estimated_size) { - int predictor = 0; - int Colors = 0; - int BitsPerComponent = 0; - int Columns = 0; - bool bEarlyChange = true; - if (pParams) { - predictor = pParams->GetIntegerFor("Predictor"); - bEarlyChange = !!pParams->GetIntegerFor("EarlyChange", 1); - Colors = pParams->GetIntegerFor("Colors", 1); - BitsPerComponent = pParams->GetIntegerFor("BitsPerComponent", 8); - Columns = pParams->GetIntegerFor("Columns", 1); - if (!CheckFlateDecodeParams(Colors, BitsPerComponent, Columns)) { - return {DataVector(), FX_INVALID_OFFSET}; - } - } - return FlateModule::FlateOrLZWDecode(use_lzw, src_span, bEarlyChange, - predictor, Colors, BitsPerComponent, - Columns, estimated_size); + std::optional decode_params = + GetAndCheckDecodeParams(pParams); + if (!decode_params.has_value()) { + return {DataVector(), FX_INVALID_OFFSET}; + } + const fxcodec::DecodeParams& decode_vars = decode_params.value(); + return FlateModule::FlateOrLZWDecode( + use_lzw, src_span, decode_vars.early_change, decode_vars.predictor, + decode_vars.colors, decode_vars.bits_per_component, decode_vars.columns, + estimated_size); } std::optional GetDecoderArray( @@ -498,13 +514,8 @@ std::optional PDF_DataDecode( } #if defined(PDF_ENABLE_BROTLI) else if (decoder == "BrotliDecode" && BrotliDecoder::GetBrotliEnabled()) { - if (bImageAcc && i == nSize - 1) { - result.image_encoding = "BrotliDecode"; - result.image_params = std::move(pParam); - return result; - } DataAndBytesConsumed decode_result = - BrotliDecoder::Decode(last_span, estimated_size); + BrotliDecode(last_span, pParam, estimated_size); new_buf = std::move(decode_result.data); bytes_consumed = decode_result.bytes_consumed; } diff --git a/core/fpdfapi/parser/fpdf_parser_decode.h b/core/fpdfapi/parser/fpdf_parser_decode.h index 56279b3e81..8e364938ba 100644 --- a/core/fpdfapi/parser/fpdf_parser_decode.h +++ b/core/fpdfapi/parser/fpdf_parser_decode.h @@ -26,8 +26,19 @@ class CPDF_Dictionary; class CPDF_Object; namespace fxcodec { + class ScanlineDecoder; -} + +// Parameters come from ISO 32000-1:2008, table 8. +struct DecodeParams { + int predictor = 0; + int colors = 1; + int bits_per_component = 8; + int columns = 1; + bool early_change = true; +}; + +} // namespace fxcodec // Indexed by 8-bit char code, contains unicode code points. extern const std::array kPDFDocEncoding; @@ -60,6 +71,12 @@ fxcodec::DataAndBytesConsumed A85Decode(pdfium::span src_span); fxcodec::DataAndBytesConsumed HexDecode(pdfium::span src_span); +#if defined(PDF_ENABLE_BROTLI) +fxcodec::DataAndBytesConsumed BrotliDecode(pdfium::span src_span, + const CPDF_Dictionary* params, + uint32_t estimated_size); +#endif + fxcodec::DataAndBytesConsumed FlateOrLZWDecode( bool use_lzw, pdfium::span src_span, @@ -76,6 +93,11 @@ using DecoderArray = std::optional GetDecoderArray( RetainPtr dict); +// May return a struct for the DecodeParams. Returns default values if params +// is a nullptr and returns std::nullopt if the DecodeParams are invalid. +std::optional GetAndCheckDecodeParams( + const CPDF_Dictionary* params); + struct PDFDataDecodeResult { PDFDataDecodeResult(); PDFDataDecodeResult(DataVector data, diff --git a/core/fpdfapi/parser/fpdf_parser_decode_embeddertest.cpp b/core/fpdfapi/parser/fpdf_parser_decode_embeddertest.cpp index 24c3b215cb..0d8aa41805 100644 --- a/core/fpdfapi/parser/fpdf_parser_decode_embeddertest.cpp +++ b/core/fpdfapi/parser/fpdf_parser_decode_embeddertest.cpp @@ -24,12 +24,14 @@ TEST_F(FPDFParserDecodeEmbedderTest, Bug552046) { TEST_F(FPDFParserDecodeEmbedderTest, Bug555784) { // Tests bad input to the run length decoder that caused a heap overflow. - // Should not cause a crash when rendered. + // Should not cause a crash when rendered. The image is 81915 pixels wide + // with 3277 DeviceN components at 16 bits each, which is a valid but + // enourmous pitch, so its one scanline does get decoded and drawn. ASSERT_TRUE(OpenDocument("bug_555784.pdf")); ScopedPage page = LoadScopedPage(0); ASSERT_TRUE(page); ScopedFPDFBitmap bitmap = RenderLoadedPage(page.get()); - CompareBitmap(bitmap.get(), pdfium::kBlankPage612By792Png); + CompareBitmap(bitmap.get(), "bug_555784"); } TEST_F(FPDFParserDecodeEmbedderTest, Bug455199) { diff --git a/core/fpdfapi/render/cpdf_docrenderdata.cpp b/core/fpdfapi/render/cpdf_docrenderdata.cpp index 73f42ae5e8..90cabd1e28 100644 --- a/core/fpdfapi/render/cpdf_docrenderdata.cpp +++ b/core/fpdfapi/render/cpdf_docrenderdata.cpp @@ -127,8 +127,8 @@ RetainPtr CPDF_DocRenderData::CreateTransferFunc( } for (uint32_t i = 0; i < 3; ++i) { - pFuncs[2 - i] = CPDF_Function::Load(pArray->GetDirectObjectAt(i)); - if (!pFuncs[2 - i]) { + pFuncs[i] = CPDF_Function::Load(pArray->GetDirectObjectAt(i)); + if (!pFuncs[i]) { return nullptr; } } @@ -161,7 +161,7 @@ RetainPtr CPDF_DocRenderData::CreateTransferFunc( continue; } pFuncs[i]->Call(pdfium::span_from_ref(input), output); - size_t o = FXSYS_roundf(output[0] * 255); + uint8_t o = std::clamp(FXSYS_roundf(output[0] * 255.0f), 0, 255); if (o != v) { bIdentity = false; } @@ -174,7 +174,7 @@ RetainPtr CPDF_DocRenderData::CreateTransferFunc( if (pFuncs[0]->OutputCount() <= kMaxOutputs) { pFuncs[0]->Call(pdfium::span_from_ref(input), output); } - size_t o = FXSYS_roundf(output[0] * 255); + uint8_t o = std::clamp(FXSYS_roundf(output[0] * 255.0f), 0, 255); if (o != v) { bIdentity = false; } diff --git a/core/fpdfapi/render/cpdf_docrenderdata_unittest.cpp b/core/fpdfapi/render/cpdf_docrenderdata_unittest.cpp index 4a5f85b22e..71f740304d 100644 --- a/core/fpdfapi/render/cpdf_docrenderdata_unittest.cpp +++ b/core/fpdfapi/render/cpdf_docrenderdata_unittest.cpp @@ -24,24 +24,20 @@ using ::testing::ElementsAreArray; namespace { constexpr uint8_t kExpectedType0FunctionSamples[] = { - 0, 3, 6, 9, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, - 46, 49, 52, 55, 58, 60, 63, 66, 68, 71, 74, 76, 79, 81, 84, - 86, 88, 90, 93, 95, 97, 99, 101, 103, 105, 106, 108, 110, 111, 113, - 114, 115, 117, 118, 119, 120, 121, 122, 123, 124, 125, 125, 126, 126, 127, - 127, 127, 127, 127, 127, 127, 127, 127, 127, 126, 126, 125, 125, 124, 123, - 123, 122, 121, 120, 119, 117, 116, 115, 113, 112, 110, 109, 107, 105, 104, - 102, 100, 98, 96, 94, 92, 89, 87, 85, 82, 80, 77, 75, 72, 70, - 67, 64, 62, 59, 56, 53, 50, 48, 45, 42, 39, 36, 33, 30, 27, - 23, 20, 17, 14, 11, 8, 5, 2, 254, 251, 248, 245, 242, 239, 236, - 233, 229, 226, 223, 220, 217, 214, 211, 208, 206, 203, 200, 197, 194, 192, - 189, 186, 184, 181, 179, 176, 174, 171, 169, 167, 164, 162, 160, 158, 156, - 154, 152, 151, 149, 147, 146, 144, 143, 141, 140, 139, 137, 136, 135, 134, - 133, 133, 132, 131, 131, 130, 130, 129, 129, 129, 129, 129, 129, 129, 129, - 129, 129, 130, 130, 131, 131, 132, 133, 134, 135, 136, 137, 138, 139, 141, - 142, 143, 145, 146, 148, 150, 151, 153, 155, 157, 159, 161, 163, 166, 168, - 170, 172, 175, 177, 180, 182, 185, 188, 190, 193, 196, 198, 201, 204, 207, - 210, 213, 216, 219, 222, 225, 228, 231, 234, 237, 240, 243, 247, 250, 253, - 0}; + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26}; constexpr uint8_t kExpectedType2FunctionSamples[] = { 26, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, @@ -60,20 +56,24 @@ constexpr uint8_t kExpectedType2FunctionSamples[] = { 13, 13, 13, 13, 13, 13, 13, 13, 13}; constexpr uint8_t kExpectedType4FunctionSamples[] = { - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, 26}; + 0, 3, 6, 9, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, + 46, 49, 52, 55, 58, 60, 63, 66, 68, 71, 74, 76, 79, 81, 84, + 86, 88, 90, 93, 95, 97, 99, 101, 103, 105, 106, 108, 110, 111, 113, + 114, 115, 117, 118, 119, 120, 121, 122, 123, 124, 125, 125, 126, 126, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 126, 126, 125, 125, 124, 123, + 123, 122, 121, 120, 119, 117, 116, 115, 113, 112, 110, 109, 107, 105, 104, + 102, 100, 98, 96, 94, 92, 89, 87, 85, 82, 80, 77, 75, 72, 70, + 67, 64, 62, 59, 56, 53, 50, 48, 45, 42, 39, 36, 33, 30, 27, + 23, 20, 17, 14, 11, 8, 5, 2, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0}; RetainPtr CreateType0FunctionStreamReference( CPDF_IndirectObjectHolder& holder) { @@ -211,16 +211,57 @@ TEST(CPDFDocRenderDataTest, TransferFunctionArray) { ElementsAreArray(kExpectedType2FunctionSamples)); EXPECT_THAT(func->GetSamplesB(), ElementsAreArray(kExpectedType4FunctionSamples)); - EXPECT_EQ(0x001a0d00u, func->TranslateColor(0x00ffffff)); - EXPECT_EQ(0x001a1a00u, func->TranslateColor(0x00ff0000)); - EXPECT_EQ(0x00190d00u, func->TranslateColor(0x0000ff00)); - EXPECT_EQ(0x00191a00u, func->TranslateColor(0x000000ff)); - EXPECT_EQ(0x001a0f87u, func->TranslateColor(0x00cccccc)); - EXPECT_EQ(0x0019176du, func->TranslateColor(0x00123456)); - EXPECT_EQ(0x001a0d00u, func->TranslateColor(0xffffffff)); - EXPECT_EQ(0x00191a00u, func->TranslateColor(0xff000000)); - EXPECT_EQ(0x001a0d00u, func->TranslateColor(0xccffffff)); - EXPECT_EQ(0x00191a00u, func->TranslateColor(0x99000000)); + EXPECT_EQ(0x00000d1au, func->TranslateColor(0x00ffffff)); + EXPECT_EQ(0x00001a19u, func->TranslateColor(0x00ff0000)); + EXPECT_EQ(0x00000d19u, func->TranslateColor(0x0000ff00)); + EXPECT_EQ(0x00001a1au, func->TranslateColor(0x000000ff)); + EXPECT_EQ(0x00000f1au, func->TranslateColor(0x00cccccc)); + EXPECT_EQ(0x00371719u, func->TranslateColor(0x00123456)); + EXPECT_EQ(0x00000d1au, func->TranslateColor(0xffffffff)); + EXPECT_EQ(0x00001a19u, func->TranslateColor(0xff000000)); + EXPECT_EQ(0x00000d1au, func->TranslateColor(0xccffffff)); + EXPECT_EQ(0x00001a19u, func->TranslateColor(0x99000000)); +} + +TEST(CPDFDocRenderDataTest, TransferFunctionClamping) { + // Type 2 exponential function: f(x) = C0 + x^N * (C1 - C0). + // With C0 = -1.0, C1 = 2.0, N = 1: + // f(0.0) = -1.0 (negative, underflow) + // f(0.5) = 0.5 + // f(1.0) = 2.0 (greater than 1.0, overflow) + auto func_dict = pdfium::MakeRetain(); + func_dict->SetNewFor("FunctionType", 2); + func_dict->SetNewFor("N", 1); + + auto domain_array = func_dict->SetNewFor("Domain"); + domain_array->AppendNew(0); + domain_array->AppendNew(1); + + auto c0_array = func_dict->SetNewFor("C0"); + c0_array->AppendNew(-1.0f); + + auto c1_array = func_dict->SetNewFor("C1"); + c1_array->AppendNew(2.0f); + + TestDocRenderData render_data; + auto func = render_data.CreateTransferFuncForTesting(func_dict); + ASSERT_TRUE(func); + EXPECT_FALSE(func->GetIdentity()); + + // Input 0 gives -1.0f, clamped to 0. + EXPECT_EQ(0, func->GetSamplesR()[0]); + EXPECT_EQ(0, func->GetSamplesG()[0]); + EXPECT_EQ(0, func->GetSamplesB()[0]); + + // Input 128 gives 0.505882f -> 129. + EXPECT_EQ(129, func->GetSamplesR()[128]); + EXPECT_EQ(129, func->GetSamplesG()[128]); + EXPECT_EQ(129, func->GetSamplesB()[128]); + + // Input 255 gives 2.0f, clamped to 255. + EXPECT_EQ(255, func->GetSamplesR()[255]); + EXPECT_EQ(255, func->GetSamplesG()[255]); + EXPECT_EQ(255, func->GetSamplesB()[255]); } TEST(CPDFDocRenderDataTest, BadTransferFunctions) { diff --git a/core/fpdfapi/render/cpdf_imagerenderer.cpp b/core/fpdfapi/render/cpdf_imagerenderer.cpp index 239bc544b4..6942aa3763 100644 --- a/core/fpdfapi/render/cpdf_imagerenderer.cpp +++ b/core/fpdfapi/render/cpdf_imagerenderer.cpp @@ -410,7 +410,7 @@ bool CPDF_ImageRenderer::DrawMaskedImage() { } #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer() && + if (CFX_GEModule::IsSkiaRenderer() && render_status_->GetRenderDevice()->SetBitsWithMask( bitmap_device->GetBitmap(), mask_bitmap, rect.left, rect.top, alpha_, blend_type_)) { diff --git a/core/fpdfapi/render/cpdf_renderstatus.cpp b/core/fpdfapi/render/cpdf_renderstatus.cpp index cd4f6743ea..2aa56af5c0 100644 --- a/core/fpdfapi/render/cpdf_renderstatus.cpp +++ b/core/fpdfapi/render/cpdf_renderstatus.cpp @@ -58,7 +58,6 @@ #include "core/fxcrt/data_vector.h" #include "core/fxcrt/epdf_tls.h" #include "core/fxcrt/fx_2d_size.h" -#include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/fx_system.h" #include "core/fxcrt/notreached.h" #include "core/fxcrt/span.h" @@ -142,7 +141,7 @@ FXDIB_Format GetFormatForLuminosity(bool is_luminosity) { return FXDIB_Format::kBgrx; #else // BUILDFLAG(IS_APPLE) #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { return FXDIB_Format::kBgrx; } #endif // defined(PDF_USE_SKIA) @@ -960,7 +959,8 @@ bool CPDF_RenderStatus::ProcessText(CPDF_TextObject* textobj, pFont.Get(), font_size, text_matrix, fill_argb, options_); } -// TODO(npm): Font fallback for type 3 fonts? (Completely separate code!!) +// TODO(crbug.com/567622442): Font fallback for type 3 fonts? (Completely +// separate code!!) bool CPDF_RenderStatus::ProcessType3Text(CPDF_TextObject* textobj, const CFX_Matrix& mtObj2Device) { CPDF_Type3Font* pType3Font = textobj->text_state().GetFont()->AsType3Font(); @@ -1105,20 +1105,16 @@ bool CPDF_RenderStatus::ProcessType3Text(CPDF_TextObject* textobj, CFX_Point origin(FXSYS_roundf(matrix.e), FXSYS_roundf(matrix.f)); if (glyphs.empty()) { - FX_SAFE_INT32 left = origin.x; - left += pBitmap->left(); - if (!left.IsValid()) { + TextGlyphPos glyph; + glyph.glyph_ = pBitmap; + glyph.origin_ = origin; + std::optional glyph_origin = glyph.GetOrigin({0, 0}); + if (!glyph_origin.has_value()) { continue; } - FX_SAFE_INT32 top = origin.y; - top -= pBitmap->top(); - if (!top.IsValid()) { - continue; - } - - device_->SetBitMask(pBitmap->GetBitmap(), left.ValueOrDie(), - top.ValueOrDie(), fill_argb); + device_->SetBitMask(pBitmap->GetBitmap(), glyph_origin->x, + glyph_origin->y, fill_argb); } else { glyphs[iChar].glyph_ = pBitmap; glyphs[iChar].origin_ = origin; @@ -1361,7 +1357,7 @@ void CPDF_RenderStatus::CompositeDIBitmap( } else { if (alpha != 1.0f) { #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { CFX_Matrix matrix = CFX_RenderDevice::GetFlipMatrix( bitmap->GetWidth(), bitmap->GetHeight(), left, top); device_->StartDIBits(std::move(bitmap), alpha, /*argb=*/0, matrix, @@ -1540,7 +1536,7 @@ RetainPtr CPDF_RenderStatus::LoadSMask( for (size_t i = 0; i < transfers.size(); ++i) { float input = i / 255.0f; pFunc->Call(pdfium::span_from_ref(input), results); - transfers[i] = FXSYS_roundf(results[0] * 255); + transfers[i] = std::clamp(FXSYS_roundf(results[0] * 255.0f), 0, 255); } } else { // Fill |transfers| with 0, 1, ... N. diff --git a/core/fpdfapi/render/cpdf_rendertiling.cpp b/core/fpdfapi/render/cpdf_rendertiling.cpp index 5f48594f2b..f01364ca06 100644 --- a/core/fpdfapi/render/cpdf_rendertiling.cpp +++ b/core/fpdfapi/render/cpdf_rendertiling.cpp @@ -26,7 +26,17 @@ namespace { -RetainPtr DrawPatternBitmap( +std::optional CheckedFloatToInt(float value) { + if (!pdfium::IsValueInRangeForNumericType(value)) { + return std::nullopt; + } + return static_cast(value); +} + +} // namespace + +// static +RetainPtr CPDF_RenderTiling::DrawPatternBitmap( CPDF_Document* doc, CPDF_PageImageCache* pCache, CPDF_TilingPattern* pPattern, @@ -72,15 +82,6 @@ RetainPtr DrawPatternBitmap( return pBitmap; } -std::optional CheckedFloatToInt(float value) { - if (!pdfium::IsValueInRangeForNumericType(value)) { - return std::nullopt; - } - return static_cast(value); -} - -} // namespace - // static RetainPtr CPDF_RenderTiling::Draw( CPDF_RenderStatus* pRenderStatus, diff --git a/core/fpdfapi/render/cpdf_rendertiling.h b/core/fpdfapi/render/cpdf_rendertiling.h index 34fb56bf43..2a124461c2 100644 --- a/core/fpdfapi/render/cpdf_rendertiling.h +++ b/core/fpdfapi/render/cpdf_rendertiling.h @@ -7,11 +7,14 @@ #ifndef CORE_FPDFAPI_RENDER_CPDF_RENDERTILING_H_ #define CORE_FPDFAPI_RENDER_CPDF_RENDERTILING_H_ +#include "core/fpdfapi/render/cpdf_renderoptions.h" #include "core/fxcrt/retain_ptr.h" class CFX_DIBitmap; class CFX_Matrix; +class CPDF_Document; class CPDF_Form; +class CPDF_PageImageCache; class CPDF_PageObject; class CPDF_RenderStatus; class CPDF_TilingPattern; @@ -27,6 +30,17 @@ class CPDF_RenderTiling { const FX_RECT& clip_box, bool bStroke); + // Renders a single cell of pPattern into a width x height bitmap. + static RetainPtr DrawPatternBitmap( + CPDF_Document* doc, + CPDF_PageImageCache* pCache, + CPDF_TilingPattern* pPattern, + CPDF_Form* pPatternForm, + const CFX_Matrix& mtObject2Device, + int width, + int height, + const CPDF_RenderOptions::Options& draw_options); + CPDF_RenderTiling() = delete; CPDF_RenderTiling(const CPDF_RenderTiling&) = delete; CPDF_RenderTiling& operator=(const CPDF_RenderTiling&) = delete; diff --git a/core/fpdfapi/render/cpdf_type3cache.cpp b/core/fpdfapi/render/cpdf_type3cache.cpp index 4dbfabdbd0..a9308e1d5d 100644 --- a/core/fpdfapi/render/cpdf_type3cache.cpp +++ b/core/fpdfapi/render/cpdf_type3cache.cpp @@ -165,5 +165,6 @@ std::unique_ptr CPDF_Type3Cache::RenderGlyph( if (!pResBitmap) { return nullptr; } - return std::make_unique(left, -top, std::move(pResBitmap)); + return std::make_unique(CFX_Point(left, -top), + std::move(pResBitmap)); } diff --git a/core/fpdfapi/render/fpdf_progressive_render_embeddertest.cpp b/core/fpdfapi/render/fpdf_progressive_render_embeddertest.cpp index 994ba8066c..32cbbee3e4 100644 --- a/core/fpdfapi/render/fpdf_progressive_render_embeddertest.cpp +++ b/core/fpdfapi/render/fpdf_progressive_render_embeddertest.cpp @@ -300,11 +300,8 @@ void FPDFProgressiveRenderEmbedderTest::VerifyRenderingWithColorScheme( ScopedFPDFBitmap bitmap = RenderPageWithForcedColorScheme( page.get(), form_handle(), flags, color_scheme, background_color); ASSERT_TRUE(bitmap); - if (fuzzy) { - CompareBitmapWithFuzzyExpectationSuffix(bitmap.get(), basename); - } else { - CompareBitmapWithExpectationSuffix(bitmap.get(), basename); - } + CompareBitmapWithExpectationSuffix( + bitmap.get(), basename, fuzzy ? kFuzzyDiffOptions : kExactDiffOptions); } TEST_F(FPDFProgressiveRenderEmbedderTest, RenderTextWithColorScheme) { diff --git a/core/fpdfdoc/BUILD.gn b/core/fpdfdoc/BUILD.gn index 09f6e022c2..879a7690a4 100644 --- a/core/fpdfdoc/BUILD.gn +++ b/core/fpdfdoc/BUILD.gn @@ -126,7 +126,6 @@ source_set("fpdfdoc") { ] deps = [ "../../constants", - "../fpdfapi/edit:contentstream_write_utils", "../fpdfapi/font", "../fpdfapi/page", "../fpdfapi/parser", diff --git a/core/fpdfdoc/cpdf_appearancegenerator_common.cpp b/core/fpdfdoc/cpdf_appearancegenerator_common.cpp index ee83b03705..05dfbb5440 100644 --- a/core/fpdfdoc/cpdf_appearancegenerator_common.cpp +++ b/core/fpdfdoc/cpdf_appearancegenerator_common.cpp @@ -20,7 +20,7 @@ #include "constants/annotation_common.h" #include "constants/transparency.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfapi/page/cpdf_form.h" #include "core/fpdfapi/page/cpdf_paintedbounds.h" #include "core/fpdfapi/parser/cpdf_array.h" diff --git a/core/fpdfdoc/cpdf_appearancegenerator_forms.cpp b/core/fpdfdoc/cpdf_appearancegenerator_forms.cpp index cb69283a7f..f728dbe543 100644 --- a/core/fpdfdoc/cpdf_appearancegenerator_forms.cpp +++ b/core/fpdfdoc/cpdf_appearancegenerator_forms.cpp @@ -20,7 +20,7 @@ #include "constants/appearance.h" #include "constants/form_fields.h" #include "constants/form_flags.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfapi/font/cpdf_font.h" #include "core/fpdfapi/page/cpdf_docpagedata.h" #include "core/fpdfapi/parser/cpdf_array.h" diff --git a/core/fpdfdoc/cpdf_appearancegenerator_freetext.cpp b/core/fpdfdoc/cpdf_appearancegenerator_freetext.cpp index f27bbcc855..544d8d7781 100644 --- a/core/fpdfdoc/cpdf_appearancegenerator_freetext.cpp +++ b/core/fpdfdoc/cpdf_appearancegenerator_freetext.cpp @@ -18,7 +18,7 @@ #include #include "constants/annotation_common.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfapi/font/cpdf_font.h" #include "core/fpdfapi/page/cpdf_docpagedata.h" #include "core/fpdfapi/parser/cpdf_array.h" diff --git a/core/fpdfdoc/cpdf_appearancegenerator_icons.cpp b/core/fpdfdoc/cpdf_appearancegenerator_icons.cpp index 6f64b20da6..bc5a48de5e 100644 --- a/core/fpdfdoc/cpdf_appearancegenerator_icons.cpp +++ b/core/fpdfdoc/cpdf_appearancegenerator_icons.cpp @@ -15,7 +15,7 @@ #include "constants/annotation_common.h" #include "constants/form_fields.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfapi/font/cpdf_font.h" #include "core/fpdfapi/page/cpdf_docpagedata.h" #include "core/fpdfapi/parser/cpdf_array.h" diff --git a/core/fpdfdoc/cpdf_appearancegenerator_internal.h b/core/fpdfdoc/cpdf_appearancegenerator_internal.h index 3678d826b9..35ad59fa2d 100644 --- a/core/fpdfdoc/cpdf_appearancegenerator_internal.h +++ b/core/fpdfdoc/cpdf_appearancegenerator_internal.h @@ -19,7 +19,7 @@ #include #include -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfdoc/cpdf_annot.h" #include "core/fpdfdoc/cpdf_annotfontmap.h" #include "core/fpdfdoc/cpdf_appearancegenerator.h" diff --git a/core/fpdfdoc/cpdf_appearancegenerator_markup.cpp b/core/fpdfdoc/cpdf_appearancegenerator_markup.cpp index 847f3bb117..b61a3cabf5 100644 --- a/core/fpdfdoc/cpdf_appearancegenerator_markup.cpp +++ b/core/fpdfdoc/cpdf_appearancegenerator_markup.cpp @@ -17,7 +17,7 @@ #include #include "constants/annotation_common.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfdoc/cpdf_annot.h" diff --git a/core/fpdfdoc/cpdf_appearancegenerator_redaction.cpp b/core/fpdfdoc/cpdf_appearancegenerator_redaction.cpp index d9e4c888ec..69510cc564 100644 --- a/core/fpdfdoc/cpdf_appearancegenerator_redaction.cpp +++ b/core/fpdfdoc/cpdf_appearancegenerator_redaction.cpp @@ -17,7 +17,7 @@ #include #include "constants/annotation_common.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfapi/font/cpdf_font.h" #include "core/fpdfapi/page/cpdf_docpagedata.h" #include "core/fpdfapi/parser/cpdf_array.h" diff --git a/core/fpdfdoc/cpdf_appearancegenerator_shapes.cpp b/core/fpdfdoc/cpdf_appearancegenerator_shapes.cpp index 916f5787d7..25c21dbb20 100644 --- a/core/fpdfdoc/cpdf_appearancegenerator_shapes.cpp +++ b/core/fpdfdoc/cpdf_appearancegenerator_shapes.cpp @@ -19,7 +19,7 @@ #include #include "constants/annotation_common.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfapi/page/cpdf_docpagedata.h" #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" diff --git a/core/fpdfdoc/cpdf_appearancegenerator_text.cpp b/core/fpdfdoc/cpdf_appearancegenerator_text.cpp index 946af28cfb..4e49dc7bd0 100644 --- a/core/fpdfdoc/cpdf_appearancegenerator_text.cpp +++ b/core/fpdfdoc/cpdf_appearancegenerator_text.cpp @@ -14,7 +14,7 @@ #include #include "constants/font_encodings.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfapi/font/cpdf_font.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_document.h" diff --git a/core/fpdfdoc/cpdf_generateap.cpp b/core/fpdfdoc/cpdf_generateap.cpp index 546f463ecf..8c920b63d0 100644 --- a/core/fpdfdoc/cpdf_generateap.cpp +++ b/core/fpdfdoc/cpdf_generateap.cpp @@ -16,7 +16,6 @@ #include "constants/font_encodings.h" #include "constants/form_fields.h" #include "constants/form_flags.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" #include "core/fpdfapi/font/cpdf_font.h" #include "core/fpdfapi/page/cpdf_docpagedata.h" #include "core/fpdfapi/parser/cpdf_array.h" @@ -40,6 +39,7 @@ #include "core/fpdfdoc/cpvt_word.h" #include "core/fxcrt/fx_string_wrappers.h" #include "core/fxcrt/notreached.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fxge/cfx_renderdevice.h" namespace { diff --git a/core/fpdfdoc/cpdf_structelement.cpp b/core/fpdfdoc/cpdf_structelement.cpp index 4448a02ff5..a7514bedd1 100644 --- a/core/fpdfdoc/cpdf_structelement.cpp +++ b/core/fpdfdoc/cpdf_structelement.cpp @@ -28,7 +28,8 @@ CPDF_StructElement::CPDF_StructElement(const CPDF_StructTree* pTree, RetainPtr dict) : tree_(pTree), dict_(std::move(dict)), - type_(tree_->GetRoleMapNameFor(dict_->GetNameFor("S").AsStringView())) { + type_(tree_->GetRoleMapNameFor(GetRawType().AsStringView()) + .AsStringView()) { LoadKids(); } @@ -40,6 +41,10 @@ CPDF_StructElement::~CPDF_StructElement() { } } +ByteString CPDF_StructElement::GetRawType() const { + return dict_->GetNameFor("S"); +} + ByteString CPDF_StructElement::GetObjType() const { return dict_->GetByteStringFor("Type"); } diff --git a/core/fpdfdoc/cpdf_structelement.h b/core/fpdfdoc/cpdf_structelement.h index 1578ade1a2..8e37c89eab 100644 --- a/core/fpdfdoc/cpdf_structelement.h +++ b/core/fpdfdoc/cpdf_structelement.h @@ -23,6 +23,7 @@ class CPDF_StructElement final : public Retainable { CONSTRUCT_VIA_MAKE_RETAIN; ByteString GetType() const { return type_; } + ByteString GetRawType() const; ByteString GetObjType() const; WideString GetAltText() const; WideString GetActualText() const; diff --git a/core/fpdfdoc/cpvt_section.cpp b/core/fpdfdoc/cpvt_section.cpp index b03c3e1c53..95378e5fe3 100644 --- a/core/fpdfdoc/cpvt_section.cpp +++ b/core/fpdfdoc/cpvt_section.cpp @@ -81,7 +81,7 @@ bool IsPunctuation(uint32_t word) { if (word >= 0x0080 && word <= 0x00FF) { return (word == 0x0082 || word == 0x0084 || word == 0x0085 || word == 0x0091 || word == 0x0092 || word == 0x0093 || - word <= 0x0094 || word == 0x0096 || word == 0x00B4 || + word == 0x0094 || word == 0x0096 || word == 0x00B4 || word == 0x00B8); } if (word >= 0x2000 && word <= 0x206F) { diff --git a/core/fpdfdoc/cpvt_section_unittest.cpp b/core/fpdfdoc/cpvt_section_unittest.cpp index 50856339c5..472a2bfdcd 100644 --- a/core/fpdfdoc/cpvt_section_unittest.cpp +++ b/core/fpdfdoc/cpvt_section_unittest.cpp @@ -323,3 +323,52 @@ TEST_F(CPVT_SectionTest, OutputLines_ParagraphSeparator) { EXPECT_LT(section.GetWordFromArray(0)->fWordX, section.GetWordFromArray(1)->fWordX); } + +TEST_F(CPVT_SectionTest, OutputLines_Multiline_CurrencySymbol) { + CPVT_VariableText vt(provider_.get()); + vt.SetFontSize(10.0f); + // Narrow width to force word wrap (stub font chars are 0.1 wide at font size + // 10). + vt.SetPlateRect(CFX_FloatRect(0, 0, 0.35f, 1000)); + vt.SetAutoReturn(true); + vt.Initialize(); + + // Test '$' (0x0024): prefix currency symbol does not divide from '1'. + // With "A $1", "A " fits on line 0 (width 0.2), but adding "$1" (width 0.2) + // exceeds 0.35. Since '$' is a prefix symbol, it stays with '1' on line 1. + { + CPVT_Section section(&vt); + PopulateSectionWithText(section, {'A', ' ', '$', '1'}); + section.Rearrange(); + + ASSERT_EQ(2, section.GetLineArraySize()); + const CPVT_Section::Line* line0 = section.GetLineFromArray(0); + ASSERT_TRUE(line0); + EXPECT_EQ(0, line0->line_info_.nBeginWordIndex); + EXPECT_EQ(1, line0->line_info_.nEndWordIndex); + + const CPVT_Section::Line* line1 = section.GetLineFromArray(1); + ASSERT_TRUE(line1); + EXPECT_EQ(2, line1->line_info_.nBeginWordIndex); + EXPECT_EQ(3, line1->line_info_.nEndWordIndex); + } + + // Test Euro (0x0080): defined as a currency symbol in IsCurrencySymbol(). + // Like '$', it does not divide from '1' and stays with '1' on line 1. + { + CPVT_Section section(&vt); + PopulateSectionWithText(section, {'A', ' ', 0x0080, '1'}); + section.Rearrange(); + + ASSERT_EQ(2, section.GetLineArraySize()); + const CPVT_Section::Line* line0 = section.GetLineFromArray(0); + ASSERT_TRUE(line0); + EXPECT_EQ(0, line0->line_info_.nBeginWordIndex); + EXPECT_EQ(1, line0->line_info_.nEndWordIndex); + + const CPVT_Section::Line* line1 = section.GetLineFromArray(1); + ASSERT_TRUE(line1); + EXPECT_EQ(2, line1->line_info_.nBeginWordIndex); + EXPECT_EQ(3, line1->line_info_.nEndWordIndex); + } +} diff --git a/core/fpdftext/cpdf_textpage.cpp b/core/fpdftext/cpdf_textpage.cpp index dbb586ed99..70d098df34 100644 --- a/core/fpdftext/cpdf_textpage.cpp +++ b/core/fpdftext/cpdf_textpage.cpp @@ -116,8 +116,7 @@ DataVector GetUnicodeNormalization(wchar_t wch) { wFind = maps.front(); maps = maps.subspan<1u>(); } - const auto range = maps.first(static_cast(wFind)); - return DataVector(range.begin(), range.end()); + return ToDataVector(maps.first(static_cast(wFind))); } float MaskPercentFilled(const std::vector& mask, @@ -401,6 +400,24 @@ CFX_FloatRect GetLooseBounds(const CPDF_TextPage::CharInfo& charinfo) { return charinfo.char_box(); } +// EmbedPDF(bug-fix): whether `chars`, the line so far, ends in right-to-left +// text that isn't /ActualText. Spaces and other characters without a strong +// direction are skipped. +bool FollowsLiteralRightToLeftText( + pdfium::span chars) { + for (size_t i = chars.size(); i > 0; --i) { + const CPDF_TextPage::CharInfo& info = chars[i - 1]; + const BidiClass bidi = pdfium::unicode::GetBidiClass(info.unicode()); + if (bidi == BidiClass::kL) { + return false; + } + if (bidi == BidiClass::kR || bidi == BidiClass::kAL) { + return info.char_type() != CPDF_TextPage::CharType::kActualText; + } + } + return false; +} + } // namespace CPDF_TextPage::TransformedTextObject::TransformedTextObject() = default; @@ -951,7 +968,7 @@ void CPDF_TextPage::CloseTempLine() { } prev_char_is_space = true; } - CFX_BidiString bidi(str, /*auto_order=*/false); + CFX_BidiString bidi(str, /*auto_order=*/true); if (rtl_) { bidi.SetOverallDirectionRight(); } @@ -1113,7 +1130,7 @@ CPDF_TextPage::MarkedContentState CPDF_TextPage::PreMarkedContent( return MarkedContentState::kDelay; } -void CPDF_TextPage::ProcessMarkedContent(const TransformedTextObject& obj) { +bool CPDF_TextPage::ProcessMarkedContent(const TransformedTextObject& obj) { CPDF_TextObject* const text_obj = obj.text_obj_; const CPDF_ContentMarks* marks = text_obj->GetContentMarks(); const size_t content_marks_count = marks->CountItems(); @@ -1126,10 +1143,14 @@ void CPDF_TextPage::ProcessMarkedContent(const TransformedTextObject& obj) { } } if (actual_text.IsEmpty()) { - return; + return false; } - const bool is_rtl = IsRightToLeft(*text_obj); + const bool is_rtl = + IsRightToLeft(*text_obj) || + (prev_text_obj_ && IsRightToLeft(*prev_text_obj_) && + CFX_BidiString(actual_text, /*auto_order=*/true).OverallDirection() == + CFX_BidiChar::Direction::kRight); CFX_Matrix matrix = text_obj->GetTextMatrix() * obj.form_matrix_; CFX_FloatRect rect = text_obj->GetRect(); float step = 0; @@ -1160,6 +1181,7 @@ void CPDF_TextPage::ProcessMarkedContent(const TransformedTextObject& obj) { CharInfo::Synthetic(CharType::kActualText, wc, text_obj->GetPos(), char_box, matrix, text_obj)); } + return is_rtl; } void CPDF_TextPage::FindPreviousTextObject() { @@ -1219,8 +1241,24 @@ void CPDF_TextPage::ProcessTransformedTextObjects() { curline_rect_ = text_obj->GetRect(); } + // Save these before ProcessMarkedContent() or ProcessTextObjectItems() + // modifies the containers. + const size_t orig_char_list_index = temp_char_list_.size(); + const size_t orig_buf_index = temp_text_buf_.GetLength(); if (ePreMKC == MarkedContentState::kDelay) { - ProcessMarkedContent(obj); + // EmbedPDF(bug-fix): a right-to-left /ActualText run is reversed here + // only after literal right-to-left text, whose segment CloseTempLine() + // reverses as a whole. A run that starts its segment stays in logical + // order, because CloseTempLine() passes such a segment forward: reversing + // it here turned Acrobat's /ActualText around each shaped Arabic or + // Hebrew run back to front. + if (ProcessMarkedContent(obj) && + FollowsLiteralRightToLeftText( + pdfium::span(temp_char_list_).first(orig_char_list_index))) { + // TODO(thestig): It would be nicer if there was a way to avoid doing + // this second reversal. + ReverseTempTextBufs(orig_char_list_index, orig_buf_index); + } prev_text_obj_ = text_obj; prev_matrix_ = form_matrix; continue; @@ -1230,10 +1268,6 @@ void CPDF_TextPage::ProcessTransformedTextObjects() { prev_matrix_ = form_matrix; const CFX_Matrix matrix = text_obj->GetTextMatrix() * form_matrix; - // Save these before ProcessTextObjectItems() modifies the containers. - const size_t orig_char_list_index = temp_char_list_.size(); - const size_t orig_buf_index = temp_text_buf_.GetLength(); - if (ProcessTextObjectItems(text_obj, form_matrix, matrix)) { ReverseTempTextBufs(orig_char_list_index, orig_buf_index); } diff --git a/core/fpdftext/cpdf_textpage.h b/core/fpdftext/cpdf_textpage.h index 36fdffbf3f..bb4ae8cd5c 100644 --- a/core/fpdftext/cpdf_textpage.h +++ b/core/fpdftext/cpdf_textpage.h @@ -208,7 +208,11 @@ class CPDF_TextPage { CPDF_TextObject* text_obj2) const; void CloseTempLine(); MarkedContentState PreMarkedContent(const CPDF_TextObject* text_obj); - void ProcessMarkedContent(const TransformedTextObject& obj); + // Processes `/ActualText` marked content for `obj` and appends character info + // and text to `temp_char_list_` and `temp_text_buf_`. + // Returns true if the caller needs to call ReverseTempTextBufs() because the + // text is RTL, requiring the newly appended character order to be reversed. + bool ProcessMarkedContent(const TransformedTextObject& obj); void FindPreviousTextObject(); void AddCharInfo(wchar_t wc, const CharInfo& info, bool is_rtl); TextOrientation GetTextObjectWritingMode( diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index f56e94eccc..e358904c74 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -5,11 +5,11 @@ import("../../pdfium.gni") import("../../testing/test.gni") -if (pdf_enable_rust_bmp || pdf_enable_rust_png) { +if (pdf_enable_rust_bmp || pdf_enable_rust_jpeg || pdf_enable_rust_png) { import("//build/rust/rust_static_library.gni") } -if (pdf_enable_rust_bmp || pdf_enable_rust_png) { +if (pdf_enable_rust_bmp || pdf_enable_rust_jpeg || pdf_enable_rust_png) { config("pdfium_rust_ffi_config") { include_dirs = [ "$target_gen_dir/../.." ] } @@ -30,6 +30,21 @@ if (pdf_enable_rust_bmp) { } } +if (pdf_enable_rust_jpeg) { + rust_static_library("rust_jpeg_ffi") { + crate_name = "rust_jpeg_ffi" + allow_unsafe = true + crate_root = "jpeg/rust_jpeg_ffi.rs" + sources = [ crate_root ] + cxx_bindings = [ crate_root ] + public_configs = [ ":pdfium_rust_ffi_config" ] + deps = [ + "//build/rust:cxx_cppdeps", + "//third_party/rust/zune_jpeg/v0_5:lib", + ] + } +} + if (pdf_enable_rust_png) { rust_static_library("pdfium_rust_png_ffi") { crate_name = "pdfium_rust_png_ffi" @@ -60,6 +75,8 @@ source_set("fxcodec") { "fx_codec_def.h", "icc/icc_transform.cpp", "icc/icc_transform.h", + "image_predictors.cpp", + "image_predictors.h", "jbig2/jbig2_arith_decoder.cpp", "jbig2/jbig2_arith_decoder.h", "jbig2/jbig2_arith_int_decoder.cpp", @@ -170,10 +187,13 @@ source_set("fxcodec") { } if (pdf_enable_rust_jpeg) { sources += [ - "jpeg/skia_jpeg_context.cpp", - "jpeg/skia_jpeg_context.h", + "jpeg/rust_jpeg_context.cpp", + "jpeg/rust_jpeg_context.h", + ] + deps += [ + ":rust_jpeg_ffi", + "//build/rust:cxx_cppdeps", ] - deps += [ "//skia" ] } else { sources += [ "jpeg/libjpeg_jpeg_context.cpp", @@ -205,23 +225,30 @@ source_set("fxcodec") { } } - # Windows requires libjpeg for JpegModule::JpegEncode (used for EMF printing) - # regardless of whether Rust JPEG is enabled for decoding. Libjpeg is also - # used as fallback for CMYK / 4-component JPEG decoding in JpegModule. - sources += [ - "jpeg/jpeg_common.c", - "jpeg/jpeg_common.h", - "jpeg/libjpeg_scanline_decoder.cpp", - "jpeg/libjpeg_scanline_decoder.h", - ] - deps += [ "//third_party:jpeg" ] - if (pdf_enable_rust_jpeg) { sources += [ - "jpeg/skia_scanline_decoder.cpp", - "jpeg/skia_scanline_decoder.h", + "jpeg/rust_jpeg_scanline_decoder.cpp", + "jpeg/rust_jpeg_scanline_decoder.h", + ] + deps += [ + ":rust_jpeg_ffi", + "//build/rust:cxx_cppdeps", + ] + } else { + sources += [ + "jpeg/libjpeg_scanline_decoder.cpp", + "jpeg/libjpeg_scanline_decoder.h", + ] + } + + if (!pdf_enable_rust_jpeg || is_win) { + # Windows requires libjpeg for JpegModule::JpegEncode (used for EMF + # printing). + sources += [ + "jpeg/jpeg_common.c", + "jpeg/jpeg_common.h", ] - deps += [ "//skia" ] + deps += [ "//third_party:jpeg" ] } visibility = [ "../../*" ] } @@ -240,6 +267,7 @@ pdfium_unittest_source_set("unittests") { ":fxcodec", "../../third_party:libopenjpeg2", "../fpdfapi/parser", + "../fxge", ] pdfium_root_dir = "../../" @@ -249,7 +277,6 @@ pdfium_unittest_source_set("unittests") { "gif/lzw_decompressor_unittest.cpp", "progressive_decoder_unittest.cpp", ] - deps += [ "../fxge" ] if (pdf_use_skia) { sources += [ "codec_memory_sk_stream_unittest.cpp" ] deps += [ "//skia" ] diff --git a/core/fxcodec/basic/basicmodule.cpp b/core/fxcodec/basic/basicmodule.cpp index 0f04d459be..de23eee642 100644 --- a/core/fxcodec/basic/basicmodule.cpp +++ b/core/fxcodec/basic/basicmodule.cpp @@ -13,6 +13,7 @@ #include "core/fxcrt/byteorder.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/data_vector.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/raw_span.h" @@ -100,15 +101,14 @@ bool RLScanlineDecoder::Create(pdfium::span src_buf, FX_SAFE_UINT32 pitch = width; pitch *= nComps; pitch *= bpc; - pitch += 31; - pitch /= 32; + pitch = fxcrt::CeilDiv(pitch, 32); pitch *= 4; if (!pitch.IsValid()) { return false; } pitch_ = pitch.ValueOrDie(); // Overflow should already have been checked before this is called. - line_bytes_ = (static_cast(width) * nComps * bpc + 7) / 8; + line_bytes_ = fxcrt::CeilDiv(static_cast(width) * nComps * bpc, 8); scanline_.resize(pitch_); return CheckDestSize(); } @@ -242,8 +242,7 @@ DataVector BasicModule::RunLengthEncode( // 4 output chars for every 3 input, plus up to 4 more for the 1-2 chars // rounded off plus the terminating character. FX_SAFE_SIZE_T estimated_size = src_span.size(); - estimated_size += 2; - estimated_size /= 3; + estimated_size = fxcrt::CeilDiv(estimated_size, 3); estimated_size *= 4; estimated_size += 1; DataVector result(estimated_size.ValueOrDie()); diff --git a/core/fxcodec/bmp/cfx_bmpdecompressor.cpp b/core/fxcodec/bmp/cfx_bmpdecompressor.cpp index 086920ec82..1cd622c3ff 100644 --- a/core/fxcodec/bmp/cfx_bmpdecompressor.cpp +++ b/core/fxcodec/bmp/cfx_bmpdecompressor.cpp @@ -19,6 +19,7 @@ #include "core/fxcrt/byteorder.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/data_vector.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/numerics/safe_math.h" #include "core/fxcrt/span_util.h" @@ -51,8 +52,7 @@ constexpr uint32_t kBmpBitfields = 3L; constexpr uint32_t kBmpMaxImageDimension = 65535; uint8_t HalfRoundUp(uint8_t value) { - uint16_t value16 = value; - return static_cast((value16 + 1) / 2); + return fxcrt::CeilDiv(value, 2); } } // namespace diff --git a/core/fxcodec/bmp/rust_bmp_context.cpp b/core/fxcodec/bmp/rust_bmp_context.cpp index 677a8625e9..24097a5f8e 100644 --- a/core/fxcodec/bmp/rust_bmp_context.cpp +++ b/core/fxcodec/bmp/rust_bmp_context.cpp @@ -13,6 +13,7 @@ #include "core/fxcodec/progressive_decoder_context_delegate.h" #include "core/fxcrt/byteorder.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/span.h" #include "core/fxge/dib/fx_dib.h" @@ -145,8 +146,8 @@ ProgressiveDecoderContext::Status RustBmpContext::ReadHeader( ProgressiveDecoderContext::Status RustBmpContext::StartDecode() { ProgressiveDecoderContextDelegate::Format format = - (components_ == 4) ? ProgressiveDecoderContextDelegate::Format::kArgb - : ProgressiveDecoderContextDelegate::Format::kRgb; + (components_ == 4) ? ProgressiveDecoderContextDelegate::Format::kBgra + : ProgressiveDecoderContextDelegate::Format::kBgr; if (!delegate_->PrepareDirectOutput(width_, height_, format)) { return ProgressiveDecoderContext::Status::kError; } @@ -160,8 +161,7 @@ ProgressiveDecoderContext::Status RustBmpContext::ContinueDecode() { FX_SAFE_SIZE_T safe_row_bytes = width_; safe_row_bytes *= components_; - safe_row_bytes += 3; - safe_row_bytes /= 4; + safe_row_bytes = fxcrt::CeilDiv(safe_row_bytes, 4); safe_row_bytes *= 4; if (!safe_row_bytes.IsValid()) { return ProgressiveDecoderContext::Status::kError; diff --git a/core/fxcodec/brotli/brotli_decoder.cpp b/core/fxcodec/brotli/brotli_decoder.cpp index bef8d1a677..cc96b58cfa 100644 --- a/core/fxcodec/brotli/brotli_decoder.cpp +++ b/core/fxcodec/brotli/brotli_decoder.cpp @@ -4,11 +4,14 @@ #include "core/fxcodec/brotli/brotli_decoder.h" +#include #include #include +#include "core/fxcodec/image_predictors.h" #include "core/fxcrt/check.h" #include "core/fxcrt/data_vector.h" +#include "core/fxcrt/fx_extension.h" #include "core/fxcrt/numerics/safe_conversions.h" #include "third_party/brotli/include/brotli/decode.h" @@ -26,22 +29,27 @@ struct BrotliDecoderStateDeleter { } // namespace DataAndBytesConsumed BrotliDecoder::Decode(pdfium::span src_span, + int predictor_value, + int colors, + int bits_per_component, + int columns, uint32_t estimated_decode_size) { CHECK(g_brotli_enabled); if (src_span.empty()) { - return {DataVector(), 0u}; + return {DataVector(), FX_INVALID_OFFSET}; } if (estimated_decode_size == 0) { estimated_decode_size = pdfium::checked_cast(src_span.size()); } if (estimated_decode_size > kMaxDecodeBytes) { - return {DataVector(), 0u}; + return {DataVector(), FX_INVALID_OFFSET}; } std::unique_ptr state( BrotliDecoderCreateInstance(nullptr, nullptr, nullptr)); if (!state) { - return {DataVector(), 0u}; + return {DataVector(), FX_INVALID_OFFSET}; } + BrotliDecoderSetParameter(state.get(), BROTLI_DECODER_PARAM_LARGE_WINDOW, 1u); DataVector decoded_buffer(estimated_decode_size); size_t available_in = src_span.size(); @@ -56,16 +64,19 @@ DataAndBytesConsumed BrotliDecoder::Decode(pdfium::span src_span, BrotliDecoderDecompressStream(state.get(), &available_in, &next_in, &available_out, &next_out, &total_out); if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT && - decoded_buffer.size() <= kMaxDecodeBytes / 2) { - decoded_buffer.resize(decoded_buffer.size() * 2); + decoded_buffer.size() < kMaxDecodeBytes) { + decoded_buffer.resize( + std::min(decoded_buffer.size() * 2, kMaxDecodeBytes)); continue; } if (result == BROTLI_DECODER_RESULT_SUCCESS) { decoded_buffer.resize(total_out); - return {std::move(decoded_buffer), - static_cast(src_span.subspan(available_in).size())}; + return fxcodec::ApplyPredictor( + std::move(decoded_buffer), predictor_value, colors, + bits_per_component, columns, + static_cast(src_span.subspan(available_in).size())); } - return {DataVector(), 0u}; + return {DataVector(), FX_INVALID_OFFSET}; } } diff --git a/core/fxcodec/brotli/brotli_decoder.h b/core/fxcodec/brotli/brotli_decoder.h index d9c6095dab..c97ef47894 100644 --- a/core/fxcodec/brotli/brotli_decoder.h +++ b/core/fxcodec/brotli/brotli_decoder.h @@ -13,6 +13,10 @@ class BrotliDecoder { public: static DataAndBytesConsumed Decode(pdfium::span src_span, + int predictor_value, + int colors, + int bits_per_component, + int columns, uint32_t estimated_decode_size); static void SetBrotliEnabled(bool enabled); static bool GetBrotliEnabled(); diff --git a/core/fxcodec/brotli/brotli_embeddertest.cpp b/core/fxcodec/brotli/brotli_embeddertest.cpp index 7e8207e976..5fd9a62a66 100644 --- a/core/fxcodec/brotli/brotli_embeddertest.cpp +++ b/core/fxcodec/brotli/brotli_embeddertest.cpp @@ -84,6 +84,18 @@ TEST_F(BrotliEnabledEmbedderTest, BrotliRectangles) { CompareBitmapWithExpectationSuffix(bitmap.get(), pdfium::kRectanglesPng); } +TEST_F(BrotliEnabledEmbedderTest, BrotliDecodeParms) { + ASSERT_TRUE(OpenDocument("brotli_decodeparms.pdf")); + + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + ScopedFPDFBitmap bitmap = RenderLoadedPage(page.get()); + ASSERT_TRUE(bitmap); + + CompareBitmapWithExpectationSuffix(bitmap.get(), "brotli_decode_parms"); +} + TEST_F(BrotliEnabledEmbedderTest, BrotliWithLength1Argument) { ASSERT_TRUE(OpenDocument("hello_world_brotli_with_length1.pdf")); diff --git a/core/fxcodec/fax/faxmodule.cpp b/core/fxcodec/fax/faxmodule.cpp index 4acd313072..047ad32a33 100644 --- a/core/fxcodec/fax/faxmodule.cpp +++ b/core/fxcodec/fax/faxmodule.cpp @@ -10,17 +10,21 @@ #include #include +#include #include #include +#include #include #include "build/build_config.h" #include "core/fxcodec/scanlinedecoder.h" #include "core/fxcrt/binary_buffer.h" +#include "core/fxcrt/byteorder.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/data_vector.h" #include "core/fxcrt/fx_2d_size.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_memory.h" #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/raw_span.h" @@ -36,20 +40,6 @@ namespace fxcodec { namespace { -constexpr std::array kOneLeadPos = {{ - 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -}}; - // Limit of image dimension. Use the same limit as the JBIG2 codecs. constexpr int kFaxMaxImageDimension = 65535; @@ -60,7 +50,7 @@ int FindBit(pdfium::span data_buf, int max_pos, int start_pos, bool bit) { - DCHECK_GE(start_pos, 0); + CHECK_GE(start_pos, 0); if (start_pos >= max_pos) { return max_pos; } @@ -71,34 +61,30 @@ int FindBit(pdfium::span data_buf, const int byte_pos = start_pos / 8; uint8_t data = (data_buf[byte_pos] ^ bit_xor) & (0xff >> bit_offset); if (data) { - return byte_pos * 8 + kOneLeadPos[data]; + return byte_pos * 8 + std::countl_zero(data); } start_pos += 7; } - const int max_byte = (max_pos + 7) / 8; + const int max_byte = fxcrt::CeilDiv(max_pos, 8); int byte_pos = start_pos / 8; - // Try reading in bigger chunks in case there are long runs to be skipped. - static constexpr int kBulkReadSize = 8; - if (max_byte >= kBulkReadSize && byte_pos < max_byte - kBulkReadSize) { - static constexpr uint8_t skip_block_0[kBulkReadSize] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - static constexpr uint8_t skip_block_1[kBulkReadSize] = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; - const auto skip_block = - bit ? pdfium::span(skip_block_0) : pdfium::span(skip_block_1); - while (byte_pos < max_byte - kBulkReadSize && - data_buf.subspan(static_cast(byte_pos), - static_cast(kBulkReadSize)) == skip_block) { - byte_pos += kBulkReadSize; + const uint32_t word_xor = bit ? 0x00000000 : 0xffffffff; + while (byte_pos + 4 <= max_byte) { + const uint32_t word = + fxcrt::GetUInt32MSBFirst( + data_buf.subspan(static_cast(byte_pos)).first<4u>()) ^ + word_xor; + if (word) { + return std::min(byte_pos * 8 + std::countl_zero(word), max_pos); } + byte_pos += 4; } while (byte_pos < max_byte) { uint8_t data = data_buf[byte_pos] ^ bit_xor; if (data) { - return std::min(byte_pos * 8 + kOneLeadPos[data], max_pos); + return std::min(byte_pos * 8 + std::countl_zero(data), max_pos); } ++byte_pos; } @@ -284,20 +270,20 @@ uint32_t GetSrcBitSize(pdfium::span src_buf) { return pdfium::checked_cast(src_buf.size() * 8); } -int FaxGetRun(pdfium::span ins_array, - pdfium::span src_buf, - uint32_t* bitpos) { +std::optional FaxGetRun(pdfium::span ins_array, + pdfium::span src_buf, + uint32_t* bitpos) { const uint32_t bitsize = GetSrcBitSize(src_buf); uint32_t code = 0; int ins_off = 0; while (true) { uint8_t ins = ins_array[ins_off++]; if (ins == 0xff) { - return -1; + return std::nullopt; } if (*bitpos >= bitsize) { - return -1; + return std::nullopt; } code <<= 1; @@ -331,9 +317,6 @@ void FaxG4GetRow(pdfium::span src_buf, int a1; int a2; - int b1; - int b2; - FaxG4FindB1B2(ref_buf, columns, a0, a0color, &b1, &b2); int v_delta = 0; if (!NextBit(src_buf, bitpos)) { @@ -354,23 +337,31 @@ void FaxG4GetRow(pdfium::span src_buf, // Mode "Horizontal". int run_len1 = 0; while (true) { - int run = FaxGetRun( + std::optional run = FaxGetRun( a0color ? pdfium::span( kFaxWhiteRunIns) : pdfium::span( kFaxBlackRunIns), src_buf, bitpos); - run_len1 += run; - if (run < 64) { + if (!run.has_value()) { + return; + } + // Since `run` is 16-bit, it cannot cause `run_len1` to overflow + // with the `columns` check below. + run_len1 += run.value(); + if (run_len1 > columns) { + // No legal 1-D run exceeds the row width, which is bound by + // `kFaxMaxImageDimension`. + return; + } + + if (run.value() < 64) { break; } } if (a0 < 0) { ++run_len1; } - if (run_len1 < 0) { - return; - } a1 = a0 + run_len1; if (!a0color) { @@ -379,20 +370,28 @@ void FaxG4GetRow(pdfium::span src_buf, int run_len2 = 0; while (true) { - int run = FaxGetRun( + std::optional run = FaxGetRun( a0color ? pdfium::span( kFaxBlackRunIns) : pdfium::span( kFaxWhiteRunIns), src_buf, bitpos); - run_len2 += run; - if (run < 64) { + if (!run.has_value()) { + return; + } + // Since `run` is 16-bit, it cannot cause `run_len2` to overflow + // with the `columns` check below. + run_len2 += run.value(); + if (run_len2 > columns) { + // No legal 1-D run exceeds the row width, which is bound by + // `kFaxMaxImageDimension`. + return; + } + + if (run.value() < 64) { break; } } - if (run_len2 < 0) { - return; - } a2 = a1 + run_len2; if (a0color) { FaxFillBits(dest_buf, columns, a1, a2); @@ -410,6 +409,10 @@ void FaxG4GetRow(pdfium::span src_buf, } if (NextBit(src_buf, bitpos)) { + int b1; + int b2; + FaxG4FindB1B2(ref_buf, columns, a0, a0color, &b1, &b2); + // Mode "Pass". if (!a0color) { FaxFillBits(dest_buf, columns, a0, b2); @@ -460,6 +463,11 @@ void FaxG4GetRow(pdfium::span src_buf, } else { // Mode "Vertical", V(0). } + + int b1; + int b2; + FaxG4FindB1B2(ref_buf, columns, a0, a0color, &b1, &b2); + a1 = b1 + v_delta; if (!a0color) { FaxFillBits(dest_buf, columns, a0, a1); @@ -507,13 +515,13 @@ void FaxGet1DLine(pdfium::span src_buf, int run_len = 0; while (true) { - int run = + std::optional run = FaxGetRun(color ? pdfium::span( kFaxWhiteRunIns) : pdfium::span( kFaxBlackRunIns), src_buf, bitpos); - if (run < 0) { + if (!run.has_value()) { while (*bitpos < bitsize) { if (NextBit(src_buf, bitpos)) { return; @@ -521,8 +529,16 @@ void FaxGet1DLine(pdfium::span src_buf, } return; } - run_len += run; - if (run < 64) { + // Since `run` is 16-bit, it cannot cause `run_len` to overflow with the + // `columns` check below. + run_len += run.value(); + if (run_len > columns) { + // No legal 1-D run exceeds the row width, which is bound by + // `kFaxMaxImageDimension`. + return; + } + + if (run.value() < 64) { break; } } @@ -649,7 +665,7 @@ pdfium::span FaxDecoder::GetNextLine() { uint32_t FaxDecoder::GetSrcOffset() { return pdfium::checked_cast( - std::min((bitpos_ + 7) / 8, src_span_.size())); + std::min(fxcrt::CeilDiv(bitpos_, 8), src_span_.size())); } void FaxDecoder::InvertBuffer() { diff --git a/core/fxcodec/flate/flatemodule.cpp b/core/fxcodec/flate/flatemodule.cpp index 0c3cbfe623..39f58a047a 100644 --- a/core/fxcodec/flate/flatemodule.cpp +++ b/core/fxcodec/flate/flatemodule.cpp @@ -16,11 +16,12 @@ #include #include "core/fxcodec/data_and_bytes_consumed.h" +#include "core/fxcodec/image_predictors.h" #include "core/fxcodec/scanlinedecoder.h" #include "core/fxcrt/check.h" #include "core/fxcrt/data_vector.h" #include "core/fxcrt/fixed_size_data_vector.h" -#include "core/fxcrt/fx_2d_size.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_extension.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/notreached.h" @@ -124,7 +125,7 @@ class CLZWDecoder { CLZWDecoder(pdfium::span src_span, bool early_change); bool Decode(); - uint32_t GetSrcSize() const { return (src_bit_pos_ + 7) / 8; } + uint32_t GetSrcSize() const { return fxcrt::CeilDiv(src_bit_pos_, 8); } DataVector TakeDestBuf() { dest_buf_.resize(dest_byte_pos_); return std::move(dest_buf_); @@ -308,185 +309,6 @@ bool CLZWDecoder::Decode() { return dest_byte_pos_ != 0; } -uint8_t GetLeftValue(pdfium::span span, - size_t i, - uint32_t bytes_per_pixel) { - return i >= bytes_per_pixel ? span[i - bytes_per_pixel] : 0; -} - -uint8_t GetUpValue(pdfium::span span, size_t i) { - return span.empty() ? 0 : span[i]; -} - -uint8_t GetUpperLeftValue(pdfium::span span, - size_t i, - uint32_t bytes_per_pixel) { - if (i >= bytes_per_pixel && !span.empty()) { - return span[i - bytes_per_pixel]; - } - return 0; -} - -uint8_t PaethPredictor(uint8_t a, uint8_t b, uint8_t c) { - int p = static_cast(a) + b - c; - int pa = abs(p - a); - int pb = abs(p - b); - int pc = abs(p - c); - if (pa <= pb && pa <= pc) { - return a; - } - return pb <= pc ? b : c; -} - -void PngPredictLine(pdfium::span dest_span, - pdfium::span src_span, - pdfium::span last_span, - size_t row_size, - uint32_t bytes_per_pixel) { - const uint8_t tag = src_span.front(); - pdfium::span remaining_src_span = - src_span.subspan(1u, row_size); - switch (tag) { - case 1: { - for (size_t i = 0; i < remaining_src_span.size(); ++i) { - uint8_t left = GetLeftValue(dest_span, i, bytes_per_pixel); - dest_span[i] = remaining_src_span[i] + left; - } - break; - } - case 2: { - for (size_t i = 0; i < remaining_src_span.size(); ++i) { - uint8_t up = GetUpValue(last_span, i); - dest_span[i] = remaining_src_span[i] + up; - } - break; - } - case 3: { - for (size_t i = 0; i < remaining_src_span.size(); ++i) { - uint8_t left = GetLeftValue(dest_span, i, bytes_per_pixel); - uint8_t up = GetUpValue(last_span, i); - dest_span[i] = remaining_src_span[i] + (up + left) / 2; - } - break; - } - case 4: { - for (size_t i = 0; i < remaining_src_span.size(); ++i) { - uint8_t left = GetLeftValue(dest_span, i, bytes_per_pixel); - uint8_t up = GetUpValue(last_span, i); - uint8_t upper_left = GetUpperLeftValue(last_span, i, bytes_per_pixel); - dest_span[i] = - remaining_src_span[i] + PaethPredictor(left, up, upper_left); - } - break; - } - default: { - fxcrt::Copy(remaining_src_span, dest_span); - break; - } - } -} - -std::optional> PngPredictor( - int colors, - int bits_per_component, - int columns, - pdfium::span src_span) { - const uint32_t row_size = - fxge::CalculatePitch8(bits_per_component, colors, columns).value_or(0); - if (row_size == 0) { - return std::nullopt; - } - - const uint32_t src_row_size = row_size + 1; - if (src_row_size == 0) { - // Avoid divide by 0. - return std::nullopt; - } - const size_t row_count = (src_span.size() + row_size) / src_row_size; - if (row_count == 0) { - return std::nullopt; - } - - const uint32_t last_row_size = src_span.size() % src_row_size; - size_t dest_size = Fx2DSizeOrDie(row_size, row_count); - if (last_row_size) { - dest_size -= src_row_size - last_row_size; - } - DataVector dest_buf(dest_size); - pdfium::span remaining_src_span = src_span; - pdfium::span remaining_dest_span = pdfium::span(dest_buf); - pdfium::span prev_dest_span; - const uint32_t bytes_per_pixel = (colors * bits_per_component + 7) / 8; - for (size_t row = 0; row < row_count; row++) { - const size_t remaining_row_size = - std::min(row_size, remaining_src_span.size() - 1); - PngPredictLine(remaining_dest_span, remaining_src_span, prev_dest_span, - remaining_row_size, bytes_per_pixel); - remaining_src_span = remaining_src_span.subspan(remaining_row_size + 1); - prev_dest_span = remaining_dest_span; - remaining_dest_span = remaining_dest_span.subspan(remaining_row_size); - } - return dest_buf; -} - -void TiffPredictLine(pdfium::span dest_span, - int bits_per_component, - int colors, - int columns) { - if (bits_per_component == 1) { - int row_bits = std::min(bits_per_component * colors * columns, - pdfium::checked_cast(dest_span.size() * 8)); - int index_pre = 0; - int col_pre = 0; - for (int i = 1; i < row_bits; i++) { - int col = i % 8; - int index = i / 8; - if (((dest_span[index] >> (7 - col)) & 1) ^ - ((dest_span[index_pre] >> (7 - col_pre)) & 1)) { - dest_span[index] |= 1 << (7 - col); - } else { - dest_span[index] &= ~(1 << (7 - col)); - } - index_pre = index; - col_pre = col; - } - return; - } - int bytes_per_pixel = bits_per_component * colors / 8; - if (bits_per_component == 16) { - for (size_t i = bytes_per_pixel; i + 1 < dest_span.size(); i += 2) { - uint16_t pixel = (dest_span[i - bytes_per_pixel] << 8) | - dest_span[i - bytes_per_pixel + 1]; - pixel += (dest_span[i] << 8) | dest_span[i + 1]; - dest_span[i] = pixel >> 8; - dest_span[i + 1] = (uint8_t)pixel; - } - } else { - for (size_t i = bytes_per_pixel; i < dest_span.size(); i++) { - dest_span[i] += dest_span[i - bytes_per_pixel]; - } - } -} - -bool TiffPredictor(int colors, - int bits_per_component, - int columns, - pdfium::span data_span) { - const uint32_t row_size = - fxge::CalculatePitch8(bits_per_component, colors, columns).value_or(0); - if (row_size == 0) { - return false; - } - - while (!data_span.empty()) { - auto row_span = - data_span.first(std::min(row_size, data_span.size())); - TiffPredictLine(row_span, bits_per_component, colors, columns); - data_span = data_span.subspan(row_span.size()); - } - return true; -} - uint32_t EstimateFlateUncompressBufferSize(uint32_t orig_size, size_t src_size) { static constexpr uint32_t kMaxInitialAllocSize = 10000000; @@ -506,18 +328,6 @@ DataAndBytesConsumed FlateUncompress(pdfium::span src_buf, return uncompressor.TakeResult(); } -enum class PredictorType : uint8_t { kNone, kTiff, kPng }; -// Values come from ISO 32000-1:2008, table 10. -static PredictorType GetPredictor(int predictor) { - if (predictor >= 10) { - return PredictorType::kPng; - } - if (predictor == 2) { - return PredictorType::kTiff; - } - return PredictorType::kNone; -} - class FlateScanlineDecoder : public ScanlineDecoder { public: FlateScanlineDecoder(pdfium::span src_span, @@ -671,7 +481,8 @@ void FlatePredictorScanlineDecoder::GetNextLineWithPredictedPitch() { case PredictorType::kPng: { const uint32_t row_size = fxge::CalculatePitch8OrDie(bits_per_component_, colors_, columns_); - const uint32_t bytes_per_pixel = (bits_per_component_ * colors_ + 7) / 8; + const uint32_t bytes_per_pixel = + fxcrt::CeilDiv(bits_per_component_ * colors_, 8); FlateOutput(flate_.get(), predict_raw_); PngPredictLine(scanline_, predict_raw_, last_line_, row_size, bytes_per_pixel); @@ -702,7 +513,8 @@ void FlatePredictorScanlineDecoder::GetNextLineWithoutPredictedPitch() { } const uint32_t row_size = fxge::CalculatePitch8OrDie(bits_per_component_, colors_, columns_); - const uint32_t bytes_per_pixel = (bits_per_component_ * colors_ + 7) / 8; + const uint32_t bytes_per_pixel = + fxcrt::CeilDiv(bits_per_component_ * colors_, 8); switch (predictor_) { case PredictorType::kPng: { while (bytes_to_go) { @@ -879,7 +691,6 @@ DataAndBytesConsumed FlateModule::FlateOrLZWDecode( uint32_t estimated_size) { DataVector dest_buf; uint32_t bytes_consumed = FX_INVALID_OFFSET; - PredictorType predictor_type = GetPredictor(predictor); if (bLZW) { auto decoder = std::make_unique(src_span, bEarlyChange); @@ -895,23 +706,8 @@ DataAndBytesConsumed FlateModule::FlateOrLZWDecode( bytes_consumed = result.bytes_consumed; } - switch (predictor_type) { - case PredictorType::kNone: { - return {std::move(dest_buf), bytes_consumed}; - } - case PredictorType::kPng: { - std::optional> result = - PngPredictor(Colors, BitsPerComponent, Columns, dest_buf); - if (!result.has_value()) { - return {std::move(dest_buf), FX_INVALID_OFFSET}; - } - return {std::move(result.value()), bytes_consumed}; - } - case PredictorType::kTiff: { - bool ret = TiffPredictor(Colors, BitsPerComponent, Columns, dest_buf); - return {std::move(dest_buf), ret ? bytes_consumed : FX_INVALID_OFFSET}; - } - } + return ApplyPredictor(std::move(dest_buf), predictor, Colors, + BitsPerComponent, Columns, bytes_consumed); } // EmbedPDF(attachments): streams a large attachment out in bounded chunks. diff --git a/core/fxcodec/icc/icc_transform.cpp b/core/fxcodec/icc/icc_transform.cpp index 69ec39c7c0..d453935174 100644 --- a/core/fxcodec/icc/icc_transform.cpp +++ b/core/fxcodec/icc/icc_transform.cpp @@ -135,10 +135,10 @@ std::unique_ptr IccTransform::CreateTransformSRGB( void IccTransform::Translate(pdfium::span src_values, pdfium::span dest_values) { uint8_t output[4]; - // TODO(npm): Currently the CmsDoTransform method is part of LCMS and it will - // apply some member of transform_ to the input. We need to go over all the - // places which set transform to verify that only `src_values.size()` - // components are used. + // TODO(crbug.com/567626895): Currently the CmsDoTransform method is part of + // LCMS and it will apply some member of transform_ to the input. We need to + // go over all the places which set transform to verify that only + // `src_values.size()` components are used. if (lab_) { DataVector inputs(std::max(src_values.size(), 16)); for (uint32_t i = 0; i < src_values.size(); ++i) { diff --git a/core/fxcodec/image_predictors.cpp b/core/fxcodec/image_predictors.cpp new file mode 100644 index 0000000000..aa86ee3337 --- /dev/null +++ b/core/fxcodec/image_predictors.cpp @@ -0,0 +1,328 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/fxcodec/image_predictors.h" + +#include + +#include +#include +#include + +#include "core/fxcrt/fx_2d_size.h" +#include "core/fxcrt/fx_ceil_div.h" +#include "core/fxcrt/fx_extension.h" +#include "core/fxcrt/notreached.h" +#include "core/fxcrt/numerics/safe_conversions.h" +#include "core/fxcrt/span_util.h" +#include "core/fxcrt/stl_util.h" +#include "core/fxcrt/zip.h" +#include "core/fxge/calculate_pitch.h" + +namespace fxcodec { + +namespace { + +uint8_t PaethPredictor(uint8_t a, uint8_t b, uint8_t c) { + int p = static_cast(a) + b - c; + int pa = abs(p - a); + int pb = abs(p - b); + int pc = abs(p - c); + if (pa <= pb && pa <= pc) { + return a; + } + return pb <= pc ? b : c; +} + +std::optional> PngPredictor( + int colors, + int bits_per_component, + int columns, + pdfium::span src_span) { + const uint32_t row_size = + fxge::CalculatePitch8(bits_per_component, colors, columns).value_or(0); + if (row_size == 0) { + return std::nullopt; + } + + const uint32_t src_row_size = row_size + 1; + if (src_row_size == 0) { + // Avoid divide by 0. + return std::nullopt; + } + const size_t row_count = (src_span.size() + row_size) / src_row_size; + if (row_count == 0) { + return std::nullopt; + } + + const uint32_t last_row_size = src_span.size() % src_row_size; + size_t dest_size = Fx2DSizeOrDie(row_size, row_count); + if (last_row_size) { + dest_size -= src_row_size - last_row_size; + } + DataVector dest_buf(dest_size); + pdfium::span remaining_src_span = src_span; + pdfium::span remaining_dest_span = pdfium::span(dest_buf); + pdfium::span prev_dest_span; + const uint32_t bytes_per_pixel = + fxcrt::CeilDiv(colors * bits_per_component, 8); + for (size_t row = 0; row < row_count; row++) { + const size_t remaining_row_size = + std::min(row_size, remaining_src_span.size() - 1); + PngPredictLine(remaining_dest_span, remaining_src_span, prev_dest_span, + remaining_row_size, bytes_per_pixel); + remaining_src_span = remaining_src_span.subspan(remaining_row_size + 1); + prev_dest_span = remaining_dest_span; + remaining_dest_span = remaining_dest_span.subspan(remaining_row_size); + } + return dest_buf; +} + +bool TiffPredictor(int colors, + int bits_per_component, + int columns, + pdfium::span data_span) { + const uint32_t row_size = + fxge::CalculatePitch8(bits_per_component, colors, columns).value_or(0); + if (row_size == 0) { + return false; + } + + while (!data_span.empty()) { + auto row_span = + data_span.first(std::min(row_size, data_span.size())); + TiffPredictLine(row_span, bits_per_component, colors, columns); + data_span = data_span.subspan(row_span.size()); + } + return true; +} + +} // namespace + +PredictorType GetPredictor(int predictor) { + if (predictor >= 10) { + return PredictorType::kPng; + } + if (predictor == 2) { + return PredictorType::kTiff; + } + return PredictorType::kNone; +} + +const DataAndBytesConsumed ApplyPredictor(DataVector decoded_buf, + int predictor, + int colors, + int bits_per_component, + int columns, + uint32_t bytes_consumed) { + PredictorType predictor_type = GetPredictor(predictor); + switch (predictor_type) { + case PredictorType::kNone: { + return {std::move(decoded_buf), bytes_consumed}; + } + case PredictorType::kPng: { + std::optional> result = + PngPredictor(colors, bits_per_component, columns, decoded_buf); + if (!result.has_value()) { + return {std::move(decoded_buf), FX_INVALID_OFFSET}; + } + return {std::move(result.value()), bytes_consumed}; + } + case PredictorType::kTiff: { + bool ret = + TiffPredictor(colors, bits_per_component, columns, decoded_buf); + return {std::move(decoded_buf), ret ? bytes_consumed : FX_INVALID_OFFSET}; + } + } + NOTREACHED(); +} + +// Fixed-size pixel views for the Sub filter's per-channel fast paths. The +// channel names are deliberately positional: PNG predictors operate on raw +// bytes and never care about color order. +struct Pixel3 { + uint8_t c0; + uint8_t c1; + uint8_t c2; +}; + +struct Pixel4 { + uint8_t c0; + uint8_t c1; + uint8_t c2; + uint8_t c3; +}; + +void PngPredictLine(pdfium::span dest_span, + pdfium::span src_span, + pdfium::span last_span, + size_t row_size, + uint32_t bytes_per_pixel) { + const uint8_t tag = src_span.front(); + // `row_size` is a count, not an end index, so this is exactly `row_size` + // bytes after the tag byte. PngPredictor() (below) pre-clamps the + // `row_size` it passes for a stream's last, possibly truncated, row, and + // this CHECKs rather than clamps, so `row_size` and `src_span.size()` + // are equal on every path that reaches the code below. If this is ever + // relaxed from a CHECK to a clamp, `row_size` stops being a valid loop + // bound and every use below needs the post-clamp span size instead. + src_span = src_span.subspan(1u, row_size); + // Size the row views once up front, then hoist the two special cases out + // of the loops: the first `bytes_per_pixel` bytes of a row, where the left + // and upper-left neighbors are zero, and the first row of the image, where + // up and upper-left are zero. + dest_span = dest_span.first(row_size); + if (!last_span.empty()) { + last_span = last_span.first(row_size); + } + const size_t bpp = bytes_per_pixel; + const size_t lead = std::min(bpp, row_size); + switch (tag) { + case 1: { + // Sub: left neighbor only, so the first row needs no special casing. + // The lead bytes have a zero left neighbor. + fxcrt::Copy(src_span.first(lead), dest_span); + // A Sub-filtered row's channels form independent additive chains; the + // generic loop below re-reads dest_span[i - bpp], a value it stored `bpp` + // iterations earlier, which serializes the loop on store-to-load + // forwarding. For the common 3- and 4-byte pixel sizes, walking the + // row as pixel structs and carrying the running channel values in a + // local removes that dependency (and, via fxcrt::Zip(), all + // per-element bounds checks). + if (bpp == 3 && row_size >= 3) { + auto src_px = fxcrt::reinterpret_span(src_span); + auto dest_px = fxcrt::reinterpret_span(dest_span); + Pixel3 carry = dest_px.front(); + for (auto [s, d] : + fxcrt::Zip(src_px.subspan(1u), dest_px.subspan(1u))) { + carry.c0 += s.c0; + carry.c1 += s.c1; + carry.c2 += s.c2; + d = carry; + } + for (size_t i = row_size - row_size % 3; i < row_size; ++i) { + dest_span[i] = src_span[i] + dest_span[i - 3]; + } + break; + } + if (bpp == 4 && row_size >= 4) { + auto src_px = fxcrt::reinterpret_span(src_span); + auto dest_px = fxcrt::reinterpret_span(dest_span); + Pixel4 carry = dest_px.front(); + for (auto [s, d] : + fxcrt::Zip(src_px.subspan(1u), dest_px.subspan(1u))) { + carry.c0 += s.c0; + carry.c1 += s.c1; + carry.c2 += s.c2; + carry.c3 += s.c3; + d = carry; + } + for (size_t i = row_size - row_size % 4; i < row_size; ++i) { + dest_span[i] = src_span[i] + dest_span[i - 4]; + } + break; + } + for (size_t i = lead; i < row_size; ++i) { + dest_span[i] = src_span[i] + dest_span[i - bpp]; + } + break; + } + case 2: { + // Up: previous-row neighbor only, zero on the first row. + if (last_span.empty()) { + fxcrt::Copy(src_span, dest_span); + break; + } + for (auto [s, u, d] : fxcrt::Zip(src_span, last_span, dest_span)) { + d = s + u; + } + break; + } + case 3: { + // Average: (left + up) / 2, with the zero cases hoisted. + if (last_span.empty()) { + fxcrt::Copy(src_span.first(lead), dest_span); + for (size_t i = lead; i < row_size; ++i) { + dest_span[i] = src_span[i] + dest_span[i - bpp] / 2; + } + break; + } + for (auto [s, u, d] : + fxcrt::Zip(src_span.first(lead), last_span, dest_span)) { + d = s + u / 2; + } + for (size_t i = lead; i < row_size; ++i) { + dest_span[i] = src_span[i] + (last_span[i] + dest_span[i - bpp]) / 2; + } + break; + } + case 4: { + // Paeth. With up == upper_left == 0 (first row) the predictor always + // selects the left neighbor, so the first row reduces to Sub; with + // left == upper_left == 0 (lead bytes) it always selects up. + if (last_span.empty()) { + fxcrt::Copy(src_span.first(lead), dest_span); + for (size_t i = lead; i < row_size; ++i) { + dest_span[i] = src_span[i] + dest_span[i - bpp]; + } + break; + } + for (auto [s, u, d] : + fxcrt::Zip(src_span.first(lead), last_span, dest_span)) { + d = s + u; + } + for (size_t i = lead; i < row_size; ++i) { + dest_span[i] = + src_span[i] + PaethPredictor(dest_span[i - bpp], last_span[i], + last_span[i - bpp]); + } + break; + } + default: { + fxcrt::Copy(src_span, dest_span); + break; + } + } +} + +void TiffPredictLine(pdfium::span dest_span, + int bits_per_component, + int colors, + int columns) { + if (bits_per_component == 1) { + int row_bits = std::min(bits_per_component * colors * columns, + pdfium::checked_cast(dest_span.size() * 8)); + int index_pre = 0; + int col_pre = 0; + for (int i = 1; i < row_bits; i++) { + int col = i % 8; + int index = i / 8; + if (((dest_span[index] >> (7 - col)) & 1) ^ + ((dest_span[index_pre] >> (7 - col_pre)) & 1)) { + dest_span[index] |= 1 << (7 - col); + } else { + dest_span[index] &= ~(1 << (7 - col)); + } + index_pre = index; + col_pre = col; + } + return; + } + int bytes_per_pixel = bits_per_component * colors / 8; + if (bits_per_component == 16) { + for (size_t i = bytes_per_pixel; i + 1 < dest_span.size(); i += 2) { + uint16_t pixel = (dest_span[i - bytes_per_pixel] << 8) | + dest_span[i - bytes_per_pixel + 1]; + pixel += (dest_span[i] << 8) | dest_span[i + 1]; + dest_span[i] = pixel >> 8; + dest_span[i + 1] = (uint8_t)pixel; + } + } else { + for (size_t i = bytes_per_pixel; i < dest_span.size(); i++) { + dest_span[i] += dest_span[i - bytes_per_pixel]; + } + } +} + +} // namespace fxcodec diff --git a/core/fxcodec/image_predictors.h b/core/fxcodec/image_predictors.h new file mode 100644 index 0000000000..bd98f4b79d --- /dev/null +++ b/core/fxcodec/image_predictors.h @@ -0,0 +1,42 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CORE_FXCODEC_IMAGE_PREDICTORS_H_ +#define CORE_FXCODEC_IMAGE_PREDICTORS_H_ + +#include +#include + +#include "core/fxcodec/data_and_bytes_consumed.h" +#include "core/fxcrt/data_vector.h" +#include "core/fxcrt/span.h" + +namespace fxcodec { + +enum class PredictorType : uint8_t { kNone, kTiff, kPng }; + +// Values come from ISO 32000-1:2008, table 10. +PredictorType GetPredictor(int predictor); + +const DataAndBytesConsumed ApplyPredictor(DataVector decoded_buf, + int predictor, + int colors, + int bits_per_component, + int columns, + uint32_t bytes_consumed); + +void PngPredictLine(pdfium::span dest_span, + pdfium::span src_span, + pdfium::span last_span, + size_t row_size, + uint32_t bytes_per_pixel); + +void TiffPredictLine(pdfium::span dest_span, + int bits_per_component, + int colors, + int columns); + +} // namespace fxcodec + +#endif // CORE_FXCODEC_IMAGE_PREDICTORS_H_ diff --git a/core/fxcodec/jbig2/jbig2_context.cpp b/core/fxcodec/jbig2/jbig2_context.cpp index 219946e010..be5e2eb960 100644 --- a/core/fxcodec/jbig2/jbig2_context.cpp +++ b/core/fxcodec/jbig2/jbig2_context.cpp @@ -18,11 +18,14 @@ #include "core/fxcodec/jbig2/jbig2_grd_proc.h" #include "core/fxcodec/jbig2/jbig2_grrd_proc.h" #include "core/fxcodec/jbig2/jbig2_htrd_proc.h" +#include "core/fxcodec/jbig2/jbig2_huffman_decoder.h" #include "core/fxcodec/jbig2/jbig2_pdd_proc.h" #include "core/fxcodec/jbig2/jbig2_sdd_proc.h" #include "core/fxcodec/jbig2/jbig2_trd_proc.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/fixed_size_data_vector.h" +#include "core/fxcrt/fx_ceil_div.h" +#include "core/fxcrt/fx_ceil_log2.h" #include "core/fxcrt/fx_memory_wrappers.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/pauseindicator_iface.h" @@ -238,7 +241,7 @@ JBig2_Result CJBig2_Context::ParseSegmentHeader(CJBig2_Segment* pSegment) { } int number_of_bits_to_skip = 1 + pSegment->referred_to_segment_count_; - stream_->addOffset((number_of_bits_to_skip + 7) / 8); + stream_->addOffset(fxcrt::CeilDiv(number_of_bits_to_skip, 8)); } else { if (stream_->read1Byte(&cTemp) != 0) { return JBig2_Result::kFailure; @@ -710,20 +713,16 @@ JBig2_Result CJBig2_Context::ParseTextRegion(CJBig2_Segment* pSegment) { uint8_t SBSYMCODELEN = 0; if (pTRD->SBHUFF) { - std::vector SBSYMCODES = + CJBig2_HuffmanTable SBSYMCODES = DecodeSymbolIDHuffmanTable(pTRD->SBNUMSYMS); - if (SBSYMCODES.empty()) { + if (!SBSYMCODES.IsOK()) { return JBig2_Result::kFailure; } stream_->alignByte(); pTRD->SBSYMCODES = std::move(SBSYMCODES); } else { - dwTemp = 0; - while ((uint32_t)(1 << dwTemp) < pTRD->SBNUMSYMS) { - ++dwTemp; - } - SBSYMCODELEN = static_cast(dwTemp); + SBSYMCODELEN = fxcrt::CeilLog2(pTRD->SBNUMSYMS); } if (pTRD->SBHUFF) { @@ -1210,79 +1209,61 @@ JBig2_Result CJBig2_Context::ParseRegionInfo(JBig2RegionInfo* pRI) { return JBig2_Result::kSuccess; } -std::vector CJBig2_Context::DecodeSymbolIDHuffmanTable( +CJBig2_HuffmanTable CJBig2_Context::DecodeSymbolIDHuffmanTable( uint32_t SBNUMSYMS) { - const size_t kRunCodesSize = 35; - std::array huffman_codes; - for (size_t i = 0; i < kRunCodesSize; ++i) { - if (stream_->readNBits(4, &huffman_codes[i].codelen) != 0) { - return std::vector(); + std::array runcode_lengths; + for (uint8_t& runcode_length : runcode_lengths) { + uint32_t code_length; + if (stream_->readNBits(4, &code_length) != 0) { + return CJBig2_HuffmanTable(); } + runcode_length = code_length; } - if (!HuffmanAssignCode(huffman_codes)) { - return std::vector(); + + CJBig2_HuffmanTable runcode_table(runcode_lengths); + if (!runcode_table.IsOK()) { + return CJBig2_HuffmanTable(); } - std::vector SBSYMCODES(SBNUMSYMS); + CJBig2_HuffmanDecoder runcode_decoder(stream_.get()); + + std::vector SBSYMCODES(SBNUMSYMS); int32_t run = 0; int32_t i = 0; while (i < static_cast(SBNUMSYMS)) { - size_t j; - FX_SAFE_INT32 nSafeVal = 0; - int32_t nBits = 0; - uint32_t nTemp; - while (true) { - if (stream_->read1Bit(&nTemp) != 0) { - return std::vector(); - } - - nSafeVal <<= 1; - if (!nSafeVal.IsValid()) { - return std::vector(); - } - - nSafeVal |= nTemp; - ++nBits; - const int32_t nVal = nSafeVal.ValueOrDie(); - for (j = 0; j < kRunCodesSize; ++j) { - if (nBits == huffman_codes[j].codelen && - nVal == huffman_codes[j].code) { - break; - } - } - if (j < kRunCodesSize) { - break; - } + int32_t runcode; + if (runcode_decoder.DecodeAValue(&runcode_table, &runcode) != 0) { + return CJBig2_HuffmanTable(); } - int32_t runcode = static_cast(j); + uint32_t temp; if (runcode < 32) { - SBSYMCODES[i].codelen = runcode; + SBSYMCODES[i] = runcode; run = 0; } else if (runcode == 32) { - if (stream_->readNBits(2, &nTemp) != 0) { - return std::vector(); + if (stream_->readNBits(2, &temp) != 0) { + return CJBig2_HuffmanTable(); } - run = nTemp + 3; + run = temp + 3; } else if (runcode == 33) { - if (stream_->readNBits(3, &nTemp) != 0) { - return std::vector(); + if (stream_->readNBits(3, &temp) != 0) { + return CJBig2_HuffmanTable(); } - run = nTemp + 3; + run = temp + 3; } else if (runcode == 34) { - if (stream_->readNBits(7, &nTemp) != 0) { - return std::vector(); + if (stream_->readNBits(7, &temp) != 0) { + return CJBig2_HuffmanTable(); } - run = nTemp + 11; + run = temp + 11; } if (run > 0) { if (i + run > (int)SBNUMSYMS) { - return std::vector(); + return CJBig2_HuffmanTable(); } for (int32_t k = 0; k < run; ++k) { if (runcode == 32 && i > 0) { - SBSYMCODES[i + k].codelen = SBSYMCODES[i - 1].codelen; + SBSYMCODES[i + k] = SBSYMCODES[i - 1]; } else { - SBSYMCODES[i + k].codelen = 0; + SBSYMCODES[i + k] = 0; } } i += run; @@ -1290,10 +1271,7 @@ std::vector CJBig2_Context::DecodeSymbolIDHuffmanTable( ++i; } } - if (!HuffmanAssignCode(SBSYMCODES)) { - return std::vector(); - } - return SBSYMCODES; + return CJBig2_HuffmanTable(SBSYMCODES); } const CJBig2_HuffmanTable* CJBig2_Context::GetHuffmanTable(size_t idx) { @@ -1304,34 +1282,3 @@ const CJBig2_HuffmanTable* CJBig2_Context::GetHuffmanTable(size_t idx) { } return huffman_tables_[idx].get(); } - -// static -bool CJBig2_Context::HuffmanAssignCode( - pdfium::span symcodes) { - int lenmax = 0; - for (const auto& symcode : symcodes) { - lenmax = std::max(symcode.codelen, lenmax); - } - std::vector lencounts(lenmax + 1); - std::vector firstcodes(lenmax + 1); - for (const auto& symcode : symcodes) { - ++lencounts[symcode.codelen]; - } - lencounts[0] = 0; - for (int i = 1; i <= lenmax; ++i) { - FX_SAFE_INT32 shifted = firstcodes[i - 1]; - shifted += lencounts[i - 1]; - shifted <<= 1; - if (!shifted.IsValid()) { - return false; - } - firstcodes[i] = shifted.ValueOrDie(); - int curcode = firstcodes[i]; - for (auto& symcode : symcodes) { - if (symcode.codelen == i) { - symcode.code = curcode++; - } - } - } - return true; -} diff --git a/core/fxcodec/jbig2/jbig2_context.h b/core/fxcodec/jbig2/jbig2_context.h index 8dbc467317..07f0777d2d 100644 --- a/core/fxcodec/jbig2/jbig2_context.h +++ b/core/fxcodec/jbig2/jbig2_context.h @@ -39,8 +39,6 @@ class CJBig2_Context { ~CJBig2_Context(); - static bool HuffmanAssignCode(pdfium::span symcodes); - bool GetFirstPage(pdfium::span pBuf, int32_t width, int32_t height, @@ -83,7 +81,7 @@ class CJBig2_Context { JBig2_Result ParseTable(CJBig2_Segment* pSegment); JBig2_Result ParseRegionInfo(JBig2RegionInfo* pRI); - std::vector DecodeSymbolIDHuffmanTable(uint32_t SBNUMSYMS); + CJBig2_HuffmanTable DecodeSymbolIDHuffmanTable(uint32_t SBNUMSYMS); const CJBig2_HuffmanTable* GetHuffmanTable(size_t idx); std::unique_ptr global_context_; diff --git a/core/fxcodec/jbig2/jbig2_decoder.cpp b/core/fxcodec/jbig2/jbig2_decoder.cpp index 9fd3a5e57d..20e17c1d01 100644 --- a/core/fxcodec/jbig2/jbig2_decoder.cpp +++ b/core/fxcodec/jbig2/jbig2_decoder.cpp @@ -7,6 +7,7 @@ #include "core/fxcodec/jbig2/jbig2_decoder.h" #include +#include #include "core/fxcodec/jbig2/jbig2_context.h" #include "core/fxcodec/jbig2/jbig2_document_context.h" diff --git a/core/fxcodec/jbig2/jbig2_define.h b/core/fxcodec/jbig2/jbig2_define.h index f2796f5945..a4c770a956 100644 --- a/core/fxcodec/jbig2/jbig2_define.h +++ b/core/fxcodec/jbig2/jbig2_define.h @@ -17,16 +17,13 @@ struct JBig2RegionInfo { uint8_t flags; }; -struct JBig2HuffmanCode { - int32_t codelen; - int32_t code; -}; - constexpr int32_t kJBig2OOB = 1; +// Somewhat arbitrary limits to limit damage done by malicious inputs. +// Should be well above what's found in real-world files. constexpr int32_t kJBig2MaxReferredSegmentCount = 64; -constexpr uint32_t kJBig2MaxExportSymbols = 65535; -constexpr uint32_t kJBig2MaxNewSymbols = 65535; +constexpr uint32_t kJBig2MaxExportSymbols = 1 << 20; +constexpr uint32_t kJBig2MaxNewSymbols = 1 << 20; constexpr uint32_t kJBig2MaxPatternIndex = 65535; constexpr int32_t kJBig2MaxImageSize = 65535; diff --git a/core/fxcodec/jbig2/jbig2_grd_proc.cpp b/core/fxcodec/jbig2/jbig2_grd_proc.cpp index 14fb21df47..f234e77274 100644 --- a/core/fxcodec/jbig2/jbig2_grd_proc.cpp +++ b/core/fxcodec/jbig2/jbig2_grd_proc.cpp @@ -21,7 +21,7 @@ namespace { -// TODO(npm): Name this constants better or merge some together. +// TODO(crbug.com/567627774): Name this constants better or merge some together. constexpr std::array kOptConstant1 = { {0x9b25, 0x0795, 0x00e5}}; constexpr std::array kOptConstant2 = { @@ -125,7 +125,7 @@ std::unique_ptr CJBig2_GRDProc::DecodeArithOpt3( int LTP = 0; const LineLayout layout = GetLineLayout(GBW); - // TODO(npm): Why is the height only trimmed when OPT is 0? + // TODO(crbug.com/567627774): Why is the height only trimmed when OPT is 0? const uint32_t height = OPT == 0 ? GBH & 0x7fffffff : GBH; pdfium::span row_write; pdfium::span row_prev1; diff --git a/core/fxcodec/jbig2/jbig2_huffman_decoder.cpp b/core/fxcodec/jbig2/jbig2_huffman_decoder.cpp index 3d2061d596..65b81c471f 100644 --- a/core/fxcodec/jbig2/jbig2_huffman_decoder.cpp +++ b/core/fxcodec/jbig2/jbig2_huffman_decoder.cpp @@ -6,6 +6,8 @@ #include "core/fxcodec/jbig2/jbig2_huffman_decoder.h" +#include + #include "core/fxcodec/jbig2/jbig2_define.h" #include "core/fxcrt/fx_safe_types.h" @@ -16,44 +18,42 @@ CJBig2_HuffmanDecoder::~CJBig2_HuffmanDecoder() = default; int CJBig2_HuffmanDecoder::DecodeAValue(const CJBig2_HuffmanTable* pTable, int* nResult) { - FX_SAFE_INT32 nSafeVal = 0; - int nBits = 0; - while (true) { + FX_SAFE_UINT32 nSafeVal = 0; + const unsigned max_codelen = pTable->MaxCodeLen(); + for (unsigned nBits = 1; nBits <= max_codelen; ++nBits) { uint32_t nTmp; if (stream_->read1Bit(&nTmp) == -1) { - break; + return -1; } nSafeVal <<= 1; if (!nSafeVal.IsValid()) { - break; + return -1; } nSafeVal |= nTmp; - ++nBits; - const int32_t nVal = nSafeVal.ValueOrDie(); - for (uint32_t i = 0; i < pTable->Size(); ++i) { - const JBig2HuffmanCode& code = pTable->GetCODES()[i]; - if (code.codelen != nBits || code.code != nVal) { - continue; - } - - if (pTable->IsHTOOB() && i == pTable->Size() - 1) { - return kJBig2OOB; - } - - if (stream_->readNBits(pTable->GetRANGELEN()[i], &nTmp) == -1) { - return -1; - } - - uint32_t offset = pTable->IsHTOOB() ? 3 : 2; - if (i == pTable->Size() - offset) { - *nResult = pTable->GetRANGELOW()[i] - nTmp; - } else { - *nResult = pTable->GetRANGELOW()[i] + nTmp; - } - return 0; + std::optional line = + pTable->FindLine(nBits, nSafeVal.ValueOrDie()); + if (!line.has_value()) { + continue; + } + + const uint32_t i = line.value(); + if (pTable->IsHTOOB() && i == pTable->Size() - 1) { + return kJBig2OOB; + } + + if (stream_->readNBits(pTable->GetRANGELEN()[i], &nTmp) == -1) { + return -1; + } + + uint32_t offset = pTable->IsHTOOB() ? 3 : 2; + if (i == pTable->Size() - offset) { + *nResult = pTable->GetRANGELOW()[i] - nTmp; + } else { + *nResult = pTable->GetRANGELOW()[i] + nTmp; } + return 0; } return -1; } diff --git a/core/fxcodec/jbig2/jbig2_huffman_table.cpp b/core/fxcodec/jbig2/jbig2_huffman_table.cpp index f040a09a9b..e867a38749 100644 --- a/core/fxcodec/jbig2/jbig2_huffman_table.cpp +++ b/core/fxcodec/jbig2/jbig2_huffman_table.cpp @@ -6,30 +6,18 @@ #include "core/fxcodec/jbig2/jbig2_huffman_table.h" +#include #include #include #include #include "core/fxcodec/jbig2/jbig2_bit_stream.h" -#include "core/fxcodec/jbig2/jbig2_context.h" #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_safe_types.h" -#include "core/fxcrt/raw_span.h" namespace { -struct JBig2TableLine { - uint8_t PREFLEN; - uint8_t RANDELEN; - int32_t RANGELOW; -}; - -struct HuffmanTable { - bool HTOOB; - pdfium::raw_span lines; -}; - constexpr JBig2TableLine kTableLine1[] = {{1, 4, 0}, {2, 8, 16}, {3, 16, 272}, @@ -133,33 +121,62 @@ static_assert(CJBig2_HuffmanTable::kNumHuffmanTables == } // namespace CJBig2_HuffmanTable::CJBig2_HuffmanTable(size_t idx) { - const HuffmanTable& table = kHuffmanTables[idx]; - HTOOB = table.HTOOB; - NTEMP = static_cast(table.lines.size()); - ok_ = ParseFromStandardTable(idx); + ok_ = ParseFromTable(kHuffmanTables[idx]); DCHECK(ok_); } -CJBig2_HuffmanTable::CJBig2_HuffmanTable(CJBig2_BitStream* pStream) - : HTOOB(false), NTEMP(0) { +CJBig2_HuffmanTable::CJBig2_HuffmanTable(CJBig2_BitStream* pStream) { ok_ = ParseFromCodedBuffer(pStream); } +CJBig2_HuffmanTable::CJBig2_HuffmanTable(pdfium::span prefix_lengths) { + std::vector lines(prefix_lengths.size() + 2); + for (size_t i = 0; const auto& length : prefix_lengths) { + lines[i].PREFLEN = length; + lines[i].RANGELEN = 0; + lines[i].RANGELOW = static_cast(i); + ++i; + } + // Dummy open-ended intervals: + lines[prefix_lengths.size()] = {0, 0, 0}; + lines[prefix_lengths.size() + 1] = {0, 0, 0}; + + ok_ = ParseFromTable({.HTOOB = false, .lines = lines}); +} + +CJBig2_HuffmanTable::CJBig2_HuffmanTable() noexcept = default; +CJBig2_HuffmanTable::CJBig2_HuffmanTable(CJBig2_HuffmanTable&&) noexcept = + default; +CJBig2_HuffmanTable& CJBig2_HuffmanTable::operator=( + CJBig2_HuffmanTable&&) noexcept = default; CJBig2_HuffmanTable::~CJBig2_HuffmanTable() = default; -bool CJBig2_HuffmanTable::ParseFromStandardTable(size_t idx) { - pdfium::span lines = - kHuffmanTables[idx].lines.first(NTEMP); +std::optional CJBig2_HuffmanTable::FindLine(unsigned codelen, + uint32_t code) const { + if (codelen > max_codelen_ || code < first_codes_[codelen]) { + return std::nullopt; + } + const uint32_t rank = code - first_codes_[codelen]; + if (rank >= code_counts_[codelen]) { + return std::nullopt; + } + return lines_by_length_[first_line_indices_[codelen] + rank]; +} + +bool CJBig2_HuffmanTable::ParseFromTable(const HuffmanTable& table) { + HTOOB = table.HTOOB; + NTEMP = static_cast(table.lines.size()); + pdfium::span lines = table.lines; CODES.resize(lines.size()); RANGELEN.resize(lines.size()); RANGELOW.resize(lines.size()); for (size_t i = 0; const auto& line : lines) { CODES[i].codelen = line.PREFLEN; - RANGELEN[i] = line.RANDELEN; + RANGELEN[i] = line.RANGELEN; RANGELOW[i] = line.RANGELOW; ++i; } - return CJBig2_Context::HuffmanAssignCode(CODES); + return AssignCodesAndBuildIndex(); } bool CJBig2_HuffmanTable::ParseFromCodedBuffer(CJBig2_BitStream* pStream) { @@ -233,7 +250,52 @@ bool CJBig2_HuffmanTable::ParseFromCodedBuffer(CJBig2_BitStream* pStream) { ++NTEMP; } - return CJBig2_Context::HuffmanAssignCode(pdfium::span(CODES).first(NTEMP)); + return AssignCodesAndBuildIndex(); +} + +bool CJBig2_HuffmanTable::AssignCodesAndBuildIndex() { + max_codelen_ = 0; + for (const auto& code : CODES) { + max_codelen_ = std::max(code.codelen, max_codelen_); + } + const size_t table_size = static_cast(max_codelen_) + 1; + code_counts_.assign(table_size, 0); + for (const auto& code : CODES) { + ++code_counts_[code.codelen]; + } + code_counts_[0] = 0; + + first_codes_.assign(table_size, 0); + first_line_indices_.assign(table_size, 0); + FX_SAFE_UINT32 first_code = 0; + uint32_t next_line_index = 0; + + for (unsigned codelen = 1; codelen <= max_codelen_; ++codelen) { + first_code += code_counts_[codelen - 1]; + first_code <<= 1; + if (!first_code.IsValid()) { + return false; + } + first_codes_[codelen] = first_code.ValueOrDie(); + first_line_indices_[codelen] = next_line_index; + next_line_index += code_counts_[codelen]; + } + + // One pass over the lines assigns the codes and fills the index, with + // `filled` as the per length cursor. + lines_by_length_.resize(next_line_index); + std::vector filled(table_size); + for (size_t i = 0; i < CODES.size(); ++i) { + const uint32_t codelen = CODES[i].codelen; + if (codelen == 0) { + continue; + } + CODES[i].code = first_codes_[codelen] + filled[codelen]; + lines_by_length_[first_line_indices_[codelen] + filled[codelen]] = + static_cast(i); + ++filled[codelen]; + } + return true; } void CJBig2_HuffmanTable::ExtendBuffers(bool increment) { diff --git a/core/fxcodec/jbig2/jbig2_huffman_table.h b/core/fxcodec/jbig2/jbig2_huffman_table.h index 7f5d5a066d..07afded04f 100644 --- a/core/fxcodec/jbig2/jbig2_huffman_table.h +++ b/core/fxcodec/jbig2/jbig2_huffman_table.h @@ -10,16 +10,48 @@ #include #include +#include #include #include "core/fxcodec/jbig2/jbig2_define.h" +#include "core/fxcrt/raw_span.h" class CJBig2_BitStream; +struct JBig2HuffmanCode { + uint32_t codelen; + uint32_t code; +}; + +struct JBig2TableLine { + uint8_t PREFLEN; + uint8_t RANGELEN; + int32_t RANGELOW; +}; + +struct HuffmanTable { + bool HTOOB; + + // If HTOOB is set: The last line is for the OOB symbol. + // Its RANGELEN and RANGELOW are ignored. + // The last two lines (before the OOB symbol line, if present) + // are for the -infinity...value range and the value...infinity range. + // If this table has no -infinity...value range, set its PREFLEN to 0. + pdfium::raw_span lines; +}; + class CJBig2_HuffmanTable { public: explicit CJBig2_HuffmanTable(size_t idx); explicit CJBig2_HuffmanTable(CJBig2_BitStream* pStream); + + // Creates a huffman table that maps a canonical code with length + // prefix_lengths[i] to i. + explicit CJBig2_HuffmanTable(pdfium::span prefix_lengths); + + CJBig2_HuffmanTable() noexcept; + CJBig2_HuffmanTable(CJBig2_HuffmanTable&&) noexcept; + CJBig2_HuffmanTable& operator=(CJBig2_HuffmanTable&&) noexcept; ~CJBig2_HuffmanTable(); bool IsHTOOB() const { return HTOOB; } @@ -29,19 +61,36 @@ class CJBig2_HuffmanTable { const std::vector& GetRANGELOW() const { return RANGELOW; } bool IsOK() const { return ok_; } + unsigned MaxCodeLen() const { return max_codelen_; } + + // Returns the index of the line whose code has `codelen` bits and the value + // `code`, if there is one. + std::optional FindLine(unsigned codelen, uint32_t code) const; + static constexpr size_t kNumHuffmanTables = 16; private: - bool ParseFromStandardTable(size_t table_idx); + bool ParseFromTable(const HuffmanTable& table); bool ParseFromCodedBuffer(CJBig2_BitStream* pStream); void ExtendBuffers(bool increment); + bool AssignCodesAndBuildIndex(); - bool ok_; - bool HTOOB; - uint32_t NTEMP; + bool ok_ = false; + bool HTOOB = false; + uint32_t NTEMP = 0; std::vector CODES; std::vector RANGELEN; std::vector RANGELOW; + + // Index for FindLine(). The first three are indexed by code length and have + // `max_codelen_` + 1 entries. + unsigned max_codelen_ = 0; + std::vector first_codes_; + std::vector code_counts_; + std::vector first_line_indices_; + // Line indices ordered by code length, then by line index. Lines without a + // code, i.e. with a code length of zero, are not in here. + std::vector lines_by_length_; }; #endif // CORE_FXCODEC_JBIG2_JBIG2_HUFFMAN_TABLE_H_ diff --git a/core/fxcodec/jbig2/jbig2_image.cpp b/core/fxcodec/jbig2/jbig2_image.cpp index 4678a70a7e..b127e824c9 100644 --- a/core/fxcodec/jbig2/jbig2_image.cpp +++ b/core/fxcodec/jbig2/jbig2_image.cpp @@ -17,6 +17,7 @@ #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_2d_size.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_coordinates.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/notreached.h" @@ -39,7 +40,7 @@ uint32_t BitIndexToAlignedUint32(uint32_t index) { } size_t GetMiddleElementCount(int xd0, int xd1) { - return pdfium::checked_cast((xd1 / 32) - ((xd0 + 31) / 32)); + return pdfium::checked_cast((xd1 / 32) - fxcrt::CeilDiv(xd0, 32)); } uint32_t DoCompose(JBig2ComposeOp op, uint32_t val1, uint32_t val2) { diff --git a/core/fxcodec/jbig2/jbig2_sdd_proc.cpp b/core/fxcodec/jbig2/jbig2_sdd_proc.cpp index bc867e2a8e..a6cf7f8a93 100644 --- a/core/fxcodec/jbig2/jbig2_sdd_proc.cpp +++ b/core/fxcodec/jbig2/jbig2_sdd_proc.cpp @@ -20,6 +20,8 @@ #include "core/fxcodec/jbig2/jbig2_huffman_table.h" #include "core/fxcodec/jbig2/jbig2_symbol_dict.h" #include "core/fxcodec/jbig2/jbig2_trd_proc.h" +#include "core/fxcrt/fx_ceil_div.h" +#include "core/fxcrt/fx_ceil_log2.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/span_util.h" #include "core/fxcrt/stl_util.h" @@ -37,11 +39,8 @@ std::unique_ptr CJBig2_SDDProc::DecodeArith( auto IAAI = std::make_unique(); auto IAEX = std::make_unique(); - uint32_t SBSYMCODELENA = 0; - while ((uint32_t)(1 << SBSYMCODELENA) < (SDNUMINSYMS + SDNUMNEWSYMS)) { - SBSYMCODELENA++; - } - JBig2IntDecoderState ids(static_cast(SBSYMCODELENA)); + uint8_t SBSYMCODELENA = fxcrt::CeilLog2(SDNUMINSYMS + SDNUMNEWSYMS); + JBig2IntDecoderState ids(SBSYMCODELENA); std::vector> SDNEWSYMS(SDNUMNEWSYMS); uint32_t HCHEIGHT = 0; @@ -304,18 +303,12 @@ std::unique_ptr CJBig2_SDDProc::DecodeHuffman( pDecoder->SBNUMINSTANCES = REFAGGNINST; pDecoder->SBSTRIPS = 1; pDecoder->SBNUMSYMS = SDNUMINSYMS + NSYMSDECODED; - std::vector SBSYMCODES(pDecoder->SBNUMSYMS); - uint32_t nTmp = 1; - while (static_cast(1 << nTmp) < - SDNUMINSYMS + SDNUMNEWSYMS) { - ++nTmp; - } - uint8_t SBSYMCODELEN = static_cast(nTmp); - for (uint32_t i = 0; i < pDecoder->SBNUMSYMS; ++i) { - SBSYMCODES[i].codelen = SBSYMCODELEN; - SBSYMCODES[i].code = i; - } - pDecoder->SBSYMCODES = std::move(SBSYMCODES); + uint8_t SBSYMCODELEN = + std::max(1, fxcrt::CeilLog2(SDNUMINSYMS + SDNUMNEWSYMS)); + std::vector SBSYMCODES( + /*count=*/pDecoder->SBNUMSYMS, /*value=*/SBSYMCODELEN); + pDecoder->SBSYMCODES = CJBig2_HuffmanTable(SBSYMCODES); + DCHECK(pDecoder->SBSYMCODES.IsOK()); std::vector> SBSYMS(pDecoder->SBNUMSYMS); fxcrt::Copy(pdfium::span(SDINSYMS).first(SDNUMINSYMS), pdfium::span(SBSYMS)); @@ -347,18 +340,12 @@ std::unique_ptr CJBig2_SDDProc::DecodeHuffman( } } else if (REFAGGNINST == 1) { - uint32_t nTmp = 1; - while ((uint32_t)(1 << nTmp) < SDNUMINSYMS + SDNUMNEWSYMS) { - nTmp++; - } - uint8_t SBSYMCODELEN = (uint8_t)nTmp; - uint32_t IDI = 0; - for (uint32_t n = 0; n < SBSYMCODELEN; ++n) { - if (pStream->read1Bit(&nTmp) != 0) { - return nullptr; - } + uint8_t SBSYMCODELEN = + std::max(1, fxcrt::CeilLog2(SDNUMINSYMS + SDNUMNEWSYMS)); - IDI = (IDI << 1) | nTmp; + uint32_t IDI = 0; + if (pStream->readNBits(SBSYMCODELEN, &IDI) != 0) { + return nullptr; } uint32_t SBNUMSYMS = SDNUMINSYMS + NSYMSDECODED; @@ -371,18 +358,18 @@ std::unique_ptr CJBig2_SDDProc::DecodeHuffman( return nullptr; } - auto SBHUFFRDX = std::make_unique(15); + auto SBHUFFRDXRDY = std::make_unique(15); auto SBHUFFRSIZE = std::make_unique(1); int32_t RDXI; int32_t RDYI; - if ((pHuffmanDecoder->DecodeAValue(SBHUFFRDX.get(), &RDXI) != 0) || - (pHuffmanDecoder->DecodeAValue(SBHUFFRDX.get(), &RDYI) != 0) || + if ((pHuffmanDecoder->DecodeAValue(SBHUFFRDXRDY.get(), &RDXI) != 0) || + (pHuffmanDecoder->DecodeAValue(SBHUFFRDXRDY.get(), &RDYI) != 0) || (pHuffmanDecoder->DecodeAValue(SBHUFFRSIZE.get(), &nVal) != 0)) { return nullptr; } pStream->alignByte(); - nTmp = pStream->getOffset(); + uint32_t offset_before_grr = pStream->getOffset(); auto pGRRD = std::make_unique(); pGRRD->GRW = SYMWIDTH; @@ -404,7 +391,7 @@ std::unique_ptr CJBig2_SDDProc::DecodeHuffman( pStream->alignByte(); pStream->addOffset(2); - if ((uint32_t)nVal != (pStream->getOffset() - nTmp)) { + if ((uint32_t)nVal != (pStream->getOffset() - offset_before_grr)) { return nullptr; } } @@ -427,9 +414,7 @@ std::unique_ptr CJBig2_SDDProc::DecodeHuffman( return nullptr; } - // OK to not use FX_SAFE_UINT32 to calculate `stride` because - // `kJBig2MaxImageSize` is limiting the size. - const uint32_t stride = (TOTWIDTH + 7) / 8; + const uint32_t stride = fxcrt::CeilDiv(TOTWIDTH, 8); FX_SAFE_UINT32 safe_image_size = stride; safe_image_size *= HCHEIGHT; if (!safe_image_size.IsValid() || diff --git a/core/fxcodec/jbig2/jbig2_trd_proc.cpp b/core/fxcodec/jbig2/jbig2_trd_proc.cpp index abc97d1d93..3cc8d6d7fa 100644 --- a/core/fxcodec/jbig2/jbig2_trd_proc.cpp +++ b/core/fxcodec/jbig2/jbig2_trd_proc.cpp @@ -6,6 +6,7 @@ #include "core/fxcodec/jbig2/jbig2_trd_proc.h" +#include #include #include @@ -13,6 +14,7 @@ #include "core/fxcodec/jbig2/jbig2_arith_int_decoder.h" #include "core/fxcodec/jbig2/jbig2_grrd_proc.h" #include "core/fxcodec/jbig2/jbig2_huffman_decoder.h" +#include "core/fxcrt/fx_ceil_log2.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/maybe_owned.h" @@ -115,12 +117,9 @@ std::unique_ptr CJBig2_TRDProc::DecodeHuffman( } uint8_t CURT = 0; if (SBSTRIPS != 1) { - uint32_t nTmp = 1; - while (static_cast(1 << nTmp) < SBSTRIPS) { - ++nTmp; - } + uint32_t num_bits = std::max(1, fxcrt::CeilLog2(SBSTRIPS)); int32_t nVal; - if (pStream->readNBits(nTmp, &nVal) != 0) { + if (pStream->readNBits(num_bits, &nVal) != 0) { return nullptr; } @@ -132,33 +131,12 @@ std::unique_ptr CJBig2_TRDProc::DecodeHuffman( } int32_t TI = SAFE_TI.ValueOrDie(); - FX_SAFE_INT32 nSafeVal = 0; - int32_t nBits = 0; - uint32_t IDI; - for (;;) { - uint32_t nTmp; - if (pStream->read1Bit(&nTmp) != 0) { - return nullptr; - } - - nSafeVal <<= 1; - if (!nSafeVal.IsValid()) { - return nullptr; - } - nSafeVal |= nTmp; - ++nBits; - const int32_t nVal = nSafeVal.ValueOrDie(); - for (IDI = 0; IDI < SBNUMSYMS; ++IDI) { - if (nBits == SBSYMCODES[IDI].codelen && - nVal == SBSYMCODES[IDI].code) { - break; - } - } - if (IDI < SBNUMSYMS) { - break; - } + int32_t IDI; + if (pHuffmanDecoder->DecodeAValue(&SBSYMCODES, &IDI) != 0) { + return nullptr; } + bool RI = false; if (SBREFINE != 0 && pStream->read1Bit(&RI) != 0) { return nullptr; diff --git a/core/fxcodec/jbig2/jbig2_trd_proc.h b/core/fxcodec/jbig2/jbig2_trd_proc.h index 3d8c8869da..6e167f2d05 100644 --- a/core/fxcodec/jbig2/jbig2_trd_proc.h +++ b/core/fxcodec/jbig2/jbig2_trd_proc.h @@ -13,6 +13,8 @@ #include #include +#include "core/fxcodec/jbig2/jbig2_huffman_decoder.h" +#include "core/fxcodec/jbig2/jbig2_huffman_table.h" #include "core/fxcodec/jbig2/jbig2_image.h" #include "core/fxcrt/span.h" #include "core/fxcrt/unowned_ptr.h" @@ -21,9 +23,7 @@ class CJBig2_ArithDecoder; class CJBig2_ArithIaidDecoder; class CJBig2_ArithIntDecoder; class CJBig2_BitStream; -class CJBig2_HuffmanTable; class JBig2ArithCtx; -struct JBig2HuffmanCode; struct JBig2IntDecoderState { explicit JBig2IntDecoderState(uint8_t SBSYMCODELEN); @@ -73,7 +73,7 @@ class CJBig2_TRDProc { uint32_t SBNUMINSTANCES; uint32_t SBSTRIPS; uint32_t SBNUMSYMS; - std::vector SBSYMCODES; + CJBig2_HuffmanTable SBSYMCODES; std::vector> SBSYMS; JBig2ComposeOp SBCOMBOP; JBig2Corner REFCORNER; diff --git a/core/fxcodec/jpeg/DEPS b/core/fxcodec/jpeg/DEPS index a78ca5aada..132f1d0a0e 100644 --- a/core/fxcodec/jpeg/DEPS +++ b/core/fxcodec/jpeg/DEPS @@ -6,10 +6,7 @@ include_rules = [ ] specific_include_rules = { - "skia_jpeg_context\.(cpp|h)": [ - '+third_party/skia/include', - ], - "skia_scanline_decoder\.(cpp|h)": [ - '+third_party/skia/include', + "rust_.*": [ + '+third_party/rust/cxx', ], } diff --git a/core/fxcodec/jpeg/jpeg_common.c b/core/fxcodec/jpeg/jpeg_common.c index d2798f688f..9f06ecf514 100644 --- a/core/fxcodec/jpeg/jpeg_common.c +++ b/core/fxcodec/jpeg/jpeg_common.c @@ -88,6 +88,63 @@ void jpeg_common_error_fatal(j_common_ptr cinfo) { } #if BUILDFLAG(IS_WIN) +boolean jpeg_common_create_compress(JpegCompressCommon* jpeg_compress) { + if (setjmp(jpeg_compress->jmpbuf) == -1) { + return FALSE; + } + jpeg_compress->cinfo.err = &jpeg_compress->error_mgr; + jpeg_compress->cinfo.client_data = jpeg_compress; + jpeg_compress->error_mgr.error_exit = jpeg_common_error_fatal; + jpeg_compress->error_mgr.emit_message = jpeg_common_error_do_nothing_int; + jpeg_compress->error_mgr.output_message = jpeg_common_error_do_nothing; + jpeg_compress->error_mgr.format_message = jpeg_common_error_do_nothing_char; + jpeg_compress->error_mgr.reset_error_mgr = jpeg_common_error_do_nothing; + jpeg_create_compress(&jpeg_compress->cinfo); + jpeg_compress->dest_mgr.init_destination = jpeg_common_dest_do_nothing; + jpeg_compress->dest_mgr.term_destination = jpeg_common_dest_do_nothing; + jpeg_compress->dest_mgr.empty_output_buffer = jpeg_common_dest_empty; + jpeg_compress->cinfo.dest = &jpeg_compress->dest_mgr; + return TRUE; +} + +void jpeg_common_destroy_compress(JpegCompressCommon* jpeg_compress) { + jpeg_destroy_compress(&jpeg_compress->cinfo); +} + +boolean jpeg_common_set_defaults(JpegCompressCommon* jpeg_compress) { + if (setjmp(jpeg_compress->jmpbuf) == -1) { + return FALSE; + } + jpeg_set_defaults(&jpeg_compress->cinfo); + return TRUE; +} + +boolean jpeg_common_start_compress(JpegCompressCommon* jpeg_compress, + boolean write_all_tables) { + if (setjmp(jpeg_compress->jmpbuf) == -1) { + return FALSE; + } + jpeg_start_compress(&jpeg_compress->cinfo, write_all_tables); + return TRUE; +} + +int jpeg_common_write_scanlines(JpegCompressCommon* jpeg_compress, + JSAMPROW* scanlines, + unsigned int num_lines) { + if (setjmp(jpeg_compress->jmpbuf) == -1) { + return -1; + } + return (int)jpeg_write_scanlines(&jpeg_compress->cinfo, scanlines, num_lines); +} + +boolean jpeg_common_finish_compress(JpegCompressCommon* jpeg_compress) { + if (setjmp(jpeg_compress->jmpbuf) == -1) { + return FALSE; + } + jpeg_finish_compress(&jpeg_compress->cinfo); + return TRUE; +} + void jpeg_common_dest_do_nothing(j_compress_ptr cinfo) {} boolean jpeg_common_dest_empty(j_compress_ptr cinfo) { diff --git a/core/fxcodec/jpeg/jpeg_common.h b/core/fxcodec/jpeg/jpeg_common.h index b82ea604c3..88b3331556 100644 --- a/core/fxcodec/jpeg/jpeg_common.h +++ b/core/fxcodec/jpeg/jpeg_common.h @@ -62,6 +62,24 @@ void jpeg_common_error_do_nothing_char(j_common_ptr cinfo, char* arg); void jpeg_common_error_fatal(j_common_ptr cinfo); #if BUILDFLAG(IS_WIN) +struct JpegCompressCommon { + jmp_buf jmpbuf; + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr error_mgr; + struct jpeg_destination_mgr dest_mgr; +}; +typedef struct JpegCompressCommon JpegCompressCommon; + +boolean jpeg_common_create_compress(JpegCompressCommon* jpeg_compress); +void jpeg_common_destroy_compress(JpegCompressCommon* jpeg_compress); +boolean jpeg_common_set_defaults(JpegCompressCommon* jpeg_compress); +boolean jpeg_common_start_compress(JpegCompressCommon* jpeg_compress, + boolean write_all_tables); +int jpeg_common_write_scanlines(JpegCompressCommon* jpeg_compress, + JSAMPROW* scanlines, + unsigned int num_lines); +boolean jpeg_common_finish_compress(JpegCompressCommon* jpeg_compress); + void jpeg_common_dest_do_nothing(j_compress_ptr cinfo); boolean jpeg_common_dest_empty(j_compress_ptr cinfo); #endif // BUILDFLAG(IS_WIN) diff --git a/core/fxcodec/jpeg/jpegmodule.cpp b/core/fxcodec/jpeg/jpegmodule.cpp index 3cf8818a96..82239252a7 100644 --- a/core/fxcodec/jpeg/jpegmodule.cpp +++ b/core/fxcodec/jpeg/jpegmodule.cpp @@ -10,20 +10,20 @@ #include #include "build/build_config.h" -#include "core/fxcodec/jpeg/libjpeg_scanline_decoder.h" #include "core/fxcodec/scanlinedecoder.h" #include "core/fxcrt/span.h" #if defined(PDF_ENABLE_RUST_JPEG) -#include "core/fxcodec/jpeg/skia_scanline_decoder.h" +#include "core/fxcodec/jpeg/rust_jpeg_scanline_decoder.h" +#else +#include "core/fxcodec/jpeg/libjpeg_scanline_decoder.h" #endif #if BUILDFLAG(IS_WIN) -#include - #include "core/fxcodec/jpeg/jpeg_common.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/fx_memory.h" +#include "core/fxcrt/fx_memory_wrappers.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/retain_ptr.h" @@ -33,6 +33,22 @@ namespace fxcodec { +#if BUILDFLAG(IS_WIN) +namespace { + +struct ScopedJpegCompressCommon { + ~ScopedJpegCompressCommon() { + if (created) { + jpeg_common_destroy_compress(&common); + } + } + JpegCompressCommon common = {}; + bool created = false; +}; + +} // namespace +#endif // BUILDFLAG(IS_WIN) + // static std::unique_ptr JpegModule::CreateDecoder( pdfium::span src_span, @@ -40,45 +56,34 @@ std::unique_ptr JpegModule::CreateDecoder( uint32_t height, int nComps, bool ColorTransform, - uint32_t scale_denom) { + uint32_t scale_denom, + bool prefer_bgr_output) { #if defined(PDF_ENABLE_RUST_JPEG) - auto decoder = SkiaScanlineDecoder::Create(src_span, width, height, nComps, - ColorTransform, scale_denom); - if (decoder) { - return decoder; - } -#endif + // RustJpegScanlineDecoder does not implement `prefer_bgr_output`; it never + // reports ScanlinesAreBgr(), so callers translate as usual. + return RustJpegScanlineDecoder::Create(src_span, width, height, nComps, + ColorTransform, scale_denom); +#else return LibjpegScanlineDecoder::Create(src_span, width, height, nComps, - ColorTransform, scale_denom); + ColorTransform, scale_denom, + prefer_bgr_output); +#endif } // static std::optional JpegModule::LoadInfo( pdfium::span src_span) { #if defined(PDF_ENABLE_RUST_JPEG) - auto info = SkiaScanlineDecoder::LoadInfo(src_span); - if (info.has_value()) { - return info; - } -#endif + return RustJpegScanlineDecoder::LoadInfo(src_span); +#else return LibjpegScanlineDecoder::LoadInfo(src_span); +#endif } #if BUILDFLAG(IS_WIN) bool JpegModule::JpegEncode(const RetainPtr& pSource, uint8_t** dest_buf, size_t* dest_size) { - jpeg_error_mgr jerr; - jerr.error_exit = jpeg_common_error_do_nothing; - jerr.emit_message = jpeg_common_error_do_nothing_int; - jerr.output_message = jpeg_common_error_do_nothing; - jerr.format_message = jpeg_common_error_do_nothing_char; - jerr.reset_error_mgr = jpeg_common_error_do_nothing; - - jpeg_compress_struct cinfo = {}; // Aggregate initialization. - static_assert(std::is_aggregate_v); - cinfo.err = &jerr; - jpeg_create_compress(&cinfo); const int bytes_per_pixel = pSource->GetBPP() / 8; uint32_t nComponents = bytes_per_pixel >= 3 ? 3 : 1; uint32_t pitch = pSource->GetPitch(); @@ -92,48 +97,57 @@ bool JpegModule::JpegEncode(const RetainPtr& pSource, return false; } + static constexpr uint32_t kMinTryBufLen = 1024; uint32_t dest_buf_length = safe_buf_len.ValueOrDie(); - *dest_buf = FX_TryAlloc(uint8_t, dest_buf_length); - const int MIN_TRY_BUF_LEN = 1024; - while (!(*dest_buf) && dest_buf_length > MIN_TRY_BUF_LEN) { - dest_buf_length >>= 1; - *dest_buf = FX_TryAlloc(uint8_t, dest_buf_length); + std::unique_ptr local_dest; + for (; dest_buf_length >= kMinTryBufLen; dest_buf_length >>= 1) { + local_dest.reset(FX_TryAlloc(uint8_t, dest_buf_length)); + if (local_dest) { + break; + } } - if (!(*dest_buf)) { + if (!local_dest) { return false; } - jpeg_destination_mgr dest; - dest.init_destination = jpeg_common_dest_do_nothing; - dest.term_destination = jpeg_common_dest_do_nothing; - dest.empty_output_buffer = jpeg_common_dest_empty; - dest.next_output_byte = *dest_buf; - dest.free_in_buffer = dest_buf_length; - cinfo.dest = &dest; - cinfo.image_width = width; - cinfo.image_height = height; - cinfo.input_components = nComponents; + ScopedJpegCompressCommon compress_common; + compress_common.created = + jpeg_common_create_compress(&compress_common.common); + if (!compress_common.created) { + return false; + } + + compress_common.common.dest_mgr.next_output_byte = local_dest.get(); + compress_common.common.dest_mgr.free_in_buffer = dest_buf_length; + compress_common.common.cinfo.image_width = width; + compress_common.common.cinfo.image_height = height; + compress_common.common.cinfo.input_components = nComponents; if (nComponents == 1) { - cinfo.in_color_space = JCS_GRAYSCALE; + compress_common.common.cinfo.in_color_space = JCS_GRAYSCALE; } else if (nComponents == 3) { - cinfo.in_color_space = JCS_RGB; + compress_common.common.cinfo.in_color_space = JCS_RGB; } else { - cinfo.in_color_space = JCS_CMYK; + compress_common.common.cinfo.in_color_space = JCS_CMYK; } - uint8_t* line_buf = nullptr; + + std::unique_ptr line_buf; if (nComponents > 1) { - line_buf = FX_Alloc2D(uint8_t, width, nComponents); + line_buf.reset(FX_Alloc2D(uint8_t, width, nComponents)); } - jpeg_set_defaults(&cinfo); - jpeg_start_compress(&cinfo, TRUE); - JSAMPROW row_pointer[1]; + if (!jpeg_common_set_defaults(&compress_common.common) || + !jpeg_common_start_compress(&compress_common.common, TRUE)) { + return false; + } + + JSAMPROW row_pointer; JDIMENSION row; - while (cinfo.next_scanline < cinfo.image_height) { + while (compress_common.common.cinfo.next_scanline < + compress_common.common.cinfo.image_height) { pdfium::span src_scan = - pSource->GetScanline(cinfo.next_scanline); + pSource->GetScanline(compress_common.common.cinfo.next_scanline); if (nComponents > 1) { - uint8_t* dest_scan = line_buf; + uint8_t* dest_scan = line_buf.get(); if (nComponents == 3) { UNSAFE_TODO({ for (uint32_t i = 0; i < width; i++) { @@ -150,29 +164,36 @@ bool JpegModule::JpegEncode(const RetainPtr& pSource, } }); } - row_pointer[0] = line_buf; + row_pointer = line_buf.get(); } else { - row_pointer[0] = const_cast(src_scan.data()); + row_pointer = const_cast(src_scan.data()); + } + row = compress_common.common.cinfo.next_scanline; + if (jpeg_common_write_scanlines(&compress_common.common, &row_pointer, 1) < + 0) { + return false; } - row = cinfo.next_scanline; - jpeg_write_scanlines(&cinfo, row_pointer, 1); UNSAFE_TODO({ - if (cinfo.next_scanline == row) { + if (compress_common.common.cinfo.next_scanline == row) { static constexpr size_t kJpegBlockSize = 1048576; - *dest_buf = - FX_Realloc(uint8_t, *dest_buf, dest_buf_length + kJpegBlockSize); - dest.next_output_byte = - *dest_buf + dest_buf_length - dest.free_in_buffer; + local_dest.reset(FX_Realloc(uint8_t, local_dest.release(), + dest_buf_length + kJpegBlockSize)); + compress_common.common.dest_mgr.next_output_byte = + local_dest.get() + dest_buf_length - + compress_common.common.dest_mgr.free_in_buffer; dest_buf_length += kJpegBlockSize; - dest.free_in_buffer += kJpegBlockSize; + compress_common.common.dest_mgr.free_in_buffer += kJpegBlockSize; } }); } - jpeg_finish_compress(&cinfo); - jpeg_destroy_compress(&cinfo); - FX_Free(line_buf); - *dest_size = dest_buf_length - static_cast(dest.free_in_buffer); + if (!jpeg_common_finish_compress(&compress_common.common)) { + return false; + } + *dest_size = + dest_buf_length - + static_cast(compress_common.common.dest_mgr.free_in_buffer); + *dest_buf = local_dest.release(); return true; } #endif // BUILDFLAG(IS_WIN) diff --git a/core/fxcodec/jpeg/jpegmodule.h b/core/fxcodec/jpeg/jpegmodule.h index 94874668ba..f9f2d9aa9f 100644 --- a/core/fxcodec/jpeg/jpegmodule.h +++ b/core/fxcodec/jpeg/jpegmodule.h @@ -41,13 +41,21 @@ class JpegModule { // 1/`scale_denom` in each dimension. Must be one of 1, 2, 4, or 8 (libjpeg's // supported power-of-two DCT scalings). The resulting decoder reports the // scaled-down dimensions via GetWidth()/GetHeight(). + // + // `prefer_bgr_output` requests scanlines with the red and blue samples + // swapped, i.e. in B,G,R order. The request is honored only when the + // decode output would otherwise be plain 3-component RGB; whether it took + // effect is reported by ScanlineDecoder::ScanlinesAreBgr() on the result. + // libjpeg-turbo emits the swapped order at no cost (JCS_EXT_BGR), which + // lets a caller whose destination is BGR skip a per-pixel swap. static std::unique_ptr CreateDecoder( pdfium::span src_span, uint32_t width, uint32_t height, int nComps, bool ColorTransform, - uint32_t scale_denom); + uint32_t scale_denom, + bool prefer_bgr_output); static std::optional LoadInfo( pdfium::span src_span); diff --git a/core/fxcodec/jpeg/jpegmodule_unittest.cpp b/core/fxcodec/jpeg/jpegmodule_unittest.cpp index f8e7403b67..c764ded6d9 100644 --- a/core/fxcodec/jpeg/jpegmodule_unittest.cpp +++ b/core/fxcodec/jpeg/jpegmodule_unittest.cpp @@ -13,6 +13,8 @@ #include "core/fxcodec/scanlinedecoder.h" #include "core/fxcrt/span.h" +#include "core/fxcrt/span_util.h" +#include "core/fxge/dib/fx_dib.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/utils/file_util.h" #include "testing/utils/path_service.h" @@ -48,10 +50,10 @@ TEST(JpegModuleTest, LoadInfoValid) { } TEST(JpegModuleTest, CreateDecoderInvalid) { - EXPECT_FALSE(JpegModule::CreateDecoder({}, 120, 120, 3, true, 1)); + EXPECT_FALSE(JpegModule::CreateDecoder({}, 120, 120, 3, true, 1, false)); std::vector invalid = {0x00, 0x01, 0x02, 0x03}; - EXPECT_FALSE(JpegModule::CreateDecoder(invalid, 120, 120, 3, true, 1)); + EXPECT_FALSE(JpegModule::CreateDecoder(invalid, 120, 120, 3, true, 1, false)); } TEST(JpegModuleTest, CreateDecoderValid) { @@ -59,7 +61,7 @@ TEST(JpegModuleTest, CreateDecoderValid) { ASSERT_FALSE(jpeg_data.empty()); std::unique_ptr decoder = - JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 1); + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 1, false); ASSERT_TRUE(decoder); EXPECT_EQ(120, decoder->GetWidth()); EXPECT_EQ(120, decoder->GetHeight()); @@ -94,7 +96,7 @@ TEST(JpegModuleTest, DecodeWithoutTrailerLeavesSourceAlone) { const std::vector original = jpeg_data; std::unique_ptr decoder = - JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 1); + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 1, false); ASSERT_TRUE(decoder); for (int line = 0; line < 120; ++line) { EXPECT_FALSE(decoder->GetScanline(line).empty()) << "line " << line; @@ -108,7 +110,7 @@ TEST(JpegModuleTest, CreateDecoderDownscaled) { // 1/2 scale (scale_denom = 2). std::unique_ptr decoder2 = - JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 2); + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 2, false); ASSERT_TRUE(decoder2); EXPECT_EQ(60, decoder2->GetWidth()); EXPECT_EQ(60, decoder2->GetHeight()); @@ -121,7 +123,7 @@ TEST(JpegModuleTest, CreateDecoderDownscaled) { // 1/4 scale (scale_denom = 4). std::unique_ptr decoder4 = - JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 4); + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 4, false); ASSERT_TRUE(decoder4); EXPECT_EQ(30, decoder4->GetWidth()); EXPECT_EQ(30, decoder4->GetHeight()); @@ -134,7 +136,7 @@ TEST(JpegModuleTest, CreateDecoderDownscaled) { // 1/8 scale (scale_denom = 8). std::unique_ptr decoder8 = - JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 8); + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 8, false); ASSERT_TRUE(decoder8); EXPECT_EQ(15, decoder8->GetWidth()); EXPECT_EQ(15, decoder8->GetHeight()); @@ -146,4 +148,44 @@ TEST(JpegModuleTest, CreateDecoderDownscaled) { } } +TEST(JpegModuleTest, CreateDecoderBgrOutput) { + std::vector jpeg_data = LoadMonaLisa(); + ASSERT_FALSE(jpeg_data.empty()); + + std::unique_ptr rgb_decoder = + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 1, false); + ASSERT_TRUE(rgb_decoder); + EXPECT_FALSE(rgb_decoder->ScanlinesAreBgr()); + + std::unique_ptr bgr_decoder = + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 1, true); + ASSERT_TRUE(bgr_decoder); + + if (bgr_decoder->ScanlinesAreBgr()) { + // libjpeg honored the request (not the case for e.g. the Skia decoder): + // every scanline must hold exactly the RGB decode's samples with the + // red and blue positions exchanged. + EXPECT_EQ(3, bgr_decoder->CountComps()); + EXPECT_EQ(8, bgr_decoder->GetBPC()); + for (int line = 0; line < 120; ++line) { + pdfium::span rgb_bytes = rgb_decoder->GetScanline(line); + pdfium::span bgr_bytes = bgr_decoder->GetScanline(line); + ASSERT_EQ(360u, rgb_bytes.size()) << "line " << line; + ASSERT_EQ(360u, bgr_bytes.size()) << "line " << line; + // Reading one scanline as R,G,B structs and the other as B,G,R structs + // makes the same-samples-swapped-positions property a field-by-field + // comparison. + auto rgb = + fxcrt::reinterpret_span>(rgb_bytes); + auto bgr = + fxcrt::reinterpret_span>(bgr_bytes); + for (size_t i = 0; i < 120; ++i) { + ASSERT_EQ(rgb[i].red, bgr[i].red) << "line " << line << " px " << i; + ASSERT_EQ(rgb[i].green, bgr[i].green) << "line " << line << " px " << i; + ASSERT_EQ(rgb[i].blue, bgr[i].blue) << "line " << line << " px " << i; + } + } + } +} + } // namespace fxcodec diff --git a/core/fxcodec/jpeg/libjpeg_jpeg_context.cpp b/core/fxcodec/jpeg/libjpeg_jpeg_context.cpp index 64cb274447..d2ec7638c1 100644 --- a/core/fxcodec/jpeg/libjpeg_jpeg_context.cpp +++ b/core/fxcodec/jpeg/libjpeg_jpeg_context.cpp @@ -42,7 +42,7 @@ ProgressiveDecoderContextDelegate::Format GetCodecFormat(int num_components) { case 1: return ProgressiveDecoderContextDelegate::Format::k8bppGray; case 3: - return ProgressiveDecoderContextDelegate::Format::kRgb; + return ProgressiveDecoderContextDelegate::Format::kBgr; case 4: return ProgressiveDecoderContextDelegate::Format::kCmyk; default: @@ -117,11 +117,14 @@ FXCODEC_STATUS LibjpegJpegContext::StartDecode() { return error_status; } } + ProgressiveDecoderContextDelegate::Format src_format = + GetCodecFormat(common_.cinfo.num_components); + if (src_format == ProgressiveDecoderContextDelegate::Format::kInvalid) { + return FXCODEC_STATUS::kError; + } FX_SAFE_SIZE_T line_size = common_.cinfo.image_width; line_size *= common_.cinfo.num_components; line_buf_.resize(FxAlignToBoundary<4>(line_size).ValueOrDie()); - ProgressiveDecoderContextDelegate::Format src_format = - GetCodecFormat(common_.cinfo.num_components); if (!delegate_->PrepareScanlineResampling( common_.cinfo.image_width, common_.cinfo.image_height, src_format)) { return FXCODEC_STATUS::kError; @@ -170,6 +173,10 @@ int LibjpegJpegContext::ReadHeader(int* width, if (ret != JPEG_HEADER_OK) { return kError; } + if (GetCodecFormat(common_.cinfo.num_components) == + ProgressiveDecoderContextDelegate::Format::kInvalid) { + return kError; + } *width = common_.cinfo.image_width; *height = common_.cinfo.image_height; *nComps = common_.cinfo.num_components; diff --git a/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp b/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp index ba6264b725..6e05c67386 100644 --- a/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp +++ b/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp @@ -16,6 +16,7 @@ #include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/data_vector.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/raw_span.h" @@ -43,7 +44,7 @@ pdfium::span JpegScanSOI(pdfium::span src_span) { // power-of-two scalings (scale_denom of 1, 2, 4, 8), which reduce to // ceil(dim / scale_denom). uint32_t ScaledJpegSize(uint32_t dim, uint32_t scale_denom) { - return (dim + scale_denom - 1) / scale_denom; + return fxcrt::CeilDiv(dim, scale_denom); } } // namespace @@ -55,10 +56,11 @@ std::unique_ptr LibjpegScanlineDecoder::Create( uint32_t height, int nComps, bool ColorTransform, - uint32_t scale_denom) { + uint32_t scale_denom, + bool prefer_bgr_output) { auto decoder = std::make_unique(); if (!decoder->CreateImpl(src_span, width, height, nComps, ColorTransform, - scale_denom)) { + scale_denom, prefer_bgr_output)) { return nullptr; } return decoder; @@ -122,6 +124,22 @@ bool LibjpegScanlineDecoder::InitDecode(bool bAcceptKnownBadHeader) { common_.cinfo.out_color_space = common_.cinfo.jpeg_color_space; } +#ifdef JCS_EXTENSIONS + // When the caller asked for BGR scanlines and the output would otherwise + // be plain 3-component RGB, have libjpeg-turbo emit the swapped order + // directly (JCS_EXT_BGR): the same decode pipeline, storing the three + // converted samples at exchanged offsets, so the per-sample values are + // identical to a JCS_RGB decode. Deliberately not done when the output + // is anything else (grayscale, CMYK, or the raw non-JCS_RGB pass-through + // selected above), where no swap exists to fold in. + scanlines_are_bgr_ = prefer_bgr_output_ && + common_.cinfo.num_components == 3 && + common_.cinfo.out_color_space == JCS_RGB; + if (scanlines_are_bgr_) { + common_.cinfo.out_color_space = JCS_EXT_BGR; + } +#endif // JCS_EXTENSIONS + orig_width_ = common_.cinfo.image_width; orig_height_ = common_.cinfo.image_height; @@ -166,7 +184,9 @@ bool LibjpegScanlineDecoder::CreateImpl(pdfium::span src_span, uint32_t height, int nComps, bool ColorTransform, - uint32_t scale_denom) { + uint32_t scale_denom, + bool prefer_bgr_output) { + prefer_bgr_output_ = prefer_bgr_output; // Only power-of-two scalings up to 1/8 are supported: ScaledJpegSize() (which // sizes the scanline buffer via CalcPitch()) only matches libjpeg's actual // output dimensions for these. A different value would desynchronize the two @@ -258,9 +278,7 @@ void LibjpegScanlineDecoder::CalcPitch() { // `InitDecode()`. DCHECK_GT(output_width_, 0); pitch_ = static_cast(output_width_) * common_.cinfo.num_components; - pitch_ += 3; - pitch_ /= 4; - pitch_ *= 4; + pitch_ = fxcrt::CeilDiv(pitch_, 4) * 4; } void LibjpegScanlineDecoder::InitDecompressSrc() { @@ -308,33 +326,30 @@ bool LibjpegScanlineDecoder::IsSofSegment(size_t marker_offset) const { void LibjpegScanlineDecoder::PatchUpKnownBadHeaderWithInvalidHeight( size_t dimension_offset) { - DCHECK(src_span_.size() > dimension_offset + 1u); - auto pData = GetWritableSrcData().subspan(dimension_offset); - pData[0] = (orig_height_ >> 8) & 0xff; - pData[1] = orig_height_ & 0xff; + auto corrected_span = GetWritableSrcData().subspan(dimension_offset); + corrected_span[0] = (orig_height_ >> 8) & 0xff; + corrected_span[1] = orig_height_ & 0xff; } void LibjpegScanlineDecoder::PatchUpTrailer() { - // EmbedPDF(reads-dont-write): a trailer already in place needs no patch, - // so the usual JPEG is decoded from its own bytes, uncopied. - if (src_span_[src_span_.size() - 2] == 0xff && - src_span_[src_span_.size() - 1] == 0xd9) { + if (src_span_.size() < 2) { + return; + } + auto trailer_span = src_span_.last<2>(); + if (trailer_span[0] == 0xff && trailer_span[1] == 0xd9) { return; } - auto pData = GetWritableSrcData(); - pData[src_span_.size() - 2] = 0xff; - pData[src_span_.size() - 1] = 0xd9; + auto corrected_trailer_span = GetWritableSrcData().last<2>(); + corrected_trailer_span[0] = 0xff; + corrected_trailer_span[1] = 0xd9; } -// EmbedPDF(reads-dont-write): the source is the image stream's own bytes, -// which a layer shares with its frozen base and another thread may be -// decoding at the same time. A patch goes into a private copy instead. pdfium::span LibjpegScanlineDecoder::GetWritableSrcData() { - if (patched_src_.empty()) { - patched_src_ = DataVector(src_span_.begin(), src_span_.end()); - src_span_ = pdfium::span(patched_src_); + if (corrected_src_data_.empty()) { + corrected_src_data_ = ToDataVector(src_span_); + src_span_ = corrected_src_data_; } - return patched_src_; + return corrected_src_data_; } // static diff --git a/core/fxcodec/jpeg/libjpeg_scanline_decoder.h b/core/fxcodec/jpeg/libjpeg_scanline_decoder.h index fa729b9477..47702aa9af 100644 --- a/core/fxcodec/jpeg/libjpeg_scanline_decoder.h +++ b/core/fxcodec/jpeg/libjpeg_scanline_decoder.h @@ -28,7 +28,8 @@ class LibjpegScanlineDecoder final : public ScanlineDecoder { uint32_t height, int nComps, bool ColorTransform, - uint32_t scale_denom); + uint32_t scale_denom, + bool prefer_bgr_output); static std::optional LoadInfo( pdfium::span src_span); @@ -47,17 +48,16 @@ class LibjpegScanlineDecoder final : public ScanlineDecoder { uint32_t height, int nComps, bool ColorTransform, - uint32_t scale_denom); + uint32_t scale_denom, + bool prefer_bgr_output); bool InitDecode(bool bAcceptKnownBadHeader); void CalcPitch(); void InitDecompressSrc(); bool HasKnownBadHeaderWithInvalidHeight(size_t dimension_offset) const; bool IsSofSegment(size_t marker_offset) const; void PatchUpKnownBadHeaderWithInvalidHeight(size_t dimension_offset); - // Patch up the JPEG trailer, even if it is correct. + // Patch up the JPEG trailer if missing. void PatchUpTrailer(); - // EmbedPDF(reads-dont-write): a private copy of the source, made on the - // first patch. pdfium::span GetWritableSrcData(); // For a given invalid height byte offset in @@ -66,14 +66,13 @@ class LibjpegScanlineDecoder final : public ScanlineDecoder { static constexpr size_t kSofMarkerByteOffset = 5; JpegCommon common_ = {}; + DataVector corrected_src_data_; // Must outlive `src_span_`. pdfium::raw_span src_span_; - // EmbedPDF(reads-dont-write): the patched copy `src_span_` points at once a - // header or trailer needed patching; empty until then. - DataVector patched_src_; DataVector scanline_buf_; bool decompress_created_ = false; bool started_ = false; bool jpeg_transform_ = false; + bool prefer_bgr_output_ = false; uint32_t scale_denom_ = 1; }; diff --git a/core/fxcodec/jpeg/rust_jpeg_context.cpp b/core/fxcodec/jpeg/rust_jpeg_context.cpp new file mode 100644 index 0000000000..97afe38c56 --- /dev/null +++ b/core/fxcodec/jpeg/rust_jpeg_context.cpp @@ -0,0 +1,156 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/fxcodec/jpeg/rust_jpeg_context.h" + +#include + +#include "core/fxcodec/cfx_codec_memory.h" +#include "core/fxcodec/jpeg/rust_jpeg_ffi.rs.h" +#include "core/fxcodec/progressive_decoder_context_delegate.h" +#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" +#include "core/fxcrt/data_vector.h" +#include "core/fxcrt/fx_ceil_div.h" +#include "core/fxcrt/fx_safe_types.h" +#include "core/fxcrt/span.h" +#include "core/fxge/dib/cfx_dibitmap.h" + +namespace fxcodec { + +RustJpegContext::RustJpegContext(ProgressiveDecoderContextDelegate* delegate) + : delegate_(delegate) {} + +RustJpegContext::~RustJpegContext() = default; + +bool RustJpegContext::ReadHeader() { + if (state_ == State::kGotHeader || state_ == State::kFinishedDecoding) { + return true; + } + if (state_ == State::kError || !codec_memory_) { + return false; + } + + CHECK_EQ(state_, State::kNoDecoder); + + pdfium::span src_span = codec_memory_->GetBufferSpan(); + rust_jpeg::JpegHeaderInfo header_info{}; + rust::Slice src_slice(src_span); + if (!rust_jpeg::read_jpeg_info(src_slice, header_info)) { + // More data may be needed to parse the header during progressive decoding, + // so do not transition to `State::kError` here. + return false; + } + + switch (header_info.num_components) { + case 1: + src_format_ = ProgressiveDecoderContextDelegate::Format::k8bppGray; + break; + case 3: + src_format_ = ProgressiveDecoderContextDelegate::Format::kRgb; + break; + case 4: + src_format_ = ProgressiveDecoderContextDelegate::Format::kCmyk; + break; + default: + state_ = State::kError; + return false; + } + + width_ = header_info.width; + height_ = header_info.height; + num_components_ = header_info.num_components; + x_density_ = header_info.x_density; + y_density_ = header_info.y_density; + density_unit_ = header_info.density_unit; + + state_ = State::kGotHeader; + return true; +} + +bool RustJpegContext::ProcessData() { + if (state_ == State::kFinishedDecoding) { + return true; + } + if (state_ == State::kError) { + return false; + } + + if (state_ == State::kNoDecoder && !ReadHeader()) { + return false; + } + + CHECK_EQ(state_, State::kGotHeader); + + if (!delegate_->PrepareScanlineResampling(width_, height_, src_format_)) { + state_ = State::kError; + return false; + } + + pdfium::span src_span = codec_memory_->GetBufferSpan(); + rust::Slice src_slice(src_span); + + FX_SAFE_SIZE_T row_bytes = width_; + row_bytes *= num_components_; + row_bytes = fxcrt::CeilDiv(row_bytes, 4); + row_bytes *= 4; + if (!row_bytes.IsValid()) { + state_ = State::kError; + return false; + } + + FX_SAFE_SIZE_T total_bytes = row_bytes; + total_bytes *= height_; + if (!total_bytes.IsValid()) { + state_ = State::kError; + return false; + } + + DataVector decoded_buf(total_bytes.ValueOrDie()); + rust::Slice out_slice(decoded_buf); + if (!rust_jpeg::decode_jpeg_to_buf(src_slice, out_slice, + row_bytes.ValueOrDie(), + /*scale_denom=*/1)) { + state_ = State::kError; + return false; + } + + size_t stride = row_bytes.ValueOrDie(); + for (uint32_t y = 0; y < height_; ++y) { + pdfium::span scanline = + pdfium::span(decoded_buf).subspan(y * stride, stride); + delegate_->ResampleScanline(y, scanline); + } + + state_ = State::kFinishedDecoding; + return true; +} + +void RustJpegContext::Input(RetainPtr codec_memory) { + codec_memory_ = std::move(codec_memory); +} + +FXCODEC_STATUS RustJpegContext::StartDecode(RetainPtr bitmap) { + bitmap_ = std::move(bitmap); + FXCODEC_STATUS status = FXCODEC_STATUS::kDecodeToBeContinued; + if (!delegate_->ReadMoreData(0, &status)) { + return status; + } + return FXCODEC_STATUS::kDecodeToBeContinued; +} + +FXCODEC_STATUS RustJpegContext::ContinueDecode() { + FXCODEC_STATUS status = FXCODEC_STATUS::kDecodeFinished; + while (delegate_->ReadMoreData(std::nullopt, &status)) { + } + if (status == FXCODEC_STATUS::kError || !ProcessData()) { + state_ = State::kError; + status = FXCODEC_STATUS::kError; + } + bitmap_ = nullptr; + return state_ == State::kFinishedDecoding ? FXCODEC_STATUS::kDecodeFinished + : status; +} + +} // namespace fxcodec diff --git a/core/fxcodec/jpeg/rust_jpeg_context.h b/core/fxcodec/jpeg/rust_jpeg_context.h new file mode 100644 index 0000000000..c85d61b09e --- /dev/null +++ b/core/fxcodec/jpeg/rust_jpeg_context.h @@ -0,0 +1,65 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CORE_FXCODEC_JPEG_RUST_JPEG_CONTEXT_H_ +#define CORE_FXCODEC_JPEG_RUST_JPEG_CONTEXT_H_ + +#include + +#include "core/fxcodec/fx_codec_def.h" +#include "core/fxcodec/progressive_decoder_context.h" +#include "core/fxcodec/progressive_decoder_context_delegate.h" +#include "core/fxcrt/retain_ptr.h" +#include "core/fxcrt/unowned_ptr.h" + +class CFX_DIBitmap; + +namespace fxcodec { + +class RustJpegContext final : public ProgressiveDecoderContext { + public: + explicit RustJpegContext(ProgressiveDecoderContextDelegate* delegate); + ~RustJpegContext() override; + + bool ReadHeader(); + bool ProcessData(); + + uint32_t width() const { return width_; } + uint32_t height() const { return height_; } + int num_components() const { return num_components_; } + uint16_t x_density() const { return x_density_; } + uint16_t y_density() const { return y_density_; } + uint8_t density_unit() const { return density_unit_; } + + // ProgressiveDecoderContext: + void Input(RetainPtr codec_memory) override; + + FXCODEC_STATUS StartDecode(RetainPtr bitmap); + FXCODEC_STATUS ContinueDecode(); + + private: + enum class State { + kNoDecoder, + kGotHeader, + kFinishedDecoding, + kError, + }; + + State state_ = State::kNoDecoder; + uint32_t width_ = 0; + uint32_t height_ = 0; + int num_components_ = 0; + ProgressiveDecoderContextDelegate::Format src_format_ = + ProgressiveDecoderContextDelegate::Format::kInvalid; + uint16_t x_density_ = 0; + uint16_t y_density_ = 0; + uint8_t density_unit_ = 0; + UnownedPtr const delegate_; + RetainPtr codec_memory_; + RetainPtr bitmap_; +}; + +} // namespace fxcodec + +#endif // CORE_FXCODEC_JPEG_RUST_JPEG_CONTEXT_H_ diff --git a/core/fxcodec/jpeg/rust_jpeg_ffi.rs b/core/fxcodec/jpeg/rust_jpeg_ffi.rs new file mode 100644 index 0000000000..765642ecaf --- /dev/null +++ b/core/fxcodec/jpeg/rust_jpeg_ffi.rs @@ -0,0 +1,213 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use zune_jpeg::zune_core::bytestream::ZCursor; +use zune_jpeg::zune_core::colorspace::ColorSpace; +use zune_jpeg::JpegDecoder; + +#[cxx::bridge(namespace = "fxcodec::rust_jpeg")] +mod ffi { + pub struct JpegHeaderInfo { + pub width: u32, + pub height: u32, + pub num_components: i32, + pub bits_per_component: i32, + pub color_transform: bool, + pub x_density: u16, + pub y_density: u16, + pub density_unit: u8, + pub max_h_samp: u8, + pub max_v_samp: u8, + } + + extern "Rust" { + fn read_jpeg_info(src: &[u8], info: &mut JpegHeaderInfo) -> bool; + fn decode_jpeg_to_buf( + src: &[u8], + out_buf: &mut [u8], + out_pitch: usize, + scale_denom: u32, + ) -> bool; + } +} + +fn parse_jfif_density(src: &[u8]) -> (u16, u16, u8) { + let mut pos = 0; + while pos + 1 < src.len() { + if src[pos] == 0xff && src[pos + 1] == 0xd8 { + pos += 2; + break; + } + pos += 1; + } + while pos + 3 < src.len() { + if src[pos] != 0xff { + break; + } + let marker = src[pos + 1]; + if marker == 0xff { + pos += 1; + continue; + } + pos += 2; + if marker == 0xd8 || marker == 0xd9 || marker == 0x00 { + continue; + } + if pos + 2 > src.len() { + break; + } + let len = u16::from_be_bytes([src[pos], src[pos + 1]]) as usize; + if marker == 0xe0 && len >= 16 && pos + len <= src.len() { + let payload = &src[pos + 2..pos + len]; + if payload.starts_with(b"JFIF\0") && payload.len() >= 12 { + let density_unit = payload[7]; + let x_density = u16::from_be_bytes([payload[8], payload[9]]); + let y_density = u16::from_be_bytes([payload[10], payload[11]]); + return (x_density, y_density, density_unit); + } + } + pos += len; + } + (0, 0, 0) +} + +fn read_jpeg_info(src: &[u8], info: &mut ffi::JpegHeaderInfo) -> bool { + let cursor = ZCursor::new(src); + let mut decoder = JpegDecoder::new(cursor); + if decoder.decode_headers().is_err() { + return false; + } + let img_info = match decoder.info() { + Some(i) => i, + None => return false, + }; + info.width = img_info.width as u32; + info.height = img_info.height as u32; + info.num_components = img_info.components as i32; + info.bits_per_component = 8; + info.color_transform = + matches!(decoder.input_colorspace(), Some(ColorSpace::YCbCr) | Some(ColorSpace::YCCK)); + let (max_h_samp, max_v_samp) = match img_info.sample_ratio { + zune_jpeg::SampleRatios::None => (1, 1), + zune_jpeg::SampleRatios::H => (2, 1), + zune_jpeg::SampleRatios::V => (1, 2), + zune_jpeg::SampleRatios::HV => (2, 2), + zune_jpeg::SampleRatios::Generic(h, v) => (h as u8, v as u8), + }; + info.max_h_samp = max_h_samp; + info.max_v_samp = max_v_samp; + let (x_density, y_density, density_unit) = parse_jfif_density(src); + info.x_density = x_density; + info.y_density = y_density; + info.density_unit = density_unit; + true +} + +fn ycck_to_cmyk_inplace(pixels: &mut [u8]) { + for chunk in pixels.chunks_exact_mut(4) { + let y = chunk[0] as i32; + let cb = chunk[1] as i32 - 128; + let cr = chunk[2] as i32 - 128; + let k = chunk[3]; + + // 16-bit fixed-point scaling (SCALE = 65536, ONE_HALF = 32768). + let r = y + ((91881 * cr + 32768) >> 16); + let g = y + ((-22554 * cb - 46802 * cr + 32768) >> 16); + let b = y + ((116130 * cb + 32768) >> 16); + + chunk[0] = (255 - r).clamp(0, 255) as u8; + chunk[1] = (255 - g).clamp(0, 255) as u8; + chunk[2] = (255 - b).clamp(0, 255) as u8; + chunk[3] = k; + } +} + +fn decode_jpeg_to_buf(src: &[u8], out_buf: &mut [u8], out_pitch: usize, scale_denom: u32) -> bool { + let cursor = ZCursor::new(src); + let mut decoder = JpegDecoder::new(cursor); + if decoder.decode_headers().is_err() { + return false; + } + let img_info = match decoder.info() { + Some(i) => i, + None => return false, + }; + let is_ycck = decoder.input_colorspace() == Some(ColorSpace::YCCK); + if img_info.components == 1 { + let options = decoder.options().jpeg_set_out_colorspace(ColorSpace::Luma); + decoder.set_options(options); + } else if img_info.components == 4 { + let out_cs = if is_ycck { ColorSpace::YCCK } else { ColorSpace::CMYK }; + let options = decoder.options().jpeg_set_out_colorspace(out_cs); + decoder.set_options(options); + } + let output_colorspace = match decoder.output_colorspace() { + Some(cs) => cs, + None => return false, + }; + let mut pixels = match decoder.decode() { + Ok(p) => p, + Err(_) => return false, + }; + if is_ycck { + ycck_to_cmyk_inplace(&mut pixels); + } + let orig_width = img_info.width as usize; + let orig_height = img_info.height as usize; + let num_comps = output_colorspace.num_components(); + let src_row_len = orig_width * num_comps; + + if pixels.len() < orig_height * src_row_len { + return false; + } + + let scale = (scale_denom as usize).max(1); + let out_width = orig_width.div_ceil(scale); + let out_height = orig_height.div_ceil(scale); + let dst_row_len = out_width * num_comps; + + if scale == 1 { + for y in 0..out_height { + let src_row = &pixels[y * src_row_len..(y + 1) * src_row_len]; + let dst_start = y * out_pitch; + if dst_start + dst_row_len > out_buf.len() { + return false; + } + out_buf[dst_start..dst_start + dst_row_len].copy_from_slice(src_row); + } + } else { + for out_y in 0..out_height { + let dst_start = out_y * out_pitch; + if dst_start + dst_row_len > out_buf.len() { + return false; + } + let dst_row = &mut out_buf[dst_start..dst_start + dst_row_len]; + let y_start = out_y * scale; + let y_end = (y_start + scale).min(orig_height); + + for out_x in 0..out_width { + let x_start = out_x * scale; + let x_end = (x_start + scale).min(orig_width); + let count = (y_end - y_start) * (x_end - x_start); + let dst_pixel = &mut dst_row[out_x * num_comps..(out_x + 1) * num_comps]; + + if count == 0 { + continue; + } + + for c in 0..num_comps { + let mut sum: u32 = 0; + for sy in y_start..y_end { + let row_offset = sy * src_row_len; + for sx in x_start..x_end { + sum += pixels[row_offset + sx * num_comps + c] as u32; + } + } + dst_pixel[c] = ((sum + (count as u32 / 2)) / count as u32) as u8; + } + } + } + } + true +} diff --git a/core/fxcodec/jpeg/rust_jpeg_scanline_decoder.cpp b/core/fxcodec/jpeg/rust_jpeg_scanline_decoder.cpp new file mode 100644 index 0000000000..f67880c514 --- /dev/null +++ b/core/fxcodec/jpeg/rust_jpeg_scanline_decoder.cpp @@ -0,0 +1,252 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/fxcodec/jpeg/rust_jpeg_scanline_decoder.h" + +#include +#include +#include +#include + +#include "core/fxcodec/jpeg/rust_jpeg_ffi.rs.h" +#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" +#include "core/fxcrt/fx_ceil_div.h" +#include "core/fxcrt/fx_safe_types.h" +#include "core/fxcrt/span_util.h" + +namespace fxcodec { + +namespace { + +constexpr size_t kKnownBadHeaderWithInvalidHeightByteOffsetStarts[] = {94, 163}; + +// For a given invalid height byte offset in +// `kKnownBadHeaderWithInvalidHeightByteOffsetStarts`, the SOFn marker should +// be this many bytes before that. +constexpr size_t kSofMarkerByteOffset = 5; + +pdfium::span JpegScanSOI(pdfium::span src_span) { + if (src_span.size() < 2) { + return src_span; + } + for (size_t offset = 0; offset + 1 < src_span.size(); ++offset) { + if (src_span[offset] == 0xff && src_span[offset + 1] == 0xd8) { + return src_span.subspan(offset); + } + } + return src_span; +} + +uint32_t ScaledJpegSize(uint32_t dim, uint32_t scale_denom) { + return fxcrt::CeilDiv(dim, scale_denom); +} + +} // namespace + +// static +std::unique_ptr RustJpegScanlineDecoder::Create( + pdfium::span src_span, + uint32_t width, + uint32_t height, + int num_components, + bool color_transform, + uint32_t scale_denom) { + auto decoder = std::make_unique(); + if (!decoder->CreateImpl(src_span, width, height, num_components, + color_transform, scale_denom)) { + return nullptr; + } + return decoder; +} + +RustJpegScanlineDecoder::RustJpegScanlineDecoder() = default; + +RustJpegScanlineDecoder::~RustJpegScanlineDecoder() { + // Span in superclass can't outlive our buffer. + last_scanline_ = pdfium::span(); +} + +bool RustJpegScanlineDecoder::CreateImpl(pdfium::span src_span, + uint32_t width, + uint32_t height, + int num_components, + bool color_transform, + uint32_t scale_denom) { + src_span_ = JpegScanSOI(src_span); + if (src_span_.size() < 2) { + return false; + } + + PatchUpTrailer(); + + rust_jpeg::JpegHeaderInfo header_info{}; + rust::Slice src_slice(src_span_); + if (!rust_jpeg::read_jpeg_info(src_slice, header_info)) { + bool patched = false; + for (size_t offset : kKnownBadHeaderWithInvalidHeightByteOffsetStarts) { + if (HasKnownBadHeaderWithInvalidHeight(offset, width)) { + PatchUpKnownBadHeaderWithInvalidHeight(offset, height); + patched = true; + break; + } + } + if (!patched) { + return false; + } + // Recreate `src_slice` since patching updates `src_span_` to point to + // `corrected_src_data_`. + src_slice = rust::Slice(src_span_); + if (!rust_jpeg::read_jpeg_info(src_slice, header_info)) { + return false; + } + } + + orig_width_ = header_info.width; + orig_height_ = header_info.height; + + DCHECK(scale_denom == 1 || scale_denom == 2 || scale_denom == 4 || + scale_denom == 8); + + comps_ = header_info.num_components; + bpc_ = header_info.bits_per_component; + + constexpr int kDctSize = 8; + const int max_h_samp = std::max(1, header_info.max_h_samp); + const int max_v_samp = std::max(1, header_info.max_v_samp); + if (orig_width_ % (max_h_samp * kDctSize) != 0 || + orig_height_ % (max_v_samp * kDctSize) != 0) { + scale_denom = 1; + } + + output_width_ = ScaledJpegSize(orig_width_, scale_denom); + output_height_ = ScaledJpegSize(orig_height_, scale_denom); + + CalcPitch(); + + FX_SAFE_SIZE_T buf_size = pitch_; + buf_size *= output_height_; + if (!buf_size.IsValid()) { + return false; + } + decoded_image_ = DataVector(buf_size.ValueOrDie()); + + rust::Slice out_slice(decoded_image_); + if (!rust_jpeg::decode_jpeg_to_buf(src_slice, out_slice, pitch_, + scale_denom)) { + return false; + } + + next_row_ = 0; + return true; +} + +bool RustJpegScanlineDecoder::Rewind() { + next_row_ = 0; + return true; +} + +pdfium::span RustJpegScanlineDecoder::GetNextLine() { + if (next_row_ >= static_cast(output_height_)) { + return pdfium::span(); + } + + size_t offset = next_row_ * pitch_; + if (offset + pitch_ > decoded_image_.size()) { + return pdfium::span(); + } + + ++next_row_; + return pdfium::span(decoded_image_).subspan(offset, pitch_); +} + +uint32_t RustJpegScanlineDecoder::GetSrcOffset() { + return 0; +} + +void RustJpegScanlineDecoder::CalcPitch() { + DCHECK_GT(output_width_, 0); + pitch_ = static_cast(output_width_) * comps_; + pitch_ = fxcrt::CeilDiv(pitch_, 4) * 4; +} + +bool RustJpegScanlineDecoder::HasKnownBadHeaderWithInvalidHeight( + size_t dimension_offset, + uint32_t orig_width) const { + if (src_span_.size() <= dimension_offset + 3u) { + return false; + } + if (dimension_offset < kSofMarkerByteOffset) { + return false; + } + if (!IsSofSegment(dimension_offset - kSofMarkerByteOffset)) { + return false; + } + const auto header_dimensions = src_span_.subspan(dimension_offset); + uint8_t expected_width_byte1 = (orig_width >> 8) & 0xff; + uint8_t expected_width_byte2 = orig_width & 0xff; + return header_dimensions[0] == 0xff && header_dimensions[1] == 0xff && + header_dimensions[2] == expected_width_byte1 && + header_dimensions[3] == expected_width_byte2; +} + +void RustJpegScanlineDecoder::PatchUpKnownBadHeaderWithInvalidHeight( + size_t dimension_offset, + uint32_t orig_height) { + auto corrected_span = GetWritableSrcData().subspan(dimension_offset); + corrected_span[0] = (orig_height >> 8) & 0xff; + corrected_span[1] = orig_height & 0xff; +} + +bool RustJpegScanlineDecoder::IsSofSegment(size_t marker_offset) const { + if (src_span_.size() <= marker_offset + 1) { + return false; + } + const auto header_marker = src_span_.subspan(marker_offset); + return header_marker[0] == 0xff && header_marker[1] >= 0xc0 && + header_marker[1] <= 0xcf; +} + +void RustJpegScanlineDecoder::PatchUpTrailer() { + auto trailer_span = src_span_.last<2>(); + if (trailer_span[0] == 0xff && trailer_span[1] == 0xd9) { + return; + } + auto corrected_trailer_span = GetWritableSrcData().last<2>(); + corrected_trailer_span[0] = 0xff; + corrected_trailer_span[1] = 0xd9; +} + +pdfium::span RustJpegScanlineDecoder::GetWritableSrcData() { + if (corrected_src_data_.empty()) { + corrected_src_data_ = ToDataVector(src_span_); + src_span_ = corrected_src_data_; + } + return corrected_src_data_; +} + +// static +std::optional RustJpegScanlineDecoder::LoadInfo( + pdfium::span src_span) { + src_span = JpegScanSOI(src_span); + if (src_span.size() < 2) { + return std::nullopt; + } + + rust_jpeg::JpegHeaderInfo header_info{}; + rust::Slice src_slice(src_span); + if (!rust_jpeg::read_jpeg_info(src_slice, header_info)) { + return std::nullopt; + } + + JpegModule::ImageInfo info; + info.width = header_info.width; + info.height = header_info.height; + info.num_components = header_info.num_components; + info.color_transform = header_info.color_transform; + info.bits_per_components = header_info.bits_per_component; + return info; +} + +} // namespace fxcodec diff --git a/core/fxcodec/jpeg/skia_scanline_decoder.h b/core/fxcodec/jpeg/rust_jpeg_scanline_decoder.h similarity index 54% rename from core/fxcodec/jpeg/skia_scanline_decoder.h rename to core/fxcodec/jpeg/rust_jpeg_scanline_decoder.h index e96550e909..93c1cbeb32 100644 --- a/core/fxcodec/jpeg/skia_scanline_decoder.h +++ b/core/fxcodec/jpeg/rust_jpeg_scanline_decoder.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CORE_FXCODEC_JPEG_SKIA_SCANLINE_DECODER_H_ -#define CORE_FXCODEC_JPEG_SKIA_SCANLINE_DECODER_H_ +#ifndef CORE_FXCODEC_JPEG_RUST_JPEG_SCANLINE_DECODER_H_ +#define CORE_FXCODEC_JPEG_RUST_JPEG_SCANLINE_DECODER_H_ #include @@ -13,13 +13,12 @@ #include "core/fxcodec/jpeg/jpegmodule.h" #include "core/fxcodec/scanlinedecoder.h" #include "core/fxcrt/data_vector.h" +#include "core/fxcrt/raw_span.h" #include "core/fxcrt/span.h" -class SkCodec; - namespace fxcodec { -class SkiaScanlineDecoder final : public ScanlineDecoder { +class RustJpegScanlineDecoder final : public ScanlineDecoder { public: static std::unique_ptr Create( pdfium::span src_span, @@ -32,8 +31,8 @@ class SkiaScanlineDecoder final : public ScanlineDecoder { static std::optional LoadInfo( pdfium::span src_span); - SkiaScanlineDecoder(); - ~SkiaScanlineDecoder() override; + RustJpegScanlineDecoder(); + ~RustJpegScanlineDecoder() override; // ScanlineDecoder: [[nodiscard]] bool Rewind() override; @@ -48,13 +47,21 @@ class SkiaScanlineDecoder final : public ScanlineDecoder { bool color_transform, uint32_t scale_denom); void CalcPitch(); + bool HasKnownBadHeaderWithInvalidHeight(size_t dimension_offset, + uint32_t orig_width) const; + void PatchUpKnownBadHeaderWithInvalidHeight(size_t dimension_offset, + uint32_t orig_height); + bool IsSofSegment(size_t marker_offset) const; + // Patch up the JPEG trailer if missing. + void PatchUpTrailer(); + pdfium::span GetWritableSrcData(); - std::unique_ptr decoder_; - DataVector scanline_buf_; - DataVector row_decode_buf_; - uint32_t scale_denom_ = 1; + DataVector corrected_src_data_; // Must outlive `src_span_`. + pdfium::raw_span src_span_; + DataVector decoded_image_; + uint32_t next_row_ = 0; }; } // namespace fxcodec -#endif // CORE_FXCODEC_JPEG_SKIA_SCANLINE_DECODER_H_ +#endif // CORE_FXCODEC_JPEG_RUST_JPEG_SCANLINE_DECODER_H_ diff --git a/core/fxcodec/jpeg/skia_jpeg_context.cpp b/core/fxcodec/jpeg/skia_jpeg_context.cpp deleted file mode 100644 index 7d508c3d68..0000000000 --- a/core/fxcodec/jpeg/skia_jpeg_context.cpp +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2026 The PDFium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "core/fxcodec/jpeg/skia_jpeg_context.h" - -#include - -#include "core/fxcodec/cfx_codec_memory.h" -#include "core/fxcodec/codec_memory_sk_stream.h" -#include "core/fxcodec/progressive_decoder_context_delegate.h" -#include "core/fxcrt/check.h" -#include "core/fxcrt/check_op.h" -#include "core/fxcrt/fx_safe_types.h" -#include "core/fxcrt/notreached.h" -#include "core/fxcrt/span.h" -#include "core/fxge/dib/cfx_dibitmap.h" -#include "third_party/skia/include/codec/SkCodec.h" -#include "third_party/skia/include/codec/SkJpegDecoder.h" -#include "third_party/skia/include/core/SkColorType.h" -#include "third_party/skia/include/core/SkImageInfo.h" -#include "third_party/skia/include/core/SkStream.h" - -namespace fxcodec { - -SkiaJpegContext::SkiaJpegContext(ProgressiveDecoderContextDelegate* delegate) - : delegate_(delegate) {} - -SkiaJpegContext::~SkiaJpegContext() = default; - -bool SkiaJpegContext::ReadHeader(RetainPtr codec_memory) { - if (state_ == State::kGotDecoder || state_ == State::kFinishedDecoding) { - return true; - } - if (state_ == State::kError) { - return false; - } - - CHECK_EQ(state_, State::kNoDecoder); - CHECK(!decoder_); - - if (!codec_memory_) { - codec_memory_ = codec_memory; - } - CHECK_EQ(&*codec_memory_, &*codec_memory); - - auto stream = std::make_unique(std::move(codec_memory)); - SkCodec::Result result = SkCodec::kSuccess; - decoder_ = SkJpegDecoder::Decode(std::move(stream), &result); - switch (result) { - case SkCodec::kSuccess: { - SkImageInfo info = decoder_->getInfo(); - if (!delegate_->PrepareDirectOutput( - info.width(), info.height(), - ProgressiveDecoderContextDelegate::Format::kArgb)) { - decoder_.reset(); - state_ = State::kError; - return false; - } - state_ = State::kGotDecoder; - return true; - } - case SkCodec::kIncompleteInput: - codec_memory_->Seek(0); - return true; - default: - decoder_.reset(); - state_ = State::kError; - return false; - } -} - -bool SkiaJpegContext::ProcessData(RetainPtr codec_memory) { - if (state_ == State::kNoDecoder) { - if (!ReadHeader(std::move(codec_memory))) { - return false; - } - if (state_ == State::kNoDecoder) { - return true; - } - } - - if (state_ == State::kGotDecoder) { - CHECK(bitmap_); - SkImageInfo dst_info = - decoder_->getInfo().makeColorType(kBGRA_8888_SkColorType); - - pdfium::span dst_buffer = delegate_->AskImageBuf(); - FX_SAFE_SIZE_T row_bytes = dst_buffer.size(); - row_bytes /= dst_info.height(); - - SkCodec::Result result = decoder_->getPixels(dst_info, dst_buffer.data(), - row_bytes.ValueOrDie()); - decoder_.reset(); - switch (result) { - case SkCodec::kSuccess: - state_ = State::kFinishedDecoding; - return true; - case SkCodec::kIncompleteInput: - // SkJpegDecoder does not support incremental decoding. When - // incomplete, retry the entire operation from the beginning. - state_ = State::kNoDecoder; - codec_memory_->Seek(0); - return true; - default: - state_ = State::kError; - return false; - } - } - - CHECK_EQ(state_, State::kFinishedDecoding); - return true; -} - -void SkiaJpegContext::Input(RetainPtr codec_memory) { - codec_memory_ = std::move(codec_memory); -} - -FXCODEC_STATUS SkiaJpegContext::StartDecode(RetainPtr bitmap) { - bitmap_ = std::move(bitmap); - CHECK_EQ(bitmap_->GetFormat(), FXDIB_Format::kBgra); - FXCODEC_STATUS status = FXCODEC_STATUS::kDecodeToBeContinued; - if (!delegate_->ReadMoreData(0, &status)) { - return status; - } - return FXCODEC_STATUS::kDecodeToBeContinued; -} - -FXCODEC_STATUS SkiaJpegContext::ContinueDecode() { - FXCODEC_STATUS status = FXCODEC_STATUS::kDecodeFinished; - while (state_ != State::kFinishedDecoding) { - if (!ProcessData(codec_memory_)) { - status = FXCODEC_STATUS::kError; - break; - } - if (state_ == State::kFinishedDecoding) { - break; - } - status = FXCODEC_STATUS::kError; - if (!delegate_->ReadMoreData(std::nullopt, &status)) { - break; - } - } - bitmap_ = nullptr; - return state_ == State::kFinishedDecoding ? FXCODEC_STATUS::kDecodeFinished - : status; -} - -} // namespace fxcodec diff --git a/core/fxcodec/jpeg/skia_jpeg_context.h b/core/fxcodec/jpeg/skia_jpeg_context.h deleted file mode 100644 index 2392a533f8..0000000000 --- a/core/fxcodec/jpeg/skia_jpeg_context.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2026 The PDFium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CORE_FXCODEC_JPEG_SKIA_JPEG_CONTEXT_H_ -#define CORE_FXCODEC_JPEG_SKIA_JPEG_CONTEXT_H_ - -#include - -#include "core/fxcodec/fx_codec_def.h" -#include "core/fxcodec/progressive_decoder_context.h" -#include "core/fxcrt/retain_ptr.h" -#include "core/fxcrt/unowned_ptr.h" - -class CFX_CodecMemory; -class CFX_DIBitmap; -class SkCodec; - -namespace fxcodec { - -class ProgressiveDecoderContextDelegate; - -class SkiaJpegContext final : public ProgressiveDecoderContext { - public: - // Caller needs to guarantee that `delegate` lives longer than - // `SkiaJpegContext`. - explicit SkiaJpegContext(ProgressiveDecoderContextDelegate* delegate); - ~SkiaJpegContext() override; - - // ProgressiveDecoderContext: - void Input(RetainPtr codec_memory) override; - - FXCODEC_STATUS StartDecode(RetainPtr bitmap); - FXCODEC_STATUS ContinueDecode(); - - bool ReadHeader(RetainPtr codec_memory); - - private: - enum class State { - kNoDecoder, - kGotDecoder, - kFinishedDecoding, - kError, - }; - - bool ProcessData(RetainPtr codec_memory); - - State state_ = State::kNoDecoder; - UnownedPtr const delegate_; - RetainPtr bitmap_; - std::unique_ptr decoder_; - RetainPtr codec_memory_; -}; - -} // namespace fxcodec - -#endif // CORE_FXCODEC_JPEG_SKIA_JPEG_CONTEXT_H_ diff --git a/core/fxcodec/jpeg/skia_scanline_decoder.cpp b/core/fxcodec/jpeg/skia_scanline_decoder.cpp deleted file mode 100644 index 875601cc26..0000000000 --- a/core/fxcodec/jpeg/skia_scanline_decoder.cpp +++ /dev/null @@ -1,290 +0,0 @@ -// Copyright 2026 The PDFium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "core/fxcodec/jpeg/skia_scanline_decoder.h" - -#include -#include -#include -#include - -#include "core/fxcrt/check.h" -#include "core/fxcrt/check_op.h" -#include "core/fxcrt/fx_safe_types.h" -#include "core/fxcrt/notreached.h" -#include "core/fxcrt/span_util.h" -#include "core/fxcrt/zip.h" -#include "core/fxge/dib/fx_dib.h" -#include "third_party/skia/include/codec/SkCodec.h" -#include "third_party/skia/include/codec/SkJpegDecoder.h" -#include "third_party/skia/include/core/SkAlphaType.h" -#include "third_party/skia/include/core/SkColorType.h" -#include "third_party/skia/include/core/SkImageInfo.h" -#include "third_party/skia/include/core/SkStream.h" - -namespace fxcodec { - -namespace { - -pdfium::span JpegScanSOI(pdfium::span src_span) { - if (src_span.size() < 2) { - return src_span; - } - for (size_t offset = 0; offset + 1 < src_span.size(); ++offset) { - if (src_span[offset] == 0xff && src_span[offset + 1] == 0xd8) { - return src_span.subspan(offset); - } - } - return src_span; -} - -uint32_t ScaledJpegSize(uint32_t dim, uint32_t scale_denom) { - return (dim + scale_denom - 1) / scale_denom; -} - -// Manually parse JPEG markers to find the Start of Frame (SOF) marker -// to extract the horizontal and vertical sampling factors. This is used -// to determine the Maximum Coded Unit (MCU) size. -std::pair GetJpegMaxSamplingFactors( - pdfium::span src_span) { - size_t offset = 0; - while (offset + 1 < src_span.size()) { - if (src_span[offset] != 0xff) { - ++offset; - continue; - } - while (offset + 1 < src_span.size() && src_span[offset + 1] == 0xff) { - ++offset; - } - if (offset + 1 >= src_span.size()) { - break; - } - uint8_t marker = src_span[offset + 1]; - offset += 2; - if (marker == 0xd8 || (marker >= 0xd0 && marker <= 0xd7) || - marker == 0x01 || marker == 0x00) { - continue; - } - if (marker >= 0xc0 && marker <= 0xcf && marker != 0xc4 && marker != 0xc8 && - marker != 0xcc) { - if (offset + 8 > src_span.size()) { - break; - } - uint8_t num_comps = src_span[offset + 7]; - if (offset + 8 + static_cast(num_comps) * 3 > src_span.size()) { - break; - } - int max_h = 1; - int max_v = 1; - for (size_t c = 0; c < num_comps; ++c) { - uint8_t samp = src_span[offset + 8 + c * 3 + 1]; - max_h = std::max(max_h, static_cast(samp >> 4)); - max_v = std::max(max_v, static_cast(samp & 0x0f)); - } - return {std::max(1, max_h), std::max(1, max_v)}; - } - if (marker == 0xd9 || marker == 0xda) { - break; - } - if (offset + 2 > src_span.size()) { - break; - } - size_t length = - (static_cast(src_span[offset]) << 8) | src_span[offset + 1]; - if (length < 2) { - break; - } - offset += length; - } - return {1, 1}; -} - -} // namespace - -// static -std::unique_ptr SkiaScanlineDecoder::Create( - pdfium::span src_span, - uint32_t width, - uint32_t height, - int num_components, - bool color_transform, - uint32_t scale_denom) { - auto decoder = std::make_unique(); - if (!decoder->CreateImpl(src_span, width, height, num_components, - color_transform, scale_denom)) { - return nullptr; - } - return decoder; -} - -SkiaScanlineDecoder::SkiaScanlineDecoder() = default; - -SkiaScanlineDecoder::~SkiaScanlineDecoder() = default; - -bool SkiaScanlineDecoder::CreateImpl(pdfium::span src_span, - uint32_t width, - uint32_t height, - int num_components, - bool color_transform, - uint32_t scale_denom) { - DCHECK(scale_denom == 1 || scale_denom == 2 || scale_denom == 4 || - scale_denom == 8); - if (!color_transform) { - return false; - } - if (num_components != 1 && num_components != 3) { - return false; - } - - scale_denom_ = scale_denom; - src_span = JpegScanSOI(src_span); - if (src_span.size() < 2) { - return false; - } - - auto stream = SkMemoryStream::MakeDirect(src_span.data(), src_span.size()); - SkCodec::Result result = SkCodec::kSuccess; - decoder_ = SkJpegDecoder::Decode(std::move(stream), &result); - if (!decoder_ || result != SkCodec::kSuccess) { - return false; - } - - orig_width_ = decoder_->dimensions().width(); - orig_height_ = decoder_->dimensions().height(); - if (orig_width_ < static_cast(width) || - orig_height_ < static_cast(height)) { - return false; - } - - // Skia's JPEG decoder does not support downscaling if the image dimensions - // are not aligned to the Maximum Coded Unit (MCU) boundaries. - auto [max_h_samp, max_v_samp] = GetJpegMaxSamplingFactors(src_span); - if (orig_width_ % (max_h_samp * 8) != 0 || - orig_height_ % (max_v_samp * 8) != 0) { - scale_denom_ = 1; - } - - comps_ = num_components; - bpc_ = 8; - output_width_ = ScaledJpegSize(orig_width_, scale_denom_); - output_height_ = ScaledJpegSize(orig_height_, scale_denom_); - - CalcPitch(); - scanline_buf_ = DataVector(pitch_); - - if (comps_ == 3) { - FX_SAFE_SIZE_T safe_row_size = output_width_; - safe_row_size *= 4; - row_decode_buf_.resize(safe_row_size.ValueOrDie()); - } - - return true; -} - -bool SkiaScanlineDecoder::Rewind() { - if (!decoder_) { - return false; - } - - SkColorType color_type = kUnknown_SkColorType; - switch (comps_) { - case 1: - color_type = kGray_8_SkColorType; - break; - case 3: - color_type = kRGBA_8888_SkColorType; - break; - default: - NOTREACHED(); - } - - SkImageInfo dst_info = SkImageInfo::Make(output_width_, output_height_, - color_type, kOpaque_SkAlphaType); - SkCodec::Options options; - return decoder_->startScanlineDecode(dst_info, &options) == SkCodec::kSuccess; -} - -pdfium::span SkiaScanlineDecoder::GetNextLine() { - if (!decoder_) { - return pdfium::span(); - } - - if (comps_ == 3) { - int lines = decoder_->getScanlines(row_decode_buf_.data(), 1, - row_decode_buf_.size()); - if (lines <= 0) { - return pdfium::span(); - } - pdfium::span src_bytes(row_decode_buf_); - pdfium::span dst_bytes(scanline_buf_); - auto src_span = - fxcrt::reinterpret_span>(src_bytes).first( - static_cast(output_width_)); - auto dst_span = fxcrt::reinterpret_span>(dst_bytes); - for (auto [src, dst] : fxcrt::Zip(src_span, dst_span)) { - dst.red = src.red; - dst.green = src.green; - dst.blue = src.blue; - } - return scanline_buf_; - } - - int lines = decoder_->getScanlines(scanline_buf_.data(), 1, pitch_); - if (lines <= 0) { - return pdfium::span(); - } - return scanline_buf_; -} - -uint32_t SkiaScanlineDecoder::GetSrcOffset() { - return 0; -} - -void SkiaScanlineDecoder::CalcPitch() { - DCHECK_GT(output_width_, 0); - pitch_ = static_cast(output_width_) * comps_; - pitch_ += 3; - pitch_ /= 4; - pitch_ *= 4; -} - -// static -std::optional SkiaScanlineDecoder::LoadInfo( - pdfium::span src_span) { - src_span = JpegScanSOI(src_span); - if (src_span.size() < 2) { - return std::nullopt; - } - - auto stream = SkMemoryStream::MakeDirect(src_span.data(), src_span.size()); - SkCodec::Result result = SkCodec::kSuccess; - auto decoder = SkJpegDecoder::Decode(std::move(stream), &result); - if (!decoder || result != SkCodec::kSuccess) { - return std::nullopt; - } - - SkImageInfo sk_info = decoder->getInfo(); - JpegModule::ImageInfo info; - info.width = sk_info.width(); - info.height = sk_info.height(); - switch (sk_info.colorType()) { - case kGray_8_SkColorType: - info.num_components = 1; - break; - case kRGB_565_SkColorType: - case kRGB_888x_SkColorType: - case kRGBA_8888_SkColorType: - case kBGRA_8888_SkColorType: - info.num_components = 3; - break; - default: - info.num_components = 4; - break; - } - info.color_transform = true; - info.bits_per_components = 8; - return info; -} - -} // namespace fxcodec diff --git a/core/fxcodec/jpx/cjpx_decoder.cpp b/core/fxcodec/jpx/cjpx_decoder.cpp index 94003318c0..b61dd5cd30 100644 --- a/core/fxcodec/jpx/cjpx_decoder.cpp +++ b/core/fxcodec/jpx/cjpx_decoder.cpp @@ -16,6 +16,7 @@ #include "core/fxcodec/jpx/jpx_decode_utils.h" #include "core/fxcrt/check_op.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/ptr_util.h" @@ -155,7 +156,7 @@ void sycc444_to_rgb(opj_image_t* img) { bool sycc420_422_size_is_valid(pdfium::span components) { return components[0].w != std::numeric_limits::max() && - (components[0].w + 1) / 2 == components[1].w && + fxcrt::CeilDiv(components[0].w, 2) == components[1].w && components[1].w == components[2].w && components[1].h == components[2].h; } @@ -163,7 +164,7 @@ bool sycc420_422_size_is_valid(pdfium::span components) { bool sycc420_size_is_valid(pdfium::span components) { return sycc420_422_size_is_valid(components) && components[0].h != std::numeric_limits::max() && - (components[0].h + 1) / 2 == components[1].h; + fxcrt::CeilDiv(components[0].h, 2) == components[1].h; } bool sycc420_must_extend_cbcr(OPJ_UINT32 y, OPJ_UINT32 cbcr) { diff --git a/core/fxcodec/png/libpng_png_context.cpp b/core/fxcodec/png/libpng_png_context.cpp index 8dd1d963ab..bc3ac79db5 100644 --- a/core/fxcodec/png/libpng_png_context.cpp +++ b/core/fxcodec/png/libpng_png_context.cpp @@ -72,7 +72,7 @@ void _png_get_header_func(png_structp png_ptr, png_infop info_ptr) { // Notifies the delegate of image dimensions and metadata. if (!context->delegate_->PrepareDirectOutput( width, height, - fxcodec::ProgressiveDecoderContextDelegate::Format::kArgb)) { + fxcodec::ProgressiveDecoderContextDelegate::Format::kBgra)) { // Note that `png_error` function is marked as `PNG_NORETURN`. png_error(context->png_, "Read Header Callback Error"); } diff --git a/core/fxcodec/png/rust_png_context.cpp b/core/fxcodec/png/rust_png_context.cpp index 5135e13942..dd2aeadef5 100644 --- a/core/fxcodec/png/rust_png_context.cpp +++ b/core/fxcodec/png/rust_png_context.cpp @@ -40,7 +40,7 @@ bool RustPngContext::ReadHeader() { width_ = header_info.width; height_ = header_info.height; if (!delegate_->PrepareDirectOutput( - width_, height_, ProgressiveDecoderContextDelegate::Format::kArgb)) { + width_, height_, ProgressiveDecoderContextDelegate::Format::kBgra)) { state_ = State::kError; return false; } diff --git a/core/fxcodec/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index ad07c35263..404cd027e7 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -11,6 +11,7 @@ #include #include "build/build_config.h" +#include "core/fxcodec/fx_codec.h" #include "core/fxcodec/gif/cfx_gifcontext.h" #include "core/fxcodec/progressive_decoder_context.h" #include "core/fxcodec/tiff/libtiff_tiff_context.h" @@ -26,7 +27,6 @@ #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/span_util.h" #include "core/fxcrt/stl_util.h" -#include "core/fxcrt/to_underlying.h" #include "core/fxge/dib/cfx_cmyk_to_srgb.h" #include "core/fxge/dib/cfx_dibitmap.h" #include "core/fxge/dib/fx_dib.h" @@ -42,7 +42,7 @@ #endif #if defined(PDF_ENABLE_RUST_JPEG) -#include "core/fxcodec/jpeg/skia_jpeg_context.h" +#include "core/fxcodec/jpeg/rust_jpeg_context.h" #else #include "core/fxcodec/jpeg/libjpeg_jpeg_context.h" #endif @@ -73,7 +73,7 @@ std::unique_ptr CreateDecoderContext( return std::make_unique(delegate); case FXCODEC_IMAGE_JPG: { #if defined(PDF_ENABLE_RUST_JPEG) - return std::make_unique(delegate); + return std::make_unique(delegate); #else auto context = std::make_unique(delegate); if (!context->create_ok_) { @@ -133,12 +133,15 @@ bool ProgressiveDecoder::PrepareScanlineResampling( Format src_format, pdfium::span palette, std::optional fill_argb) { - if (!device_bitmap_) { + if (!device_bitmap_ || src_format == Format::kInvalid || src_width <= 0 || + src_height <= 0) { return false; } src_width_ = src_width; src_height_ = src_height; src_format_ = src_format; + src_components_count_ = GetCompsFromFormat(src_format); + src_bits_per_component_ = 8; SetTransMethod(); decode_buf_.resize(GetScanlineSize()); FXDIB_ResampleOptions options; @@ -147,7 +150,7 @@ bool ProgressiveDecoder::PrepareScanlineResampling( src_width_, options); if (!palette.empty()) { device_bitmap_->SetPalette(palette); - src_palette_ = DataVector(palette.begin(), palette.end()); + src_palette_ = ToDataVector(palette); } if (fill_argb.has_value()) { device_bitmap_->Clear(fill_argb.value()); @@ -170,7 +173,7 @@ void ProgressiveDecoder::ResampleScanline( bool ProgressiveDecoder::PrepareDirectOutput(int src_width, int src_height, Format src_format) { - if (src_width <= 0 || src_height <= 0 || src_format != Format::kArgb) { + if (src_width <= 0 || src_height <= 0 || src_format != Format::kBgra) { return false; } src_width_ = src_width; @@ -185,13 +188,13 @@ pdfium::span ProgressiveDecoder::AskScanlineBuf(int line) { CHECK_GE(line, 0); CHECK_LT(line, src_height_); CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); - CHECK_EQ(src_format_, Format::kArgb); + CHECK_EQ(src_format_, Format::kBgra); return device_bitmap_->GetWritableScanline(line); } pdfium::span ProgressiveDecoder::AskImageBuf() { CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); - CHECK_EQ(src_format_, Format::kArgb); + CHECK_EQ(src_format_, Format::kBgra); return device_bitmap_->GetWritableBuffer(); } @@ -232,11 +235,11 @@ bool ProgressiveDecoder::BmpDetectImageTypeInBuffer( format = FXDIB_Format::k8bppRgb; break; case 3: - src_format_ = Format::kRgb; + src_format_ = Format::kBgr; format = FXDIB_Format::kBgr; break; case 4: - src_format_ = Format::kRgb32; + src_format_ = Format::kBgrx; format = FXDIB_Format::kBgrx; break; default: @@ -406,19 +409,33 @@ bool ProgressiveDecoder::JpegDetectImageTypeInBuffer( context_->Input(codec_memory_); #if defined(PDF_ENABLE_RUST_JPEG) - auto* ctx = static_cast(context_.get()); + auto* ctx = static_cast(context_.get()); src_width_ = 0; src_height_ = 0; - if (ctx->ReadHeader(codec_memory_)) { + while (!ctx->ReadHeader()) { FXCODEC_STATUS status = FXCODEC_STATUS::kError; - while (src_width_ == 0 && ReadMoreData(std::nullopt, &status)) { - if (!ctx->ReadHeader(codec_memory_)) { - break; - } + if (!ReadMoreData(std::nullopt, &status)) { + break; } } - bool got_metadata = (src_width_ > 0 && src_height_ > 0); + bool got_metadata = (ctx->width() > 0 && ctx->height() > 0); + if (got_metadata) { + src_width_ = ctx->width(); + src_height_ = ctx->height(); + src_components_count_ = ctx->num_components(); + src_bits_per_component_ = 8; +#if defined(PDF_ENABLE_XFA) + if (pAttribute) { + pAttribute->x_dpi_ = ctx->x_density(); + pAttribute->y_dpi_ = ctx->y_density(); + pAttribute->dpi_unit_ = + static_cast(ctx->density_unit()); + } +#endif + } else { + status_ = FXCODEC_STATUS::kError; + } context_.reset(); return got_metadata; #else @@ -459,7 +476,7 @@ FXCODEC_STATUS ProgressiveDecoder::JpegStartDecode() { return status_; } context_->Input(codec_memory_); - auto* ctx = static_cast(context_.get()); + auto* ctx = static_cast(context_.get()); status_ = ctx->StartDecode(device_bitmap_); return status_; #else @@ -475,7 +492,7 @@ FXCODEC_STATUS ProgressiveDecoder::JpegStartDecode() { FXCODEC_STATUS ProgressiveDecoder::JpegContinueDecode() { #if defined(PDF_ENABLE_RUST_JPEG) - auto* ctx = static_cast(context_.get()); + auto* ctx = static_cast(context_.get()); status_ = ctx->ContinueDecode(); if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) { device_bitmap_ = nullptr; @@ -530,6 +547,9 @@ bool ProgressiveDecoder::PngDetectImageTypeInBuffer() { #endif bool got_metadata = (src_width_ > 0 && src_height_ > 0); + if (!got_metadata) { + status_ = FXCODEC_STATUS::kError; + } context_.reset(); return got_metadata; } @@ -678,11 +698,11 @@ bool ProgressiveDecoder::ReadMoreDataInternal(size_t unconsumed_bytes, } FXCODEC_STATUS ProgressiveDecoder::LoadImageInfo( - RetainPtr pFile, - FXCODEC_IMAGE_TYPE imageType, - CFX_DIBAttribute* pAttribute, - bool bSkipImageTypeCheck) { - DCHECK(pAttribute); + RetainPtr file, + FXCODEC_IMAGE_TYPE image_type, + CFX_DIBAttribute* attribute, + bool skip_fallback_type_detection) { + DCHECK(attribute); switch (status_) { case FXCODEC_STATUS::kFrameReady: @@ -694,7 +714,7 @@ FXCODEC_STATUS ProgressiveDecoder::LoadImageInfo( case FXCODEC_STATUS::kDecodeFinished: break; } - file_ = std::move(pFile); + file_ = std::move(file); if (!file_) { status_ = FXCODEC_STATUS::kError; return status_; @@ -704,21 +724,22 @@ FXCODEC_STATUS ProgressiveDecoder::LoadImageInfo( src_height_ = 0; src_components_count_ = 0; src_bits_per_component_ = 0; - if (imageType != FXCODEC_IMAGE_UNKNOWN && - DetectImageType(imageType, pAttribute)) { - image_type_ = imageType; + if (image_type != FXCODEC_IMAGE_UNKNOWN && + DetectImageType(image_type, attribute)) { + image_type_ = image_type; status_ = FXCODEC_STATUS::kFrameReady; return status_; } - // If we got here then the image data does not match the requested decoder. - // If we're skipping the type check then bail out at this point and return - // the failed status. - if (bSkipImageTypeCheck) { + // The image data does not match the requested decoder. If skipping fallback + // type detection, bail out at this point and return the failed status. + if (skip_fallback_type_detection) { + status_ = FXCODEC_STATUS::kError; + file_ = nullptr; return status_; } for (int type = FXCODEC_IMAGE_UNKNOWN + 1; type < FXCODEC_IMAGE_MAX; type++) { - if (DetectImageType(static_cast(type), pAttribute)) { + if (DetectImageType(static_cast(type), attribute)) { image_type_ = static_cast(type); status_ = FXCODEC_STATUS::kFrameReady; return status_; @@ -748,11 +769,14 @@ void ProgressiveDecoder::SetTransMethod() { case Format::k8bppRgb: trans_method_ = TransformMethod::k8BppRgbToRgbNoAlpha; break; - case Format::kRgb: - case Format::kRgb32: - case Format::kArgb: + case Format::kBgr: + case Format::kBgrx: + case Format::kBgra: trans_method_ = TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha; break; + case Format::kRgb: + trans_method_ = TransformMethod::kRgbToBgrMaybeAlpha; + break; case Format::kCmyk: trans_method_ = TransformMethod::kCmykToRgbMaybeAlpha; break; @@ -775,14 +799,17 @@ void ProgressiveDecoder::SetTransMethod() { trans_method_ = TransformMethod::k8BppRgbToRgbNoAlpha; } break; - case Format::kRgb: - case Format::kRgb32: + case Format::kBgr: + case Format::kBgrx: trans_method_ = TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha; break; + case Format::kRgb: + trans_method_ = TransformMethod::kRgbToBgrMaybeAlpha; + break; case Format::kCmyk: trans_method_ = TransformMethod::kCmykToRgbMaybeAlpha; break; - case Format::kArgb: + case Format::kBgra: trans_method_ = TransformMethod::kArgbToArgb; break; } @@ -804,8 +831,7 @@ void ProgressiveDecoder::ResampleScanline( Format src_format) { uint8_t* src_scan = src_span.data(); uint8_t* dest_scan = pDeviceBitmap->GetWritableScanline(dest_line).data(); - const auto src_bytes_per_pixel = - (fxcrt::to_underlying(src_format) & 0xff) / 8; + const int src_bytes_per_pixel = GetCompsFromFormat(src_format); const int dest_bytes_per_pixel = pDeviceBitmap->GetBPP() / 8; for (int dest_col = 0; dest_col < src_width_; dest_col++) { CStretchEngine::PixelWeight* pPixelWeights = @@ -892,6 +918,27 @@ void ProgressiveDecoder::ResampleScanline( break; }); } + case TransformMethod::kRgbToBgrMaybeAlpha: { + UNSAFE_TODO({ + uint32_t dest_b = 0; + uint32_t dest_g = 0; + uint32_t dest_r = 0; + for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_; + j++) { + uint32_t pixel_weight = + pPixelWeights->weights_[j - pPixelWeights->src_start_]; + const uint8_t* src_pixel = src_scan + j * src_bytes_per_pixel; + dest_r += pixel_weight * (*src_pixel++); + dest_g += pixel_weight * (*src_pixel++); + dest_b += pixel_weight * (*src_pixel); + } + *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b); + *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g); + *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r); + dest_scan += dest_bytes_per_pixel - 3; + break; + }); + } case TransformMethod::kCmykToRgbMaybeAlpha: { UNSAFE_TODO({ uint32_t dest_b = 0; @@ -958,13 +1005,6 @@ void ProgressiveDecoder::Resample(const RetainPtr& pDeviceBitmap, FXDIB_Format ProgressiveDecoder::GetBitmapFormat() const { switch (image_type_) { case FXCODEC_IMAGE_JPG: -#if defined(PDF_ENABLE_RUST_JPEG) - // When using the Skia/Rust decoder, Skia prefers standard 32-bit BGRA - // buffers. - return FXDIB_Format::kBgra; -#else - return GetBitsPerPixel() <= 24 ? FXDIB_Format::kBgr : FXDIB_Format::kBgrx; -#endif case FXCODEC_IMAGE_BMP: return GetBitsPerPixel() <= 24 ? FXDIB_Format::kBgr : FXDIB_Format::kBgrx; case FXCODEC_IMAGE_PNG: diff --git a/core/fxcodec/progressive_decoder.h b/core/fxcodec/progressive_decoder.h index 42733c12c0..84e8b358da 100644 --- a/core/fxcodec/progressive_decoder.h +++ b/core/fxcodec/progressive_decoder.h @@ -41,10 +41,10 @@ class ProgressiveDecoder final : public ProgressiveDecoderContextDelegate { ProgressiveDecoder(); ~ProgressiveDecoder() override; - FXCODEC_STATUS LoadImageInfo(RetainPtr pFile, - FXCODEC_IMAGE_TYPE imageType, - CFX_DIBAttribute* pAttribute, - bool bSkipImageTypeCheck); + FXCODEC_STATUS LoadImageInfo(RetainPtr file, + FXCODEC_IMAGE_TYPE image_type, + CFX_DIBAttribute* attribute, + bool skip_fallback_type_detection); int32_t GetWidth() const { return src_width_; } int32_t GetHeight() const { return src_height_; } @@ -82,6 +82,7 @@ class ProgressiveDecoder final : public ProgressiveDecoderContextDelegate { k8BppRgbToRgbNoAlpha, k8BppRgbToArgb, kRgbMaybeAlphaToRgbMaybeAlpha, + kRgbToBgrMaybeAlpha, kCmykToRgbMaybeAlpha, kArgbToArgb, }; diff --git a/core/fxcodec/progressive_decoder_context_delegate.h b/core/fxcodec/progressive_decoder_context_delegate.h index c3bcae8efa..b3a2108a61 100644 --- a/core/fxcodec/progressive_decoder_context_delegate.h +++ b/core/fxcodec/progressive_decoder_context_delegate.h @@ -11,6 +11,7 @@ #include "core/fxcodec/fx_codec_def.h" #include "core/fxcrt/span.h" +#include "core/fxcrt/to_underlying.h" #include "core/fxge/dib/fx_dib.h" namespace fxcodec { @@ -18,17 +19,29 @@ namespace fxcodec { class ProgressiveDecoderContextDelegate { public: // Do not use `FXDIB_Format` here since these decoders can return pixel - // formats not supported by it. + // formats not supported by it. The LSB is the bits-per-pixel, and the + // MSB just needs ensure these are distinct. enum class Format : uint16_t { kInvalid = 0, k8bppGray = 0x108, k8bppRgb = 0x008, - kRgb = 0x018, - kRgb32 = 0x020, - kArgb = 0x220, + kBgr = 0x018, + kRgb = 0x118, + kBgrx = 0x020, + kBgra = 0x220, kCmyk = 0x120, }; + // Bits per pixel, not bytes. + static int GetBppFromFormat(Format format) { + return fxcrt::to_underlying(format) & 0xff; + } + + // AKA bytes per pixel, assuming 8-bits per component. + static int GetCompsFromFormat(Format format) { + return GetBppFromFormat(format) / 8; + } + virtual ~ProgressiveDecoderContextDelegate() = default; // I/O: Refills input buffer from the file stream when decoder runs out of diff --git a/core/fxcodec/progressive_decoder_unittest.cpp b/core/fxcodec/progressive_decoder_unittest.cpp index 719df58b6e..ef1f28906e 100644 --- a/core/fxcodec/progressive_decoder_unittest.cpp +++ b/core/fxcodec/progressive_decoder_unittest.cpp @@ -1070,4 +1070,19 @@ TEST_F(ProgressiveDecoderTest, PngGammaChunk) { ElementsAre(0xBB, 0xBB, 0xBB, 0xFF))); } +TEST_F(ProgressiveDecoderTest, JpegInvalidComponents) { + // JPEG with SOF3 marker containing 2 color components (unsupported). + static constexpr uint8_t kInput[] = { + 0xFF, 0xD8, 0xFE, 0xFF, 0xC3, 0x00, 0x0E, 0x04, 0x00, 0x01, 0x00, + 0x2B, 0x02, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xF0, 0x03, 0x03}; + + ProgressiveDecoder decoder; + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_JPG, &attr, true); + EXPECT_EQ(FXCODEC_STATUS::kError, status); +} + } // namespace fxcodec diff --git a/core/fxcodec/scanlinedecoder.h b/core/fxcodec/scanlinedecoder.h index d590307633..6913d485c6 100644 --- a/core/fxcodec/scanlinedecoder.h +++ b/core/fxcodec/scanlinedecoder.h @@ -36,6 +36,12 @@ class ScanlineDecoder { int CountComps() const { return comps_; } int GetBPC() const { return bpc_; } + // True when 3-component scanlines are returned in B,G,R sample order + // instead of the natural R,G,B order. Only LibjpegScanlineDecoder ever + // sets this, and only when it was asked to and could comply; see + // JpegModule::CreateDecoder(). + bool ScanlinesAreBgr() const { return scanlines_are_bgr_; } + virtual uint32_t GetSrcOffset() = 0; protected: @@ -49,6 +55,7 @@ class ScanlineDecoder { int comps_; int bpc_; uint32_t pitch_; + bool scanlines_are_bgr_ = false; int next_line_ = -1; pdfium::raw_span last_scanline_; }; diff --git a/core/fxcrt/BUILD.gn b/core/fxcrt/BUILD.gn index 060ea838d0..4cc4b218f2 100644 --- a/core/fxcrt/BUILD.gn +++ b/core/fxcrt/BUILD.gn @@ -54,6 +54,7 @@ source_set("fxcrt") { "component_export.h", "containers/adapters.h", "containers/contains.h", + "containers/to_vector.h", "containers/unique_ptr_adapters.h", "data_vector.h", "debug/alias.cc", @@ -63,6 +64,8 @@ source_set("fxcrt") { "fx_2d_size.h", "fx_bidi.cpp", "fx_bidi.h", + "fx_ceil_div.h", + "fx_ceil_log2.h", "fx_codepage.cpp", "fx_codepage.h", "fx_codepage_forward.h", @@ -121,6 +124,8 @@ source_set("fxcrt") { "span_io.h", "span_util.h", "stl_util.h", + "stream_write_utils.cpp", + "stream_write_utils.h", "string_data_template.cpp", "string_data_template.h", "string_template.cpp", @@ -155,7 +160,10 @@ source_set("fxcrt") { "../../:pdfium_strict_config", "../../:pdfium_noshorten_config", ] - deps = [ "//third_party/fast_float" ] + deps = [ + "//third_party/dragonbox", + "//third_party/fast_float", + ] public_deps = [ "../../:freetype_common", "//third_party/abseil-cpp:absl", @@ -236,8 +244,11 @@ pdfium_unittest_source_set("unittests") { "cfx_seekablestreamproxy_unittest.cpp", "cfx_timer_unittest.cpp", "code_point_view_unittest.cpp", + "containers/to_vector_unittest.cpp", + "data_vector_unittest.cpp", "fixed_size_data_vector_unittest.cpp", "fx_bidi_unittest.cpp", + "fx_ceil_div_unittest.cpp", "fx_coordinates_unittest.cpp", "fx_extension_unittest.cpp", "fx_memcpy_wrappers_unittest.cpp", @@ -260,6 +271,7 @@ pdfium_unittest_source_set("unittests") { "span_io_unittest.cpp", "span_util_unittest.cpp", "stl_util_unittest.cpp", + "stream_write_utils_unittest.cpp", "to_underlying_unittest.cpp", "tree_node_unittest.cpp", "unowned_ptr_unittest.cpp", diff --git a/core/fxcrt/DEPS b/core/fxcrt/DEPS index e7ddebbc3d..054b46718b 100644 --- a/core/fxcrt/DEPS +++ b/core/fxcrt/DEPS @@ -1,4 +1,5 @@ include_rules = [ + '-core/fxge', '+partition_alloc', '+third_party/icu', ] @@ -10,4 +11,7 @@ specific_include_rules = { 'fx_string.cpp': [ '+third_party/fast_float/src/include/fast_float/fast_float.h', ], + 'stream_write_utils.cpp': [ + '+third_party/dragonbox/src/include/dragonbox/dragonbox.h', + ], } diff --git a/core/fxcrt/containers/to_vector.h b/core/fxcrt/containers/to_vector.h new file mode 100644 index 0000000000..d6371bf772 --- /dev/null +++ b/core/fxcrt/containers/to_vector.h @@ -0,0 +1,25 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CORE_FXCRT_CONTAINERS_TO_VECTOR_H_ +#define CORE_FXCRT_CONTAINERS_TO_VECTOR_H_ + +#include +#include +#include + +namespace pdfium { + +template +auto ToVector(Range&& range) { + using ValueType = + std::conditional_t, + std::decay_t>, T>; + return std::vector(std::ranges::begin(range), + std::ranges::end(range)); +} + +} // namespace pdfium + +#endif // CORE_FXCRT_CONTAINERS_TO_VECTOR_H_ diff --git a/core/fxcrt/containers/to_vector_unittest.cpp b/core/fxcrt/containers/to_vector_unittest.cpp new file mode 100644 index 0000000000..5cd582a404 --- /dev/null +++ b/core/fxcrt/containers/to_vector_unittest.cpp @@ -0,0 +1,49 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/fxcrt/containers/to_vector.h" + +#include + +#include +#include + +#include "core/fxcrt/span.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(ToVectorTest, FromSpan) { + static constexpr uint8_t kData[] = {1, 2, 3, 4}; + pdfium::span span(kData); + std::vector vec = pdfium::ToVector(span); + ASSERT_EQ(4u, vec.size()); + EXPECT_EQ(1, vec[0]); + EXPECT_EQ(2, vec[1]); + EXPECT_EQ(3, vec[2]); + EXPECT_EQ(4, vec[3]); +} + +TEST(ToVectorTest, FromEmptySpan) { + pdfium::span span; + std::vector vec = pdfium::ToVector(span); + EXPECT_TRUE(vec.empty()); +} + +TEST(ToVectorTest, FromArray) { + std::array arr = {10, 20, 30}; + std::vector vec = pdfium::ToVector(arr); + ASSERT_EQ(3u, vec.size()); + EXPECT_EQ(10, vec[0]); + EXPECT_EQ(20, vec[1]); + EXPECT_EQ(30, vec[2]); +} + +TEST(ToVectorTest, WithExplicitType) { + static constexpr uint16_t kData[] = {10, 20, 30}; + pdfium::span span(kData); + std::vector vec = pdfium::ToVector(span); + ASSERT_EQ(3u, vec.size()); + EXPECT_EQ(10u, vec[0]); + EXPECT_EQ(20u, vec[1]); + EXPECT_EQ(30u, vec[2]); +} diff --git a/core/fxcrt/css/BUILD.gn b/core/fxcrt/css/BUILD.gn index cd138b66a4..22bec47ac1 100644 --- a/core/fxcrt/css/BUILD.gn +++ b/core/fxcrt/css/BUILD.gn @@ -55,10 +55,7 @@ source_set("css") { "../../../:pdfium_strict_config", "../../../:pdfium_noshorten_config", ] - deps = [ - "../", - "../../fxge", - ] + deps = [ "../" ] visibility = [ "../../../*" ] } diff --git a/core/fxcrt/css/cfx_csscolorvalue.cpp b/core/fxcrt/css/cfx_csscolorvalue.cpp index aba2fbf3f5..3945b91463 100644 --- a/core/fxcrt/css/cfx_csscolorvalue.cpp +++ b/core/fxcrt/css/cfx_csscolorvalue.cpp @@ -6,7 +6,7 @@ #include "core/fxcrt/css/cfx_csscolorvalue.h" -CFX_CSSColorValue::CFX_CSSColorValue(FX_ARGB value) +CFX_CSSColorValue::CFX_CSSColorValue(CFX_CSSColor value) : CFX_CSSValue(PrimitiveType::kRGB), value_(value) {} CFX_CSSColorValue::~CFX_CSSColorValue() = default; diff --git a/core/fxcrt/css/cfx_csscolorvalue.h b/core/fxcrt/css/cfx_csscolorvalue.h index 3e551fcd83..1f4b20f1fc 100644 --- a/core/fxcrt/css/cfx_csscolorvalue.h +++ b/core/fxcrt/css/cfx_csscolorvalue.h @@ -7,18 +7,32 @@ #ifndef CORE_FXCRT_CSS_CFX_CSSCOLORVALUE_H_ #define CORE_FXCRT_CSS_CFX_CSSCOLORVALUE_H_ +#include + #include "core/fxcrt/css/cfx_cssvalue.h" -#include "core/fxge/dib/fx_dib.h" + +// ARGB color packed as 0xAARRGGBB. +using CFX_CSSColor = uint32_t; + +// Explicit ARGB byte packing avoids an upward layering dependency on FX_ARGB +// and fxge pixel-packing functions/macros (such as ArgbEncode()). +constexpr CFX_CSSColor CFX_CSSColorPack(uint8_t a, + uint8_t r, + uint8_t g, + uint8_t b) { + return (static_cast(a) << 24) | (static_cast(r) << 16) | + (static_cast(g) << 8) | static_cast(b); +} class CFX_CSSColorValue final : public CFX_CSSValue { public: - explicit CFX_CSSColorValue(FX_ARGB color); + explicit CFX_CSSColorValue(CFX_CSSColor color); ~CFX_CSSColorValue() override; - FX_ARGB Value() const { return value_; } + CFX_CSSColor Value() const { return value_; } private: - FX_ARGB value_; + CFX_CSSColor value_; }; #endif // CORE_FXCRT_CSS_CFX_CSSCOLORVALUE_H_ diff --git a/core/fxcrt/css/cfx_csscomputedstyle.cpp b/core/fxcrt/css/cfx_csscomputedstyle.cpp index 3f137567e8..d574fc2c0b 100644 --- a/core/fxcrt/css/cfx_csscomputedstyle.cpp +++ b/core/fxcrt/css/cfx_csscomputedstyle.cpp @@ -53,7 +53,7 @@ float CFX_CSSComputedStyle::GetFontSize() const { return inherited_data_.ffont_size_; } -FX_ARGB CFX_CSSComputedStyle::GetColor() const { +CFX_CSSColor CFX_CSSComputedStyle::GetColor() const { return inherited_data_.font_color_; } @@ -73,7 +73,7 @@ void CFX_CSSComputedStyle::SetFontSize(float fFontSize) { inherited_data_.ffont_size_ = fFontSize; } -void CFX_CSSComputedStyle::SetColor(FX_ARGB dwFontColor) { +void CFX_CSSComputedStyle::SetColor(CFX_CSSColor dwFontColor) { inherited_data_.font_color_ = dwFontColor; } diff --git a/core/fxcrt/css/cfx_csscomputedstyle.h b/core/fxcrt/css/cfx_csscomputedstyle.h index e67d1e8775..0905ad1825 100644 --- a/core/fxcrt/css/cfx_csscomputedstyle.h +++ b/core/fxcrt/css/cfx_csscomputedstyle.h @@ -11,11 +11,11 @@ #include #include "core/fxcrt/css/cfx_css.h" +#include "core/fxcrt/css/cfx_csscolorvalue.h" #include "core/fxcrt/css/cfx_csscustomproperty.h" #include "core/fxcrt/mask.h" #include "core/fxcrt/retain_ptr.h" #include "core/fxcrt/widestring.h" -#include "core/fxge/dib/fx_dib.h" class CFX_CSSValueList; @@ -32,7 +32,7 @@ class CFX_CSSComputedStyle final : public Retainable { RetainPtr font_family_; float ffont_size_ = 12.0f; float fline_height_ = 14.0f; - FX_ARGB font_color_ = 0xFF000000; + CFX_CSSColor font_color_ = 0xFF000000; uint16_t wfont_weight_ = 400; CFX_CSSFontVariant font_variant_ = CFX_CSSFontVariant::Normal; CFX_CSSFontStyle font_style_ = CFX_CSSFontStyle::Normal; @@ -66,12 +66,12 @@ class CFX_CSSComputedStyle final : public Retainable { CFX_CSSFontVariant GetFontVariant() const; CFX_CSSFontStyle GetFontStyle() const; float GetFontSize() const; - FX_ARGB GetColor() const; + CFX_CSSColor GetColor() const; void SetFontWeight(uint16_t wFontWeight); void SetFontVariant(CFX_CSSFontVariant eFontVariant); void SetFontStyle(CFX_CSSFontStyle eFontStyle); void SetFontSize(float fFontSize); - void SetColor(FX_ARGB dwFontColor); + void SetColor(CFX_CSSColor dwFontColor); const CFX_CSSRect* GetBorderWidth() const; const CFX_CSSRect* GetMarginWidth() const; diff --git a/core/fxcrt/css/cfx_cssdata.h b/core/fxcrt/css/cfx_cssdata.h index d342be33d9..1473ead732 100644 --- a/core/fxcrt/css/cfx_cssdata.h +++ b/core/fxcrt/css/cfx_cssdata.h @@ -8,10 +8,10 @@ #define CORE_FXCRT_CSS_CFX_CSSDATA_H_ #include "core/fxcrt/css/cfx_css.h" +#include "core/fxcrt/css/cfx_csscolorvalue.h" #include "core/fxcrt/css/cfx_cssnumbervalue.h" #include "core/fxcrt/css/cfx_cssvalue.h" #include "core/fxcrt/widestring.h" -#include "core/fxge/dib/fx_dib.h" class CFX_CSSData { public: @@ -33,7 +33,7 @@ class CFX_CSSData { struct Color { const char* name; // Raw, POD struct. - FX_ARGB value; + CFX_CSSColor value; }; static const Property* GetPropertyByName(WideStringView name); diff --git a/core/fxcrt/css/cfx_cssdeclaration.cpp b/core/fxcrt/css/cfx_cssdeclaration.cpp index 7ace32cff4..c5130b9d3c 100644 --- a/core/fxcrt/css/cfx_cssdeclaration.cpp +++ b/core/fxcrt/css/cfx_cssdeclaration.cpp @@ -73,20 +73,21 @@ std::optional CFX_CSSDeclaration::ParseCSSString( } // static. -std::optional CFX_CSSDeclaration::ParseCSSColor(WideStringView value) { +std::optional CFX_CSSDeclaration::ParseCSSColor( + WideStringView value) { if (value.Front() == '#') { // Note: empty-tolerant Front(). switch (value.GetLength()) { case 4: { uint8_t red = Hex2Dec((uint8_t)value[1], (uint8_t)value[1]); uint8_t green = Hex2Dec((uint8_t)value[2], (uint8_t)value[2]); uint8_t blue = Hex2Dec((uint8_t)value[3], (uint8_t)value[3]); - return ArgbEncode(255, red, green, blue); + return CFX_CSSColorPack(255, red, green, blue); } case 7: { uint8_t red = Hex2Dec((uint8_t)value[1], (uint8_t)value[2]); uint8_t green = Hex2Dec((uint8_t)value[3], (uint8_t)value[4]); uint8_t blue = Hex2Dec((uint8_t)value[5], (uint8_t)value[6]); - return ArgbEncode(255, red, green, blue); + return CFX_CSSColorPack(255, red, green, blue); } default: return std::nullopt; @@ -113,7 +114,7 @@ std::optional CFX_CSSDeclaration::ParseCSSColor(WideStringView value) { ? FXSYS_roundf(maybe_number.value().value * 2.55f) : FXSYS_roundf(maybe_number.value().value); } - return ArgbEncode(255, rgb[0], rgb[1], rgb[2]); + return CFX_CSSColorPack(255, rgb[0], rgb[1], rgb[2]); } const CFX_CSSData::Color* pColor = CFX_CSSData::GetColorByName(value); @@ -340,7 +341,7 @@ void CFX_CSSDeclaration::ParseValueListProperty( break; case CFX_CSSValue::PrimitiveType::kRGB: if (dwType & CFX_CSSVALUETYPE_MaybeColor) { - FX_ARGB color = + CFX_CSSColor color = ParseCSSColor(maybe_next.value().string_view).value_or(0); list.push_back(pdfium::MakeRetain(color)); } diff --git a/core/fxcrt/css/cfx_cssdeclaration.h b/core/fxcrt/css/cfx_cssdeclaration.h index 21bce28732..c55a0ef67d 100644 --- a/core/fxcrt/css/cfx_cssdeclaration.h +++ b/core/fxcrt/css/cfx_cssdeclaration.h @@ -11,6 +11,7 @@ #include #include +#include "core/fxcrt/css/cfx_css.h" #include "core/fxcrt/css/cfx_cssdata.h" #include "core/fxcrt/retain_ptr.h" #include "core/fxcrt/widestring.h" @@ -26,7 +27,7 @@ class CFX_CSSDeclaration { std::vector>::const_iterator; static std::optional ParseCSSString(WideStringView value); - static std::optional ParseCSSColor(WideStringView value); + static std::optional ParseCSSColor(WideStringView value); CFX_CSSDeclaration(); ~CFX_CSSDeclaration(); @@ -47,7 +48,7 @@ class CFX_CSSDeclaration { void AddProperty(const WideString& prop, const WideString& value); size_t PropertyCountForTesting() const; - std::optional ParseColorForTest(WideStringView value); + std::optional ParseColorForTest(WideStringView value); private: void ParseFontProperty(WideStringView value, bool bImportant); diff --git a/core/fxcrt/css/cfx_cssdeclaration_unittest.cpp b/core/fxcrt/css/cfx_cssdeclaration_unittest.cpp index 081ba52591..67fe06bd20 100644 --- a/core/fxcrt/css/cfx_cssdeclaration_unittest.cpp +++ b/core/fxcrt/css/cfx_cssdeclaration_unittest.cpp @@ -4,12 +4,15 @@ #include "core/fxcrt/css/cfx_cssdeclaration.h" +#include + #include +#include "core/fxcrt/css/cfx_csscolorvalue.h" #include "testing/gtest/include/gtest/gtest.h" TEST(CFXCSSDeclarationTest, HexEncodingParsing) { - std::optional maybe_color; + std::optional maybe_color; // Length value invalid. EXPECT_FALSE(CFX_CSSDeclaration::ParseCSSColor(L"#00")); @@ -19,51 +22,37 @@ TEST(CFXCSSDeclarationTest, HexEncodingParsing) { // Invalid characters maybe_color = CFX_CSSDeclaration::ParseCSSColor(L"#zxytlm"); ASSERT_TRUE(maybe_color.has_value()); - EXPECT_EQ(0, FXARGB_R(maybe_color.value())); - EXPECT_EQ(0, FXARGB_G(maybe_color.value())); - EXPECT_EQ(0, FXARGB_B(maybe_color.value())); + EXPECT_EQ(0xFF000000u, maybe_color.value()); maybe_color = CFX_CSSDeclaration::ParseCSSColor(L"#000"); ASSERT_TRUE(maybe_color.has_value()); - EXPECT_EQ(0, FXARGB_R(maybe_color.value())); - EXPECT_EQ(0, FXARGB_G(maybe_color.value())); - EXPECT_EQ(0, FXARGB_B(maybe_color.value())); + EXPECT_EQ(0xFF000000u, maybe_color.value()); maybe_color = CFX_CSSDeclaration::ParseCSSColor(L"#FFF"); ASSERT_TRUE(maybe_color.has_value()); - EXPECT_EQ(255, FXARGB_R(maybe_color.value())); - EXPECT_EQ(255, FXARGB_G(maybe_color.value())); - EXPECT_EQ(255, FXARGB_B(maybe_color.value())); + EXPECT_EQ(0xFFFFFFFFu, maybe_color.value()); maybe_color = CFX_CSSDeclaration::ParseCSSColor(L"#F0F0F0"); ASSERT_TRUE(maybe_color.has_value()); - EXPECT_EQ(240, FXARGB_R(maybe_color.value())); - EXPECT_EQ(240, FXARGB_G(maybe_color.value())); - EXPECT_EQ(240, FXARGB_B(maybe_color.value())); + EXPECT_EQ(0xFFF0F0F0u, maybe_color.value()); // Upper and lower case characters. maybe_color = CFX_CSSDeclaration::ParseCSSColor(L"#1b2F3c"); ASSERT_TRUE(maybe_color.has_value()); - EXPECT_EQ(27, FXARGB_R(maybe_color.value())); - EXPECT_EQ(47, FXARGB_G(maybe_color.value())); - EXPECT_EQ(60, FXARGB_B(maybe_color.value())); + EXPECT_EQ(0xFF1B2F3Cu, maybe_color.value()); } TEST(CFXCSSDeclarationTest, RGBEncodingParsing) { - std::optional maybe_color; + std::optional maybe_color; // Invalid input for rgb() syntax. EXPECT_FALSE(CFX_CSSDeclaration::ParseCSSColor(L"blahblahblah")); maybe_color = CFX_CSSDeclaration::ParseCSSColor(L"rgb(0, 0, 0)"); ASSERT_TRUE(maybe_color.has_value()); - EXPECT_EQ(0, FXARGB_R(maybe_color.value())); - EXPECT_EQ(0, FXARGB_G(maybe_color.value())); - EXPECT_EQ(0, FXARGB_B(maybe_color.value())); + EXPECT_EQ(0xFF000000u, maybe_color.value()); maybe_color = CFX_CSSDeclaration::ParseCSSColor(L"rgb(128,255,48)"); ASSERT_TRUE(maybe_color.has_value()); - EXPECT_EQ(128, FXARGB_R(maybe_color.value())); - EXPECT_EQ(255, FXARGB_G(maybe_color.value())); - EXPECT_EQ(48, FXARGB_B(maybe_color.value())); + EXPECT_EQ(0xFF80FF30u, maybe_color.value()); } diff --git a/core/fxcrt/data_vector.h b/core/fxcrt/data_vector.h index 258c5a9f0e..d09cf2d14d 100644 --- a/core/fxcrt/data_vector.h +++ b/core/fxcrt/data_vector.h @@ -5,6 +5,8 @@ #ifndef CORE_FXCRT_DATA_VECTOR_H_ #define CORE_FXCRT_DATA_VECTOR_H_ +#include +#include #include #include "core/fxcrt/fx_memory_wrappers.h" @@ -14,8 +16,18 @@ namespace fxcrt { template using DataVector = std::vector>; +template +auto ToDataVector(Range&& range) { + using ValueType = + std::conditional_t, + std::decay_t>, T>; + return DataVector(std::ranges::begin(range), + std::ranges::end(range)); +} + } // namespace fxcrt using fxcrt::DataVector; +using fxcrt::ToDataVector; #endif // CORE_FXCRT_DATA_VECTOR_H_ diff --git a/core/fxcrt/data_vector_unittest.cpp b/core/fxcrt/data_vector_unittest.cpp new file mode 100644 index 0000000000..eba20fff3d --- /dev/null +++ b/core/fxcrt/data_vector_unittest.cpp @@ -0,0 +1,52 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/fxcrt/data_vector.h" + +#include + +#include + +#include "core/fxcrt/span.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace fxcrt { + +TEST(DataVectorTest, ToDataVectorFromSpan) { + static constexpr uint8_t kData[] = {1, 2, 3, 4}; + pdfium::span span(kData); + DataVector vec = ToDataVector(span); + ASSERT_EQ(4u, vec.size()); + EXPECT_EQ(1, vec[0]); + EXPECT_EQ(2, vec[1]); + EXPECT_EQ(3, vec[2]); + EXPECT_EQ(4, vec[3]); +} + +TEST(DataVectorTest, ToDataVectorFromEmptySpan) { + pdfium::span span; + DataVector vec = ToDataVector(span); + EXPECT_TRUE(vec.empty()); +} + +TEST(DataVectorTest, ToDataVectorFromArray) { + std::array arr = {10, 20, 30}; + DataVector vec = ToDataVector(arr); + ASSERT_EQ(3u, vec.size()); + EXPECT_EQ(10, vec[0]); + EXPECT_EQ(20, vec[1]); + EXPECT_EQ(30, vec[2]); +} + +TEST(DataVectorTest, ToDataVectorWithExplicitType) { + static constexpr uint16_t kData[] = {10, 20, 30}; + pdfium::span span(kData); + DataVector vec = ToDataVector(span); + ASSERT_EQ(3u, vec.size()); + EXPECT_EQ(10u, vec[0]); + EXPECT_EQ(20u, vec[1]); + EXPECT_EQ(30u, vec[2]); +} + +} // namespace fxcrt diff --git a/core/fxcrt/fx_ceil_div.h b/core/fxcrt/fx_ceil_div.h new file mode 100644 index 0000000000..527a009dcf --- /dev/null +++ b/core/fxcrt/fx_ceil_div.h @@ -0,0 +1,43 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CORE_FXCRT_FX_CEIL_DIV_H_ +#define CORE_FXCRT_FX_CEIL_DIV_H_ + +#include + +#include "core/fxcrt/check_op.h" +#include "core/fxcrt/fx_safe_types.h" + +namespace fxcrt { + +// Returns `dividend / divisor` rounded towards positive infinity. +// `divisor` must be positive. +// Never overflows, unlike `(dividend + divisor - 1) / divisor`. +template + requires(std::is_integral_v && !std::is_same_v) +constexpr T CeilDiv(T dividend, std::type_identity_t divisor) { + CHECK_GT(divisor, T{0}); + // Integer division truncates towards zero, so it only rounds away from + // positive infinity when there is a positive remainder. + const T quotient = dividend / divisor; + return dividend % divisor > T{0} ? static_cast(quotient + 1) : quotient; +} + +// Same, but for checked types. +template + requires(std::is_integral_v && !std::is_same_v) +constexpr pdfium::CheckedNumeric CeilDiv(pdfium::CheckedNumeric dividend, + std::type_identity_t divisor) { + if (!dividend.IsValid()) { + return dividend; + } + // ValueOrDie() returns a StrictNumeric, which converts to both `T` and + // CheckedNumeric. Without the cast, this would self-recurse. + return CeilDiv(static_cast(dividend.ValueOrDie()), divisor); +} + +} // namespace fxcrt + +#endif // CORE_FXCRT_FX_CEIL_DIV_H_ diff --git a/core/fxcrt/fx_ceil_div_unittest.cpp b/core/fxcrt/fx_ceil_div_unittest.cpp new file mode 100644 index 0000000000..b1702c2718 --- /dev/null +++ b/core/fxcrt/fx_ceil_div_unittest.cpp @@ -0,0 +1,98 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/fxcrt/fx_ceil_div.h" + +#include + +#include + +#include "core/fxcrt/fx_safe_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace fxcrt { + +static_assert(CeilDiv(0, 8) == 0); +static_assert(CeilDiv(1, 8) == 1); +static_assert(CeilDiv(8, 8) == 1); +static_assert(CeilDiv(9, 8) == 2); + +TEST(CeilDiv, Exact) { + EXPECT_EQ(0, CeilDiv(0, 4)); + EXPECT_EQ(1, CeilDiv(4, 4)); + EXPECT_EQ(2, CeilDiv(8, 4)); + EXPECT_EQ(25, CeilDiv(100, 4)); +} + +TEST(CeilDiv, RoundsUp) { + EXPECT_EQ(1, CeilDiv(1, 4)); + EXPECT_EQ(1, CeilDiv(3, 4)); + EXPECT_EQ(2, CeilDiv(5, 4)); + EXPECT_EQ(2, CeilDiv(7, 4)); +} + +TEST(CeilDiv, DivisorOfOne) { + EXPECT_EQ(0, CeilDiv(0, 1)); + EXPECT_EQ(7, CeilDiv(7, 1)); +} + +TEST(CeilDiv, NegativeDividend) { + // Rounding towards positive infinity, i.e. towards zero here. + EXPECT_EQ(0, CeilDiv(-1, 4)); + EXPECT_EQ(0, CeilDiv(-3, 4)); + EXPECT_EQ(-1, CeilDiv(-4, 4)); + EXPECT_EQ(-1, CeilDiv(-7, 4)); + EXPECT_EQ(-2, CeilDiv(-8, 4)); +} + +TEST(CeilDiv, NoOverflowNearMax) { + constexpr uint32_t kMax32 = std::numeric_limits::max(); + EXPECT_EQ(kMax32 / 8 + 1, CeilDiv(kMax32, 8)); + EXPECT_EQ(kMax32, CeilDiv(kMax32, 1)); + + constexpr int32_t kMaxSigned32 = std::numeric_limits::max(); + EXPECT_EQ(kMaxSigned32 / 8 + 1, CeilDiv(kMaxSigned32, 8)); + + constexpr int32_t kMinSigned32 = std::numeric_limits::min(); + EXPECT_EQ(kMinSigned32 / 8, CeilDiv(kMinSigned32, 8)); + + constexpr uint64_t kMax64 = std::numeric_limits::max(); + EXPECT_EQ(kMax64 / 8 + 1, CeilDiv(kMax64, 8)); +} + +// The divisor is not deduced, so an untyped literal picks up the dividend's +// type instead of promoting the result to int. +TEST(CeilDiv, NarrowTypes) { + constexpr uint8_t kMax8 = std::numeric_limits::max(); + static_assert(std::is_same_v); + EXPECT_EQ(128, CeilDiv(kMax8, 2)); + EXPECT_EQ(3, CeilDiv(uint8_t{5}, 2)); + + constexpr uint16_t kMax16 = std::numeric_limits::max(); + EXPECT_EQ(32768, CeilDiv(kMax16, 2)); +} + +TEST(CeilDiv, CheckedType) { + FX_SAFE_UINT32 value = 9; + EXPECT_EQ(2u, CeilDiv(value, 8).ValueOrDie()); + + FX_SAFE_SIZE_T size = 16; + EXPECT_EQ(2u, CeilDiv(size, 8).ValueOrDie()); +} + +TEST(CeilDiv, CheckedTypePropagatesInvalid) { + FX_SAFE_UINT32 invalid = std::numeric_limits::max(); + invalid *= 2; + EXPECT_FALSE(CeilDiv(invalid, 8).IsValid()); +} + +TEST(CeilDiv, CheckedTypeNoOverflowNearMax) { + // Dividing cannot overflow, so a valid dividend stays valid no matter how + // close to the maximum it is. + constexpr uint32_t kMax32 = std::numeric_limits::max(); + FX_SAFE_UINT32 near_max = kMax32; + EXPECT_EQ(kMax32 / 8 + 1, CeilDiv(near_max, 8).ValueOrDie()); +} + +} // namespace fxcrt diff --git a/core/fxcrt/fx_ceil_log2.h b/core/fxcrt/fx_ceil_log2.h new file mode 100644 index 0000000000..f87829ef15 --- /dev/null +++ b/core/fxcrt/fx_ceil_log2.h @@ -0,0 +1,22 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CORE_FXCRT_CEIL_LOG2_H_ +#define CORE_FXCRT_CEIL_LOG2_H_ + +#include + +namespace fxcrt { + +constexpr uint8_t CeilLog2(uint32_t value) { + uint8_t temp = 0; + while (static_cast(1u << temp) < value) { + ++temp; + } + return temp; +} + +} // namespace fxcrt + +#endif // CORE_FXCRT_CEIL_LOG2_H_ diff --git a/core/fxcrt/fx_coordinates.h b/core/fxcrt/fx_coordinates.h index 008908f37a..e3305c4095 100644 --- a/core/fxcrt/fx_coordinates.h +++ b/core/fxcrt/fx_coordinates.h @@ -181,7 +181,7 @@ struct FX_RECT { int32_t bottom = 0; }; -// LTRB rectangles (y-axis runs upwards). +// LBRT rectangles (y-axis runs upwards). class CFX_FloatRect { public: constexpr CFX_FloatRect() = default; diff --git a/core/fxcrt/fx_coordinates_test_support.cpp b/core/fxcrt/fx_coordinates_test_support.cpp index d3db24f2d0..613c2aabfe 100644 --- a/core/fxcrt/fx_coordinates_test_support.cpp +++ b/core/fxcrt/fx_coordinates_test_support.cpp @@ -19,3 +19,9 @@ std::ostream& operator<<(std::ostream& os, const CFX_RectF& rect) { << rect.left << ", top " << rect.top << ")]"; return os; } + +std::ostream& operator<<(std::ostream& os, const FX_RECT& rect) { + os << "rect[w " << rect.Width() << " x h " << rect.Height() << " (left " + << rect.left << ", top " << rect.top << ")]"; + return os; +} diff --git a/core/fxcrt/fx_coordinates_test_support.h b/core/fxcrt/fx_coordinates_test_support.h index 107a6e4dc1..f49f253548 100644 --- a/core/fxcrt/fx_coordinates_test_support.h +++ b/core/fxcrt/fx_coordinates_test_support.h @@ -11,9 +11,12 @@ class CFX_FloatRect; class CFX_RectF; +struct FX_RECT; std::ostream& operator<<(std::ostream& os, const CFX_FloatRect& rect); std::ostream& operator<<(std::ostream& os, const CFX_RectF& rect); +std::ostream& operator<<(std::ostream& os, const FX_RECT& rect); + #endif // CORE_FXCRT_FX_COORDINATES_TEST_SUPPORT_H_ diff --git a/core/fxcrt/fx_coordinates_unittest.cpp b/core/fxcrt/fx_coordinates_unittest.cpp index 670ebabec8..c7ede22175 100644 --- a/core/fxcrt/fx_coordinates_unittest.cpp +++ b/core/fxcrt/fx_coordinates_unittest.cpp @@ -43,17 +43,6 @@ void VerifyFloatRectNear(float left, EXPECT_NEAR_FIVE_PLACES(right, rect.right); } -void VerifyFXRect(int left, - int bottom, - int right, - int top, - const FX_RECT& rect) { - EXPECT_EQ(left, rect.left); - EXPECT_EQ(bottom, rect.bottom); - EXPECT_EQ(right, rect.right); - EXPECT_EQ(top, rect.top); -} - void VerifyMatrixExact(const CFX_Matrix& expected, const CFX_Matrix& actual) { EXPECT_FLOAT_EQ(expected.a, actual.a); EXPECT_FLOAT_EQ(expected.b, actual.b); @@ -122,21 +111,21 @@ TEST(CFXFloatRectTest, GetInnerRect) { CFX_FloatRect rect; inner_rect = rect.GetInnerRect(); - VerifyFXRect(0, 0, 0, 0, inner_rect); + EXPECT_EQ(FX_RECT(0, 0, 0, 0), inner_rect); // Function converts from float to int using floor() for top and right, and // ceil() for left and bottom. rect = CFX_FloatRect(-1.1f, 3.6f, 4.4f, -5.7f); inner_rect = rect.GetInnerRect(); - VerifyFXRect(-1, 4, 4, -6, inner_rect); + EXPECT_EQ(FX_RECT(-1, -6, 4, 4), inner_rect); rect = CFX_FloatRect(kMinFloat, kMinFloat, kMinFloat, kMinFloat); inner_rect = rect.GetInnerRect(); - VerifyFXRect(0, 1, 1, 0, inner_rect); + EXPECT_EQ(FX_RECT(0, 0, 1, 1), inner_rect); rect = CFX_FloatRect(-kMinFloat, -kMinFloat, -kMinFloat, -kMinFloat); inner_rect = rect.GetInnerRect(); - VerifyFXRect(-1, 0, 0, -1, inner_rect); + EXPECT_EQ(FX_RECT(-1, -1, 0, 0), inner_rect); // Check at limits of integer range. When saturated would expect to get values // that are clamped to the limits of integers, but instead it is returning all @@ -145,12 +134,12 @@ TEST(CFXFloatRectTest, GetInnerRect) { rect = CFX_FloatRect(kMinIntAsFloat, kMinIntAsFloat, kMaxIntAsFloat, kMaxIntAsFloat); inner_rect = rect.GetInnerRect(); - VerifyFXRect(kMinInt, kMaxInt, kMaxInt, kMinInt, inner_rect); + EXPECT_EQ(FX_RECT(kMinInt, kMinInt, kMaxInt, kMaxInt), inner_rect); rect = CFX_FloatRect(kMinIntAsFloat - 1.0f, kMinIntAsFloat - 1.0f, kMaxIntAsFloat + 1.0f, kMaxIntAsFloat + 1.0f); inner_rect = rect.GetInnerRect(); - VerifyFXRect(kMinInt, kMaxInt, kMaxInt, kMinInt, inner_rect); + EXPECT_EQ(FX_RECT(kMinInt, kMinInt, kMaxInt, kMaxInt), inner_rect); } TEST(CFXFloatRectTest, GetOuterRect) { @@ -158,33 +147,33 @@ TEST(CFXFloatRectTest, GetOuterRect) { CFX_FloatRect rect; outer_rect = rect.GetOuterRect(); - VerifyFXRect(0, 0, 0, 0, outer_rect); + EXPECT_EQ(FX_RECT(0, 0, 0, 0), outer_rect); // Function converts from float to int using floor() for left and bottom, and // ceil() for right and top. rect = CFX_FloatRect(-1.1f, 3.6f, 4.4f, -5.7f); outer_rect = rect.GetOuterRect(); - VerifyFXRect(-2, 3, 5, -5, outer_rect); + EXPECT_EQ(FX_RECT(-2, -5, 5, 3), outer_rect); rect = CFX_FloatRect(kMinFloat, kMinFloat, kMinFloat, kMinFloat); outer_rect = rect.GetOuterRect(); - VerifyFXRect(0, 1, 1, 0, outer_rect); + EXPECT_EQ(FX_RECT(0, 0, 1, 1), outer_rect); rect = CFX_FloatRect(-kMinFloat, -kMinFloat, -kMinFloat, -kMinFloat); outer_rect = rect.GetOuterRect(); - VerifyFXRect(-1, 0, 0, -1, outer_rect); + EXPECT_EQ(FX_RECT(-1, -1, 0, 0), outer_rect); // Check at limits of integer range. When saturated would expect to get values // that are clamped to the limits of integers. rect = CFX_FloatRect(kMinIntAsFloat, kMinIntAsFloat, kMaxIntAsFloat, kMaxIntAsFloat); outer_rect = rect.GetOuterRect(); - VerifyFXRect(kMinInt, kMaxInt, kMaxInt, kMinInt, outer_rect); + EXPECT_EQ(FX_RECT(kMinInt, kMinInt, kMaxInt, kMaxInt), outer_rect); rect = CFX_FloatRect(kMinIntAsFloat - 1.0f, kMinIntAsFloat - 1.0f, kMaxIntAsFloat + 1.0f, kMaxIntAsFloat + 1.0f); outer_rect = rect.GetOuterRect(); - VerifyFXRect(kMinInt, kMaxInt, kMaxInt, kMinInt, outer_rect); + EXPECT_EQ(FX_RECT(kMinInt, kMinInt, kMaxInt, kMaxInt), outer_rect); } TEST(CFXFloatRectTest, Normalize) { @@ -289,6 +278,18 @@ TEST(CFXRectFTest, Print) { EXPECT_EQ("rect[w 4.25 x h 3.25 (left 10.5, top 20.5)]", os.str()); } +TEST(FXRectTest, Print) { + std::ostringstream os; + FX_RECT rect; + os << rect; + EXPECT_EQ("rect[w 0 x h 0 (left 0, top 0)]", os.str()); + + os.str(""); + rect = FX_RECT(10, 20, 14, 23); + os << rect; + EXPECT_EQ("rect[w 4 x h 3 (left 10, top 20)]", os.str()); +} + TEST(CFXMatrixTest, ReverseIdentity) { CFX_Matrix rev = CFX_Matrix().GetInverse(); VerifyMatrixExact(CFX_Matrix(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f), rev); diff --git a/core/fpdfapi/edit/cpdf_contentstream_write_utils.cpp b/core/fxcrt/stream_write_utils.cpp similarity index 96% rename from core/fpdfapi/edit/cpdf_contentstream_write_utils.cpp rename to core/fxcrt/stream_write_utils.cpp index d62d463a1f..4c226490da 100644 --- a/core/fpdfapi/edit/cpdf_contentstream_write_utils.cpp +++ b/core/fxcrt/stream_write_utils.cpp @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include #include @@ -10,7 +10,12 @@ #include #include "core/fxcrt/span.h" + +#if defined(USE_SYSTEM_DRAGONBOX) +#include +#else #include "third_party/dragonbox/src/include/dragonbox/dragonbox.h" +#endif namespace { @@ -108,6 +113,8 @@ unsigned FloatToDecimal( } // namespace +namespace fxcrt { + std::ostream& WriteFloat(std::ostream& stream, float value) { char buffer[kMaximumFloatToDecimalLength]; unsigned size = FloatToDecimal(value, buffer); @@ -138,3 +145,5 @@ std::ostream& WriteRect(std::ostream& stream, const CFX_FloatRect& rect) { WriteFloat(stream, rect.Height()); return stream; } + +} // namespace fxcrt diff --git a/core/fpdfapi/edit/cpdf_contentstream_write_utils.h b/core/fxcrt/stream_write_utils.h similarity index 70% rename from core/fpdfapi/edit/cpdf_contentstream_write_utils.h rename to core/fxcrt/stream_write_utils.h index dfebe6b584..dfb0318525 100644 --- a/core/fpdfapi/edit/cpdf_contentstream_write_utils.h +++ b/core/fxcrt/stream_write_utils.h @@ -2,13 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CORE_FPDFAPI_EDIT_CPDF_CONTENTSTREAM_WRITE_UTILS_H_ -#define CORE_FPDFAPI_EDIT_CPDF_CONTENTSTREAM_WRITE_UTILS_H_ +#ifndef CORE_FXCRT_STREAM_WRITE_UTILS_H_ +#define CORE_FXCRT_STREAM_WRITE_UTILS_H_ #include #include "core/fxcrt/fx_coordinates.h" +namespace fxcrt { + // Writes `value` to `stream` as a decimal string in a form suitable for use in // a PDF content stream. Does not use scientific notation. Omits leading zeroes. std::ostream& WriteFloat(std::ostream& stream, float value); @@ -17,4 +19,11 @@ std::ostream& WriteMatrix(std::ostream& stream, const CFX_Matrix& matrix); std::ostream& WritePoint(std::ostream& stream, const CFX_PointF& point); std::ostream& WriteRect(std::ostream& stream, const CFX_FloatRect& rect); -#endif // CORE_FPDFAPI_EDIT_CPDF_CONTENTSTREAM_WRITE_UTILS_H_ +} // namespace fxcrt + +using fxcrt::WriteFloat; +using fxcrt::WriteMatrix; +using fxcrt::WritePoint; +using fxcrt::WriteRect; + +#endif // CORE_FXCRT_STREAM_WRITE_UTILS_H_ diff --git a/core/fpdfapi/edit/cpdf_contentstream_write_utils_unittest.cpp b/core/fxcrt/stream_write_utils_unittest.cpp similarity index 97% rename from core/fpdfapi/edit/cpdf_contentstream_write_utils_unittest.cpp rename to core/fxcrt/stream_write_utils_unittest.cpp index a28964783f..6940e77b0e 100644 --- a/core/fpdfapi/edit/cpdf_contentstream_write_utils_unittest.cpp +++ b/core/fxcrt/stream_write_utils_unittest.cpp @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include diff --git a/core/fxge/BUILD.gn b/core/fxge/BUILD.gn index 4f1fc59977..483ed2f560 100644 --- a/core/fxge/BUILD.gn +++ b/core/fxge/BUILD.gn @@ -291,6 +291,7 @@ pdfium_unittest_source_set("unittests") { ":fxge", "../fpdfapi/page", "../fpdfapi/parser", + "../fxcrt:test_support", ] configs = [] pdfium_root_dir = "../../" diff --git a/core/fxge/calculate_pitch.cpp b/core/fxge/calculate_pitch.cpp index 5cbcb0983a..58f40f236d 100644 --- a/core/fxge/calculate_pitch.cpp +++ b/core/fxge/calculate_pitch.cpp @@ -4,6 +4,7 @@ #include "core/fxge/calculate_pitch.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxge/dib/fx_dib.h" @@ -16,17 +17,14 @@ FX_SAFE_UINT32 CalculatePitch8Safely(uint32_t bpc, FX_SAFE_UINT32 pitch = bpc; pitch *= components; pitch *= width; - pitch += 7; - pitch /= 8; - return pitch; + return fxcrt::CeilDiv(pitch, 8); } FX_SAFE_UINT32 CalculatePitch32Safely(int bpp, int width) { FX_SAFE_UINT32 pitch = bpp; pitch *= width; - pitch += 31; - pitch /= 32; // quantized to number of 32-bit words. - pitch *= 4; // and then back to bytes, (not just /8 in one step). + pitch = fxcrt::CeilDiv(pitch, 32); // quantized to 32-bit word count. + pitch *= 4; // and then back to bytes, (not just /8 in one step). return pitch; } diff --git a/core/fxge/cfx_face.cpp b/core/fxge/cfx_face.cpp index 7bdfd5de5c..bb686fbfcd 100644 --- a/core/fxge/cfx_face.cpp +++ b/core/fxge/cfx_face.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,7 @@ #include "core/fxcrt/numerics/clamped_math.h" #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/numerics/safe_math.h" +#include "core/fxcrt/stl_util.h" #include "core/fxcrt/to_underlying.h" #include "core/fxcrt/unowned_ptr.h" #include "core/fxge/cfx_cttgsubtable.h" @@ -43,16 +45,9 @@ #include "third_party/skia/include/core/SkTypeface.h" // nogncheck #endif -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) -#include "third_party/skia/include/core/SkFont.h" // nogncheck -#include "third_party/skia/include/core/SkFontMetrics.h" // nogncheck -#include "third_party/skia/include/core/SkFontTypes.h" // nogncheck -#include "third_party/skia/include/core/SkPath.h" // nogncheck -#include "third_party/skia/include/core/SkRect.h" // nogncheck -#endif - #if defined(PDF_ENABLE_FONTATIONS) #include "core/fxge/skrifa/src/main.rs.h" +#include "third_party/abseil-cpp/absl/cleanup/cleanup.h" #include "third_party/rust/cxx/v1/cxx.h" #endif @@ -175,49 +170,73 @@ int Outline_CubicTo(const FT_Vector* control1, } #if defined(PDF_ENABLE_FONTATIONS) +constexpr float kFixedPpem = 64.0f; + +CFX_PointF ToCFXPointF(const skrifa::Point& pt) { + return CFX_PointF(pt.x, pt.y); +} + +void CloseContours(pdfium::span points, + std::vector& contours) { + if (!points.empty()) { + uint16_t last = static_cast(points.size() - 1); + if (contours.empty() || contours.back() != last) { + contours.push_back(last); + } + } +} + std::unique_ptr ConvertOutline(const skrifa::Outline& outline) { + if (outline.verbs.empty() || outline.points.empty()) { + return nullptr; + } auto skrifa_path = std::make_unique(); - auto point_idx = 0; + size_t point_idx = 0; CFX_PointF current_point(0, 0); for (auto verb : outline.verbs) { switch (verb) { case skrifa::PathVerb::MoveTo: { - auto p = outline.points[point_idx++]; - current_point = CFX_PointF(p.x, p.y); + if (point_idx >= outline.points.size()) { + return nullptr; + } + current_point = ToCFXPointF(outline.points[point_idx++]); skrifa_path->AppendPoint(current_point, CFX_Path::Point::Type::kMove); break; } case skrifa::PathVerb::LineTo: { - auto p = outline.points[point_idx++]; - current_point = CFX_PointF(p.x, p.y); + if (point_idx >= outline.points.size()) { + return nullptr; + } + current_point = ToCFXPointF(outline.points[point_idx++]); skrifa_path->AppendPoint(current_point, CFX_Path::Point::Type::kLine); break; } case skrifa::PathVerb::QuadTo: { - auto c0 = outline.points[point_idx++]; - auto p = outline.points[point_idx++]; - // Convert quadratic to cubic bezier to match FreeType - // decomposition. + if (point_idx + 1 >= outline.points.size()) { + return nullptr; + } + CFX_PointF c0 = ToCFXPointF(outline.points[point_idx++]); skrifa_path->AppendPoint( CFX_PointF(current_point.x + (c0.x - current_point.x) * 2 / 3, current_point.y + (c0.y - current_point.y) * 2 / 3), CFX_Path::Point::Type::kBezier); + current_point = ToCFXPointF(outline.points[point_idx++]); skrifa_path->AppendPoint( - CFX_PointF(c0.x + (p.x - c0.x) / 3, c0.y + (p.y - c0.y) / 3), + CFX_PointF(c0.x + (current_point.x - c0.x) / 3, + c0.y + (current_point.y - c0.y) / 3), CFX_Path::Point::Type::kBezier); - current_point = CFX_PointF(p.x, p.y); skrifa_path->AppendPoint(current_point, CFX_Path::Point::Type::kBezier); break; } case skrifa::PathVerb::CurveTo: { - auto c0 = outline.points[point_idx++]; - auto c1 = outline.points[point_idx++]; - auto p = outline.points[point_idx++]; - skrifa_path->AppendPoint(CFX_PointF(c0.x, c0.y), - CFX_Path::Point::Type::kBezier); - skrifa_path->AppendPoint(CFX_PointF(c1.x, c1.y), - CFX_Path::Point::Type::kBezier); - current_point = CFX_PointF(p.x, p.y); + if (point_idx + 2 >= outline.points.size()) { + return nullptr; + } + CFX_PointF c0 = ToCFXPointF(outline.points[point_idx++]); + CFX_PointF c1 = ToCFXPointF(outline.points[point_idx++]); + current_point = ToCFXPointF(outline.points[point_idx++]); + skrifa_path->AppendPoint(c0, CFX_Path::Point::Type::kBezier); + skrifa_path->AppendPoint(c1, CFX_Path::Point::Type::kBezier); skrifa_path->AppendPoint(current_point, CFX_Path::Point::Type::kBezier); break; } @@ -228,6 +247,192 @@ std::unique_ptr ConvertOutline(const skrifa::Outline& outline) { } return skrifa_path; } + +struct ConvertedFTOutline { + std::vector points; + std::vector tags; + std::vector contours; +}; + +FT_Vector ToFTVector(const skrifa::Point& p, const CFX_Matrix& scaled_matrix) { + CFX_PointF pt = scaled_matrix.Transform(ToCFXPointF(p)); + return {static_cast(std::round(pt.x)), + static_cast(std::round(pt.y))}; +} + +ConvertedFTOutline ConvertToFTOutline(const skrifa::Outline& outline, + const CFX_Matrix& matrix, + int upem, + float x_scale) { + const float scale = kFixedPpem / upem; + const CFX_Matrix scaled_matrix(matrix.a * scale * x_scale, matrix.b * scale, + matrix.c * scale * x_scale, matrix.d * scale, + 0, 0); + ConvertedFTOutline result; + size_t point_idx = 0; + for (auto verb : outline.verbs) { + switch (verb) { + case skrifa::PathVerb::MoveTo: { + if (point_idx >= outline.points.size()) { + break; + } + CloseContours(result.points, result.contours); + result.points.push_back( + ToFTVector(outline.points[point_idx++], scaled_matrix)); + result.tags.push_back(FT_CURVE_TAG_ON); + break; + } + case skrifa::PathVerb::LineTo: { + if (point_idx >= outline.points.size()) { + break; + } + result.points.push_back( + ToFTVector(outline.points[point_idx++], scaled_matrix)); + result.tags.push_back(FT_CURVE_TAG_ON); + break; + } + case skrifa::PathVerb::QuadTo: { + if (point_idx + 1 >= outline.points.size()) { + break; + } + result.points.push_back( + ToFTVector(outline.points[point_idx++], scaled_matrix)); + result.tags.push_back(FT_CURVE_TAG_CONIC); + + result.points.push_back( + ToFTVector(outline.points[point_idx++], scaled_matrix)); + result.tags.push_back(FT_CURVE_TAG_ON); + break; + } + case skrifa::PathVerb::CurveTo: { + if (point_idx + 2 >= outline.points.size()) { + break; + } + result.points.push_back( + ToFTVector(outline.points[point_idx++], scaled_matrix)); + result.tags.push_back(FT_CURVE_TAG_CUBIC); + + result.points.push_back( + ToFTVector(outline.points[point_idx++], scaled_matrix)); + result.tags.push_back(FT_CURVE_TAG_CUBIC); + + result.points.push_back( + ToFTVector(outline.points[point_idx++], scaled_matrix)); + result.tags.push_back(FT_CURVE_TAG_ON); + break; + } + case skrifa::PathVerb::Close: + CloseContours(result.points, result.contours); + break; + } + } + CloseContours(result.points, result.contours); + return result; +} + +// Backing store for an FT_Outline built from a skrifa outline. FreeType does +// not take ownership of these, so they must outlive the FT_Outline itself. +struct FtOutlineData { + std::vector points; + std::vector tags; + std::vector contours; +}; + +bool AppendFtPoint(FtOutlineData& data, + const skrifa::Point& point, + uint8_t tag) { + float sx = std::round(point.x * kFixedPpem); + float sy = std::round(point.y * kFixedPpem); + if (!std::isfinite(sx) || !std::isfinite(sy) || + !pdfium::IsValueInRangeForNumericType(sx) || + !pdfium::IsValueInRangeForNumericType(sy)) { + return false; + } + data.points.push_back({static_cast(sx), static_cast(sy)}); + data.tags.push_back(tag); + return true; +} + +// Converts `outline`, whose points are in pixels, into the 26.6 fixed point +// form that FreeType's rasterizer expects. Returns nullopt if `outline` is +// empty, is malformed, or does not fit within FreeType's 16-bit counts. +std::optional BuildFtOutline(const skrifa::Outline& outline) { + // FT_Outline::n_points and n_contours are signed 16-bit quantities. + static constexpr size_t kMaxPoints = std::numeric_limits::max(); + + FtOutlineData data; + auto end_contour = [&data] { + if (data.points.empty()) { + return; + } + const uint16_t last = + pdfium::checked_cast(data.points.size() - 1); + if (data.contours.empty() || data.contours.back() != last) { + data.contours.push_back(last); + } + }; + + size_t point_idx = 0; + for (auto verb : outline.verbs) { + switch (verb) { + case skrifa::PathVerb::MoveTo: + if (outline.points.size() - point_idx < 1 || + data.points.size() + 1 > kMaxPoints) { + return std::nullopt; + } + end_contour(); + if (!AppendFtPoint(data, outline.points[point_idx++], + FT_CURVE_TAG_ON)) { + return std::nullopt; + } + break; + case skrifa::PathVerb::LineTo: + if (outline.points.size() - point_idx < 1 || + data.points.size() + 1 > kMaxPoints) { + return std::nullopt; + } + if (!AppendFtPoint(data, outline.points[point_idx++], + FT_CURVE_TAG_ON)) { + return std::nullopt; + } + break; + case skrifa::PathVerb::QuadTo: + if (outline.points.size() - point_idx < 2 || + data.points.size() + 2 > kMaxPoints) { + return std::nullopt; + } + if (!AppendFtPoint(data, outline.points[point_idx++], + FT_CURVE_TAG_CONIC) || + !AppendFtPoint(data, outline.points[point_idx++], + FT_CURVE_TAG_ON)) { + return std::nullopt; + } + break; + case skrifa::PathVerb::CurveTo: + if (outline.points.size() - point_idx < 3 || + data.points.size() + 3 > kMaxPoints) { + return std::nullopt; + } + if (!AppendFtPoint(data, outline.points[point_idx++], + FT_CURVE_TAG_CUBIC) || + !AppendFtPoint(data, outline.points[point_idx++], + FT_CURVE_TAG_CUBIC) || + !AppendFtPoint(data, outline.points[point_idx++], + FT_CURVE_TAG_ON)) { + return std::nullopt; + } + break; + case skrifa::PathVerb::Close: + end_contour(); + break; + } + } + end_contour(); + if (data.points.empty()) { + return std::nullopt; + } + return data; +} #endif // defined(PDF_ENABLE_FONTATIONS) FT_Encoding ToFTEncoding(fxge::FontEncoding encoding) { @@ -398,224 +603,210 @@ RetainPtr CFX_Face::New(RetainPtr cache_entry, uint32_t face_index) { CFX_FontMgr* font_mgr = CFX_GEModule::Get()->GetFontMgr(); pdfium::span data = font_stream->span(); - FT_FaceRec* face_rec = nullptr; - if (FT_New_Memory_Face(font_mgr->GetFTLibrary(), data.data(), - pdfium::checked_cast(data.size()), - pdfium::checked_cast(face_index), - &face_rec) != 0) { - return nullptr; - } - if (FT_Set_Pixel_Sizes(face_rec, 64, 64) != 0) { - return nullptr; - } - std::unique_ptr skrifa_font; + bool skip_freetype = false; + #if defined(PDF_ENABLE_FONTATIONS) - auto raw_font = skrifa::new_font(rust::Slice(data), face_index); - if (raw_font->is_ok()) { + if (CFX_GEModule::IsFontations()) { + auto raw_font = + skrifa::new_font(rust::Slice(data), face_index); + if (!raw_font->is_ok()) { + // Everything guarded by CFX_GEModule::IsFontations() dereferences + // `skrifa_font_`, and this backend does not fall back to FreeType, so a + // font that Fontations cannot parse is of no use. Reject it rather than + // keeping a face that only FreeType can read. + return nullptr; + } skrifa_font = std::make_unique(std::move(raw_font)); + skip_freetype = skrifa_font->font->font_type() == skrifa::FaceFormat::Type1; } #endif // defined(PDF_ENABLE_FONTATIONS) + FT_FaceRec* face_rec = nullptr; + if (!skip_freetype) { + if (FT_New_Memory_Face(font_mgr->GetFTLibrary(), data.data(), + pdfium::checked_cast(data.size()), + pdfium::checked_cast(face_index), + &face_rec) != 0) { + return nullptr; + } + if (FT_Set_Pixel_Sizes(face_rec, 64, 64) != 0) { + return nullptr; + } + } + // Private ctor. - RetainPtr result = pdfium::WrapRetain( - new CFX_Face(std::move(cache_entry), std::move(font_stream), face_rec, - std::move(skrifa_font))); + return pdfium::WrapRetain(new CFX_Face(std::move(cache_entry), + std::move(font_stream), face_rec, + std::move(skrifa_font))); +} -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - result->GetOrCreateSkTypeface(); -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return result; +// static +wchar_t CFX_Face::UnicodeFromAdobeName(const char* name) { +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + uint32_t unicode = 0; + return skrifa::agl_name_to_unicode(name, unicode) + ? static_cast(unicode) + : 0; + } +#endif // defined(PDF_ENABLE_FONTATIONS) + return static_cast(FXFT_unicode_from_adobe_name(name) & 0x7FFFFFFF); +} + +// static +ByteString CFX_Face::AdobeNameFromUnicode(wchar_t unicode) { +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + std::array skrifa_glyph_name; + auto result = + skrifa::agl_unicode_to_name(static_cast(unicode), + rust::Slice(skrifa_glyph_name)); + return ByteString(ByteStringView(result)); + } +#endif // defined(PDF_ENABLE_FONTATIONS) + char glyph_name[64]; + FXFT_adobe_name_from_unicode(glyph_name, unicode); + return ByteString(glyph_name); } bool CFX_Face::HasGlyphNames() const { - const bool ft_result = !!(GetRec()->face_flags & FT_FACE_FLAG_GLYPH_NAMES); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - CHECK_EQ(ft_result, skrifa_font_->font->has_glyph_names()); + if (CFX_GEModule::IsFontations()) { + return skrifa_font_->font->has_glyph_names(); } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return !!(GetRec()->face_flags & FT_FACE_FLAG_GLYPH_NAMES); } bool CFX_Face::IsTtOt() const { - const bool ft_result = !!(GetRec()->face_flags & FT_FACE_FLAG_SFNT); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - if (skia_typeface_) { - CHECK_EQ(ft_result, skia_typeface_->countTables() > 0); +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + return skrifa_font_->font->is_sfnt(); } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; +#endif // defined(PDF_ENABLE_FONTATIONS) + const FT_FaceRec* rec = GetRec(); + return rec && (rec->face_flags & FT_FACE_FLAG_SFNT); } ByteString CFX_Face::GetFontFormat() { - const ByteString ft_result(FT_Get_Font_Format(GetRec())); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - const char* skrifa_format; + if (CFX_GEModule::IsFontations()) { switch (skrifa_font_->font->font_type()) { case skrifa::FaceFormat::TrueType: - skrifa_format = "TrueType"; - break; + return "TrueType"; case skrifa::FaceFormat::Type1: - skrifa_format = "Type 1"; - break; + return "Type 1"; case skrifa::FaceFormat::Cff: - skrifa_format = "CFF"; - break; + return "CFF"; default: - skrifa_format = ""; - break; + return ""; } - CHECK_EQ(ft_result, ByteString(skrifa_format)); } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return ByteString(FT_Get_Font_Format(GetRec())); } bool CFX_Face::IsTricky() const { - const bool ft_result = !!(GetRec()->face_flags & FT_FACE_FLAG_TRICKY); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - CHECK_EQ(ft_result, skrifa_font_->font->is_tricky()); + if (CFX_GEModule::IsFontations()) { + return skrifa_font_->font->is_tricky(); } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return !!(GetRec()->face_flags & FT_FACE_FLAG_TRICKY); } bool CFX_Face::IsFixedWidth() const { - const bool ft_result = !!(GetRec()->face_flags & FT_FACE_FLAG_FIXED_WIDTH); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - if (skia_typeface_) { - CHECK_EQ(ft_result, skia_typeface_->isFixedPitch()); - } #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - CHECK_EQ(ft_result, skrifa_font_->font->is_fixed_pitch()); + if (CFX_GEModule::IsFontations()) { + return skrifa_font_->font->is_fixed_pitch(); } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return !!(GetRec()->face_flags & FT_FACE_FLAG_FIXED_WIDTH); } #if defined(PDF_ENABLE_XFA) bool CFX_Face::IsScalable() const { - const bool ft_result = !!(GetRec()->face_flags & FT_FACE_FLAG_SCALABLE); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - CHECK_EQ(ft_result, skrifa_font_->font->is_scalable()); + if (CFX_GEModule::IsFontations()) { + return skrifa_font_->font->is_scalable(); } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return !!(GetRec()->face_flags & FT_FACE_FLAG_SCALABLE); } #endif // defined(PDF_ENABLE_XFA) bool CFX_Face::IsItalic() const { - const bool ft_result = !!(GetRec()->style_flags & FT_STYLE_FLAG_ITALIC); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - if (skia_typeface_) { - CHECK_EQ(ft_result, skia_typeface_->isItalic()); +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + return skrifa_font_->font->is_italic(); } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; +#endif // defined(PDF_ENABLE_FONTATIONS) + const FT_FaceRec* rec = GetRec(); + return rec && (rec->style_flags & FT_STYLE_FLAG_ITALIC); } bool CFX_Face::IsBold() const { - const bool ft_result = !!(GetRec()->style_flags & FT_STYLE_FLAG_BOLD); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - if (skia_typeface_) { - CHECK_EQ(ft_result, skia_typeface_->isBold()); +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + return skrifa_font_->font->is_bold(); } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; +#endif // defined(PDF_ENABLE_FONTATIONS) + const FT_FaceRec* rec = GetRec(); + return rec && (rec->style_flags & FT_STYLE_FLAG_BOLD); } ByteString CFX_Face::GetFamilyName() const { - ByteString ft_result(GetRec()->family_name); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { + if (CFX_GEModule::IsFontations()) { rust::Str skrifa_result = skrifa_font_->font->family_name(); - CHECK_EQ(ft_result.IsEmpty(), skrifa_result.empty()); - if (!ft_result.IsEmpty() && !skrifa_result.empty()) { - ByteString skrifa_bs = ByteString(ByteStringView(skrifa_result)); - ByteString ft_clean = ft_result; - ByteString skrifa_clean = skrifa_bs; - MaybeRemoveSubsettedFontPrefix(ft_clean); - MaybeRemoveSubsettedFontPrefix(skrifa_clean); - CHECK_EQ(ft_clean, skrifa_clean); - } + return ByteString(ByteStringView(skrifa_result)); } #endif // defined(PDF_ENABLE_FONTATIONS) - if (skia_typeface_) { - SkString name; - skia_typeface_->getFamilyName(&name); - CHECK_EQ(ft_result, ByteString(name.c_str())); - } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return ByteString(GetRec()->family_name); } ByteString CFX_Face::GetStyleName() const { - ByteString ft_result(GetRec()->style_name); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { + if (CFX_GEModule::IsFontations()) { rust::String skrifa_result = skrifa_font_->font->style_name(); - CHECK_EQ(ft_result.IsEmpty(), skrifa_result.empty()); - if (!ft_result.IsEmpty() && !skrifa_result.empty()) { - CHECK_EQ(ft_result, ByteString(skrifa_result.c_str())); - } + return ByteString(skrifa_result.c_str()); } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return ByteString(GetRec()->style_name); } FX_RECT CFX_Face::GetBBox() const { - const FX_RECT ft_result = - FX_RECT(pdfium::checked_cast(GetRec()->bbox.xMin), - pdfium::checked_cast(GetRec()->bbox.yMin), - pdfium::checked_cast(GetRec()->bbox.xMax), - pdfium::checked_cast(GetRec()->bbox.yMax)); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - if (skia_typeface_) { - SkFont font(skia_typeface_, GetUnitsPerEm()); - SkFontMetrics metrics; - font.getMetrics(&metrics); - CHECK_EQ(ft_result.left, static_cast(metrics.fXMin)); - CHECK_EQ(ft_result.bottom, -static_cast(metrics.fTop)); - CHECK_EQ(ft_result.right, static_cast(metrics.fXMax)); - CHECK_EQ(ft_result.top, -static_cast(metrics.fBottom)); - } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + skrifa::BoundingBox bbox; + if (skrifa_font_->font->get_font_bbox(bbox)) { + return FX_RECT(pdfium::checked_cast(std::round(bbox.x_min)), + pdfium::checked_cast(std::round(bbox.y_min)), + pdfium::checked_cast(std::round(bbox.x_max)), + pdfium::checked_cast(std::round(bbox.y_max))); + } + } +#endif // defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFreetype()) { + const FT_FaceRec* rec = GetRec(); + if (rec) { + return FX_RECT(pdfium::checked_cast(rec->bbox.xMin), + pdfium::checked_cast(rec->bbox.yMin), + pdfium::checked_cast(rec->bbox.xMax), + pdfium::checked_cast(rec->bbox.yMax)); + } + } + return FX_RECT(-1000, -1000, 1000, 1000); } uint16_t CFX_Face::GetUnitsPerEm() const { - const uint16_t ft_result = - pdfium::checked_cast(GetRec()->units_per_EM); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - CHECK_EQ(ft_result, pdfium::checked_cast( - skrifa_font_->font->units_per_em())); + if (CFX_GEModule::IsFontations()) { + return pdfium::checked_cast(skrifa_font_->font->units_per_em()); } #endif // defined(PDF_ENABLE_FONTATIONS) - if (skia_typeface_) { - CHECK_EQ(ft_result, - pdfium::checked_cast(skia_typeface_->getUnitsPerEm())); - } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return pdfium::checked_cast(GetRec()->units_per_EM); } int CFX_Face::EmAdjust(int value) const { @@ -623,43 +814,21 @@ int CFX_Face::EmAdjust(int value) const { } int16_t CFX_Face::GetAscender() const { - const int16_t ft_result = pdfium::checked_cast(GetRec()->ascender); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - CHECK_EQ(ft_result, - static_cast(std::round(skrifa_font_->font->ascent()))); + if (CFX_GEModule::IsFontations()) { + return static_cast(std::round(skrifa_font_->font->ascent())); } #endif // defined(PDF_ENABLE_FONTATIONS) - if (skia_typeface_) { - SkFont font(skia_typeface_, GetUnitsPerEm()); - SkFontMetrics metrics; - font.getMetrics(&metrics); - // Freetype ascender is often exactly -metrics.fAscent. - CHECK_EQ(ft_result, static_cast(-metrics.fAscent)); - } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return pdfium::checked_cast(GetRec()->ascender); } int16_t CFX_Face::GetDescender() const { - const int16_t ft_result = pdfium::checked_cast(GetRec()->descender); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - CHECK_EQ(ft_result, - static_cast(std::round(skrifa_font_->font->descent()))); + if (CFX_GEModule::IsFontations()) { + return static_cast(std::round(skrifa_font_->font->descent())); } #endif // defined(PDF_ENABLE_FONTATIONS) - if (skia_typeface_) { - SkFont font(skia_typeface_, GetUnitsPerEm()); - SkFontMetrics metrics; - font.getMetrics(&metrics); - // Freetype descender is often exactly -metrics.fDescent. - CHECK_EQ(ft_result, static_cast(-metrics.fDescent)); - } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return pdfium::checked_cast(GetRec()->descender); } pdfium::span CFX_Face::GetData() const { @@ -667,36 +836,28 @@ pdfium::span CFX_Face::GetData() const { } size_t CFX_Face::GetSfntTable(uint32_t table, pdfium::span buffer) { - size_t ft_result = 0; +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + return skrifa_font_->font->get_sfnt_table(table, + rust::Slice(buffer)); + } +#endif // defined(PDF_ENABLE_FONTATIONS) + if (!GetRec()) { + return 0; + } unsigned long length = pdfium::checked_cast(buffer.size()); if (length) { int error = FT_Load_Sfnt_Table(GetRec(), table, 0, buffer.data(), &length); if (!error && length == buffer.size()) { - ft_result = buffer.size(); + return buffer.size(); } } else { int error = FT_Load_Sfnt_Table(GetRec(), table, 0, nullptr, &length); if (!error && length) { - ft_result = pdfium::checked_cast(length); - } - } - -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - if (skia_typeface_) { - if (buffer.empty()) { - CHECK_EQ(ft_result, skia_typeface_->getTableSize(table)); - } else { - std::vector skia_buffer(buffer.size()); - size_t skia_result = skia_typeface_->getTableData( - table, 0, skia_buffer.size(), skia_buffer.data()); - CHECK_EQ(ft_result, skia_result); - if (ft_result > 0) { - CHECK(std::equal(buffer.begin(), buffer.end(), skia_buffer.begin())); - } + return pdfium::checked_cast(length); } } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return 0; } std::unique_ptr CFX_Face::ParseGSUBTable() { @@ -731,113 +892,81 @@ std::unique_ptr CFX_Face::ParseNameTable() { } std::optional> CFX_Face::GetOs2UnicodeRange() { - auto* os2 = static_cast(FT_Get_Sfnt_Table(GetRec(), FT_SFNT_OS2)); - std::optional> ft_result; - if (os2) { - ft_result = - std::array{static_cast(os2->ulUnicodeRange1), - static_cast(os2->ulUnicodeRange2), - static_cast(os2->ulUnicodeRange3), - static_cast(os2->ulUnicodeRange4)}; - } - -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - std::optional> skrifa_result; - if (skrifa_font_ && skrifa_font_->font->is_ok()) { + if (CFX_GEModule::IsFontations()) { skrifa::UnicodeRange range; if (skrifa_font_->font->get_os2_unicode_range(range)) { - skrifa_result = std::array{range.range1, range.range2, - range.range3, range.range4}; - } - } - CHECK_EQ(ft_result.has_value(), skrifa_result.has_value()); - if (ft_result.has_value() && skrifa_result.has_value()) { - for (size_t i = 0; i < 4; ++i) { - CHECK_EQ((*ft_result)[i], (*skrifa_result)[i]); + return std::array{range.range1, range.range2, range.range3, + range.range4}; } + return std::nullopt; } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - - return ft_result; + if (!GetRec()) { + return std::nullopt; + } + auto* os2 = static_cast(FT_Get_Sfnt_Table(GetRec(), FT_SFNT_OS2)); + if (!os2) { + return std::nullopt; + } + return std::array{static_cast(os2->ulUnicodeRange1), + static_cast(os2->ulUnicodeRange2), + static_cast(os2->ulUnicodeRange3), + static_cast(os2->ulUnicodeRange4)}; } #endif // defined(PDF_ENABLE_XFA) #if defined(PDF_ENABLE_XFA) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) std::optional> CFX_Face::GetOs2CodePageRange() { - auto* os2 = static_cast(FT_Get_Sfnt_Table(GetRec(), FT_SFNT_OS2)); - std::optional> ft_result; - if (os2) { - ft_result = - std::array{static_cast(os2->ulCodePageRange1), - static_cast(os2->ulCodePageRange2)}; - } - -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - std::optional> skrifa_result; - if (skrifa_font_ && skrifa_font_->font->is_ok()) { + if (CFX_GEModule::IsFontations()) { skrifa::CodePageRange range; if (skrifa_font_->font->get_os2_code_page_range(range)) { - skrifa_result = std::array{range.range1, range.range2}; + return std::array{range.range1, range.range2}; } - } - - CHECK_EQ(ft_result.has_value(), skrifa_result.has_value()); - if (ft_result.has_value() && skrifa_result.has_value()) { - CHECK_EQ((*ft_result)[0], (*skrifa_result)[0]); - CHECK_EQ((*ft_result)[1], (*skrifa_result)[1]); + return std::nullopt; } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - - return ft_result; -} - -std::optional> CFX_Face::GetOs2Panose() { + if (!GetRec()) { + return std::nullopt; + } auto* os2 = static_cast(FT_Get_Sfnt_Table(GetRec(), FT_SFNT_OS2)); - std::optional> ft_result; - if (os2) { - ft_result = std::array{os2->panose[0], os2->panose[1]}; + if (!os2) { + return std::nullopt; } + return std::array{static_cast(os2->ulCodePageRange1), + static_cast(os2->ulCodePageRange2)}; +} -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) +std::optional> CFX_Face::GetOs2Panose() { #if defined(PDF_ENABLE_FONTATIONS) - std::optional> skrifa_result; - if (skrifa_font_ && skrifa_font_->font->is_ok()) { + if (CFX_GEModule::IsFontations()) { skrifa::Os2Panose panose; if (skrifa_font_->font->get_os2_panose(panose)) { - skrifa_result = std::array{panose.b0, panose.b1}; + return std::array{panose.b0, panose.b1}; } - } - - CHECK_EQ(ft_result.has_value(), skrifa_result.has_value()); - if (ft_result.has_value() && skrifa_result.has_value()) { - CHECK_EQ((*ft_result)[0], (*skrifa_result)[0]); - CHECK_EQ((*ft_result)[1], (*skrifa_result)[1]); + return std::nullopt; } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - - return ft_result; + if (!GetRec()) { + return std::nullopt; + } + auto* os2 = static_cast(FT_Get_Sfnt_Table(GetRec(), FT_SFNT_OS2)); + if (!os2) { + return std::nullopt; + } + return std::array{os2->panose[0], os2->panose[1]}; } #endif // defined(PDF_ENABLE_XFA) || BUILDFLAG(IS_ANDROID) || // BUILDFLAG(IS_LINUX) int CFX_Face::GetGlyphCount() const { - const int ft_result = pdfium::checked_cast(GetRec()->num_glyphs); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - CHECK_EQ(ft_result, static_cast(skrifa_font_->font->num_glyphs())); + if (CFX_GEModule::IsFontations()) { + return static_cast(skrifa_font_->font->num_glyphs()); } #endif // defined(PDF_ENABLE_FONTATIONS) - if (skia_typeface_) { - CHECK_EQ(ft_result, skia_typeface_->countGlyphs()); - } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return pdfium::checked_cast(GetRec()->num_glyphs); } std::unique_ptr CFX_Face::RenderGlyph( @@ -848,8 +977,167 @@ std::unique_ptr CFX_Face::RenderGlyph( int dest_width, FontAntiAliasingMode anti_alias, const CFX_SubstFont* subst_font) { - // TODO(https://crbug.com/42271123): Implement glyph rendering in - // Skia/Fontations. +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations() && !GetRec()) { + skrifa::Outline outline; + if (!skrifa_font_->font->unscaled_outline(glyph_index, outline)) { + return nullptr; + } + const int upem = GetUnitsPerEm(); + if (upem <= 0) { + return nullptr; + } + + CFX_Matrix effective_matrix = matrix; + if (subst_font) { + int skew = subst_font->GetEffectiveSkew(is_cid_font); + if (skew) { + if (is_vertical) { + effective_matrix.b += effective_matrix.d * skew / 100.0f; + } else { + effective_matrix.c -= effective_matrix.a * skew / 100.0f; + } + } + } + + const bool is_lcd = (anti_alias == FontAntiAliasingMode::kLcd); + const float x_scale = is_lcd ? 3.0f : 1.0f; + + ConvertedFTOutline converted = + ConvertToFTOutline(outline, effective_matrix, upem, x_scale); + if (converted.points.empty() || converted.contours.empty()) { + return nullptr; + } + + FT_Outline ft_outline; + ft_outline.n_points = static_cast(converted.points.size()); + ft_outline.n_contours = static_cast(converted.contours.size()); + ft_outline.points = converted.points.data(); + ft_outline.tags = converted.tags.data(); + ft_outline.contours = converted.contours.data(); + ft_outline.flags = FT_OUTLINE_NONE; + + if (subst_font) { + int32_t ft_matrix_xx = static_cast(matrix.a * 1024.0f); + int32_t ft_matrix_xy = static_cast(matrix.c * 1024.0f); + int skew = subst_font->GetEffectiveSkew(is_cid_font); + if (skew && !is_vertical) { + ft_matrix_xy -= ft_matrix_xx * skew / 100; + } + int level = subst_font->GetEmboldenLevelForRender( + is_cid_font, ft_matrix_xx, ft_matrix_xy); + if (level < 0) { + return nullptr; + } + if (level > 0) { + int x_strength = is_lcd ? level * 3 : level; + FT_Outline_EmboldenXY(&ft_outline, x_strength, level); + } + } + + FT_BBox cbox; + FT_Outline_Get_CBox(&ft_outline, &cbox); + int x_left = static_cast(cbox.xMin >> 6); + int y_bottom = static_cast(cbox.yMin >> 6); + int x_right = static_cast((cbox.xMax + 63) >> 6); + int y_top = static_cast((cbox.yMax + 63) >> 6); + int width = x_right - x_left; + int height = y_top - y_bottom; + if (width <= 0 || height <= 0) { + return nullptr; + } + + int dib_width; + int bitmap_left; + int bitmap_top = y_top; + if (is_lcd) { + // Pad by 2/3 of a pixel (2 subpixels, or 128 in 3x 26.6 fixed-point) on + // each side to accommodate the 5-tap LCD filter, matching FreeType's + // ft_lcd_padding() and ft_glyphslot_preset_bitmap(). + int normal_left = + static_cast(std::floor((cbox.xMin - 128) / 192.0f)); + int normal_right = + static_cast(std::floor((cbox.xMax + 128 + 191) / 192.0f)); + int normal_width = normal_right - normal_left; + if (normal_width <= 0) { + return nullptr; + } + dib_width = normal_width * 3; + bitmap_left = normal_left; + FT_Outline_Translate(&ft_outline, -normal_left * 3 * 64, -y_bottom * 64); + } else { + dib_width = width; + bitmap_left = x_left; + FT_Outline_Translate(&ft_outline, -x_left * 64, -y_bottom * 64); + } + + if (dib_width > kMaxGlyphDimension || height > kMaxGlyphDimension) { + return nullptr; + } + + const FXDIB_Format format = (anti_alias == FontAntiAliasingMode::kMono) + ? FXDIB_Format::k1bppMask + : FXDIB_Format::k8bppMask; + RetainPtr new_bitmap = pdfium::MakeRetain(); + if (!new_bitmap->Create(dib_width, height, format)) { + return nullptr; + } + new_bitmap->Clear(0); + + FT_Bitmap ft_bitmap = {}; + ft_bitmap.rows = height; + ft_bitmap.width = dib_width; + ft_bitmap.pitch = new_bitmap->GetPitch(); + ft_bitmap.buffer = new_bitmap->GetWritableBuffer().data(); + ft_bitmap.num_grays = 256; + ft_bitmap.pixel_mode = (anti_alias == FontAntiAliasingMode::kMono) + ? FT_PIXEL_MODE_MONO + : FT_PIXEL_MODE_GRAY; + + CFX_FontMgr* font_mgr = CFX_GEModule::Get()->GetFontMgr(); + int error = FT_Outline_Get_Bitmap(font_mgr->GetFTLibrary(), &ft_outline, + &ft_bitmap); + if (error) { + return nullptr; + } + + if (is_lcd) { + // Apply FreeType's FT_LCD_FILTER_DEFAULT 5-tap filter across each + // scanline, matching ft_smooth_lcd_spans(). + static constexpr std::array kLcdFilterWeights = {8, 77, 86, + 77, 8}; + std::vector raw_row(dib_width); + for (int row = 0; row < height; ++row) { + pdfium::span scanline = + new_bitmap->GetWritableScanline(row).first( + static_cast(dib_width)); + fxcrt::Copy(scanline, raw_row); + std::ranges::fill(scanline, 0); + for (int x = 0; x < dib_width; ++x) { + uint8_t coverage = raw_row[x]; + if (coverage == 0) { + continue; + } + for (int i = 0; i < static_cast(kLcdFilterWeights.size()); ++i) { + int dst_x = x + i - 2; + if (dst_x >= 0 && dst_x < dib_width) { + scanline[dst_x] = pdfium::saturated_cast( + scanline[dst_x] + + ((coverage * kLcdFilterWeights[i] + 85) >> 8)); + } + } + } + } + } + + return std::make_unique(CFX_Point(bitmap_left, bitmap_top), + std::move(new_bitmap)); + } +#endif // defined(PDF_ENABLE_FONTATIONS) + FT_FaceRec* rec = GetRec(); + if (!rec) { + return nullptr; + } FT_Matrix ft_matrix; ft_matrix.xx = matrix.a / 64 * 65536; ft_matrix.xy = matrix.c / 64 * 65536; @@ -875,47 +1163,94 @@ std::unique_ptr CFX_Face::RenderGlyph( AdjustVariationParams(glyph_index, dest_width, subst_font->GetWeight()); } - ScopedFaceTransform scoped_transform(GetRec(), &ft_matrix); - int load_flags = FT_LOAD_NO_BITMAP | FT_LOAD_PEDANTIC; - if (!IsTtOt()) { - load_flags |= FT_LOAD_NO_HINTING; - } - FT_FaceRec* rec = GetRec(); - int error = FT_Load_Glyph(rec, glyph_index, load_flags); - if (error) { - // if an error is returned, try to reload glyphs without hinting. - if (load_flags & FT_LOAD_NO_HINTING) { + int embolden_level = 0; + if (subst_font) { + embolden_level = subst_font->GetEmboldenLevelForRender( + is_cid_font, static_cast(ft_matrix.xx), + static_cast(ft_matrix.xy)); + if (embolden_level < 0) { return nullptr; } + } - load_flags |= FT_LOAD_NO_HINTING; - load_flags &= ~FT_LOAD_PEDANTIC; - error = FT_Load_Glyph(rec, glyph_index, load_flags); - if (error) { - return nullptr; + auto* glyph = rec->glyph; + glyph->format = FT_GLYPH_FORMAT_OUTLINE; + + bool loaded_fontations_outline = false; +#if defined(PDF_ENABLE_FONTATIONS) + // Backing store for `glyph->outline`; must outlive FT_Render_Glyph(). + std::optional ft_outline; + if (CFX_GEModule::IsFontations()) { + absl::Cleanup outline_cleaner = [glyph] { glyph->outline = FT_Outline{}; }; + if (skrifa_font_ && skrifa_font_->font->is_ok()) { + skrifa::Outline outline; + bool has_outline = false; + if (IsTtOt()) { + has_outline = skrifa_font_->font->hinted_outline( + glyph_index, kFixedPpem, /*is_pedantic=*/false, outline); + } + if (!has_outline) { + has_outline = skrifa_font_->font->scaled_outline(glyph_index, + kFixedPpem, outline); + } + if (has_outline) { + ft_outline = BuildFtOutline(outline); + } + if (ft_outline.has_value()) { + std::move(outline_cleaner).Cancel(); + glyph->outline.n_points = + pdfium::checked_cast(ft_outline->points.size()); + glyph->outline.n_contours = + pdfium::checked_cast(ft_outline->contours.size()); + glyph->outline.points = ft_outline->points.data(); + glyph->outline.tags = ft_outline->tags.data(); + glyph->outline.contours = ft_outline->contours.data(); + glyph->outline.flags = FT_OUTLINE_SMART_DROPOUTS; + FT_Outline_Transform(&glyph->outline, &ft_matrix); + } } + loaded_fontations_outline = true; } +#endif // defined(PDF_ENABLE_FONTATIONS) - auto* glyph = rec->glyph; - if (subst_font) { - int level = subst_font->GetEmboldenLevelForRender( - is_cid_font, static_cast(ft_matrix.xx), - static_cast(ft_matrix.xy)); - if (level < 0) { - return nullptr; + if (!loaded_fontations_outline) { + ScopedFaceTransform scoped_transform(GetRec(), &ft_matrix); + int load_flags = FT_LOAD_NO_BITMAP | FT_LOAD_PEDANTIC; + if (!IsTtOt()) { + load_flags |= FT_LOAD_NO_HINTING; } - if (level > 0) { - FT_Outline_Embolden(&glyph->outline, level); + int error = FT_Load_Glyph(rec, glyph_index, load_flags); + if (error) { + if (load_flags & FT_LOAD_NO_HINTING) { + return nullptr; + } + load_flags |= FT_LOAD_NO_HINTING; + load_flags &= ~FT_LOAD_PEDANTIC; + error = FT_Load_Glyph(rec, glyph_index, load_flags); + if (error) { + return nullptr; + } } } + +#if defined(PDF_ENABLE_FONTATIONS) + absl::Cleanup restorer = [glyph, loaded_fontations_outline] { + if (loaded_fontations_outline) { + glyph->outline = FT_Outline{}; + } + }; +#endif // defined(PDF_ENABLE_FONTATIONS) + + if (embolden_level > 0) { + FT_Outline_Embolden(&glyph->outline, embolden_level); + } CFX_FontMgr* font_mgr = CFX_GEModule::Get()->GetFontMgr(); FT_Library_SetLcdFilter(font_mgr->GetFTLibrary(), FT_LCD_FILTER_DEFAULT); - error = + int error = FT_Render_Glyph(glyph, FtRenderModeFromFontAntiAliasingMode(anti_alias)); if (error) { return nullptr; } - const FT_Bitmap& ft_bitmap = glyph->bitmap; if (ft_bitmap.width > kMaxGlyphDimension || ft_bitmap.rows > kMaxGlyphDimension) { @@ -929,13 +1264,15 @@ std::unique_ptr CFX_Face::RenderGlyph( if (!new_bitmap->Create(dib_width, ft_bitmap.rows, format)) { return nullptr; } - auto pGlyphBitmap = std::make_unique( - glyph->bitmap_left, glyph->bitmap_top, new_bitmap); + auto glyph_bitmap = std::make_unique( + CFX_Point(glyph->bitmap_left, glyph->bitmap_top), new_bitmap); const uint32_t src_pitch = abs(ft_bitmap.pitch); + // SAFETY: `ft_bitmap.buffer` contains `src_pitch * ft_bitmap.rows` bytes + // allocated and rendered by FreeType. pdfium::span src_span = - UNSAFE_TODO(pdfium::span(ft_bitmap.buffer, - src_pitch * ft_bitmap.rows)); + UNSAFE_BUFFERS(pdfium::span(ft_bitmap.buffer, + src_pitch * ft_bitmap.rows)); if (anti_alias != FontAntiAliasingMode::kMono && ft_bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { @@ -943,11 +1280,7 @@ std::unique_ptr CFX_Face::RenderGlyph( } else { new_bitmap->PopulateFromSpan(src_span, src_pitch); } -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - // TODO(https://crbug.com/42271123): Compute equivalent result via Skia or - // Skrifa. -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return pGlyphBitmap; + return glyph_bitmap; } std::unique_ptr CFX_Face::LoadGlyphPath( @@ -956,16 +1289,38 @@ std::unique_ptr CFX_Face::LoadGlyphPath( bool is_vertical, const CFX_SubstFont* subst_font) { #if defined(PDF_ENABLE_FONTATIONS) - if (CFX_GEModule::Get()->GetFontMgr()->GetFontBackend() == - CFX_FontMgr::FontBackend::kFontations) { + if (CFX_GEModule::IsFontations()) { if (skrifa_font_ && skrifa_font_->font->is_ok()) { skrifa::Outline outline; if (skrifa_font_->font->unscaled_outline(glyph_index, outline)) { - return ConvertOutline(outline); + int upem = skrifa_font_->font->units_per_em(); + if (upem > 0) { + float scale = 1.0f / static_cast(upem); + CFX_Matrix matrix(scale, 0, 0, scale, 0, 0); + if (subst_font) { + int skew = subst_font->GetSkew(); + if (skew) { + if (is_vertical) { + matrix.b += matrix.d * skew / 100.0f; + } else { + matrix.c -= matrix.a * skew / 100.0f; + } + } + } + auto path = ConvertOutline(outline); + if (path) { + path->Transform(matrix); + return path; + } + } } } + return nullptr; } #endif // defined(PDF_ENABLE_FONTATIONS) + if (!GetRec()) { + return nullptr; + } FT_FaceRec* rec = GetRec(); FT_Set_Pixel_Sizes(rec, 0, 64); FT_Matrix ft_matrix = {65536, 0, 0, 65536}; @@ -1029,44 +1384,40 @@ std::unique_ptr CFX_Face::LoadGlyphPath( } int CFX_Face::GetGlyphTTWidth(uint32_t glyph_index) const { - const auto* fontglyph = GetRec()->glyph; - DCHECK_EQ(glyph_index, fontglyph->glyph_index); - - const int ft_result = - NormalizeFontMetric(fontglyph->metrics.horiAdvance, GetUnitsPerEm()); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { + if (CFX_GEModule::IsFontations()) { skrifa::Outline outline; if (skrifa_font_->font->unscaled_outline(glyph_index, outline)) { - const int skrifa_result = NormalizeFontMetric( + return NormalizeFontMetric( static_cast(outline.advance_width + 0.5), GetUnitsPerEm()); - CHECK_EQ(ft_result, skrifa_result); } + return 0; } #endif // defined(PDF_ENABLE_FONTATIONS) - if (skia_typeface_) { - SkFont font(skia_typeface_, GetUnitsPerEm()); - font.setHinting(SkFontHinting::kNone); - uint16_t skia_glyph_index = static_cast(glyph_index); - SkScalar width; - font.getWidths(pdfium::span_from_ref(skia_glyph_index), - pdfium::span_from_ref(width)); - const int sk_result = - NormalizeFontMetric(static_cast(width + 0.5), GetUnitsPerEm()); - CHECK_EQ(ft_result, sk_result); - } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + const auto* fontglyph = GetRec()->glyph; + DCHECK_EQ(glyph_index, fontglyph->glyph_index); + return NormalizeFontMetric(fontglyph->metrics.horiAdvance, GetUnitsPerEm()); } int CFX_Face::GetGlyphWidth(uint32_t glyph_index, int dest_width, int weight, const CFX_SubstFont* subst_font) { - // EmbedPDF(substitute-font-bounds): the design set for this glyph is undone - // when the call returns. +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + skrifa::Outline outline; + if (skrifa_font_->font->unscaled_outline(glyph_index, outline)) { + return EmAdjust(static_cast(outline.advance_width)); + } + return 0; + } +#endif // defined(PDF_ENABLE_FONTATIONS) + // EmbedPDF(substitute-font-bounds): the face is checked before a design is + // set, and the design set for this glyph is undone when the call returns. FT_FaceRec* rec = GetRec(); + if (!rec) { + return 0; + } const bool adjust_variation = subst_font && subst_font->IsBuiltInGenericFont(); ScopedFaceVariation scoped_variation(rec, adjust_variation); @@ -1086,20 +1437,7 @@ int CFX_Face::GetGlyphWidth(uint32_t glyph_index, return 0; } - const int ft_result = EmAdjust(static_cast(horizontal_advance)); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - if (skia_typeface_) { - SkFont font(skia_typeface_, GetUnitsPerEm()); - font.setHinting(SkFontHinting::kNone); - uint16_t skia_glyph_index = static_cast(glyph_index); - SkScalar width; - font.getWidths(pdfium::span_from_ref(skia_glyph_index), - pdfium::span_from_ref(width)); - const int sk_result = EmAdjust(static_cast(width + 0.5)); - CHECK_EQ(ft_result, sk_result); - } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return EmAdjust(static_cast(horizontal_advance)); } // EmbedPDF(substitute-font-bounds): a glyph's bounds in the design it is @@ -1121,149 +1459,112 @@ std::optional CFX_Face::GetDrawnGlyphBBox( } ByteString CFX_Face::GetGlyphName(uint32_t glyph_index) { - char name[256] = {}; - FT_Get_Glyph_Name(GetRec(), glyph_index, name, sizeof(name)); - name[255] = 0; - ByteString ft_result(name); - -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { + if (CFX_GEModule::IsFontations()) { rust::String skrifa_result = skrifa_font_->font->glyph_name(glyph_index); - CHECK_EQ(ft_result.IsEmpty(), skrifa_result.empty()); - if (!ft_result.IsEmpty() && !skrifa_result.empty()) { - CHECK_EQ(ft_result, ByteString(skrifa_result.c_str())); - } + return ByteString(skrifa_result.c_str()); } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - - return ft_result; + if (!GetRec()) { + return ByteString(); + } + char name[256] = {}; + FT_Get_Glyph_Name(GetRec(), glyph_index, name, sizeof(name)); + name[255] = 0; + return ByteString(name); } int CFX_Face::GetCharIndex(uint32_t code) { #if defined(PDF_ENABLE_FONTATIONS) - if (CFX_GEModule::Get()->GetFontMgr()->GetFontBackend() == - CFX_FontMgr::FontBackend::kFontations) { - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - if (skrifa_font_->font->font_type() == skrifa::FaceFormat::Type1) { - if (code <= 0xFF) { - return skrifa_font_->font->code_to_gid(static_cast(code)); - } + if (CFX_GEModule::IsFontations()) { + if (!GetRec()) { + if (selected_encoding_ == fxge::FontEncoding::kUnicode || + selected_encoding_ == fxge::FontEncoding::kNone) { + return static_cast(skrifa_font_->font->unicode_to_gid(code)); } - return skrifa_font_->font->unicode_to_gid(code); - } - } -#endif // defined(PDF_ENABLE_FONTATIONS) - const int ft_result = FT_Get_Char_Index(GetRec(), code); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) -#if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - int skrifa_result = -1; - FT_CharMap charmap = GetRec()->charmap; - if (charmap) { - if (charmap->encoding == FT_ENCODING_UNICODE) { - skrifa_result = - static_cast(skrifa_font_->font->unicode_to_gid(code)); - } else if (skrifa_font_->font->font_type() == skrifa::FaceFormat::Type1 && - code <= 0xFF) { - if (charmap->encoding == FT_ENCODING_ADOBE_CUSTOM || - charmap->encoding == FT_ENCODING_ADOBE_STANDARD || - charmap->encoding == FT_ENCODING_ADOBE_EXPERT || - charmap->encoding == FT_ENCODING_APPLE_ROMAN) { - skrifa_result = static_cast( - skrifa_font_->font->code_to_gid(static_cast(code))); - } + if (skrifa_font_->font->font_type() == skrifa::FaceFormat::Type1 && + code <= 0xFF) { + return static_cast( + skrifa_font_->font->code_to_gid(static_cast(code))); } + return 0; } - if (skrifa_result != -1) { - CHECK_EQ(ft_result, skrifa_result); - } - } -#endif // defined(PDF_ENABLE_FONTATIONS) - if (skia_typeface_) { FT_CharMap charmap = GetRec()->charmap; if (charmap && charmap->encoding == FT_ENCODING_UNICODE) { - CHECK_EQ(static_cast(ft_result), - skia_typeface_->unicharToGlyph(code)); + return static_cast(skrifa_font_->font->unicode_to_gid(code)); } } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; +#endif // defined(PDF_ENABLE_FONTATIONS) + if (!GetRec()) { + return 0; + } + return FT_Get_Char_Index(GetRec(), code); } int CFX_Face::GetNameIndex(const char* name) { - int ft_result = FT_Get_Name_Index(GetRec(), name); - -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - uint32_t skrifa_result = skrifa_font_->font->name_index(name); - CHECK_EQ(ft_result, static_cast(skrifa_result)); + if (CFX_GEModule::IsFontations()) { + return static_cast(skrifa_font_->font->name_index(name)); } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - - return ft_result; + if (!GetRec()) { + return 0; + } + return FT_Get_Name_Index(GetRec(), name); } int CFX_Face::LoadGlyph(uint32_t glyph_index, bool scale) { +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + return skrifa_font_->font->has_outline(glyph_index) ? 0 : -1; + } +#endif // defined(PDF_ENABLE_FONTATIONS) + if (!GetRec()) { + return -1; + } FT_Int32 args = FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; if (!scale) { args |= FT_LOAD_NO_SCALE; } - const int ft_result = FT_Load_Glyph(GetRec(), glyph_index, args); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - if (skia_typeface_) { - bool skia_valid = - glyph_index < static_cast(skia_typeface_->countGlyphs()); - if (ft_result == 0) { - CHECK(skia_valid); - } - } -#if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - bool skrifa_result = skrifa_font_->font->has_outline(glyph_index); - CHECK_EQ(ft_result == 0, skrifa_result); - } -#endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return FT_Load_Glyph(GetRec(), glyph_index, args); } ByteString CFX_Face::GetPostscriptName() { - const char* ft_result = FT_Get_Postscript_Name(GetRec()); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { + if (CFX_GEModule::IsFontations()) { rust::Str skrifa_result = skrifa_font_->font->postscript_name(); - CHECK_EQ(!ft_result, skrifa_result.empty()); - if (ft_result && !skrifa_result.empty()) { - CHECK_EQ(ByteString(ft_result), - UNSAFE_BUFFERS( - ByteString(skrifa_result.data(), skrifa_result.size()))); - } + return ByteString(ByteStringView(skrifa_result)); } #endif // defined(PDF_ENABLE_FONTATIONS) - if (skia_typeface_) { - SkString name; - if (skia_typeface_->getPostScriptName(&name)) { - CHECK_EQ(ByteString(ft_result), ByteString(name.c_str())); - } else { - CHECK(!ft_result); - } + if (!GetRec()) { + return ByteString(); } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ByteString(ft_result); + return ByteString(FT_Get_Postscript_Name(GetRec())); } CFX_Size CFX_Face::GetPixelSize() const { + if (!GetRec()) { + return {64, 64}; + } int pixel_size_x = GetRec()->size->metrics.x_ppem; int pixel_size_y = GetRec()->size->metrics.y_ppem; return {pixel_size_x, pixel_size_y}; } std::optional CFX_Face::GetFontGlyphBBox(uint32_t glyph_index) { +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + if (!skrifa_font_->font->has_outline(glyph_index)) { + return std::nullopt; + } + skrifa::BoundingBox bbox = skrifa_font_->font->glyph_bounds(glyph_index); + const uint16_t upem = GetUnitsPerEm(); + return FX_RECT(NormalizeFontMetric(bbox.x_min, upem), + NormalizeFontMetric(bbox.y_min, upem), + NormalizeFontMetric(bbox.x_max, upem), + NormalizeFontMetric(bbox.y_max, upem)); + } +#endif // defined(PDF_ENABLE_FONTATIONS) if (IsTricky()) { int error = FT_Set_Char_Size(GetRec(), 0, 1000 * 64, 72, 72); if (error) { @@ -1296,34 +1597,38 @@ std::optional CFX_Face::GetFontGlyphBBox(uint32_t glyph_index) { return std::nullopt; } int em = GetUnitsPerEm(); - const FX_RECT ft_result = ScaledFXRectFromFTPos( + return ScaledFXRectFromFTPos( GetRec()->glyph->metrics.horiBearingX, GetRec()->glyph->metrics.horiBearingY - GetRec()->glyph->metrics.height, GetRec()->glyph->metrics.horiBearingX + GetRec()->glyph->metrics.width, GetRec()->glyph->metrics.horiBearingY, em, em); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - if (skia_typeface_) { - SkFont font(skia_typeface_, em); - font.setHinting(SkFontHinting::kNone); - uint16_t skia_glyph_index = static_cast(glyph_index); - SkRect bounds = font.getBounds(skia_glyph_index, nullptr); - - CHECK_EQ(ft_result.left, - NormalizeFontMetric(static_cast(bounds.fLeft), em)); - CHECK_EQ(ft_result.top, - NormalizeFontMetric(static_cast(-bounds.fTop), em)); - CHECK_EQ(ft_result.right, - NormalizeFontMetric(static_cast(bounds.fRight), em)); - CHECK_EQ(ft_result.bottom, - NormalizeFontMetric(static_cast(-bounds.fBottom), em)); - } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; } FX_RECT CFX_Face::GetCharBBox(uint32_t code, int glyph_index) { FX_RECT rect; +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + if (glyph_index < 0 || LoadGlyph(glyph_index, /*scale=*/false) != 0) { + return rect; + } + rect = GetGlyphBBox(glyph_index); + if (IsTricky()) { + rect.top = std::min(rect.top, static_cast(GetAscender())); + rect.bottom = std::max(rect.bottom, static_cast(GetDescender())); + } else { + if (rect.top <= kMaxRectTop) { + rect.top += rect.top / 64; + } else { + rect.top = std::numeric_limits::max(); + } + } + return rect; + } +#endif // defined(PDF_ENABLE_FONTATIONS) FT_FaceRec* rec = GetRec(); + if (!rec) { + return rect; + } if (IsTricky()) { int err = FT_Load_Glyph(rec, glyph_index, FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH); @@ -1360,87 +1665,48 @@ FX_RECT CFX_Face::GetCharBBox(uint32_t code, int glyph_index) { } else { rect.top = std::numeric_limits::max(); } -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) -#if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - skrifa::BoundingBox bbox = - skrifa_font_->font->glyph_bounds(glyph_index); - const uint16_t upem = GetUnitsPerEm(); - FX_RECT skrifa_result(NormalizeFontMetric(bbox.x_min, upem), - NormalizeFontMetric(bbox.y_max, upem), - NormalizeFontMetric(bbox.x_max, upem), - NormalizeFontMetric(bbox.y_min, upem)); - if (skrifa_result.top <= kMaxRectTop) { - skrifa_result.top += skrifa_result.top / 64; - } else { - skrifa_result.top = std::numeric_limits::max(); - } - CHECK(rect.Near(skrifa_result, 2)); - } -#endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) } } return rect; } FX_RECT CFX_Face::GetGlyphBBox(uint32_t glyph_index) const { +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + skrifa::BoundingBox bbox = skrifa_font_->font->glyph_bounds(glyph_index); + const uint16_t upem = GetUnitsPerEm(); + return FX_RECT(NormalizeFontMetric(bbox.x_min, upem), + NormalizeFontMetric(bbox.y_max, upem), + NormalizeFontMetric(bbox.x_max, upem), + NormalizeFontMetric(bbox.y_min, upem)); + } +#endif // defined(PDF_ENABLE_FONTATIONS) const auto* glyph = GetRec()->glyph; DCHECK_EQ(glyph_index, glyph->glyph_index); pdfium::ClampedNumeric left = glyph->metrics.horiBearingX; pdfium::ClampedNumeric top = glyph->metrics.horiBearingY; const uint16_t upem = GetUnitsPerEm(); - FX_RECT ft_result(NormalizeFontMetric(left, upem), - NormalizeFontMetric(top, upem), - NormalizeFontMetric(left + glyph->metrics.width, upem), - NormalizeFontMetric(top - glyph->metrics.height, upem)); - -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) -#if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - skrifa::BoundingBox bbox = skrifa_font_->font->glyph_bounds(glyph_index); - FX_RECT skrifa_result(NormalizeFontMetric(bbox.x_min, upem), - NormalizeFontMetric(bbox.y_max, upem), - NormalizeFontMetric(bbox.x_max, upem), - NormalizeFontMetric(bbox.y_min, upem)); - CHECK(ft_result.Near(skrifa_result, 2)); - } -#endif // defined(PDF_ENABLE_FONTATIONS) - if (skia_typeface_) { - SkFont font(skia_typeface_, upem); - font.setHinting(SkFontHinting::kNone); - uint16_t skia_glyph_index = static_cast(glyph_index); - - std::optional path = font.getPath(skia_glyph_index); - int skia_left = 0; - int skia_top = 0; - int skia_right = 0; - int skia_bottom = 0; - SkRect path_bounds = SkRect::MakeEmpty(); - if (path.has_value()) { - path_bounds = path->getBounds(); - skia_left = NormalizeFontMetric( - static_cast(std::round(path_bounds.fLeft)), upem); - skia_top = NormalizeFontMetric( - static_cast(std::round(-path_bounds.fTop)), upem); - skia_right = NormalizeFontMetric( - static_cast(std::round(path_bounds.fRight)), upem); - skia_bottom = NormalizeFontMetric( - static_cast(std::round(-path_bounds.fBottom)), upem); - } - - CHECK_EQ(ft_result.left, skia_left); - CHECK_EQ(ft_result.top, skia_top); - CHECK_EQ(ft_result.right, skia_right); - CHECK_EQ(ft_result.bottom, skia_bottom); - } -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return FX_RECT(NormalizeFontMetric(left, upem), + NormalizeFontMetric(top, upem), + NormalizeFontMetric(left + glyph->metrics.width, upem), + NormalizeFontMetric(top - glyph->metrics.height, upem)); } std::vector CFX_Face::GetCharCodesAndIndices( char32_t max_char) { +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + auto skrifa_result = + skrifa_font_->font->get_char_codes_and_indices(max_char); + std::vector results; + results.reserve(skrifa_result.size()); + for (const auto& item : skrifa_result) { + results.push_back({item.char_code, item.glyph_index}); + } + return results; + } +#endif // defined(PDF_ENABLE_FONTATIONS) CharCodeAndIndex char_code_and_index; char_code_and_index.char_code = static_cast( FT_Get_First_Char(GetRec(), &char_code_and_index.glyph_index)); @@ -1457,76 +1723,78 @@ std::vector CFX_Face::GetCharCodesAndIndices( } results.push_back(char_code_and_index); } - -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) -#if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - auto skrifa_result = - skrifa_font_->font->get_char_codes_and_indices(max_char); - - CHECK_EQ(results.size(), skrifa_result.size()); - for (size_t i = 0; i < results.size(); ++i) { - CHECK_EQ(results[i].char_code, skrifa_result[i].char_code); - CHECK_EQ(results[i].glyph_index, skrifa_result[i].glyph_index); - } - } -#endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return results; } CFX_Face::CharMap CFX_Face::GetCurrentCharMap() const { - // TODO(https://crbug.com/42271123): Implement charmap management in - // Skia/Fontations. - return GetRec()->charmap; + return GetRec() ? GetRec()->charmap : nullptr; } std::optional CFX_Face::GetCurrentCharMapEncoding() const { - // TODO(https://crbug.com/42271123): Implement charmap management in - // Skia/Fontations. - if (!GetRec()->charmap) { +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations() && !GetRec()) { + if (selected_encoding_ != fxge::FontEncoding::kNone) { + return selected_encoding_; + } + return fxge::FontEncoding::kUnicode; + } +#endif // defined(PDF_ENABLE_FONTATIONS) + if (!GetRec() || !GetRec()->charmap) { return std::nullopt; } return ToFontEncoding(GetRec()->charmap->encoding); } CFX_Face::CharMapId CFX_Face::GetCharMapIdByIndex(size_t index) const { - // TODO(https://crbug.com/42271123): Implement charmap management in - // Skia/Fontations. return {.platform_id = GetCharMapPlatformIdByIndex(index), .encoding_id = GetCharMapEncodingIdByIndex(index)}; } int CFX_Face::GetCharMapPlatformIdByIndex(size_t index) const { - // TODO(https://crbug.com/42271123): Implement charmap management in - // Skia/Fontations. + if (!GetRec()) { + return 0; + } return GetCharMaps()[index]->platform_id; } int CFX_Face::GetCharMapEncodingIdByIndex(size_t index) const { - // TODO(https://crbug.com/42271123): Implement charmap management in - // Skia/Fontations. + if (!GetRec()) { + return 0; + } return GetCharMaps()[index]->encoding_id; } fxge::FontEncoding CFX_Face::GetCharMapEncodingByIndex(size_t index) const { - // TODO(https://crbug.com/42271123): Implement charmap management in - // Skia/Fontations. +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations() && !GetRec()) { + if (index == 0) { + return fxge::FontEncoding::kUnicode; + } + return fxge::FontEncoding::kAdobeCustom; + } +#endif // defined(PDF_ENABLE_FONTATIONS) return ToFontEncoding(GetCharMaps()[index]->encoding); } size_t CFX_Face::GetCharMapCount() const { - // TODO(https://crbug.com/42271123): Implement charmap management in - // Skia/Fontations. - return GetRec()->charmaps +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations() && !GetRec()) { + if (skrifa_font_->font->font_type() == skrifa::FaceFormat::Type1) { + return skrifa_font_->font->encoding() != skrifa::PsEncodingKind::None ? 2 + : 1; + } + return 1; + } +#endif // defined(PDF_ENABLE_FONTATIONS) + return GetRec() && GetRec()->charmaps ? pdfium::checked_cast(GetRec()->num_charmaps) : 0; } pdfium::span CFX_Face::GetCharMaps() const { - // TODO(https://crbug.com/42271123): Implement charmap management in - // Skia/Fontations. + if (!GetRec()) { + return {}; + } size_t count = GetCharMapCount(); if (count == 0) { return {}; @@ -1536,37 +1804,61 @@ pdfium::span CFX_Face::GetCharMaps() const { } void CFX_Face::SetCharMap(CharMap map) { - // TODO(https://crbug.com/42271123): Implement charmap management in - // Skia/Fontations. - FT_Set_Charmap(GetRec(), static_cast(map)); + if (GetRec()) { + FT_Set_Charmap(GetRec(), static_cast(map)); + } } void CFX_Face::SetCharMapByIndex(size_t index) { - // TODO(https://crbug.com/42271123): Implement charmap management in - // Skia/Fontations. CHECK_LT(index, GetCharMapCount()); + +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations() && !GetRec()) { + SelectCharMap(GetCharMapEncodingByIndex(index)); + return; + } +#endif // defined(PDF_ENABLE_FONTATIONS) + // SAFETY: required from library as enforced by check above. SetCharMap(UNSAFE_BUFFERS(GetRec()->charmaps[index])); } bool CFX_Face::SelectCharMap(fxge::FontEncoding encoding) { - // TODO(https://crbug.com/42271123): Implement charmap management in - // Skia/Fontations. +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations() && !GetRec()) { + if (encoding == fxge::FontEncoding::kUnicode) { + selected_encoding_ = encoding; + return true; + } + if (skrifa_font_->font->font_type() == skrifa::FaceFormat::Type1 && + skrifa_font_->font->encoding() != skrifa::PsEncodingKind::None) { + selected_encoding_ = encoding; + return true; + } + return false; + } +#endif // defined(PDF_ENABLE_FONTATIONS) + if (!GetRec()) { + return false; + } FT_Error error = FT_Select_Charmap(GetRec(), ToFTEncoding(encoding)); - return !error; + if (error) { + return false; + } + selected_encoding_ = encoding; + return true; } #if defined(PDF_ENABLE_XFA) int CFX_Face::GetNumFaces() const { - const int ft_result = pdfium::checked_cast(GetRec()->num_faces); -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - pdfium::span data = GetData(); - uint32_t skrifa_result = skrifa::get_num_faces(rust::Slice(data)); - CHECK_EQ(ft_result, static_cast(skrifa_result)); + if (CFX_GEModule::IsFontations()) { + pdfium::span data = GetData(); + return static_cast( + skrifa::get_num_faces(rust::Slice(data))); + } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - return ft_result; + return GetRec() ? pdfium::checked_cast(GetRec()->num_faces) : 1; } #endif // defined(PDF_ENABLE_XFA) @@ -1598,27 +1890,24 @@ std::optional CFX_Face::GetCidFromGlyphIndex( #if BUILDFLAG(IS_WIN) bool CFX_Face::CanEmbed() { - FT_UShort fstype = FT_Get_FSType_Flags(GetRec()); - bool ft_result = (fstype & (FT_FSTYPE_RESTRICTED_LICENSE_EMBEDDING | - FT_FSTYPE_BITMAP_EMBEDDING_ONLY)) == 0; - -#if defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) #if defined(PDF_ENABLE_FONTATIONS) - if (skrifa_font_ && skrifa_font_->font->is_ok()) { - bool skrifa_result = false; + if (CFX_GEModule::IsFontations()) { uint16_t fs_type = 0; if (skrifa_font_->font->get_os2_fs_type(fs_type)) { - skrifa_result = - (fs_type & - (fxcrt::to_underlying(skrifa::FsType::RestrictedLicenseEmbedding) | - fxcrt::to_underlying(skrifa::FsType::BitmapEmbeddingOnly))) == 0; + return (fs_type & + (fxcrt::to_underlying( + skrifa::FsType::RestrictedLicenseEmbedding) | + fxcrt::to_underlying(skrifa::FsType::BitmapEmbeddingOnly))) == 0; } - CHECK_EQ(ft_result, skrifa_result); + return true; } #endif // defined(PDF_ENABLE_FONTATIONS) -#endif // defined(PDF_ENABLE_SKIA_TYPEFACE_CHECKS) - - return ft_result; + if (!GetRec()) { + return true; + } + FT_UShort fstype = FT_Get_FSType_Flags(GetRec()); + return (fstype & (FT_FSTYPE_RESTRICTED_LICENSE_EMBEDDING | + FT_FSTYPE_BITMAP_EMBEDDING_ONLY)) == 0; } #endif // BUILDFLAG(IS_WIN) @@ -1635,7 +1924,11 @@ CFX_Face::CFX_Face( skrifa_font_(std::move(skrifa_font)) #endif // defined(PDF_ENABLE_FONTATIONS) { +#if defined(PDF_ENABLE_FONTATIONS) + DCHECK(rec_ || skrifa_font_); +#else DCHECK(rec_); +#endif } #if defined(PDF_USE_SKIA) @@ -1658,6 +1951,9 @@ void CFX_Face::AdjustVariationParams(int glyph_index, DCHECK_GE(dest_width, 0); FT_FaceRec* rec = GetRec(); + if (!rec) { + return; + } ScopedFXFTMMVar variation_desc(rec); if (!variation_desc) { return; diff --git a/core/fxge/cfx_face.h b/core/fxge/cfx_face.h index 274a013fe0..e916e1b83e 100644 --- a/core/fxge/cfx_face.h +++ b/core/fxge/cfx_face.h @@ -21,6 +21,7 @@ #include "core/fxcrt/span.h" #include "core/fxge/freetype/fx_freetype.h" #include "core/fxge/fx_font.h" +#include "core/fxge/fx_fontencoding.h" #if defined(PDF_USE_SKIA) #include "third_party/skia/include/core/SkRefCnt.h" // nogncheck @@ -75,6 +76,8 @@ class CFX_Face final : public Retainable, public Observable { static RetainPtr New(RetainPtr cache_entry, RetainPtr font_stream, uint32_t face_index); + static wchar_t UnicodeFromAdobeName(const char* name); + static ByteString AdobeNameFromUnicode(wchar_t unicode); bool HasGlyphNames() const; bool IsTtOt() const; @@ -103,7 +106,6 @@ class CFX_Face final : public Retainable, public Observable { pdfium::span GetData() const; - std::unique_ptr ParseGSUBTable(); int GetGlyphCount() const; @@ -141,7 +143,6 @@ class CFX_Face final : public Retainable, public Observable { FX_RECT GetCharBBox(uint32_t code, int glyph_index); std::vector GetCharCodesAndIndices(char32_t max_char); - FT_Face GetFTFaceForTesting() { return GetRec(); } CharMap GetCurrentCharMap() const; std::optional GetCurrentCharMapEncoding() const; @@ -222,6 +223,7 @@ class CFX_Face final : public Retainable, public Observable { #if defined(PDF_ENABLE_FONTATIONS) std::unique_ptr const skrifa_font_; #endif // defined(PDF_ENABLE_FONTATIONS) + fxge::FontEncoding selected_encoding_ = fxge::FontEncoding::kNone; }; #endif // CORE_FXGE_CFX_FACE_H_ diff --git a/core/fxge/cfx_font.cpp b/core/fxge/cfx_font.cpp index 6cff42a624..2be39e2946 100644 --- a/core/fxge/cfx_font.cpp +++ b/core/fxge/cfx_font.cpp @@ -187,7 +187,7 @@ bool CFX_Font::LoadFaceZeroFromSpan(pdfium::span src_span, bool force_vertical, uint64_t object_tag) { vertical_ = force_vertical; - font_data_allocation_ = DataVector(src_span.begin(), src_span.end()); + font_data_allocation_ = ToDataVector(src_span); font_data_ = font_data_allocation_; return LoadFaceFromSpanStream( pdfium::MakeRetain(font_data_allocation_), 0, diff --git a/core/fxge/cfx_font_unittest.cpp b/core/fxge/cfx_font_unittest.cpp index b48b06419b..61f6fbf890 100644 --- a/core/fxge/cfx_font_unittest.cpp +++ b/core/fxge/cfx_font_unittest.cpp @@ -7,6 +7,7 @@ #include #include +#include "core/fxge/cfx_cttgsubtable.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/utils/file_util.h" #include "testing/utils/path_service.h" @@ -24,3 +25,49 @@ TEST(CfxFontTest, GetCharCodesAndIndices) { EXPECT_EQ(CharCodeAndIndex(33, 4), results[1]); EXPECT_EQ(CharCodeAndIndex(65279, 245), results[277]); } + +TEST(CfxFontTest, GetCharBBox) { + std::string font_path = PathService::GetTestFilePath("fonts/ahem/Ahem.ttf"); + std::vector bytes = GetFileContents(font_path.c_str()); + + CFX_Font font; + ASSERT_TRUE(font.LoadFaceZeroFromSpan(bytes, /*force_vertical=*/false, 0)); + + std::optional bbox = font.GetCharBBox(33, 4); + ASSERT_TRUE(bbox.has_value()); + EXPECT_EQ(FX_RECT(0, 812, 1000, -199), *bbox); + + std::optional invalid_bbox = + font.GetCharBBox(33, static_cast(-1)); + ASSERT_TRUE(invalid_bbox.has_value()); + EXPECT_EQ(FX_RECT(0, 0, 0, 0), *invalid_bbox); +} + +TEST(CfxFontTest, ParseGSUBTable) { + std::string font_path = + PathService::GetTestFilePath("fonts/bug_377948405.ttf"); + std::vector bytes = GetFileContents(font_path.c_str()); + + CFX_Font font; + ASSERT_TRUE(font.LoadFaceZeroFromSpan(bytes, /*force_vertical=*/false, 0)); + + auto gsub = font.ParseGSUBTable(); + ASSERT_TRUE(gsub); + EXPECT_EQ(0u, gsub->GetVerticalGlyph(0)); + EXPECT_EQ(0u, gsub->GetVerticalGlyph(1)); + + std::string sc_font_path = PathService::GetThirdPartyFilePath( + "NotoSansCJK/NotoSansSC-Regular.subset.otf"); + std::vector sc_bytes = GetFileContents(sc_font_path.c_str()); + + CFX_Font sc_font; + ASSERT_TRUE( + sc_font.LoadFaceZeroFromSpan(sc_bytes, /*force_vertical=*/false, 0)); + + auto sc_gsub = sc_font.ParseGSUBTable(); + ASSERT_TRUE(sc_gsub); + EXPECT_EQ(0u, sc_gsub->GetVerticalGlyph(0)); + EXPECT_EQ(0u, sc_gsub->GetVerticalGlyph(1)); + EXPECT_EQ(10u, sc_gsub->GetVerticalGlyph(2)); + EXPECT_EQ(0u, sc_gsub->GetVerticalGlyph(3)); +} diff --git a/core/fxge/cfx_fontmapper.cpp b/core/fxge/cfx_fontmapper.cpp index 1aa4e0b613..e31b728cd7 100644 --- a/core/fxge/cfx_fontmapper.cpp +++ b/core/fxge/cfx_fontmapper.cpp @@ -13,6 +13,7 @@ #include #include "build/build_config.h" +#include "core/fxcrt/byteorder.h" #include "core/fxcrt/cfx_read_only_span_stream.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/containers/adapters.h" @@ -302,6 +303,16 @@ bool IsNarrowFontName(const ByteString& name) { return false; } +uint32_t GetChecksumFromTT(pdfium::span font_data) { + uint32_t checksum = 0; + auto span = font_data.first(std::min(font_data.size(), 1024)); + while (span.size() >= 4) { + checksum += fxcrt::GetUInt32MSBFirst(span.first<4u>()); + span = span.subspan<4u>(); + } + return checksum; +} + class ScopedFontDeleter { public: FX_STACK_ALLOCATED(); @@ -335,18 +346,6 @@ std::unique_ptr CFX_FontMapper::TakeSystemFontInfo() { return std::move(font_info_); } -uint32_t CFX_FontMapper::GetChecksumFromTT(void* font_handle) { - uint32_t buffer[256]; - font_info_->GetFontData(font_handle, SystemFontInfoIface::kTableTTCF, - pdfium::as_writable_byte_span(buffer)); - - uint32_t checksum = 0; - for (auto x : buffer) { - checksum += x; - } - return checksum; -} - ByteString CFX_FontMapper::GetPSNameFromTT(void* font_handle) { size_t size = font_info_->GetFontData(font_handle, SystemFontInfoIface::kTableNAME, {}); @@ -799,17 +798,17 @@ RetainPtr CFX_FontMapper::GetCachedTTCFace(void* font_handle, size_t ttc_size, size_t data_size) { CHECK_GE(ttc_size, data_size); - uint32_t checksum = GetChecksumFromTT(font_handle); + auto font_data = FixedSizeDataVector::Uninit(ttc_size); + size_t size = font_info_->GetFontData( + font_handle, SystemFontInfoIface::kTableTTCF, font_data.span()); + if (size != ttc_size) { + return nullptr; + } + + uint32_t checksum = GetChecksumFromTT(font_data); RetainPtr cache_entry = GetTTCFontCacheEntry(ttc_size, checksum); if (!cache_entry) { - auto font_data = FixedSizeDataVector::Uninit(ttc_size); - size_t size = font_info_->GetFontData( - font_handle, SystemFontInfoIface::kTableTTCF, font_data.span()); - if (size != ttc_size) { - return nullptr; - } - cache_entry = AddTTCFontCacheEntry(ttc_size, checksum, std::move(font_data)); } diff --git a/core/fxge/cfx_fontmapper.h b/core/fxge/cfx_fontmapper.h index cd7e5d69bd..44843c8315 100644 --- a/core/fxge/cfx_fontmapper.h +++ b/core/fxge/cfx_fontmapper.h @@ -96,7 +96,6 @@ class CFX_FontMapper { private: friend class TestFontMapper; - uint32_t GetChecksumFromTT(void* font_handle); ByteString GetPSNameFromTT(void* font_handle); ByteString MatchInstalledFonts(const ByteString& norm_name); RetainPtr UseInternalSubst( diff --git a/core/fxge/cfx_fontmapper_unittest.cpp b/core/fxge/cfx_fontmapper_unittest.cpp index dff6d3e5cb..739a85fafd 100644 --- a/core/fxge/cfx_fontmapper_unittest.cpp +++ b/core/fxge/cfx_fontmapper_unittest.cpp @@ -4,8 +4,8 @@ #include "core/fxge/cfx_fontmapper.h" +#include #include -#include #include #include @@ -186,24 +186,50 @@ TEST_F(CFXFontMapperSystemFontInfoTest, GetCachedTTCFaceFailToGetData) { static constexpr size_t kTtcSize = 1024; static constexpr size_t kDataSize = 2; - { - InSequence s; - EXPECT_CALL(system_font_info(), - GetFontData(kFontHandle, SystemFontInfoIface::kTableTTCF, _)) - .WillOnce(DoAll(WithArg<2>([&](pdfium::span buffer) { - EXPECT_EQ(kTtcSize, buffer.size()); - std::iota(buffer.begin(), buffer.end(), 0); - }), - Return(kTtcSize))); - EXPECT_CALL(system_font_info(), - GetFontData(kFontHandle, SystemFontInfoIface::kTableTTCF, _)) - .WillOnce(Return(0)); - } + EXPECT_CALL(system_font_info(), + GetFontData(kFontHandle, SystemFontInfoIface::kTableTTCF, _)) + .WillOnce(Return(0)); EXPECT_FALSE( font_mapper().GetCachedTTCFace(kFontHandle, kTtcSize, kDataSize)); } +// Regression test for crbug.com/565837689 - should not crash. +TEST_F(CFXFontMapperSystemFontInfoTest, GetCachedTTCFaceWithLargeTtc) { + void* const kFontHandle = reinterpret_cast(12345); + static constexpr size_t kTtcSize = 2048; + static constexpr size_t kDataSize = 2; + + for (int i = 0; i < 2; ++i) { + // Iteration 1: + // GetCachedTTCFace() reads `kTtcSize` bytes of test data and inserts an + // entry into `ttc_face_map_`. CFX_Face::New() then fails on the test data, + // destroying the FontCacheEntry while leaving the key in `ttc_face_map_` + // with a null ObservedPtr. + // Iteration 2: + // GetCachedTTCFace() reads `kTtcSize` bytes of test data again, and + // GetTTCFontCacheEntry() looks up the key in the non-empty `ttc_face_map_`, + // forcing a key comparison on the checksum. + EXPECT_CALL(system_font_info(), + GetFontData(kFontHandle, SystemFontInfoIface::kTableTTCF, _)) + .WillOnce([](void*, uint32_t, pdfium::span buffer) { + EXPECT_EQ(kTtcSize, buffer.size()); + std::ranges::fill(buffer, 0); + return kTtcSize; + }); + + // Iteration 1: + // Inserts the checksum into `ttc_face_map_` without comparing keys because + // the map is empty. + // Iteration 2: + // Looks up the checksum in the non-empty `ttc_face_map_`, forcing a key + // comparison on the checksum. + EXPECT_FALSE( + font_mapper().GetCachedTTCFace(kFontHandle, kTtcSize, kDataSize)); + testing::Mock::VerifyAndClearExpectations(&system_font_info()); + } +} + // Regression test for crbug.com/1372234 - should not crash. TEST_F(CFXFontMapperSystemFontInfoTest, GetCachedFaceFailToGetData) { void* const kFontHandle = reinterpret_cast(12345); diff --git a/core/fxge/cfx_fontmgr.cpp b/core/fxge/cfx_fontmgr.cpp index 27057dacb1..0bedea0e35 100644 --- a/core/fxge/cfx_fontmgr.cpp +++ b/core/fxge/cfx_fontmgr.cpp @@ -67,7 +67,7 @@ sk_sp CreateSkiaFontManager(CFX_FontMgr::FontBackend backend) { #endif // defined(PDF_USE_SKIA) } // namespace -CFX_FontMgr::CFX_FontMgr(FontBackend backend) +CFX_FontMgr::CFX_FontMgr([[maybe_unused]] FontBackend backend) : ft_library_(InitializeFreeType()), #if defined(PDF_USE_SKIA) font_backend_(backend), diff --git a/core/fxge/cfx_fontmgr.h b/core/fxge/cfx_fontmgr.h index 4918f2a386..390fc9fcca 100644 --- a/core/fxge/cfx_fontmgr.h +++ b/core/fxge/cfx_fontmgr.h @@ -34,7 +34,7 @@ class SkTypeface; class CFX_FontMgr { public: - enum class FontBackend { kFreeType, kFontations }; // Currently skia-only. + enum class FontBackend { kFreeType, kFontations }; explicit CFX_FontMgr(FontBackend backend); ~CFX_FontMgr(); @@ -46,9 +46,6 @@ class CFX_FontMgr { FXFT_LibraryRec* GetFTLibrary() const { return ft_library_.get(); } -#if defined(PDF_USE_SKIA) - FontBackend GetFontBackend() const { return font_backend_; } -#endif bool FTLibrarySupportsHinting() const { return ft_library_supports_hinting_; } #if defined(PDF_USE_SKIA) diff --git a/core/fxge/cfx_gemodule.cpp b/core/fxge/cfx_gemodule.cpp index aa9e3afe38..ddb6c62a7b 100644 --- a/core/fxge/cfx_gemodule.cpp +++ b/core/fxge/cfx_gemodule.cpp @@ -39,9 +39,9 @@ WindowsPrintMode CFX_GEModule::GetPrintMode() { void CFX_GEModule::Create( std::optional> user_font_paths, RendererType renderer_type, - CFX_FontMgr::FontBackend backend) { + CFX_FontMgr::FontBackend font_backend) { DCHECK(!g_GEModule); - g_GEModule = new CFX_GEModule(user_font_paths, renderer_type, backend); + g_GEModule = new CFX_GEModule(user_font_paths, renderer_type, font_backend); g_GEModule->platform_->Init(); g_GEModule->font_mgr_->GetBuiltinMapper()->SetSystemFontInfo( g_GEModule->platform_->CreateDefaultSystemFontInfo()); @@ -64,10 +64,14 @@ CFX_GEModule* CFX_GEModule::Get() { CFX_GEModule::CFX_GEModule( std::optional> user_font_paths, RendererType renderer_type, - CFX_FontMgr::FontBackend backend) + CFX_FontMgr::FontBackend font_backend) : renderer_type_(renderer_type), +#if defined(PDF_ENABLE_FONTATIONS) + font_backend_(font_backend), +#endif platform_(PlatformIface::Create()), - font_mgr_(std::make_unique(backend)), - user_font_paths_(user_font_paths) {} + font_mgr_(std::make_unique(font_backend)), + user_font_paths_(user_font_paths) { +} CFX_GEModule::~CFX_GEModule() = default; diff --git a/core/fxge/cfx_gemodule.h b/core/fxge/cfx_gemodule.h index 03eb6a60be..85d6f3c141 100644 --- a/core/fxge/cfx_gemodule.h +++ b/core/fxge/cfx_gemodule.h @@ -81,7 +81,7 @@ class CFX_GEModule { static void Create( std::optional> user_font_paths, RendererType renderer_type, - CFX_FontMgr::FontBackend backend); + CFX_FontMgr::FontBackend font_backend); static void Destroy(); static CFX_GEModule* Get(); @@ -104,17 +104,35 @@ class CFX_GEModule { #if defined(PDF_USE_SKIA) // Runtime check to see if Skia is the renderer variant in use. - bool UseSkiaRenderer() const { return renderer_type_ == RendererType::kSkia; } + static bool IsSkiaRenderer() { + return Get()->renderer_type_ == RendererType::kSkia; + } +#endif + +#if defined(PDF_ENABLE_FONTATIONS) + static bool IsFontations() { + return Get()->font_backend_ == CFX_FontMgr::FontBackend::kFontations; + } +#endif + + static bool IsFreetype() { +#if defined(PDF_ENABLE_FONTATIONS) + return Get()->font_backend_ == CFX_FontMgr::FontBackend::kFreeType; +#else + return true; #endif + } private: CFX_GEModule(std::optional> user_font_paths, RendererType renderer_type, - CFX_FontMgr::FontBackend backend); + CFX_FontMgr::FontBackend font_backend); ~CFX_GEModule(); const RendererType renderer_type_; - +#if defined(PDF_ENABLE_FONTATIONS) + const CFX_FontMgr::FontBackend font_backend_; +#endif std::unique_ptr const platform_; // Must outlive `font_mgr_`. std::unique_ptr const font_mgr_; std::optional> user_font_paths_; diff --git a/core/fxge/cfx_glyphbitmap.cpp b/core/fxge/cfx_glyphbitmap.cpp index 77b82b10e7..6d5da126f7 100644 --- a/core/fxge/cfx_glyphbitmap.cpp +++ b/core/fxge/cfx_glyphbitmap.cpp @@ -8,10 +8,9 @@ #include "core/fxge/dib/cfx_dibitmap.h" -CFX_GlyphBitmap::CFX_GlyphBitmap(int left, - int top, +CFX_GlyphBitmap::CFX_GlyphBitmap(const CFX_Point& top_left_origin, RetainPtr bitmap) - : left_(left), top_(top), bitmap_(std::move(bitmap)) {} + : top_left_origin_(top_left_origin), bitmap_(std::move(bitmap)) {} CFX_GlyphBitmap::~CFX_GlyphBitmap() = default; diff --git a/core/fxge/cfx_glyphbitmap.h b/core/fxge/cfx_glyphbitmap.h index 123c980735..36521b6210 100644 --- a/core/fxge/cfx_glyphbitmap.h +++ b/core/fxge/cfx_glyphbitmap.h @@ -7,25 +7,25 @@ #ifndef CORE_FXGE_CFX_GLYPHBITMAP_H_ #define CORE_FXGE_CFX_GLYPHBITMAP_H_ +#include "core/fxcrt/fx_coordinates.h" #include "core/fxcrt/retain_ptr.h" class CFX_DIBitmap; class CFX_GlyphBitmap { public: - CFX_GlyphBitmap(int left, int top, RetainPtr bitmap); + CFX_GlyphBitmap(const CFX_Point& top_left_origin, + RetainPtr bitmap); CFX_GlyphBitmap(const CFX_GlyphBitmap&) = delete; CFX_GlyphBitmap& operator=(const CFX_GlyphBitmap&) = delete; ~CFX_GlyphBitmap(); RetainPtr GetWritableBitmap(); RetainPtr GetBitmap() const; - int left() const { return left_; } - int top() const { return top_; } + const CFX_Point& top_left_origin() const { return top_left_origin_; } private: - const int left_; - const int top_; + const CFX_Point top_left_origin_; const RetainPtr bitmap_; }; diff --git a/core/fxge/cfx_glyphcache.cpp b/core/fxge/cfx_glyphcache.cpp index e19bd20219..d249b5f1b3 100644 --- a/core/fxge/cfx_glyphcache.cpp +++ b/core/fxge/cfx_glyphcache.cpp @@ -12,6 +12,7 @@ #include "build/build_config.h" #include "core/fxcrt/fx_codepage.h" +#include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/span.h" #include "core/fxcrt/to_underlying.h" #include "core/fxge/cfx_font.h" @@ -64,10 +65,10 @@ UniqueKeyGen::UniqueKeyGen(const CFX_Font* font, int dest_width, FontAntiAliasingMode anti_alias, bool bNative) { - int nMatrixA = static_cast(matrix.a * 10000); - int nMatrixB = static_cast(matrix.b * 10000); - int nMatrixC = static_cast(matrix.c * 10000); - int nMatrixD = static_cast(matrix.d * 10000); + int nMatrixA = pdfium::saturated_cast(matrix.a * 10000); + int nMatrixB = pdfium::saturated_cast(matrix.b * 10000); + int nMatrixC = pdfium::saturated_cast(matrix.c * 10000); + int nMatrixD = pdfium::saturated_cast(matrix.d * 10000); #if BUILDFLAG(IS_APPLE) if (bNative) { @@ -163,7 +164,7 @@ const CFX_GlyphBitmap* CFX_GlyphCache::LoadGlyphBitmap( #if BUILDFLAG(IS_APPLE) bool bDoLookUp = !text_options->native_text; #if defined(PDF_USE_SKIA) - bDoLookUp = bDoLookUp || CFX_GEModule::Get()->UseSkiaRenderer(); + bDoLookUp = bDoLookUp || CFX_GEModule::IsSkiaRenderer(); #endif // defined(PDF_USE_SKIA) #else // BUILDFLAG(IS_APPLE) const bool bDoLookUp = true; @@ -175,7 +176,7 @@ const CFX_GlyphBitmap* CFX_GlyphCache::LoadGlyphBitmap( #if BUILDFLAG(IS_APPLE) #if defined(PDF_USE_SKIA) - DCHECK(!CFX_GEModule::Get()->UseSkiaRenderer()); + DCHECK(!CFX_GEModule::IsSkiaRenderer()); #endif // defined(PDF_USE_SKIA) auto it = size_map_.find(FaceGlyphsKey); diff --git a/core/fxge/cfx_renderdevice.cpp b/core/fxge/cfx_renderdevice.cpp index c5a7d08af1..1ccc00e546 100644 --- a/core/fxge/cfx_renderdevice.cpp +++ b/core/fxge/cfx_renderdevice.cpp @@ -806,7 +806,7 @@ bool CFX_RenderDevice::DrawPath(const CFX_Path& path, if (fill && fill_alpha && stroke_alpha < 0xff && fill_options.stroke) { #if defined(PDF_USE_SKIA) if (render_cap_fillstroke_path_) { - const bool using_skia = CFX_GEModule::Get()->UseSkiaRenderer(); + const bool using_skia = CFX_GEModule::IsSkiaRenderer(); if (using_skia) { device_driver_->SetGroupKnockout(true); } @@ -1178,7 +1178,7 @@ bool CFX_RenderDevice::DrawNormalText(pdfium::span pCharPos, // instead fall back on NORMAL anti-aliasing. anti_alias = FontAntiAliasingMode::kNormal; #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { // Since |anti_alias| doesn't affect Skia rendering, and Skia only // follows strictly to the options provided by |text_options|, we need // to update |text_options| so that Skia falls back on normal @@ -1663,7 +1663,7 @@ bool CFX_RenderDevice::AttachImpl(RetainPtr pBitmap, RetainPtr pBackdropBitmap, bool bGroupKnockout) { #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { return AttachSkiaImpl(std::move(pBitmap), bRgbByteOrder, std::move(pBackdropBitmap), bGroupKnockout); } @@ -1685,7 +1685,7 @@ bool CFX_RenderDevice::CreateWithBackdrop(int width, FXDIB_Format format, RetainPtr backdrop) { #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { return CreateSkia(width, height, format, backdrop); } #endif diff --git a/core/fxge/cfx_renderdevice_unittest.cpp b/core/fxge/cfx_renderdevice_unittest.cpp index 483802191f..7103b8ff86 100644 --- a/core/fxge/cfx_renderdevice_unittest.cpp +++ b/core/fxge/cfx_renderdevice_unittest.cpp @@ -5,6 +5,7 @@ #include "build/build_config.h" #include "core/fxcrt/fx_coordinates.h" +#include "core/fxcrt/fx_coordinates_test_support.h" #include "core/fxcrt/retain_ptr.h" #include "core/fxge/cfx_fillrenderoptions.h" #include "core/fxge/cfx_graphstatedata.h" @@ -16,6 +17,11 @@ #if BUILDFLAG(IS_WIN) #include + +#include +#include + +#include "core/fxge/cfx_gemodule.h" #include "core/fxge/win32/cfx_psfonttracker.h" #endif @@ -90,6 +96,31 @@ namespace { constexpr CFX_Matrix kIdentityMatrix; +class ScopedPrintMode { + public: + explicit ScopedPrintMode(WindowsPrintMode mode) + : old_mode_(CFX_GEModule::GetPrintMode()) { + CFX_GEModule::SetPrintMode(mode); + } + ~ScopedPrintMode() { CFX_GEModule::SetPrintMode(old_mode_); } + + private: + const WindowsPrintMode old_mode_; +}; + +struct EnhMetaFileDCDeleter { + void operator()(HDC dc) const { ::DeleteEnhMetaFile(::CloseEnhMetaFile(dc)); } +}; +using ScopedEnhMetaFileDC = + std::unique_ptr, EnhMetaFileDCDeleter>; + +struct GdiObjectDeleter { + void operator()(HGDIOBJ object) const { ::DeleteObject(object); } +}; +template +using ScopedGdiObject = + std::unique_ptr, GdiObjectDeleter>; + } // namespace class CFXWindowsRenderDeviceTest : public testing::Test { @@ -169,4 +200,49 @@ TEST_F(CFXWindowsRenderDeviceTest, GargantuanClipRectWithBaseClip) { EXPECT_TRUE(device_->SetClip_PathFill( path_data, &kIdentityMatrix, CFX_FillRenderOptions::WindingOptions())); } -#endif + +TEST(CFXRenderDeviceTest, WindowsEmfPostScriptClipBox) { + ScopedPrintMode scoped_print_mode(WindowsPrintMode::kPostScript2); + ScopedEnhMetaFileDC dc_handle( + ::CreateEnhMetaFile(nullptr, nullptr, nullptr, nullptr)); + ASSERT_TRUE(dc_handle); + + const int horz_res = ::GetDeviceCaps(dc_handle.get(), HORZRES); + const int vert_res = ::GetDeviceCaps(dc_handle.get(), VERTRES); + const FX_RECT large_clip_rect(0, 0, horz_res * 2, vert_res * 2); + + CFX_PSFontTracker font_tracker; + std::unique_ptr device = + CFX_RenderDevice::CreateForWindowsDC(dc_handle.get(), &font_tracker); + ASSERT_TRUE(device); + EXPECT_TRUE(device->SetClip_Rect(large_clip_rect)); + + // TODO(crbug.com/553140224): Clip box should be `large_clip_rect`. + EXPECT_EQ(FX_RECT(0, 2 * vert_res, 2 * horz_res, 0), device->GetClipBox()); +} + +TEST(CFXRenderDeviceTest, WindowsEmfPostScriptClipRgn) { + ScopedPrintMode scoped_print_mode(WindowsPrintMode::kPostScript2); + ScopedEnhMetaFileDC dc_handle( + ::CreateEnhMetaFile(nullptr, nullptr, nullptr, nullptr)); + ASSERT_TRUE(dc_handle); + + const int horz_res = ::GetDeviceCaps(dc_handle.get(), HORZRES); + const int vert_res = ::GetDeviceCaps(dc_handle.get(), VERTRES); + const FX_RECT large_clip_rect(0, 0, horz_res * 2, vert_res * 2); + + ScopedGdiObject clip_rgn( + ::CreateRectRgn(large_clip_rect.left, large_clip_rect.top, + large_clip_rect.right, large_clip_rect.bottom)); + ASSERT_TRUE(clip_rgn); + EXPECT_NE(ERROR, ::SelectClipRgn(dc_handle.get(), clip_rgn.get())); + + CFX_PSFontTracker font_tracker; + std::unique_ptr device = + CFX_RenderDevice::CreateForWindowsDC(dc_handle.get(), &font_tracker); + ASSERT_TRUE(device); + + // TODO(crbug.com/553140224): Clip box should be `large_clip_rect`. + EXPECT_EQ(FX_RECT(0, 2 * vert_res, 2 * horz_res, 0), device->GetClipBox()); +} +#endif // BUILDFLAG(IS_WIN) diff --git a/core/fxge/dib/cfx_dibbase.cpp b/core/fxge/dib/cfx_dibbase.cpp index a544eb386d..e945f07066 100644 --- a/core/fxge/dib/cfx_dibbase.cpp +++ b/core/fxge/dib/cfx_dibbase.cpp @@ -244,11 +244,7 @@ DataVector ConvertBuffer_PltToPltRgb8( ConvertBuffer_IndexCopy(dest_buf, dest_pitch, width, height, pSrcBitmap, src_left, src_top); const size_t plt_size = pSrcBitmap->GetRequiredPaletteSize(); - pdfium::span src_span = pSrcBitmap->GetPaletteSpan(); - CHECK_LE(plt_size, src_span.size()); - - pdfium::span src_palette_span = src_span.first(plt_size); - return DataVector(src_palette_span.begin(), src_palette_span.end()); + return ToDataVector(pSrcBitmap->GetPaletteSpan().first(plt_size)); } void ConvertBuffer_1bppMaskToRgb(pdfium::span dest_buf, @@ -881,7 +877,7 @@ bool CFX_DIBBase::GetOverlapRect(int& dest_left, } void CFX_DIBBase::SetPalette(pdfium::span src_palette) { - TakePalette(DataVector(src_palette.begin(), src_palette.end())); + TakePalette(ToDataVector(src_palette)); } void CFX_DIBBase::TakePalette(DataVector src_palette) { diff --git a/core/fxge/dib/cfx_dibitmap.cpp b/core/fxge/dib/cfx_dibitmap.cpp index 2abe0e5964..f98cb981ae 100644 --- a/core/fxge/dib/cfx_dibitmap.cpp +++ b/core/fxge/dib/cfx_dibitmap.cpp @@ -170,7 +170,7 @@ void CFX_DIBitmap::Clear(uint32_t color) { } case FXDIB_Format::kBgrx: #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { // TODO(crbug.com/42271025): This is not reliable because alpha may // be modified outside of this operation. color |= 0xFF000000; @@ -184,7 +184,7 @@ void CFX_DIBitmap::Clear(uint32_t color) { break; #if defined(PDF_USE_SKIA) case FXDIB_Format::kBgraPremul: { - CHECK(CFX_GEModule::Get()->UseSkiaRenderer()); + CHECK(CFX_GEModule::IsSkiaRenderer()); const FX_BGRA_STRUCT bgra = PreMultiplyColor(ArgbToBGRAStruct(color)); for (int row = 0; row < GetHeight(); row++) { @@ -1031,7 +1031,7 @@ CFX_DIBitmap::ScopedPremultiplier::~ScopedPremultiplier() { } bool CFX_DIBitmap::ScopedPremultiplier::NeedToPremultiplyBitmap() const { - return CFX_GEModule::Get()->UseSkiaRenderer() && + return CFX_GEModule::IsSkiaRenderer() && bitmap_->GetFormat() == FXDIB_Format::kBgra; } diff --git a/core/fxge/dib/cfx_dibitmap_unittest.cpp b/core/fxge/dib/cfx_dibitmap_unittest.cpp index 36fd9b6b4f..1ba4e5ae23 100644 --- a/core/fxge/dib/cfx_dibitmap_unittest.cpp +++ b/core/fxge/dib/cfx_dibitmap_unittest.cpp @@ -99,12 +99,12 @@ TEST(CFXDIBitmapTest, CalculatePitchAndSizeBad) { TEST(CFXDIBitmapTest, CalculatePitchAndSizeBoundary) { // Test boundary condition for pitch overflow. std::optional result = - CFX_DIBitmap::CalculatePitchAndSize(536870908, 4, FXDIB_Format::k8bppRgb, + CFX_DIBitmap::CalculatePitchAndSize(536870911, 4, FXDIB_Format::k8bppRgb, 0); ASSERT_TRUE(result); - EXPECT_EQ(536870908u, result.value().pitch); - EXPECT_EQ(2147483632u, result.value().size); - EXPECT_FALSE(CFX_DIBitmap::CalculatePitchAndSize(536870909, 4, + EXPECT_EQ(536870912u, result.value().pitch); + EXPECT_EQ(2147483648u, result.value().size); + EXPECT_FALSE(CFX_DIBitmap::CalculatePitchAndSize(536870912, 4, FXDIB_Format::k8bppRgb, 0)); // Test boundary condition for size overflow. diff --git a/core/fxge/dib/cstretchengine.cpp b/core/fxge/dib/cstretchengine.cpp index 8d0bd0f81b..c0f9782023 100644 --- a/core/fxge/dib/cstretchengine.cpp +++ b/core/fxge/dib/cstretchengine.cpp @@ -9,12 +9,14 @@ #include #include +#include #include #include #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_2d_size.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/fx_system.h" #include "core/fxcrt/pauseindicator_iface.h" @@ -36,31 +38,6 @@ namespace { // loops' exact bytes; SetSpecializedLoopsEnabledForTesting() turns them off. bool g_specialized_loops_enabled = true; -// How the bits `first` to `last` of a 1 bpp scanline are set. Bits are -// stored most significant first. -enum class BitRun { kClear, kSet, kMixed }; - -BitRun ClassifyBits(const uint8_t* scan, int first, int last) { - bool any_set = false; - bool all_set = true; - for (int byte = first / 8; byte <= last / 8; ++byte) { - uint8_t mask = 0xff; - if (byte == first / 8) { - mask &= 0xff >> (first % 8); - } - if (byte == last / 8) { - mask &= static_cast(0xff << (7 - last % 8)); - } - const uint8_t bits = UNSAFE_TODO(scan[byte]) & mask; - any_set = any_set || bits != 0; - all_set = all_set && bits == mask; - } - if (!any_set) { - return BitRun::kClear; - } - return all_set ? BitRun::kSet : BitRun::kMixed; -} - // The horizontal pass over one row of an opaque source with `kBytes` bytes // per pixel, into the destination columns `left` to `right`. Each channel is // the sum the general loop computes, added in two halves, which unsigned @@ -405,6 +382,18 @@ bool CStretchEngine::StartStretchHorz() { src_clip_.left, src_clip_.right, resample_options_)) { return false; } + if (src_width_ > 0 && (trans_method_ == TransformMethod::k1BppTo8Bpp || + trans_method_ == TransformMethod::k1BppToManyBpp)) { + // Eight pixels per source byte, so round up to whole source bytes. + FX_SAFE_SIZE_T expanded_size = src_width_; + expanded_size = fxcrt::CeilDiv(expanded_size, 8); + expanded_size *= 8; + expanded_row_ = + FixedSizeDataVector::TryUninit(expanded_size.ValueOrDie()); + if (expanded_row_.empty()) { + return false; + } + } cur_row_ = src_clip_.top; state_ = State::kHorizontal; return true; @@ -434,42 +423,11 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { pdfium::span dest_span = inter_buf_.subspan( (cur_row_ - src_clip_.top) * inter_pitch_, inter_pitch_); size_t dest_span_index = 0; - // TODO(npm): reduce duplicated code here + // TODO(crbug.com/567627774): reduce duplicated code here switch (trans_method_) { case TransformMethod::k1BppTo8Bpp: - case TransformMethod::k1BppToManyBpp: { - for (int col = dest_clip_.left; col < dest_clip_.right; ++col) { - const PixelWeight* pWeights = weight_table_.GetPixelWeight(col); - const size_t src_start = pWeights->GetSrcStart(); - pdfium::span weights = pWeights->GetWeights(); - uint32_t dest_a = 0; - // EmbedPDF(render-speedup): runs of clear or set bits at once. - // Clear bits add nothing, and set bits add 255 times each weight, - // which is 255 times the weights' sum. Only a window with both - // needs its bits one by one. - const BitRun run = - g_specialized_loops_enabled - ? ClassifyBits(src_row_span.data(), pWeights->src_start_, - pWeights->src_end_) - : BitRun::kMixed; - if (run == BitRun::kSet) { - uint32_t weight_sum = 0; - for (uint32_t weight : weights) { - weight_sum += weight; - } - dest_a = weight_sum * 255; - } else if (run == BitRun::kMixed) { - for (size_t i = 0; i < weights.size(); ++i) { - const size_t src_bit = src_start + i; - if (src_row_span[src_bit / 8] & (1 << (7 - src_bit % 8))) { - dest_a += weights[i] * 255; - } - } - } - dest_span[dest_span_index++] = PixelFromFixed(dest_a); - } - break; - } + src_row_span = Expand1BppRow(src_row_span); + [[fallthrough]]; case TransformMethod::k8BppTo8Bpp: { for (int col = dest_clip_.left; col < dest_clip_.right; ++col) { const PixelWeight* pWeights = weight_table_.GetPixelWeight(col); @@ -499,6 +457,9 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { } break; } + case TransformMethod::k1BppToManyBpp: + src_row_span = Expand1BppRow(src_row_span); + [[fallthrough]]; case TransformMethod::k8BppToManyBpp: { // 3 bytes per destination pixel, written contiguously. auto dest_pixels = @@ -811,3 +772,26 @@ void CStretchEngine::StretchVert() { dest_bitmap_->ComposeScanline(row - dest_clip_.top, dest_scanline_); } } + +pdfium::span CStretchEngine::Expand1BppRow( + pdfium::span src_row) { + // Maps a byte of eight 1bpp pixels, most significant bit first, to eight + // bytes that are 0 or 255. + static constinit auto kBitsToBytes = [] { + std::array, 256> table{}; + for (size_t value = 0; value < table.size(); ++value) { + for (size_t bit = 0; bit < 8; ++bit) { + table[value][bit] = (value >> (7 - bit)) & 1 ? 255 : 0; + } + } + return table; + }(); + + pdfium::span expanded = expanded_row_.span(); + const size_t src_bytes = expanded.size() / 8; + for (size_t i = 0; i < src_bytes; ++i) { + fxcrt::spancpy(expanded.subspan(i * 8, 8u), + pdfium::span(kBitsToBytes[src_row[i]])); + } + return expanded; +} diff --git a/core/fxge/dib/cstretchengine.h b/core/fxge/dib/cstretchengine.h index 586bb245db..e52f94b9a5 100644 --- a/core/fxge/dib/cstretchengine.h +++ b/core/fxge/dib/cstretchengine.h @@ -150,6 +150,7 @@ class CStretchEngine { private: enum class State : uint8_t { kInitial, kHorizontal, kVertical }; + // ToManyBpp means To24OrTo32Bpp here. enum class TransformMethod : uint8_t { k1BppTo8Bpp, k1BppToManyBpp, @@ -159,6 +160,11 @@ class CStretchEngine { kManyBpptoManyBppWithAlpha }; + // For 1bpp sources: expands `src_row` to one byte per pixel, 0 or 255, in + // `expanded_row_` and returns that. + pdfium::span Expand1BppRow( + pdfium::span src_row); + const FXDIB_Format dest_format_; const int dest_bpp_; const int src_bpp_; @@ -173,6 +179,7 @@ class CStretchEngine { const FX_RECT dest_clip_; DataVector dest_scanline_; FixedSizeDataVector inter_buf_; + FixedSizeDataVector expanded_row_; FX_RECT src_clip_; int inter_pitch_; int extra_mask_pitch_; diff --git a/core/fxge/dib/cstretchengine_unittest.cpp b/core/fxge/dib/cstretchengine_unittest.cpp index 9312c5f3db..baecc52705 100644 --- a/core/fxge/dib/cstretchengine_unittest.cpp +++ b/core/fxge/dib/cstretchengine_unittest.cpp @@ -13,6 +13,7 @@ #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_number.h" #include "core/fpdfapi/parser/cpdf_stream.h" +#include "core/fxcrt/containers/to_vector.h" #include "core/fxge/dib/cfx_dibitmap.h" #include "core/fxge/dib/fx_dib.h" #include "core/fxge/dib/scanlinecomposer_iface.h" @@ -32,7 +33,7 @@ class ScanlineRecorder final : public ScanlineComposerIface { public: void ComposeScanline(int line, pdfium::span scanline) override { - rows_[line] = std::vector(scanline.begin(), scanline.end()); + rows_[line] = pdfium::ToVector(scanline); } bool SetInfo(int width, diff --git a/core/fxge/dib/fx_dib.h b/core/fxge/dib/fx_dib.h index 6e941d5927..c74a3e9409 100644 --- a/core/fxge/dib/fx_dib.h +++ b/core/fxge/dib/fx_dib.h @@ -242,6 +242,7 @@ UNSAFE_BUFFER_USAGE inline void ReverseCopy3Bytes(uint8_t* dest, #if defined(PDF_USE_SKIA) SkBlendMode GetSkiaBlendMode(BlendMode blend_type); +#endif // defined(PDF_USE_SKIA) template T PreMultiplyColor(const T& input) { @@ -276,7 +277,6 @@ T UnPreMultiplyColor(const T& input) { } return output; } -#endif // defined(PDF_USE_SKIA) } // namespace fxge @@ -307,12 +307,12 @@ using fxge::FXSYS_GetBValue; using fxge::FXSYS_GetGValue; using fxge::FXSYS_GetRValue; using fxge::FXSYS_GetUnsignedAlpha; +using fxge::PreMultiplyColor; using fxge::ReverseCopy3Bytes; +using fxge::UnPreMultiplyColor; #if defined(PDF_USE_SKIA) using fxge::GetSkiaBlendMode; -using fxge::PreMultiplyColor; -using fxge::UnPreMultiplyColor; #endif #endif // CORE_FXGE_DIB_FX_DIB_H_ diff --git a/core/fxge/fx_font.cpp b/core/fxge/fx_font.cpp index 0e9f620ae6..ae6db5e04e 100644 --- a/core/fxge/fx_font.cpp +++ b/core/fxge/fx_font.cpp @@ -15,9 +15,9 @@ #include "core/fxcrt/fx_string.h" #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/widestring.h" +#include "core/fxge/cfx_face.h" #include "core/fxge/cfx_glyphbitmap.h" #include "core/fxge/dib/cfx_dibitmap.h" -#include "core/fxge/freetype/fx_freetype.h" #include "core/fxge/text_glyph_pos.h" namespace { @@ -196,13 +196,11 @@ uint16_t GetGlyphCountFromMaxp(pdfium::span maxp_table) { } wchar_t UnicodeFromAdobeName(const char* name) { - return (wchar_t)(FXFT_unicode_from_adobe_name(name) & 0x7FFFFFFF); + return CFX_Face::UnicodeFromAdobeName(name); } ByteString AdobeNameFromUnicode(wchar_t unicode) { - char glyph_name[64]; - FXFT_adobe_name_from_unicode(glyph_name, unicode); - return ByteString(glyph_name); + return CFX_Face::AdobeNameFromUnicode(unicode); } int NormalizeFontMetric(int64_t value, uint16_t upem) { diff --git a/core/fxge/skia/fx_skia_device.cpp b/core/fxge/skia/fx_skia_device.cpp index 0ee8118f97..28b16c53fa 100644 --- a/core/fxge/skia/fx_skia_device.cpp +++ b/core/fxge/skia/fx_skia_device.cpp @@ -649,7 +649,8 @@ SkFont SkFontFromCFXFont(const CFX_Font* cfx_font, const CFX_TextRenderOptions& options) { SkFont font; font.setEmbolden(cfx_font->IsSubstFontBold()); - font.setHinting(SkFontHinting::kNone); + font.setHinting(cfx_font->IsTTFont() ? SkFontHinting::kNormal + : SkFontHinting::kNone); font.setSkewX(tanf(cfx_font->GetSubstFontItalicAngle() * FXSYS_PI / 180.0)); font.setSize(SkTAbs(font_size)); font.setSubpixel(true); @@ -770,18 +771,29 @@ bool CFX_SkiaDeviceDriver::DrawDeviceText( const CFX_TextRenderOptions& options) { // `SkTextBlob` is built from `pFont`'s font data. If `pFont` doesn't contain // any font data, each text blob will have zero area to be drawn and the - // drawing command will be rejected. In this case, we fall back to drawing - // characters by their glyph bitmaps. + // drawing command will be rejected. In this case, fall back to drawing + // characters by their glyph bitmaps. Note this is the last text case that + // still depends on the fallbacks in CFX_RenderDevice::DrawNormalText(). if (pFont->GetFontSpan().empty()) { return false; } + // Skia cannot create an `SkTypeface` for every font, e.g. Fontations only + // supports SFNT formats and rejects bare Type 1 or CFF font streams. Draw + // the glyph outlines directly for such fonts. + SkTypeface* skia_typeface = + pFont->HasFace() ? pFont->GetFace()->GetOrCreateSkTypeface() : nullptr; + if (!skia_typeface) { + DrawDeviceTextAsPath(pCharPos, pFont, mtObject2Device, font_size, color, + options); + return true; + } + if (TryDrawText(pCharPos, pFont, mtObject2Device, font_size, color, options)) { return true; } - sk_sp typeface( - SkSafeRef(pFont->GetFace()->GetOrCreateSkTypeface())); + sk_sp typeface(SkSafeRef(skia_typeface)); SkPaint paint; paint.setAntiAlias(true); paint.setColor(color); @@ -846,6 +858,37 @@ bool CFX_SkiaDeviceDriver::DrawDeviceText( return true; } +void CFX_SkiaDeviceDriver::DrawDeviceTextAsPath( + pdfium::span char_pos, + const CFX_Font* font, + const CFX_Matrix& matrix, + float font_size, + uint32_t color, + const CFX_TextRenderOptions& options) { + SkPaint paint; + paint.setAntiAlias(options.aliasing_type != CFX_TextRenderOptions::kAliasing); + paint.setColor(color); + paint.setStyle(SkPaint::kFill_Style); + + for (const auto& cp : char_pos) { + const CFX_Path* glyph_path = + font->LoadGlyphPath(cp.glyph_index_, cp.font_char_width_); + if (!glyph_path) { + continue; + } + + CFX_Matrix char_matrix(font_size, 0, 0, font_size, cp.origin_.x, + cp.origin_.y); + char_matrix = cp.GetEffectiveMatrix(char_matrix); + char_matrix.Concat(matrix); + + SkPathBuilder sk_path_builder = BuildPath(*glyph_path); + sk_path_builder.setFillType(SkPathFillType::kWinding); + sk_path_builder.transform(ToSkMatrix(char_matrix)); + DrawPathImpl(sk_path_builder.detach(), paint); + } +} + // TODO(crbug.com/42271005): Merge with `DrawDeviceText()` and refactor // common logic. // TODO(crbug.com/42270786): Sometimes the thickness of the glyphs is not @@ -1450,7 +1493,7 @@ RenderDeviceDriverIface::StartResult CFX_SkiaDeviceDriver::StartDIBits( } void CFX_DIBitmap::PreMultiply() { - CHECK(CFX_GEModule::Get()->UseSkiaRenderer()); + CHECK(CFX_GEModule::IsSkiaRenderer()); if (GetFormat() != FXDIB_Format::kBgra) { return; } @@ -1474,7 +1517,7 @@ void CFX_DIBitmap::PreMultiply() { } void CFX_DIBitmap::UnPreMultiply() { - CHECK(CFX_GEModule::Get()->UseSkiaRenderer()); + CHECK(CFX_GEModule::IsSkiaRenderer()); if (GetFormat() != FXDIB_Format::kBgraPremul) { return; } diff --git a/core/fxge/skia/fx_skia_device.h b/core/fxge/skia/fx_skia_device.h index 7eac371039..05921028f7 100644 --- a/core/fxge/skia/fx_skia_device.h +++ b/core/fxge/skia/fx_skia_device.h @@ -182,6 +182,15 @@ class CFX_SkiaDeviceDriver final : public RenderDeviceDriverIface { uint32_t color, const CFX_TextRenderOptions& options); + // Fills the glyph outlines for `char_pos` directly, for fonts that Skia + // cannot render as text, e.g. those without an `SkTypeface`. + void DrawDeviceTextAsPath(pdfium::span char_pos, + const CFX_Font* font, + const CFX_Matrix& matrix, + float font_size, + uint32_t color, + const CFX_TextRenderOptions& options); + bool StartDIBitsSkia(RetainPtr bitmap, const FX_RECT& src_rect, float alpha, diff --git a/core/fxge/skia/fx_skia_device_embeddertest.cpp b/core/fxge/skia/fx_skia_device_embeddertest.cpp index 27d1662873..c159df3c2d 100644 --- a/core/fxge/skia/fx_skia_device_embeddertest.cpp +++ b/core/fxge/skia/fx_skia_device_embeddertest.cpp @@ -213,14 +213,14 @@ using FxgeSkiaEmbedderTest = EmbedderTest; } // namespace TEST(fxge, SkiaStateEmpty) { - if (!CFX_GEModule::Get()->UseSkiaRenderer()) { + if (!CFX_GEModule::IsSkiaRenderer()) { return; } Harness(&EmptyTest, {}); } TEST(fxge, SkiaStatePath) { - if (!CFX_GEModule::Get()->UseSkiaRenderer()) { + if (!CFX_GEModule::IsSkiaRenderer()) { return; } Harness(&CommonTest, {State::Change::kNo, State::Save::kYes, @@ -237,7 +237,7 @@ TEST(fxge, SkiaStatePath) { } TEST(fxge, SkiaStateText) { - if (!CFX_GEModule::Get()->UseSkiaRenderer()) { + if (!CFX_GEModule::IsSkiaRenderer()) { return; } @@ -249,7 +249,7 @@ TEST(fxge, SkiaStateText) { } TEST(fxge, SkiaStateOOSClip) { - if (!CFX_GEModule::Get()->UseSkiaRenderer()) { + if (!CFX_GEModule::IsSkiaRenderer()) { return; } Harness(&OutOfSequenceClipTest, {}); @@ -265,7 +265,7 @@ TEST_F(FxgeSkiaEmbedderTest, RenderBigImageTwice) { static constexpr int kPageWidth = kImageWidth / kPageToImageFactor; static constexpr int kPageHeight = kImageHeight / kPageToImageFactor; - if (!CFX_GEModule::Get()->UseSkiaRenderer()) { + if (!CFX_GEModule::IsSkiaRenderer()) { GTEST_SKIP() << "Skia is not the default renderer"; } diff --git a/core/fxge/skrifa/fx_skrifa_unittest.cpp b/core/fxge/skrifa/fx_skrifa_unittest.cpp index 8b0821b0e5..a018616b08 100644 --- a/core/fxge/skrifa/fx_skrifa_unittest.cpp +++ b/core/fxge/skrifa/fx_skrifa_unittest.cpp @@ -3,12 +3,20 @@ // found in the LICENSE file. #include +#include #include #include +#include "core/fxcrt/cfx_read_only_span_stream.h" #include "core/fxcrt/compiler_specific.h" -#include "core/fxge/cfx_font.h" +#include "core/fxge/cfx_face.h" +#include "core/fxge/cfx_gemodule.h" +#include "core/fxge/cfx_glyphbitmap.h" +#include "core/fxge/cfx_path.h" +#include "core/fxge/dib/cfx_dibitmap.h" +#include "core/fxge/fontdata/chromefontdata/chromefontdata.h" #include "core/fxge/fx_font.h" +#include "core/fxge/fx_fontencoding.h" #include "core/fxge/skrifa/src/main.rs.h" #include "core/fxge/skrifa/src/outlines.h" #include "testing/gtest/include/gtest/gtest.h" @@ -105,29 +113,25 @@ TEST(FxSkrifaTest, TestRobotoGlyph167Bounds) { std::vector bytes = GetFileContents(font_path.c_str()); ASSERT_FALSE(bytes.empty()); - CFX_Font font; - ASSERT_TRUE(font.LoadFaceZeroFromSpan(bytes, /*force_vertical=*/false, - /*object_tag=*/0)); - auto face = font.GetFace(); - ASSERT_TRUE(face); - - ASSERT_EQ(FT_Load_Glyph(face->GetFTFaceForTesting(), 167, FT_LOAD_NO_SCALE), - 0); - FX_RECT ft_bbox = face->GetGlyphBBox(167); - auto skrifa_font = skrifa::new_font(rust::Slice(bytes), 0); ASSERT_TRUE(skrifa_font->is_ok()); - skrifa::BoundingBox skrifa_bbox = skrifa_font->glyph_bounds(167); + EXPECT_EQ(skrifa_font->units_per_em(), 2048); - uint16_t upem = face->GetUnitsPerEm(); - EXPECT_EQ(upem, 2048); - - FX_RECT skrifa_rect(NormalizeFontMetric(skrifa_bbox.x_min, upem), - NormalizeFontMetric(skrifa_bbox.y_max, upem), - NormalizeFontMetric(skrifa_bbox.x_max, upem), - NormalizeFontMetric(skrifa_bbox.y_min, upem)); - - EXPECT_TRUE(ft_bbox.Near(skrifa_rect, 1)); + skrifa::BoundingBox skrifa_bbox = skrifa_font->glyph_bounds(167); + EXPECT_EQ(skrifa_bbox.x_min, 85.0f); + EXPECT_EQ(skrifa_bbox.y_min, -21.0f); + EXPECT_EQ(skrifa_bbox.x_max, 1135.0f); + EXPECT_EQ(skrifa_bbox.y_max, 1477.0f); + + EXPECT_TRUE(skrifa_font->is_sfnt()); + EXPECT_FALSE(skrifa_font->is_italic()); + EXPECT_FALSE(skrifa_font->is_bold()); + skrifa::BoundingBox font_bbox; + EXPECT_TRUE(skrifa_font->get_font_bbox(font_bbox)); + EXPECT_EQ(font_bbox.x_min, -1825.0f); + EXPECT_EQ(font_bbox.y_min, -555.0f); + EXPECT_EQ(font_bbox.x_max, 4188.0f); + EXPECT_EQ(font_bbox.y_max, 2163.0f); } TEST(FxSkrifaTest, TestMinionCff) { @@ -135,37 +139,24 @@ TEST(FxSkrifaTest, TestMinionCff) { std::vector bytes = GetFileContents(font_path.c_str()); ASSERT_FALSE(bytes.empty()); - CFX_Font font; - ASSERT_TRUE(font.LoadFaceZeroFromSpan(bytes, /*force_vertical=*/false, - /*object_tag=*/0)); - auto face = font.GetFace(); - ASSERT_TRUE(face); - auto skrifa_font = skrifa::new_font(rust::Slice(bytes), 0); ASSERT_TRUE(skrifa_font->is_ok()); - EXPECT_EQ(face->GetFTFaceForTesting()->num_glyphs, - static_cast(skrifa_font->num_glyphs())); + EXPECT_EQ(skrifa_font->num_glyphs(), 1250u); auto family_name = skrifa_font->family_name(); auto ps_name = skrifa_font->postscript_name(); - ByteString ft_family = face->GetFamilyName(); ByteString sk_family = UNSAFE_BUFFERS(ByteString(family_name.data(), family_name.size())); - MaybeRemoveSubsettedFontPrefix(ft_family); MaybeRemoveSubsettedFontPrefix(sk_family); - EXPECT_EQ(ft_family, sk_family); + EXPECT_EQ(sk_family, "MinionPro-Regular"); - ByteString ft_ps = face->GetPostscriptName(); ByteString sk_ps = UNSAFE_BUFFERS(ByteString(ps_name.data(), ps_name.size())); - MaybeRemoveSubsettedFontPrefix(ft_ps); MaybeRemoveSubsettedFontPrefix(sk_ps); - EXPECT_EQ(ft_ps, sk_ps); + EXPECT_EQ(sk_ps, "MinionPro-Regular"); EXPECT_TRUE(skrifa_font->has_outline(55)); - EXPECT_EQ(FT_Load_Glyph(face->GetFTFaceForTesting(), 55, FT_LOAD_NO_SCALE), - 0); } TEST(FxSkrifaTest, TestTimesBoldGlyph104Bounds) { @@ -173,29 +164,24 @@ TEST(FxSkrifaTest, TestTimesBoldGlyph104Bounds) { std::vector bytes = GetFileContents(font_path.c_str()); ASSERT_FALSE(bytes.empty()); - CFX_Font font; - ASSERT_TRUE(font.LoadFaceZeroFromSpan(bytes, /*force_vertical=*/false, - /*object_tag=*/0)); - auto face = font.GetFace(); - ASSERT_TRUE(face); - - FT_Face ft_face = face->GetFTFaceForTesting(); - ASSERT_EQ(FT_Load_Glyph(ft_face, 104, FT_LOAD_NO_SCALE), 0); - FT_Glyph_Metrics ft_metrics = ft_face->glyph->metrics; - - int ft_x_min = ft_metrics.horiBearingX; - int ft_y_max = ft_metrics.horiBearingY; - int ft_x_max = ft_metrics.horiBearingX + ft_metrics.width; - int ft_y_min = ft_metrics.horiBearingY - ft_metrics.height; - auto skrifa_font = skrifa::new_font(rust::Slice(bytes), 0); ASSERT_TRUE(skrifa_font->is_ok()); skrifa::BoundingBox skrifa_bbox = skrifa_font->glyph_bounds(104); - EXPECT_EQ(ft_x_min, skrifa_bbox.x_min); - EXPECT_EQ(ft_y_min, skrifa_bbox.y_min); - EXPECT_EQ(ft_x_max, skrifa_bbox.x_max); - EXPECT_EQ(ft_y_max, skrifa_bbox.y_max); + EXPECT_EQ(skrifa_bbox.x_min, 48.0f); + EXPECT_EQ(skrifa_bbox.y_min, -32.0f); + EXPECT_EQ(skrifa_bbox.x_max, 1444.0f); + EXPECT_EQ(skrifa_bbox.y_max, 1756.0f); + + EXPECT_TRUE(skrifa_font->is_sfnt()); + EXPECT_FALSE(skrifa_font->is_italic()); + EXPECT_TRUE(skrifa_font->is_bold()); + skrifa::BoundingBox font_bbox; + EXPECT_TRUE(skrifa_font->get_font_bbox(font_bbox)); + EXPECT_EQ(font_bbox.x_min, -1143.0f); + EXPECT_EQ(font_bbox.y_min, -628.0f); + EXPECT_EQ(font_bbox.x_max, 4096.0f); + EXPECT_EQ(font_bbox.y_max, 2101.0f); } TEST(FxSkrifaTest, TestRobotoGlyph2344Bounds) { @@ -203,27 +189,99 @@ TEST(FxSkrifaTest, TestRobotoGlyph2344Bounds) { std::vector bytes = GetFileContents(font_path.c_str()); ASSERT_FALSE(bytes.empty()); - CFX_Font font; - ASSERT_TRUE(font.LoadFaceZeroFromSpan(bytes, /*force_vertical=*/false, - /*object_tag=*/0)); - auto face = font.GetFace(); + auto skrifa_font = skrifa::new_font(rust::Slice(bytes), 0); + ASSERT_TRUE(skrifa_font->is_ok()); + + skrifa::BoundingBox skrifa_bbox = skrifa_font->glyph_bounds(2344); + EXPECT_EQ(skrifa_bbox.x_min, -74.0f); + EXPECT_EQ(skrifa_bbox.y_min, 0.0f); + EXPECT_EQ(skrifa_bbox.x_max, 634.0f); + EXPECT_EQ(skrifa_bbox.y_max, 1838.0f); +} + +TEST(FxSkrifaTest, TestType1Face) { + EXPECT_EQ(UnicodeFromAdobeName("period"), L'.'); + EXPECT_EQ(UnicodeFromAdobeName("A"), L'A'); + EXPECT_EQ(AdobeNameFromUnicode(L'.'), "period"); + EXPECT_EQ(AdobeNameFromUnicode(L'A'), "A"); + + if (!CFX_GEModule::IsFontations()) { + return; + } + + auto stream = + pdfium::MakeRetain(kFoxitSansMMFontData); + auto face = CFX_Face::New(nullptr, stream, 0); ASSERT_TRUE(face); - FT_Face ft_face = face->GetFTFaceForTesting(); - ASSERT_EQ(FT_Load_Glyph(ft_face, 2344, FT_LOAD_NO_SCALE), 0); - FT_Glyph_Metrics ft_metrics = ft_face->glyph->metrics; + EXPECT_EQ(face->GetFontFormat(), "Type 1"); + EXPECT_EQ(face->GetFamilyName(), "Chrome Sans MM"); + EXPECT_EQ(face->GetPostscriptName(), "ChromeSansMM"); + EXPECT_EQ(face->GetGlyphCount(), 230); + EXPECT_EQ(face->GetUnitsPerEm(), 1000); + EXPECT_FALSE(face->IsTtOt()); + EXPECT_FALSE(face->IsItalic()); + EXPECT_FALSE(face->IsBold()); + EXPECT_NE(face->GetBBox(), FX_RECT(-1000, -1000, 1000, 1000)); + + EXPECT_TRUE(face->SelectCharMap(fxge::FontEncoding::kAdobeCustom)); + int gid_x = face->GetCharIndex(120); + EXPECT_EQ(gid_x, 103); + + EXPECT_TRUE(face->SelectCharMap(fxge::FontEncoding::kUnicode)); + EXPECT_EQ(face->GetCharIndex(L'x'), gid_x); + + std::unique_ptr path = + face->LoadGlyphPath(gid_x, 0, false, nullptr); + ASSERT_TRUE(path); + EXPECT_EQ(path->GetPoints().size(), 12u); + + auto glyph_bitmap = + face->RenderGlyph(gid_x, /*is_cid_font=*/false, /*is_vertical=*/false, + CFX_Matrix(12.0f, 0, 0, 12.0f, 0, 0), /*dest_width=*/0, + FontAntiAliasingMode::kNormal, /*subst_font=*/nullptr); + ASSERT_TRUE(glyph_bitmap); + EXPECT_TRUE(glyph_bitmap->GetBitmap()); + EXPECT_EQ(glyph_bitmap->GetBitmap()->GetWidth(), 6); + EXPECT_EQ(glyph_bitmap->GetBitmap()->GetHeight(), 7); + + auto glyph_lcd = + face->RenderGlyph(gid_x, /*is_cid_font=*/false, /*is_vertical=*/false, + CFX_Matrix(12.0f, 0, 0, 12.0f, 0, 0), /*dest_width=*/0, + FontAntiAliasingMode::kLcd, /*subst_font=*/nullptr); + ASSERT_TRUE(glyph_lcd); + EXPECT_TRUE(glyph_lcd->GetBitmap()); + EXPECT_EQ(glyph_lcd->GetBitmap()->GetWidth(), 24); + EXPECT_EQ(glyph_lcd->GetBitmap()->GetHeight(), 7); + + auto glyph_mono = + face->RenderGlyph(gid_x, /*is_cid_font=*/false, /*is_vertical=*/false, + CFX_Matrix(12.0f, 0, 0, 12.0f, 0, 0), /*dest_width=*/0, + FontAntiAliasingMode::kMono, /*subst_font=*/nullptr); + ASSERT_TRUE(glyph_mono); + EXPECT_TRUE(glyph_mono->GetBitmap()); + EXPECT_EQ(glyph_mono->GetBitmap()->GetWidth(), 6); + EXPECT_EQ(glyph_mono->GetBitmap()->GetHeight(), 7); +} - int ft_x_min = ft_metrics.horiBearingX; - int ft_y_max = ft_metrics.horiBearingY; - int ft_x_max = ft_metrics.horiBearingX + ft_metrics.width; - int ft_y_min = ft_metrics.horiBearingY - ft_metrics.height; +TEST(FxSkrifaTest, TestGetSfntTable) { + std::string font_path = PathService::GetTestFilePath("fonts/ahem/Ahem.ttf"); + std::vector bytes = GetFileContents(font_path.c_str()); - auto skrifa_font = skrifa::new_font(rust::Slice(bytes), 0); - ASSERT_TRUE(skrifa_font->is_ok()); + auto font = skrifa::new_font(rust::Slice(bytes), 0); + ASSERT_TRUE(font->is_ok()); - skrifa::BoundingBox skrifa_bbox = skrifa_font->glyph_bounds(2344); - EXPECT_EQ(ft_x_min, skrifa_bbox.x_min); - EXPECT_EQ(ft_y_min, skrifa_bbox.y_min); - EXPECT_EQ(ft_x_max, skrifa_bbox.x_max); - EXPECT_EQ(ft_y_max, skrifa_bbox.y_max); + // 'head' table (0x68656164) is 54 bytes in TrueType fonts. + constexpr uint32_t kHeadTag = 0x68656164; + size_t length = font->get_sfnt_table(kHeadTag, rust::Slice()); + ASSERT_EQ(54u, length); + + std::vector head_data(length); + size_t read_bytes = + font->get_sfnt_table(kHeadTag, rust::Slice(head_data)); + EXPECT_EQ(54u, read_bytes); + + // Missing table tag returns 0. + constexpr uint32_t kMissingTag = 0x78797a77; + EXPECT_EQ(0u, font->get_sfnt_table(kMissingTag, rust::Slice())); } diff --git a/core/fxge/skrifa/src/main.rs b/core/fxge/skrifa/src/main.rs index d71999984f..51591d8d46 100644 --- a/core/fxge/skrifa/src/main.rs +++ b/core/fxge/skrifa/src/main.rs @@ -15,13 +15,15 @@ use read_fonts::{ string::Sid, type1::Type1Font, }, - types::GlyphId, + tables::{head::MacStyle, os2::SelectionFlags}, + types::{Fixed, GlyphId, Tag}, + TableProvider, }; use skrifa::{ charmap::Charmap, instance::{LocationRef, Size}, metrics::Metrics, - outline::OutlineGlyphFormat, + outline::{DrawSettings, Engine, HintingInstance, HintingOptions, OutlineGlyphFormat}, string::StringId, FontRef, GlyphNameSource, GlyphNames, MetadataProvider, OutlineGlyphCollection, }; @@ -124,6 +126,9 @@ mod skrifa_ffi { fn is_fixed_pitch(&self) -> bool; fn is_tricky(&self) -> bool; fn is_scalable(&self) -> bool; + fn is_sfnt(&self) -> bool; + fn is_italic(&self) -> bool; + fn is_bold(&self) -> bool; fn is_cid(&self) -> bool; fn cid_to_gid(&self, cid: u16) -> u32; fn unicode_to_gid(&self, unicode: u32) -> u32; @@ -133,6 +138,13 @@ mod skrifa_ffi { fn glyph_name(&self, gid: u32) -> String; fn scaled_outline(&self, gid: u32, ppem: f32, outline: &mut Outline) -> bool; fn unscaled_outline(&self, gid: u32, outline: &mut Outline) -> bool; + fn hinted_outline( + &self, + gid: u32, + ppem: f32, + is_pedantic: bool, + outline: &mut Outline, + ) -> bool; fn has_outline(&self, gid: u32) -> bool; fn get_os2_code_page_range(&self, range: &mut CodePageRange) -> bool; @@ -142,9 +154,11 @@ mod skrifa_ffi { fn get_char_codes_and_indices(&self, max_char: u32) -> Vec; fn name_index(&self, name: &str) -> u32; fn glyph_bounds(&self, glyph_index: u32) -> BoundingBox; + fn get_font_bbox(&self, bbox: &mut BoundingBox) -> bool; + fn get_sfnt_table(&self, table: u32, buffer: &mut [u8]) -> usize; fn agl_name_to_unicode(name: &str, unicode: &mut u32) -> bool; - fn agl_unicode_to_name(unicode: u32, name: &mut [u8]) -> bool; + fn agl_unicode_to_name(unicode: u32, name: &mut [u8]) -> &[u8]; fn get_num_faces(data: &[u8]) -> u32; } @@ -168,6 +182,11 @@ pub enum SkrifaFont<'a> { Error, } +struct HinterCache { + size: Size, + instance: Option, +} + pub struct Sfnt<'a> { font: FontRef<'a>, metrics: Metrics, @@ -178,6 +197,7 @@ pub struct Sfnt<'a> { charmap: Charmap<'a>, outlines: OutlineGlyphCollection<'a>, is_tricky: bool, + hinter: core::cell::RefCell>, } impl<'a> Sfnt<'a> { @@ -204,6 +224,7 @@ impl<'a> Sfnt<'a> { charmap, outlines, is_tricky, + hinter: core::cell::RefCell::new(None), }) } } @@ -446,6 +467,22 @@ impl SkrifaFont<'_> { } } + fn hinted_outline( + &self, + gid: u32, + ppem: f32, + is_pedantic: bool, + outline: &mut Outline, + ) -> bool { + outline.clear(); + if let Some(width) = self.hinted_outline_impl(gid, ppem, is_pedantic, outline) { + outline.advance_width = width.unwrap_or_default(); + true + } else { + false + } + } + fn has_outline(&self, gid: u32) -> bool { match self { Self::Sfnt(sfnt) => sfnt.outlines.get(GlyphId::new(gid)).is_some(), @@ -493,6 +530,42 @@ impl SkrifaFont<'_> { Self::Error => None, } } + + fn hinted_outline_impl( + &self, + gid: u32, + ppem: f32, + is_pedantic: bool, + outline: &mut impl OutlinePen, + ) -> Option> { + let Self::Sfnt(sfnt) = self else { + return None; + }; + if !sfnt.outlines.prefer_interpreter() { + return self.outline_impl(gid, Some(ppem), outline); + } + let gid = GlyphId::new(gid); + let size = Size::new(ppem); + let glyph = sfnt.outlines.get(gid)?; + let mut hinter_borrow = sfnt.hinter.borrow_mut(); + if hinter_borrow.as_ref().map(|c| c.size) != Some(size) { + let instance = HintingInstance::new( + &sfnt.outlines, + size, + LocationRef::default(), + HintingOptions { engine: Engine::Interpreter, target: Default::default() }, + ) + .ok(); + *hinter_borrow = Some(HinterCache { size, instance }); + } + let hinter = hinter_borrow.as_ref()?.instance.as_ref()?; + let metrics = glyph.draw(DrawSettings::hinted(hinter, is_pedantic), outline).ok()?; + Some( + metrics.advance_width.or_else(|| { + sfnt.font.glyph_metrics(size, LocationRef::default()).advance_width(gid) + }), + ) + } } impl Point { @@ -610,8 +683,8 @@ fn agl_name_to_unicode(name: &str, unicode: &mut u32) -> bool { } } -fn agl_unicode_to_name(unicode: u32, name: &mut [u8]) -> bool { - read_fonts::ps::agl::char_to_name(unicode, name).is_some() +fn agl_unicode_to_name(unicode: u32, name: &mut [u8]) -> &[u8] { + read_fonts::ps::agl::char_to_name(unicode, name).map(str::as_bytes).unwrap_or_default() } impl SkrifaFont<'_> { @@ -631,6 +704,54 @@ impl SkrifaFont<'_> { self.font_type() != FaceFormat::Unknown } + fn is_sfnt(&self) -> bool { + matches!(self, Self::Sfnt(_)) + } + + fn is_italic(&self) -> bool { + match self { + Self::Sfnt(sfnt) => { + if let Ok(os2) = sfnt.font.os2() { + let flags = os2.fs_selection(); + return flags.contains(SelectionFlags::ITALIC) + || flags.contains(SelectionFlags::OBLIQUE); + } + if let Ok(head) = sfnt.font.head() { + return head.mac_style().contains(MacStyle::ITALIC); + } + false + } + Self::Type1(type1) => type1.italic_angle() != 0, + Self::Cff(cff) => { + cff.meta.as_ref().is_some_and(|meta| meta.italic_angle() != Fixed::ZERO) + } + Self::Error => false, + } + } + + fn is_bold(&self) -> bool { + match self { + Self::Sfnt(sfnt) => { + if let Ok(os2) = sfnt.font.os2() { + return os2.fs_selection().contains(SelectionFlags::BOLD); + } + if let Ok(head) = sfnt.font.head() { + return head.mac_style().contains(MacStyle::BOLD); + } + false + } + Self::Type1(type1) => type1 + .weight() + .is_some_and(|w| w.eq_ignore_ascii_case("bold") || w.eq_ignore_ascii_case("black")), + Self::Cff(cff) => cff.meta.as_ref().is_some_and(|meta| { + meta.weight().is_some_and(|w| { + w.eq_ignore_ascii_case("bold") || w.eq_ignore_ascii_case("black") + }) + }), + Self::Error => false, + } + } + fn get_os2_unicode_range(&self, range: &mut UnicodeRange) -> bool { let Self::Sfnt(sfnt) = self else { return false; @@ -677,6 +798,73 @@ impl SkrifaFont<'_> { self.get_glyph_bounds(glyph_index) } + fn get_font_bbox(&self, bbox: &mut BoundingBox) -> bool { + let opt_box = match self { + Self::Sfnt(sfnt) => { + if let Some(bounds) = sfnt.metrics.bounds { + Some(BoundingBox { + x_min: bounds.x_min, + y_min: bounds.y_min, + x_max: bounds.x_max, + y_max: bounds.y_max, + }) + } else if let Ok(head) = sfnt.font.head() { + Some(BoundingBox { + x_min: head.x_min() as f32, + y_min: head.y_min() as f32, + x_max: head.x_max() as f32, + y_max: head.y_max() as f32, + }) + } else { + None + } + } + Self::Type1(type1) => { + let b = type1.bbox(); + Some(BoundingBox { + x_min: b.x_min.to_f32(), + y_min: b.y_min.to_f32(), + x_max: b.x_max.to_f32(), + y_max: b.y_max.to_f32(), + }) + } + Self::Cff(cff) => cff.meta.as_ref().map(|meta| { + let b = meta.bbox(); + BoundingBox { + x_min: b.x_min.to_f32(), + y_min: b.y_min.to_f32(), + x_max: b.x_max.to_f32(), + y_max: b.y_max.to_f32(), + } + }), + Self::Error => None, + }; + + if let Some(b) = opt_box { + *bbox = b; + true + } else { + false + } + } + + fn get_sfnt_table(&self, table: u32, buffer: &mut [u8]) -> usize { + let Some(data) = (match self { + Self::Sfnt(sfnt) => sfnt.font.table_data(Tag::from_u32(table)), + _ => None, + }) else { + return 0; + }; + if buffer.is_empty() { + return data.len(); + } + if buffer.len() != data.len() { + return 0; + } + buffer.copy_from_slice(data.as_bytes()); + buffer.len() + } + fn is_fixed_pitch(&self) -> bool { match self { Self::Sfnt(sfnt) => sfnt.metrics.is_monospace, diff --git a/core/fxge/skrifa/src/outlines.cpp b/core/fxge/skrifa/src/outlines.cpp index d52e2417fa..a5b475f8c2 100644 --- a/core/fxge/skrifa/src/outlines.cpp +++ b/core/fxge/skrifa/src/outlines.cpp @@ -76,10 +76,10 @@ void skrifa::run(rust::Str font_path) { // Convert unicode to glyph name uint8_t period_name[64] = {}; - assert( - skrifa::agl_unicode_to_name('.', rust::Slice(period_name, 40))); - assert(std::string_view(reinterpret_cast(&period_name[0])) == - "period"); + auto period_slice = + skrifa::agl_unicode_to_name('.', rust::Slice(period_name, 40)); + assert(std::string_view(reinterpret_cast(period_slice.data()), + period_slice.size()) == "period"); } void dump_outline(skrifa::Outline& outline) { diff --git a/core/fxge/text_glyph_pos.cpp b/core/fxge/text_glyph_pos.cpp index 20b3a08830..9275cc28d1 100644 --- a/core/fxge/text_glyph_pos.cpp +++ b/core/fxge/text_glyph_pos.cpp @@ -18,14 +18,14 @@ TextGlyphPos::~TextGlyphPos() = default; std::optional TextGlyphPos::GetOrigin( const CFX_Point& offset) const { FX_SAFE_INT32 left = origin_.x; - left += glyph_->left(); + left += glyph_->top_left_origin().x; left -= offset.x; if (!left.IsValid()) { return std::nullopt; } FX_SAFE_INT32 top = origin_.y; - top -= glyph_->top(); + top -= glyph_->top_left_origin().y; top -= offset.y; if (!top.IsValid()) { return std::nullopt; diff --git a/core/fxge/win32/cfx_psrenderer.cpp b/core/fxge/win32/cfx_psrenderer.cpp index b8710e50ed..ccd63f4789 100644 --- a/core/fxge/win32/cfx_psrenderer.cpp +++ b/core/fxge/win32/cfx_psrenderer.cpp @@ -17,6 +17,7 @@ #include "core/fxcrt/bytestring.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_extension.h" #include "core/fxcrt/fx_memcpy_wrappers.h" #include "core/fxcrt/fx_memory.h" @@ -95,8 +96,7 @@ ByteString GenerateType42FontDictionary(const ByteString& psname, CHECK_GT(glyphs_per_descendant_font, 0u); const size_t descendant_font_count = - (num_glyphs + glyphs_per_descendant_font - 1) / - glyphs_per_descendant_font; + fxcrt::CeilDiv(num_glyphs, glyphs_per_descendant_font); fxcrt::ostringstream output; for (size_t i = 0; i < descendant_font_count; ++i) { diff --git a/core/fxge/win32/cfx_psrenderer_unittest.cpp b/core/fxge/win32/cfx_psrenderer_unittest.cpp index b74d1a7a7d..cc5c148d7b 100644 --- a/core/fxge/win32/cfx_psrenderer_unittest.cpp +++ b/core/fxge/win32/cfx_psrenderer_unittest.cpp @@ -13,7 +13,9 @@ #include "core/fxcrt/fx_stream.h" #include "core/fxcrt/retain_ptr.h" #include "core/fxcrt/span.h" +#include "core/fxge/cfx_fillrenderoptions.h" #include "core/fxge/cfx_gemodule.h" +#include "core/fxge/cfx_path.h" #include "core/fxge/dib/cfx_dibitmap.h" #include "core/fxge/dib/fx_dib.h" #include "core/fxge/win32/cfx_psfonttracker.h" @@ -214,3 +216,25 @@ restore auto output = ByteString(ByteStringView(output_stream->GetSpan())); EXPECT_EQ(output, kExpectedOutput); } + +TEST(PSRendererTest, SetClipPathFill) { + auto output_stream = pdfium::MakeRetain(); + CFX_PSFontTracker font_tracker; + CFX_PSRenderer renderer(&font_tracker, /*encoder_iface=*/nullptr); + renderer.Init(output_stream, CFX_PSRenderer::RenderingLevel::kLevel2, + /*width=*/500, /*height=*/500); + + CFX_Path path; + path.AppendRect(20.2f, 40.4f, 100.6f, 200.8f); + renderer.SetClip_PathFill(path, /*matrix=*/nullptr, + CFX_FillRenderOptions::WindingOptions()); + // TODO(crbug.com/553140224): Clip box should be FX_RECT(20, 40, 101, 201). + EXPECT_EQ(renderer.GetClipBox(), FX_RECT(20, 241, 120, 40)); + + CFX_Path path2; + path2.AppendRect(50.1f, 80.3f, 150.7f, 250.9f); + renderer.SetClip_PathFill(path2, /*matrix=*/nullptr, + CFX_FillRenderOptions::WindingOptions()); + // TODO(crbug.com/553140224): Clip box should be FX_RECT(50, 80, 101, 201). + EXPECT_EQ(renderer.GetClipBox(), FX_RECT(50, 331, 200, 80)); +} diff --git a/core/fxge/win32/cgdi_device_driver.cpp b/core/fxge/win32/cgdi_device_driver.cpp index 2f950d579b..0ac0c5cb11 100644 --- a/core/fxge/win32/cgdi_device_driver.cpp +++ b/core/fxge/win32/cgdi_device_driver.cpp @@ -337,7 +337,7 @@ unsigned LineClip(float w, float* x, float* y) { #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { // TODO(caryclark) temporary replacement of antigrain in line function to // permit removing antigrain altogether rect_base rect = {0.0f, 0.0f, w, h}; diff --git a/docs/embedpdf/syncs/2026-10-13-3c9748bd6.md b/docs/embedpdf/syncs/2026-10-13-3c9748bd6.md new file mode 100644 index 0000000000..7cd79e809c --- /dev/null +++ b/docs/embedpdf/syncs/2026-10-13-3c9748bd6.md @@ -0,0 +1,141 @@ +# Sync 2026-10-13: upstream cfe3a2413..3c9748bd6 (154 commits) + +## Summary + +Upstream from 2026-09-01 to 2026-09-30, merged into `1a16a6e98`. + +Upstream moved the content-stream writer into `fxcrt`, made its own JPEG decoder repair malformed +input in a private copy (as our S7 fix did), sped up 1-bit image stretching and changed how 1-bit +images stretch to color, reworked right-to-left `/ActualText` extraction again, and added XFA +per-document heaps (not built for us). We took upstream's JPEG and 1-bit code and dropped ours, +and kept Acrobat-style right-to-left `/ActualText` in logical order with a small fix. + +## Likely security fixes + +- `8ca5b735d` 2026-09-25 Rank matching fuzzy suppressions by predicate specificity +- `da9d8c430` 2026-09-22 Fix form fill environment lifecycle crashes in XFA +- `1442e4fe0` 2026-09-18 Add disabled embeddertests for form fill environment lifecycle crashes +- `a5e00c61c` 2026-09-18 Remove the pdfium_diff --fuzzy option and its tolerance parameters +- `4ef1e1b47` 2026-09-15 Use a standalone cppgc heap for the XFA fuzzer process state +- `9bd9ac2ea` 2026-09-14 Plumb FXGC_Heap through XFA in place of cppgc::Heap +- `88c60350e` 2026-09-11 Add FXGC_Heap wrapper and CppHeap support for per-document heap +- `5f5ad2c21` 2026-09-02 Roll back blanket fuzziness in light of better image comparison tools + +None touches code we build: `da9d8c430`, `4ef1e1b47`, `9bd9ac2ea` and `88c60350e` are XFA (off for +us), `1442e4fe0` adds disabled tests, and the rest change upstream's pixel-comparison tooling. + +## Conflicts (8 files, 9 blocks) + +A block with an `EPDF_TLS` count is never resolved by taking upstream's side without putting `EPDF_TLS` back (rule thread-confined-globals). + +| File | Blocks | Rules in the blocks | EPDF_TLS | Upstream commits | Resolution | +| :-- | --: | :-- | --: | :-- | :-- | +| `core/fpdfapi/edit/BUILD.gn` | 1 | untagged | | `ac60fa3da` Move core/fpdfapi/edit/cpdf_contentstream_write_utils.cpp to fxcrt. | Upstream moved the content-stream writer and its unit test to `fxcrt` (`ac60fa3da`); our unit tests stay. Our 12 files that included `cpdf_contentstream_write_utils.h` now include `core/fxcrt/stream_write_utils.h` (same functions). | +| `core/fpdfapi/parser/cpdf_number.cpp` | 1 | untagged | | `ac60fa3da` Move core/fpdfapi/edit/cpdf_contentstream_write_utils.cpp to fxcrt. | Upstream's new include (`stream_write_utils.h`) with ours (`cpdf_write_generation.h`, `check.h`). | +| `core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp` | 2 | reads-dont-write | | `f90e92e69` Add fxcrt::ToDataVector() helper and adopt it across codebase; `4150fb564` Avoid const_cast when repairing malformed JPEG input data; `f36ba935e` Add and use fxcrt::CeilDiv(); +1 more | Upstream's: `4150fb564` repairs a malformed header or trailer in a private copy, the same fix we made in S7. Ours (copy, member, comments) is dropped (rule bug-fix); our guard test `JpegModuleTest.DecodeWithoutTrailerLeavesSourceAlone` stays. | +| `core/fxge/cfx_face.cpp` | 1 | substitute-font-bounds | | `8a3994383` Implement CFX_Face::GetSfntTable() via Skrifa; `18e2d3552` Add Fontations branch in CFX_Face::GetCharBBox(); `33395f55b` Add Fontations branches to four CFX_Face metadata getters; +10 more | Upstream's Fontations branch and null check in `GetGlyphWidth()`, then our scoped variation (substitute-font-bounds). | +| `core/fxge/dib/cstretchengine.cpp` | 1 | render-speedup | | `9108f0522` Remove npm@ from OWNERS and assign TODOs to bugs; `f36ba935e` Add and use fxcrt::CeilDiv(); `3f86f877e` Expand 1bpp rows to bytes before stretching them horizontally | Upstream's: 1-bit rows are expanded to bytes and stretched by the 8-bit loops (`3f86f877e`, measured 5–20% faster), and 1-bit-to-color now produces real BGR pixels through the palette. Our 1-bit run classifier wrote one gray byte per pixel, which no longer fits the color case, so it is dropped with `ClassifyBits()`; our opaque 3/4-byte row loop stays (render-speedup). | +| `fpdfsdk/cpdfsdk_formfillenvironment.cpp` | 1 | reads-dont-write | | `da9d8c430` Fix form fill environment lifecycle crashes in XFA; `d793a0ddc` Create a per-document v8::Isolate for XFA documents; `4a3662822` Ignore re-entrant form calls during passive FFI callbacks | Ours: the JavaScript name tree read through `CreateForReading()` (reads-dont-write), with upstream's `cpdfdoc_.Get()`. | +| `fpdfsdk/fpdf_attachment.cpp` | 1 | epdf-api | | `f90e92e69` Add fxcrt::ToDataVector() helper and adopt it across codebase; `93466ec40` Add FPDFAttachment_GetAFRelationship() API | Both: upstream's new `FPDFAttachment_GetAFRelationship()` (`93466ec40`), then our EPDF attachment functions. | +| `fpdfsdk/fpdf_save_embeddertest.cpp` | 1 | untagged | | `6c8196a14` Make later ToUnicode mappings supersede earlier ones | Ours: the subset save compared with the same save without subsetting (our full save serializes differently); upstream's new exact byte count dropped. | + +## Write audit (4 lines) + +Verdicts: `fresh` (created right there), `door` (through the copy-up door or its owner), `fixed` (broke a rule; fixed in this sync, with a tag), `not-built`. + +| File:line | What | Verdict | Why | +| :-- | :-- | :-- | :-- | +| `core/fxcodec/jpeg/jpegmodule.cpp:169` | const_cast | fresh | Not a PDF object: a bitmap scanline handed to libjpeg's encoder, which only reads it (libjpeg's API takes it non-const). Upstream's error handling moved the line (`0edc2c514`). | +| `core/fxge/skia/fx_skia_device.cpp:785` | GetOrCreateSkTypeface | not-built | Skia device. | +| `fpdfsdk/fpdf_attachment.cpp:320` | NewIndirect | fresh | `FPDFAttachment_SetFile()` creates the file stream right there; only `ToDataVector()` changed (`f90e92e69`). The file spec it links into is written through our `WritableAttachment()` guard, as before. | +| `fpdfsdk/fpdfxfa/cpdfxfa_context.cpp:127` | GetOrCreate | not-built | XFA. | + +## Upstream changes outside the conflicts + +- **Right-to-left `/ActualText`, again (`58d357aac`, `17cf94b52`).** Upstream now reverses an RTL + `/ActualText` run in `ProcessTransformedTextObjects()` when its glyphs are RTL or it follows RTL + text, and reverted S6's "mixed-direction line" change. That fixes literal RTL text followed by + `/ActualText`, but a run that *starts* its RTL segment is still passed forward by + `CloseTempLine()`, so reversing it first turned it back to front: exactly what Acrobat writes + around each shaped Arabic or Hebrew run (`EPDFAnnotEmbedderTest.RichTextRtlRunCarriesActualTextAndExtracts` + failed). Upstream's fixture covers Latin glyphs, so its test missed it. Our fix (bug-fix, + tagged): reverse only after literal RTL text, found by looking back past spaces to the last + character with a strong direction. Upstream's `ActualTextRtl` and `Arabic` tests still pass. +- **Content-stream writer moved to `core/fxcrt/stream_write_utils.h` (`ac60fa3da`).** +- **API changes in our tests:** `JpegModule::CreateDecoder()` takes `prefer_bgr_output` + (`32d5e1d51`); `CompareBitmapWithFuzzyExpectationSuffix()` became `kFuzzyDiffOptions` + (`a4f9c3c5d`). +- **New global:** `g_isolate_per_document` (`49026973e`, library config version 7). Library + start-up and shutdown run on every worker thread (per-thread-init), and shutdown writes it, + so it is `EPDF_TLS` now (tagged), on the allowlist as thread-local. +- **XFA per-document heaps and isolates** (`88c60350e`, `9bd9ac2ea`, `d793a0ddc`): not built. +- **New APIs:** `FPDFAttachment_GetAFRelationship()`, `FPDFPageObj_GetRenderedFillPattern()`, + `FPDFPageObj_GetRenderedStrokePattern()`, `FPDF_StructElement_GetRawType()`. +- **Windows SDK:** the same `build/` revision as S7, so S7's SDK fix is carried here too. + +## Upstream appearance generator + +Our generator is separate (upstream-sync plan §6.2): port each commit or skip it. + +| Commit | Subject | Port or skip | Why | +| :-- | :-- | :-- | :-- | +| `ac60fa3da` | Move core/fpdfapi/edit/cpdf_contentstream_write_utils.cpp to fxcrt. | port | Needed to build: our generator files include the writer from its new home, `core/fxcrt/stream_write_utils.h`; the functions are the same. | + +## Globals + +None by the heuristic. `check-globals.py` on the darwin-arm64 release build found +`g_isolate_per_document`, now thread-local (above); then ok: 255 shared writable, 25 thread-local. + +## Build and patches + +- `DEPS` changed in 15 commits; synced with `gclient sync --no-history --shallow`. The `build/` + revision is S7's. +- `patches/embedpdf-runtime/win/build.patch` and the workflow carry S7's Windows SDK fix. +- All of `patches/embedpdf-runtime/` apply (the `build/` revision is S7's, where every target's + patch was dry-run); darwin-arm64 and wasm32 applied and built. + +Build arguments added or changed: +- `pdfium.gni:106`: `"pdf_enable_fontations=true requires enable_rust=true and enable_rust_cxx=true")` +- `pdfium.gni:114`: `"pdf_enable_rust_jpeg=true requires enable_rust=true and enable_rust_cxx=true")` + +Messages only; Fontations and the Rust decoders stay off in our builds. + +## API diff + +- added `FPDFAttachment_GetAFRelationship` +- added `FPDFPageObj_GetRenderedFillPattern` +- added `FPDFPageObj_GetRenderedStrokePattern` +- added `FPDF_StructElement_GetRawType` + +The darwin-arm64 exports gain exactly these four. + +## Tests + +- [x] fork unit + embedder tests (darwin-arm64): 1259 unit tests pass. Embedder: 1607 tests, + 9 skipped; the failures are S7's 80. Linux runs in the PR's CI. +- [x] layer mode: exit 0; 203 layer-only failures, all expected (S7: 202): one new upstream + form-fill keyboard test (layer mode never drives the form-fill environment). Writers + unchanged. +- [x] tag check (`--base 3c9748bd6`): 965 blocks, 0 untagged. +- [x] C API check: ok, 521 functions in 22 headers. +- [x] globals check: ok after `g_isolate_per_document` became thread-local. +- [ ] wasm build: in the PR's CI (builds locally). +- [ ] ThreadSanitizer soak: in the PR's CI. +- [x] engine tests and engine-conformance with the local runtime (Oct 13): the same failures as + S7's runtime, `@embedpdf/engine` 34 of 1616 and `@cloudpdf/engine` 20 of 937; against fork main + before S1 that is the baseline plus S7's expected annotation-export hash change (see S7's + report). `test:redaction-perf` 10 of 10 as before; licensing passes. Bindings: only the four + new functions are added. +- changed expectations, each with its reason: + - `EPDFMeasureEmbedderTest` caption render: `kFuzzyDiffOptions`, upstream's replacement for + the fuzzy comparison (`a4f9c3c5d`); its tolerance now also checks windows. + +## Ledger changes proposed + +For review; none applied. + +1. **render-speedup, Where:** our only stretch loop left is the opaque 3/4-byte horizontal row; + the 1-bit classifier is gone (upstream's `3f86f877e`). S7's proposal to measure that row + against upstream's loop stands. +2. **Report upstream:** RTL `/ActualText` that starts its segment extracts backwards after + `58d357aac` (Acrobat's shaped Arabic and Hebrew runs). Our fix and test show the case. diff --git a/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md b/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md index b21b781799..e131315084 100644 --- a/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md +++ b/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md @@ -83,6 +83,14 @@ darwin-arm64 release build: ok, 255 shared writable (S6: 254), 24 thread-local ( - `DEPS` changed in 45 commits; synced with `gclient sync --no-history --shallow`. - `skia/BUILD.gn`: `SkLogHandler.cpp` added to `redaction_pathops`. +- Windows SDK: the rolled `build/` pins SDK 10.0.28000.0 (`06f91b7a8` in `build/`), which the + GitHub Windows runners don't have, so both Windows builds failed in this PR's CI (run + 38320172945). `patches/embedpdf-runtime/win/build.patch` now reads the SDK version from + `EMBEDPDF_WIN_SDK_VERSION` in `vs_toolchain.py` and `toolchain/win/setup_toolchain.py`, + falling back to upstream's, and the workflow sets it to the newest SDK the runner has. With + that SDK (10.0.26100.0) the build then stopped in the SDK's own headers: the rolled + `config/win/BUILD.gn` targets `NTDDI_WIN11_BR`, which only SDK 10.0.28000 defines. The patch + keeps `NTDDI_WIN11_GE`, what S6's `build/` targeted; we use no newer Windows API. - All of `patches/embedpdf-runtime/` apply: every target's patch dry-run against clean copies of the rolled files (offsets or fuzz only); darwin-arm64 and wasm32 applied and built. @@ -111,6 +119,9 @@ The darwin-arm64 exports gain exactly these two. - [x] tag check (`--base cfe3a2413`): 966 blocks, 0 untagged. - [x] C API check: ok, 521 functions in 22 headers. - [x] globals check: ok (see Globals). +- [x] CI (run 38320172945, this branch at `1a16a6e98`): source tests pass, and so do the + darwin, linux, linuxmusl-x64 and wasm32 builds (linuxmusl-arm64 was still running). Both + Windows builds failed on the Windows SDK pin (above), fixed in a follow-up commit. - [ ] wasm build: in the PR's CI (builds locally). - [ ] ThreadSanitizer soak: in the PR's CI. - [x] engine tests and engine-conformance with the local runtime (Oct 13): as before, against diff --git a/docs/frankenfonts.md b/docs/frankenfonts.md new file mode 100644 index 0000000000..aa2fee8c05 --- /dev/null +++ b/docs/frankenfonts.md @@ -0,0 +1,136 @@ +# Project Proposal: Frankenfonts — Hybrid Rust Font Parsing & FreeType/AGG Rasterization + +**Author:** Tom Sepez (`tsepez@google.com`) +**Target Project:** PDFium (`core/fxge`, standalone, and Chromium integration) +**Status:** Proposed Architecture + +--- + +## 1. Executive Summary + +PDFium historically relies on **FreeType** for both binary font table parsing +and glyph pixel rasterization. However, the overwhelming majority of historical +vulnerabilities in font engines stem from **parsing untrusted, malformed font +tables** (such as embedded TrueType, OpenType, and CFF streams in malicious +PDFs). + +**Project Frankenfonts** proposes a hybrid font architecture: +1. **Frontend (Parsing & Hinting):** Use Google's memory-safe **Fontations** + (`read-fonts` + `skrifa`) Rust crates to parse font tables, decode CMaps, + compute metrics, and execute TrueType VM / CFF hinting instructions in safe + Rust. +2. **Backend (Rasterization):** Pass the extracted vector outlines directly + into **FreeType**'s scanline rasterizer (`FT_Render_Glyph` in `ftsmooth.c` / + `ftgrays.c`) or PDFium's **AGG** rasterizer to generate anti-aliased bitmap + masks. +3. **Decoupling from Skia:** Remove the `pdf_use_skia` requirement from + `pdf_enable_fontations`, allowing standalone PDFium (including AGG builds) + to benefit from memory-safe font parsing. + +--- + +## 2. Motivation: The FreeType Dilemma + +### 2.1. The Vulnerability Surface of Embedded PDF Fonts +PDF files frequently embed custom, subsetted, or heavily modified font files. +Over the last two decades, embedded font parsing in FreeType has been one of the +primary attack vectors for remote code execution and memory corruption in +document viewers (integer overflows in table offsets, malformed CMap subtables, +corrupt CFF charstrings). + +### 2.2. Parsing vs. Rasterization +FreeType performs two fundamentally distinct tasks: +* **Task A: Binary Font Parsing & Hinting (High Risk):** Reading raw bytes from + disk/memory into C structs (`CMap`, `Hmtx`, `OS/2`, `Name`, `Post`, `glyf`, + `CFF`) and running Turing-complete bytecode interpreters in C. +* **Task B: Scanline Rasterization (Low Risk / High Fidelity):** Taking + mathematical Bézier curves and generating anti-aliased 8bpp grayscale pixel + masks (`ftgrays.c`) or subpixel LCD masks (`ftsmooth.c`). + +FreeType's rasterization math is battle-tested and produces the exact +anti-aliasing curves PDFium expects. The risk is located in **Task A +(Parsing)**. + +--- + +## 3. The Frankenfonts Architecture + +``` + ┌──────────────────────────────────────────────┐ + │ Untrusted PDF Font Data │ + └──────────────────────┬───────────────────────┘ + │ + ▼ + ┌──────────────────────────────────────────────┐ + │ Fontations Frontend (Pure Safe Rust) │ + │ - `read-fonts`: Safe Table Parsing │ + │ - `skrifa`: CMaps, Metrics, BBoxes │ + │ - `HintingInstance`: TrueType / CFF VM │ + │ - `OutlinePen`: Bézier Curve Extraction │ + └──────────────────────┬───────────────────────┘ + │ + ▼ Extracted `FT_Outline` / `CFX_Path` + ┌──────────────────────────────────────────────┐ + │ Rasterization Backend (FreeType / AGG) │ + │ - Feeds safe Bézier curves into rasterizer│ + │ - FreeType `FT_Render_Glyph` or AGG AA │ + └──────────────────────┬───────────────────────┘ + │ + ▼ + ┌──────────────────────────────────────────────┐ + │ CFX_GlyphBitmap │ + │ (Anti-Aliased Mask) │ + └──────────────────────────────────────────────┘ +``` + +--- + +## 4. Key Architectural Benefits + +### 4.1. Complete Elimination of Font Parsing CVEs +Because `read-fonts` and `skrifa` are written in 100% memory-safe Rust with +strict bounds-checking, zero-copy slicing, and fuzz-tested arithmetic, PDFium +becomes completely immune to font-table parsing vulnerabilities. FreeType is +never exposed to raw untrusted font byte streams. + +### 4.2. High Fidelity & Perceptual Parity +By feeding Skrifa's extracted vector outlines into FreeType's existing +rasterizer (`ftsmooth.c` / `ftgrays.c`), the resulting bitmap masks maintain visual fidelity +with legacy FreeType output. Subpixel hinting variances inherent to independent +TrueType VM implementations are handled via windowed MSE fuzzy test matching +and expectation suffixes. + +### 4.3. Full Standalone Independence (No Skia Required) +Currently, `pdf_enable_fontations` is artificially gated on `pdf_use_skia = +true`. Frankenfonts decouples Fontations from Skia: +* `skrifa` and `read_fonts` are vendored directly via `gnrt` in + `//third_party/rust/`. +* Standalone PDFium (`pdf_use_agg = true`, `pdf_use_skia = false`) can use + Fontations with zero dependencies on Skia. + +--- + +## 5. Implementation Roadmap + +### Phase 1: Decouple GN Flags & Public API +* Updated `pdfium.gni` to assert that `pdf_enable_fontations` only requires + `enable_rust = true`. +* Defined `PDF_ENABLE_FONTATIONS` in `BUILD.gn` whenever + `pdf_enable_fontations` is enabled. +* Exposed `CFX_FontMgr::GetFontBackend()` and `FPDF_InitLibraryWithConfig()` + runtime selection in AGG builds. +* Wired `--fontations` CLI flag into `pdfium_test`, `pdfium_embeddertests`, + and `pdfium_unittests`. + +### Phase 2: Route Font Table & Outline Queries to `skrifa` +* Implement `hinted_outline()` in `main.rs` using `skrifa::outline::HintingInstance`. +* Connect `CFX_Face::RenderGlyph()` to extract outlines through Skrifa at + the target display PPEM scale and rasterize through `FT_Render_Glyph()`. + +--- + +## 6. Conclusion + +Project Frankenfonts delivers **uncompromising memory safety against hostile +font exploits in untrusted PDFs** paired with **FreeType scanline +rasterization**, keeping standalone PDFium lean, fast, and secure. diff --git a/fpdfsdk/BUILD.gn b/fpdfsdk/BUILD.gn index ccb04aa306..d43d16a075 100644 --- a/fpdfsdk/BUILD.gn +++ b/fpdfsdk/BUILD.gn @@ -131,7 +131,6 @@ source_set("fpdfsdk") { "../constants", "../core/fdrm", "../core/fpdfapi/edit", - "../core/fpdfapi/edit:contentstream_write_utils", "../core/fpdfapi/font", "../core/fpdfapi/page", "../core/fpdfapi/parser", diff --git a/fpdfsdk/DEPS b/fpdfsdk/DEPS index 1e52515807..7c77a7e523 100644 --- a/fpdfsdk/DEPS +++ b/fpdfsdk/DEPS @@ -2,7 +2,6 @@ include_rules = [ '+core', '+fxjs', '+public', - '+v8', '+xfa/fgas/font', '+xfa/fgas/graphics', '+xfa/fwl', diff --git a/fpdfsdk/cpdfsdk_appstream.cpp b/fpdfsdk/cpdfsdk_appstream.cpp index 14a8ed06b1..6afe55baea 100644 --- a/fpdfsdk/cpdfsdk_appstream.cpp +++ b/fpdfsdk/cpdfsdk_appstream.cpp @@ -16,7 +16,6 @@ #include "constants/appearance.h" #include "constants/form_flags.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" #include "core/fpdfapi/font/cpdf_font.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_document.h" @@ -36,6 +35,7 @@ #include "core/fxcrt/notreached.h" #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/span.h" +#include "core/fxcrt/stream_write_utils.h" #include "fpdfsdk/cpdfsdk_formfillenvironment.h" #include "fpdfsdk/cpdfsdk_interactiveform.h" #include "fpdfsdk/cpdfsdk_pageview.h" diff --git a/fpdfsdk/cpdfsdk_formfillenvironment.cpp b/fpdfsdk/cpdfsdk_formfillenvironment.cpp index e23503b71a..01d1929571 100644 --- a/fpdfsdk/cpdfsdk_formfillenvironment.cpp +++ b/fpdfsdk/cpdfsdk_formfillenvironment.cpp @@ -16,6 +16,7 @@ #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfdoc/cpdf_nametree.h" +#include "core/fxcrt/autorestorer.h" #include "core/fxcrt/check.h" #include "core/fxcrt/containers/contains.h" #include "core/fxcrt/data_vector.h" @@ -32,6 +33,7 @@ #include "fxjs/ijs_runtime.h" #ifdef PDF_ENABLE_XFA +#include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" #include "fpdfsdk/fpdfxfa/cpdfxfa_widget.h" #endif @@ -74,11 +76,7 @@ CPDFSDK_FormFillEnvironment::CPDFSDK_FormFillEnvironment( CPDFSDK_FormFillEnvironment::~CPDFSDK_FormFillEnvironment() { being_destroyed_ = true; - ClearAllFocusedAnnots(); - - // |page_map_| will try to access |interactive_form_| when it cleans itself - // up. Make sure it is deleted before |interactive_form_|. - page_map_.clear(); + OnDetachFromDocument(); // Must destroy the |interactive_form_filler_| before the environment (|this|) // because any created form widgets hold a pointer to the environment. @@ -120,6 +118,9 @@ void CPDFSDK_FormFillEnvironment::OutputSelectedRect( CFX_PointF ptA = pFormField->PWLtoFFL(CFX_PointF(rect.left, rect.bottom)); CFX_PointF ptB = pFormField->PWLtoFFL(CFX_PointF(rect.right, rect.top)); + + AutoRestorer ffi_restorer(&in_passive_ffi_callback_); + in_passive_ffi_callback_ = true; info_->FFI_OutputSelectedRect(info_, pPage, ptA.x, ptB.y, ptB.x, ptA.y); } @@ -136,7 +137,7 @@ CPDFSDK_PageView* CPDFSDK_FormFillEnvironment::GetCurrentView() { IPDF_Page* CPDFSDK_FormFillEnvironment::GetCurrentPage() const { if (info_ && info_->FFI_GetCurrentPage) { return IPDFPageFromFPDFPage(info_->FFI_GetCurrentPage( - info_, FPDFDocumentFromCPDFDocument(cpdfdoc_))); + info_, FPDFDocumentFromCPDFDocument(cpdfdoc_.Get()))); } return nullptr; } @@ -356,6 +357,17 @@ void CPDFSDK_FormFillEnvironment::SubmitForm( fxcrt::CollectionSize(form_data), AsFPDFWideString(&bsUrl)); } +#ifdef PDF_ENABLE_V8 +v8::Isolate* CPDFSDK_FormFillEnvironment::GetIsolate() const { +#ifdef PDF_ENABLE_XFA + auto* context = static_cast(GetDocExtension()); + return context ? context->GetIsolate() : nullptr; +#else + return nullptr; +#endif // PDF_ENABLE_XFA +} +#endif // PDF_ENABLE_V8 + IJS_Runtime* CPDFSDK_FormFillEnvironment::GetIJSRuntime() { if (!ijs_runtime_) { ijs_runtime_ = IJS_Runtime::Create(this); @@ -366,6 +378,8 @@ IJS_Runtime* CPDFSDK_FormFillEnvironment::GetIJSRuntime() { void CPDFSDK_FormFillEnvironment::Invalidate(IPDF_Page* page, const FX_RECT& rect) { if (info_ && info_->FFI_Invalidate) { + AutoRestorer ffi_restorer(&in_passive_ffi_callback_); + in_passive_ffi_callback_ = true; info_->FFI_Invalidate(info_, FPDFPageFromIPDFPage(page), rect.left, rect.top, rect.right, rect.bottom); } @@ -374,6 +388,8 @@ void CPDFSDK_FormFillEnvironment::Invalidate(IPDF_Page* page, void CPDFSDK_FormFillEnvironment::SetCursor( IPWL_FillerNotify::CursorStyle nCursorType) { if (info_ && info_->FFI_SetCursor) { + AutoRestorer ffi_restorer(&in_passive_ffi_callback_); + in_passive_ffi_callback_ = true; info_->FFI_SetCursor(info_, static_cast(nCursorType)); } } @@ -394,6 +410,8 @@ void CPDFSDK_FormFillEnvironment::KillTimer(int nTimerID) { void CPDFSDK_FormFillEnvironment::OnChange() { if (info_ && info_->FFI_OnChange) { + AutoRestorer ffi_restorer(&in_passive_ffi_callback_); + in_passive_ffi_callback_ = true; info_->FFI_OnChange(info_); } } @@ -416,6 +434,8 @@ void CPDFSDK_FormFillEnvironment::OnSetFieldInputFocusInternal( size_t nCharacters = text.GetLength(); ByteString bsUTFText = text.ToUTF16LE(); auto* pBuffer = reinterpret_cast(bsUTFText.c_str()); + AutoRestorer ffi_restorer(&in_passive_ffi_callback_); + in_passive_ffi_callback_ = true; info_->FFI_SetTextFieldFocus( info_, pBuffer, pdfium::checked_cast(nCharacters), bFocus); } @@ -484,6 +504,8 @@ void CPDFSDK_FormFillEnvironment::DisplayCaret(IPDF_Page* page, double right, double bottom) { if (info_ && info_->version >= 2 && info_->FFI_DisplayCaret) { + AutoRestorer ffi_restorer(&in_passive_ffi_callback_); + in_passive_ffi_callback_ = true; info_->FFI_DisplayCaret(info_, FPDFPageFromIPDFPage(page), bVisible, left, top, right, bottom); } @@ -493,15 +515,15 @@ int CPDFSDK_FormFillEnvironment::GetCurrentPageIndex() const { if (!info_ || info_->version < 2 || !info_->FFI_GetCurrentPageIndex) { return -1; } - return info_->FFI_GetCurrentPageIndex(info_, - FPDFDocumentFromCPDFDocument(cpdfdoc_)); + return info_->FFI_GetCurrentPageIndex( + info_, FPDFDocumentFromCPDFDocument(cpdfdoc_.Get())); } void CPDFSDK_FormFillEnvironment::SetCurrentPage(int iCurPage) { if (!info_ || info_->version < 2 || !info_->FFI_SetCurrentPage) { return; } - info_->FFI_SetCurrentPage(info_, FPDFDocumentFromCPDFDocument(cpdfdoc_), + info_->FFI_SetCurrentPage(info_, FPDFDocumentFromCPDFDocument(cpdfdoc_.Get()), iCurPage); } @@ -511,7 +533,7 @@ void CPDFSDK_FormFillEnvironment::GotoURL(const WideString& wsURL) { } ByteString bsTo = wsURL.ToUTF16LE(); - info_->FFI_GotoURL(info_, FPDFDocumentFromCPDFDocument(cpdfdoc_), + info_->FFI_GotoURL(info_, FPDFDocumentFromCPDFDocument(cpdfdoc_.Get()), AsFPDFWideString(&bsTo)); } @@ -645,6 +667,9 @@ void CPDFSDK_FormFillEnvironment::PageEvent(int iPageCount, #endif // PDF_ENABLE_XFA void CPDFSDK_FormFillEnvironment::ClearAllFocusedAnnots() { + if (!cpdfdoc_) { + return; + } for (auto& it : page_map_) { if (it.second->IsValidSDKAnnot(GetFocusAnnot())) { ObservedPtr pObserved(it.second.get()); @@ -656,6 +681,14 @@ void CPDFSDK_FormFillEnvironment::ClearAllFocusedAnnots() { } } +void CPDFSDK_FormFillEnvironment::OnDetachFromDocument() { + ClearAllFocusedAnnots(); + page_map_.clear(); + interactive_form_.reset(); + ijs_runtime_.reset(); + cpdfdoc_.Reset(); +} + CPDFSDK_PageView* CPDFSDK_FormFillEnvironment::GetOrCreatePageView( IPDF_Page* pUnderlyingPage) { CPDFSDK_PageView* pExisting = GetPageView(pUnderlyingPage); @@ -689,7 +722,8 @@ CPDFSDK_PageView* CPDFSDK_FormFillEnvironment::GetPageViewAtIndex(int nIndex) { void CPDFSDK_FormFillEnvironment::ProcJavascriptAction() { // EmbedPDF(reads-dont-write): read the name tree without mutable getters. - auto name_tree = CPDF_NameTree::CreateForReading(cpdfdoc_, "JavaScript"); + auto name_tree = + CPDF_NameTree::CreateForReading(cpdfdoc_.Get(), "JavaScript"); if (!name_tree) { return; } @@ -763,7 +797,7 @@ IPDF_Page* CPDFSDK_FormFillEnvironment::GetPage(int nIndex) const { return nullptr; } return IPDFPageFromFPDFPage(info_->FFI_GetPage( - info_, FPDFDocumentFromCPDFDocument(cpdfdoc_), nIndex)); + info_, FPDFDocumentFromCPDFDocument(cpdfdoc_.Get()), nIndex)); } CPDFSDK_InteractiveForm* CPDFSDK_FormFillEnvironment::GetInteractiveForm() { @@ -902,6 +936,8 @@ void CPDFSDK_FormFillEnvironment::SendOnFocusChange( FPDF_ANNOTATION fpdf_annot = FPDFAnnotationFromCPDFAnnotContext(focused_annot.get()); + AutoRestorer ffi_restorer(&in_passive_ffi_callback_); + in_passive_ffi_callback_ = true; info_->FFI_OnFocusChange(info_, fpdf_annot, pPageView->GetPageIndex()); } diff --git a/fpdfsdk/cpdfsdk_formfillenvironment.h b/fpdfsdk/cpdfsdk_formfillenvironment.h index 56613dcfee..5ddd521541 100644 --- a/fpdfsdk/cpdfsdk_formfillenvironment.h +++ b/fpdfsdk/cpdfsdk_formfillenvironment.h @@ -39,6 +39,12 @@ class IJS_Runtime; class IPDF_Page; struct CFFL_FieldAction; +#ifdef PDF_ENABLE_V8 +namespace v8 { +class Isolate; +} // namespace v8 +#endif // PDF_ENABLE_V8 + // NOTE: |bsUTF16LE| must outlive the use of the result. Care must be taken // since modifying the result would impact |bsUTF16LE|. FPDF_WIDESTRING AsFPDFWideString(ByteString* bsUTF16LE); @@ -85,12 +91,15 @@ class CPDFSDK_FormFillEnvironment final bool HasPermissions(uint32_t flags) const override; void OnChange() override; + bool InPassiveFFICallback() const { return in_passive_ffi_callback_; } + CPDFSDK_PageView* GetPageViewAtIndex(int nIndex); void RemovePageView(IPDF_Page* pUnderlyingPage); void UpdateAllViews(CPDFSDK_Annot* pAnnot); bool KillFocusAnnot(Mask nFlags); void ClearAllFocusedAnnots(); + void OnDetachFromDocument(); int GetPageCount() const; @@ -107,10 +116,13 @@ class CPDFSDK_FormFillEnvironment final int zoomMode, pdfium::span fPosArray); - CPDF_Document* GetPDFDocument() const { return cpdfdoc_; } + CPDF_Document* GetPDFDocument() const { return cpdfdoc_.Get(); } CPDF_Document::Extension* GetDocExtension() const { - return cpdfdoc_->GetExtension(); + return cpdfdoc_ ? cpdfdoc_->GetExtension() : nullptr; } +#ifdef PDF_ENABLE_V8 + v8::Isolate* GetIsolate() const; +#endif // PDF_ENABLE_V8 bool IsJSPlatformPresent() const { return info_ && info_->m_pJsPlatform; } IPDF_JSPLATFORM* GetJSPlatform() const { @@ -283,10 +295,11 @@ class CPDFSDK_FormFillEnvironment final std::unique_ptr interactive_form_; ObservedPtr focus_annot_; - UnownedPtr const cpdfdoc_; + ObservedPtr cpdfdoc_; std::unique_ptr interactive_form_filler_; bool change_mask_ = false; bool being_destroyed_ = false; + bool in_passive_ffi_callback_ = false; // Holds the list of focusable annot types. // Annotations of type WIDGET are by default focusable. diff --git a/fpdfsdk/cpdfsdk_helpers.cpp b/fpdfsdk/cpdfsdk_helpers.cpp index 9c697134c9..549a66da98 100644 --- a/fpdfsdk/cpdfsdk_helpers.cpp +++ b/fpdfsdk/cpdfsdk_helpers.cpp @@ -295,8 +295,8 @@ FXDIB_Format FXDIBFormatFromFPDFFormat(int format) { return FXDIB_Format::kBgra; #if defined(PDF_USE_SKIA) case FPDFBitmap_BGRA_Premul: - return CFX_GEModule::Get()->UseSkiaRenderer() ? FXDIB_Format::kBgraPremul - : FXDIB_Format::kInvalid; + return CFX_GEModule::IsSkiaRenderer() ? FXDIB_Format::kBgraPremul + : FXDIB_Format::kInvalid; #endif default: return FXDIB_Format::kInvalid; @@ -306,7 +306,7 @@ FXDIB_Format FXDIBFormatFromFPDFFormat(int format) { void ValidateBitmapPremultiplyState(CFX_DIBitmap* bitmap) { #if defined(PDF_USE_SKIA) const bool should_be_premultiplied = - CFX_GEModule::Get()->UseSkiaRenderer() && + CFX_GEModule::IsSkiaRenderer() && bitmap->GetFormat() == FXDIB_Format::kBgraPremul; CHECK_EQ(should_be_premultiplied, bitmap->IsPremultiplied()); #else diff --git a/fpdfsdk/cpdfsdk_interactiveform.cpp b/fpdfsdk/cpdfsdk_interactiveform.cpp index 0518db94a1..41dcf0b89e 100644 --- a/fpdfsdk/cpdfsdk_interactiveform.cpp +++ b/fpdfsdk/cpdfsdk_interactiveform.cpp @@ -375,27 +375,6 @@ void CPDFSDK_InteractiveForm::UpdateField(CPDF_FormField* pFormField) { } } -bool CPDFSDK_InteractiveForm::OnKeyStrokeCommit(CPDF_FormField* pFormField, - const WideString& csValue) { - CPDF_AAction aAction = pFormField->GetAdditionalAction(); - if (!aAction.ActionExist(CPDF_AAction::kKeyStroke)) { - return true; - } - - CPDF_Action action = aAction.GetAction(CPDF_AAction::kKeyStroke); - if (!action.HasDict()) { - return true; - } - - CFFL_FieldAction fa; - fa.bModifier = false; - fa.bShift = false; - fa.sValue = csValue; - form_fill_env_->DoActionFieldJavaScript(action, CPDF_AAction::kKeyStroke, - pFormField, &fa); - return fa.bRC; -} - bool CPDFSDK_InteractiveForm::OnValidate(CPDF_FormField* pFormField, const WideString& csValue) { CPDF_AAction aAction = pFormField->GetAdditionalAction(); @@ -567,9 +546,6 @@ bool CPDFSDK_InteractiveForm::BeforeValueChange(CPDF_FormField* pField, if (!IsFormFieldTypeComboOrText(fieldType)) { return true; } - if (!OnKeyStrokeCommit(pField, csValue)) { - return false; - } return OnValidate(pField, csValue); } @@ -595,9 +571,6 @@ bool CPDFSDK_InteractiveForm::BeforeSelectionChange(CPDF_FormField* pField, if (pField->GetFieldType() != FormFieldType::kListBox) { return true; } - if (!OnKeyStrokeCommit(pField, csValue)) { - return false; - } return OnValidate(pField, csValue); } diff --git a/fpdfsdk/cpdfsdk_interactiveform.h b/fpdfsdk/cpdfsdk_interactiveform.h index e90505df29..368aec8d7f 100644 --- a/fpdfsdk/cpdfsdk_interactiveform.h +++ b/fpdfsdk/cpdfsdk_interactiveform.h @@ -56,7 +56,6 @@ class CPDFSDK_InteractiveForm final void SynchronizeField(CPDF_FormField* pFormField); #endif // PDF_ENABLE_XFA - bool OnKeyStrokeCommit(CPDF_FormField* pFormField, const WideString& csValue); bool OnValidate(CPDF_FormField* pFormField, const WideString& csValue); void OnCalculate(CPDF_FormField* pFormField); std::optional OnFormat(CPDF_FormField* pFormField); diff --git a/fpdfsdk/epdf_annot.cpp b/fpdfsdk/epdf_annot.cpp index fa9fe08283..25be04b584 100644 --- a/fpdfsdk/epdf_annot.cpp +++ b/fpdfsdk/epdf_annot.cpp @@ -23,7 +23,7 @@ #include "constants/appearance.h" #include "constants/form_fields.h" #include "core/fdrm/fx_crypt.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfapi/edit/cpdf_stringarchivestream.h" #include "core/fpdfapi/page/cpdf_annotcontext.h" #include "core/fpdfapi/page/cpdf_form.h" diff --git a/fpdfsdk/epdf_flatten.cpp b/fpdfsdk/epdf_flatten.cpp index 6d97455dba..7154b82a29 100644 --- a/fpdfsdk/epdf_flatten.cpp +++ b/fpdfsdk/epdf_flatten.cpp @@ -19,7 +19,7 @@ #include "constants/annotation_flags.h" #include "constants/font_encodings.h" #include "constants/page_object.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fpdfapi/page/cpdf_annotcontext.h" #include "core/fpdfapi/page/cpdf_page.h" #include "core/fpdfapi/parser/cpdf_array.h" diff --git a/fpdfsdk/epdf_measure_embeddertest.cpp b/fpdfsdk/epdf_measure_embeddertest.cpp index cd8f695cc7..84d792de75 100644 --- a/fpdfsdk/epdf_measure_embeddertest.cpp +++ b/fpdfsdk/epdf_measure_embeddertest.cpp @@ -948,7 +948,8 @@ TEST_F(EPDFMeasureEmbedderTest, CaptionAppearanceGolden) { } auto bitmap = RenderPageWithFlags(page.get(), nullptr, FPDF_ANNOT); ASSERT_TRUE(bitmap); - CompareBitmapWithFuzzyExpectationSuffix(bitmap.get(), "measure_captions"); + CompareBitmapWithExpectationSuffix(bitmap.get(), "measure_captions", + kFuzzyDiffOptions); } TEST_F(EPDFMeasureEmbedderTest, diff --git a/fpdfsdk/fpdf_annot_embeddertest.cpp b/fpdfsdk/fpdf_annot_embeddertest.cpp index 1065206d17..d4f02f8500 100644 --- a/fpdfsdk/fpdf_annot_embeddertest.cpp +++ b/fpdfsdk/fpdf_annot_embeddertest.cpp @@ -582,8 +582,8 @@ TEST_F(FPDFAnnotEmbedderTest, ExtractInkMultiple) { } { ScopedFPDFBitmap bitmap = RenderLoadedPageWithFlags(page, FPDF_ANNOT); - CompareBitmapWithFuzzyExpectationSuffix(bitmap.get(), - "annotation_ink_multiple"); + CompareBitmapWithExpectationSuffix(bitmap.get(), "annotation_ink_multiple", + kFuzzyDiffOptions); } UnloadPageNoEvents(page); } @@ -798,8 +798,9 @@ TEST_F(FPDFAnnotEmbedderTest, AddAndSaveUnderlineAnnotation) { ASSERT_TRUE(saved_doc); ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - VerifySavedRenderingWithFuzzyExpectationSuffix( - saved_page.get(), "annotation_highlight_long_content_added_underline"); + VerifySavedRenderingWithExpectationSuffix( + saved_page.get(), "annotation_highlight_long_content_added_underline", + kFuzzyDiffOptions); // Check that the saved document has 2 annotations on the first page EXPECT_EQ(2, FPDFPage_GetAnnotCount(saved_page.get())); @@ -914,8 +915,8 @@ TEST_F(FPDFAnnotEmbedderTest, ModifyRectQuadpointsWithAP) { "annotation_highlight_square_with_ap"; { ScopedFPDFBitmap bitmap = RenderLoadedPageWithFlags(page.get(), FPDF_ANNOT); - CompareBitmapWithFuzzyExpectationSuffix( - bitmap.get(), kAnnotationHighlightSquareWithApPng); + CompareBitmapWithExpectationSuffix( + bitmap.get(), kAnnotationHighlightSquareWithApPng, kFuzzyDiffOptions); } FS_RECTF rect; @@ -956,8 +957,8 @@ TEST_F(FPDFAnnotEmbedderTest, ModifyRectQuadpointsWithAP) { { ScopedFPDFBitmap bitmap = RenderLoadedPageWithFlags(page.get(), FPDF_ANNOT); - CompareBitmapWithFuzzyExpectationSuffix( - bitmap.get(), kAnnotationHighlightSquareWithApPng); + CompareBitmapWithExpectationSuffix( + bitmap.get(), kAnnotationHighlightSquareWithApPng, kFuzzyDiffOptions); } // Verify its annotation rectangle. @@ -978,8 +979,9 @@ TEST_F(FPDFAnnotEmbedderTest, ModifyRectQuadpointsWithAP) { // Check that updating the rectangle changes the annotation's position. { ScopedFPDFBitmap bitmap = RenderLoadedPageWithFlags(page.get(), FPDF_ANNOT); - CompareBitmapWithFuzzyExpectationSuffix( - bitmap.get(), "annotation_highlight_square_with_ap_modified_highlight"); + CompareBitmapWithExpectationSuffix( + bitmap.get(), "annotation_highlight_square_with_ap_modified_highlight", + kFuzzyDiffOptions); } { @@ -999,8 +1001,9 @@ TEST_F(FPDFAnnotEmbedderTest, ModifyRectQuadpointsWithAP) { // Check that updating the rectangle changes the square annotation's // position. ScopedFPDFBitmap bitmap = RenderLoadedPageWithFlags(page.get(), FPDF_ANNOT); - CompareBitmapWithFuzzyExpectationSuffix( - bitmap.get(), "annotation_highlight_square_with_ap_modified_square"); + CompareBitmapWithExpectationSuffix( + bitmap.get(), "annotation_highlight_square_with_ap_modified_square", + kFuzzyDiffOptions); } } @@ -1064,7 +1067,8 @@ TEST_F(FPDFAnnotEmbedderTest, RemoveAnnotation) { EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); UnloadPageNoEvents(page); - // TODO(npm): VerifySavedRendering changes annot rect dimensions by 1?? + // TODO(crbug.com/567621440): VerifySavedRendering changes annot rect + // dimensions by 1?? // Open the saved document. std::string new_file = GetString(); FPDF_FILEACCESS file_access = {}; // Aggregate initialization @@ -1339,8 +1343,8 @@ TEST_F(FPDFAnnotEmbedderTest, AddAndModifyImage) { // Check that the page renders correctly with the new image object. { ScopedFPDFBitmap bitmap = RenderLoadedPageWithFlags(page.get(), FPDF_ANNOT); - CompareBitmapWithFuzzyExpectationSuffix( - bitmap.get(), "annotation_stamp_with_ap_new_image"); + CompareBitmapWithExpectationSuffix( + bitmap.get(), "annotation_stamp_with_ap_new_image", kFuzzyDiffOptions); } { @@ -1409,8 +1413,8 @@ TEST_F(FPDFAnnotEmbedderTest, AddAndModifyText) { // Check that the page renders correctly with the new text object. { ScopedFPDFBitmap bitmap = RenderLoadedPageWithFlags(page.get(), FPDF_ANNOT); - CompareBitmapWithFuzzyExpectationSuffix( - bitmap.get(), "annotation_stamp_with_ap_new_text"); + CompareBitmapWithExpectationSuffix( + bitmap.get(), "annotation_stamp_with_ap_new_text", kFuzzyDiffOptions); } { @@ -1430,8 +1434,9 @@ TEST_F(FPDFAnnotEmbedderTest, AddAndModifyText) { // Check that the page renders correctly with the modified text object. { ScopedFPDFBitmap bitmap = RenderLoadedPageWithFlags(page.get(), FPDF_ANNOT); - CompareBitmapWithFuzzyExpectationSuffix( - bitmap.get(), "annotation_stamp_with_ap_modified_text"); + CompareBitmapWithExpectationSuffix(bitmap.get(), + "annotation_stamp_with_ap_modified_text", + kFuzzyDiffOptions); } // Remove the new annotation, and check that the page renders as before. diff --git a/fpdfsdk/fpdf_attachment.cpp b/fpdfsdk/fpdf_attachment.cpp index b6a87f1d8d..3d3733c154 100644 --- a/fpdfsdk/fpdf_attachment.cpp +++ b/fpdfsdk/fpdf_attachment.cpp @@ -511,9 +511,8 @@ FPDFAttachment_SetFile(FPDF_ATTACHMENT attachment, CPDF_String::DataType::kIsHex); // Create the file stream and have the filespec dictionary link to it. - auto pFileStream = doc->NewIndirect( - DataVector(contents_span.begin(), contents_span.end()), - std::move(pFileStreamDict)); + auto pFileStream = doc->NewIndirect(ToDataVector(contents_span), + std::move(pFileStreamDict)); auto pEFDict = pFile->AsMutableDictionary()->SetNewFor("EF"); pEFDict->SetNewFor("F", doc, pFileStream->GetObjNum()); @@ -575,6 +574,33 @@ FPDFAttachment_GetSubtype(FPDF_ATTACHMENT attachment, PDF_DecodeText(subtype.unsigned_span()), buffer_span); } +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFAttachment_GetAFRelationship(FPDF_ATTACHMENT attachment, + FPDF_WCHAR* buffer, + unsigned long buflen) { + CPDF_Object* file = CPDFObjectFromFPDFAttachment(attachment); + if (!file) { + return 0; + } + + CPDF_FileSpec spec(pdfium::WrapRetain(file)); + RetainPtr file_spec_dict = spec.GetFileSpecDict(); + if (!file_spec_dict) { + return 0; + } + + // SAFETY: required from caller. + auto buffer_span = UNSAFE_BUFFERS(SpanFromFPDFApiArgs(buffer, buflen)); + + // /AFRelationship is a name in the file specification dictionary (sibling of + // /EF, /F, /UF, /Desc), so read it like GetSubtype reads /Subtype. When the + // entry is absent GetNameFor returns an empty string, which decodes to an + // empty WideString and yields the documented empty-string result (length 2). + ByteString relationship = file_spec_dict->GetNameFor("AFRelationship"); + return Utf16EncodeMaybeCopyAndReturnLength( + PDF_DecodeText(relationship.unsigned_span()), buffer_span); +} + // EmbedPDF(epdf-api): EPDFAttachment_* setters, getters and streaming // extraction. FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV diff --git a/fpdfsdk/fpdf_attachment_embeddertest.cpp b/fpdfsdk/fpdf_attachment_embeddertest.cpp index 929bafc917..349bbea756 100644 --- a/fpdfsdk/fpdf_attachment_embeddertest.cpp +++ b/fpdfsdk/fpdf_attachment_embeddertest.cpp @@ -471,6 +471,65 @@ TEST_F(FPDFAttachmentEmbedderTest, GetSubtypeInvalid) { FPDFAttachment_GetSubtype(attachment, nullptr, 10)); } +TEST_F(FPDFAttachmentEmbedderTest, GetAFRelationship) { + ASSERT_TRUE(OpenDocument("embedded_attachments_with_af.pdf")); + ASSERT_EQ(3, FPDFDoc_GetAttachmentCount(document())); + + // Attachment 0 (1.txt) has /AFRelationship /Alternative. + FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(document(), 0); + ASSERT_TRUE(attachment); + + constexpr char kExpectedRelationship[] = "Alternative"; + unsigned long length = + FPDFAttachment_GetAFRelationship(attachment, nullptr, 0); + ASSERT_EQ(2u * (strlen(kExpectedRelationship) + 1), length); + + std::vector buf = GetFPDFWideStringBuffer(length); + EXPECT_EQ(length, + FPDFAttachment_GetAFRelationship(attachment, buf.data(), length)); + EXPECT_EQ(kExpectedRelationship, GetPlatformString(buf.data())); + + // Attachment 1 (2.txt) has no /AFRelationship -> empty string (length 2). + FPDF_ATTACHMENT no_relationship = FPDFDoc_GetAttachment(document(), 1); + ASSERT_TRUE(no_relationship); + EXPECT_EQ(2u, FPDFAttachment_GetAFRelationship(no_relationship, buf.data(), + length)); + EXPECT_EQ("", GetPlatformString(buf.data())); + + // Attachment 2 (3.txt) has a numeric /AFRelationship, which is not a name + // object -> empty string (length 2). + FPDF_ATTACHMENT numeric_relationship = FPDFDoc_GetAttachment(document(), 2); + ASSERT_TRUE(numeric_relationship); + EXPECT_EQ(2u, FPDFAttachment_GetAFRelationship(numeric_relationship, + buf.data(), length)); + EXPECT_EQ("", GetPlatformString(buf.data())); +} + +TEST_F(FPDFAttachmentEmbedderTest, GetAFRelationshipInvalid) { + ASSERT_TRUE(OpenDocument("embedded_attachments_with_af.pdf")); + FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(document(), 0); + ASSERT_TRUE(attachment); + + // Null attachment -> 0. + std::vector buf(1); + EXPECT_EQ(0u, FPDFAttachment_GetAFRelationship(nullptr, buf.data(), 1)); + + // Buffer too small: unmodified, but the full length is still returned. + constexpr char kExpectedRelationship[] = "Alternative"; + unsigned long length = + FPDFAttachment_GetAFRelationship(attachment, nullptr, 0); + ASSERT_EQ(2u * (strlen(kExpectedRelationship) + 1), length); + + std::vector small_buf(length - 1); + constexpr FPDF_WCHAR kPattern = 0xDEAD; + std::ranges::fill(small_buf, kPattern); + EXPECT_EQ(length, FPDFAttachment_GetAFRelationship( + attachment, small_buf.data(), small_buf.size())); + // The buffer was too small to hold the result, so it must be left + // unmodified: every element still holds the pre-filled pattern. + EXPECT_THAT(small_buf, testing::Each(kPattern)); +} + TEST_F(FPDFAttachmentEmbedderTest, ReadAttachmentDescription) { ASSERT_TRUE(OpenDocument("embedded_attachments_with_desc.pdf")); diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp index f577b36936..ca1210530a 100644 --- a/fpdfsdk/fpdf_edit_embeddertest.cpp +++ b/fpdfsdk/fpdf_edit_embeddertest.cpp @@ -931,12 +931,13 @@ TEST_F(FPDFEditEmbedderTest, SetPositionsVertical) { static constexpr char kExpected[] = "vertical_text_positioned"; { ScopedFPDFBitmap bitmap = RenderPage(page.get()); - CompareBitmapWithFuzzyExpectationSuffix(bitmap.get(), kExpected); + CompareBitmapWithExpectationSuffix(bitmap.get(), kExpected, + kFuzzyDiffOptions); } ASSERT_TRUE(FPDFPage_GenerateContent(page.get())); ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - VerifySavedDocumentWithFuzzyExpectationSuffix(kExpected); + VerifySavedDocumentWithExpectationSuffix(kExpected, kFuzzyDiffOptions); } TEST_F(FPDFEditEmbedderTest, SetPositionsBengali2) { @@ -1063,7 +1064,8 @@ TEST_F(FPDFEditEmbedderTest, SetPositionsBengali3) { static constexpr char kExpected[] = "set_positions_bengali3"; ScopedFPDFBitmap page_bitmap = RenderPage(page.get()); - CompareBitmapWithFuzzyExpectationSuffix(page_bitmap.get(), kExpected); + CompareBitmapWithExpectationSuffix(page_bitmap.get(), kExpected, + kFuzzyDiffOptions); ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); VerifySavedDocumentWithExpectationSuffix(kExpected); @@ -3024,8 +3026,10 @@ TEST_F(FPDFEditEmbedderTest, AddStandardFontText) { EXPECT_TRUE(FPDFTextObj_GetFontSize(text_object3, &size)); EXPECT_EQ(20, size); - // TODO(npm): Why are there issues with text rotated by 90 degrees? - // TODO(npm): FPDF_SaveAsCopy not giving the desired result after this. + // TODO(crbug.com/567621440): Why are there issues with text rotated by 90 + // degrees? + // TODO(crbug.com/567621440): FPDF_SaveAsCopy not giving the desired result + // after this. } TEST_F(FPDFEditEmbedderTest, SetFontSize) { @@ -3718,7 +3722,8 @@ TEST_F(FPDFEditEmbedderTest, DoubleGenerating) { TEST_F(FPDFEditEmbedderTest, LoadSimpleType1Font) { CreateNewDocument(); - // TODO(npm): use other fonts after disallowing loading any font as any type + // TODO(crbug.com/567623562): use other fonts after disallowing loading any + // font as any type RetainPtr stock_font = CPDF_Font::GetStockFont(cpdf_doc(), "Times-Bold"); pdfium::span span = stock_font->GetFont()->GetFontSpan(); @@ -3936,7 +3941,7 @@ TEST_F(FPDFEditEmbedderTest, TransformAnnot) { } } -// TODO(npm): Add tests using Japanese fonts in other OS. +// TODO(crbug.com/567621440): Add tests using Japanese fonts in other OS. #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) TEST_F(FPDFEditEmbedderTest, AddCIDFontText) { // Start with a blank page @@ -4151,7 +4156,7 @@ TEST_F(FPDFEditEmbedderTest, SaveAndRender) { // Now add a more complex green path. FPDF_PAGEOBJECT green_path = FPDFPageObj_CreateNewPath(20, 20); EXPECT_TRUE(FPDFPageObj_SetFillColor(green_path, 0, 255, 0, 200)); - // TODO(npm): stroking will cause the rendering to differ. + // TODO(crbug.com/567621440): stroking will cause the rendering to differ. EXPECT_TRUE(FPDFPath_SetDrawMode(green_path, FPDF_FILLMODE_WINDING, 0)); EXPECT_TRUE(FPDFPath_LineTo(green_path, 20, 63)); EXPECT_TRUE(FPDFPath_BezierTo(green_path, 55, 55, 78, 78, 90, 90)); @@ -4161,14 +4166,15 @@ TEST_F(FPDFEditEmbedderTest, SaveAndRender) { EXPECT_TRUE(FPDFPath_Close(green_path)); EXPECT_TRUE(FPDFPage_InsertObject(page.get(), green_path)); ScopedFPDFBitmap page_bitmap = RenderLoadedPage(page.get()); - CompareBitmapWithFuzzyExpectationSuffix(page_bitmap.get(), kBug779Png); + CompareBitmapWithExpectationSuffix(page_bitmap.get(), kBug779Png, + kFuzzyDiffOptions); // Now save the result, closing the page and document EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); } - VerifySavedDocumentWithFuzzyExpectationSuffix(kBug779Png); + VerifySavedDocumentWithExpectationSuffix(kBug779Png, kFuzzyDiffOptions); } TEST_F(FPDFEditEmbedderTest, AddMark) { @@ -4843,7 +4849,12 @@ TEST_F(FPDFEditEmbedderTest, GetBitmap) { ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); ScopedFPDFBitmap bitmap(FPDFImageObj_GetBitmap(obj)); EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap.get())); +#if defined(PDF_ENABLE_RUST_JPEG) + CompareBitmapWithExpectationSuffix(bitmap.get(), "embedded_images_37", + kFuzzyDiffOptions); +#else CompareBitmap(bitmap.get(), "embedded_images_37"); +#endif } { @@ -4851,7 +4862,14 @@ TEST_F(FPDFEditEmbedderTest, GetBitmap) { ASSERT_EQ(FPDF_PAGEOBJ_IMAGE, FPDFPageObj_GetType(obj)); ScopedFPDFBitmap bitmap(FPDFImageObj_GetBitmap(obj)); EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(bitmap.get())); +#if defined(PDF_ENABLE_RUST_JPEG) + CompareBitmapWithExpectationSuffix( + bitmap.get(), "embedded_images_38", + DiffOptions{.max_pixel_per_channel_delta = 3, + .max_mean_squared_error = 0.3}); +#else CompareBitmap(bitmap.get(), "embedded_images_38"); +#endif } } @@ -5518,6 +5536,134 @@ TEST_F(FPDFEditEmbedderTest, GetRenderedBitmapForTextWithBadParameters) { text_object, 1)); } +TEST_F(FPDFEditEmbedderTest, GetRenderedStrokePattern) { + ASSERT_TRUE(OpenDocument("pattern_stroke.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path_object)); + + ScopedFPDFBitmap bitmap( + FPDFPageObj_GetRenderedStrokePattern(document(), path_object)); + ASSERT_TRUE(bitmap); + // This is the case when a colored pattern has a color of its own. + CompareBitmapWithExpectationSuffix(bitmap.get(), "pattern_stroke"); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedStrokePatternUncolored) { + ASSERT_TRUE(OpenDocument("pattern_stroke_uncolored.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path_object)); + + ScopedFPDFBitmap bitmap( + FPDFPageObj_GetRenderedStrokePattern(document(), path_object)); + ASSERT_TRUE(bitmap); + // This is the case when an uncolored pattern has no color of its own. + CompareBitmapWithExpectationSuffix(bitmap.get(), "pattern_stroke_uncolored"); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedStrokePatternBadParams) { + ASSERT_TRUE(OpenDocument("pattern_stroke.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_TRUE(path_object); + + // Simple bad parameters testing. + EXPECT_FALSE(FPDFPageObj_GetRenderedStrokePattern(nullptr, nullptr)); + EXPECT_FALSE(FPDFPageObj_GetRenderedStrokePattern(document(), nullptr)); + EXPECT_FALSE(FPDFPageObj_GetRenderedStrokePattern(nullptr, path_object)); + + // A page object that is not a path has no stroke pattern, either. + FPDF_PAGEOBJECT text_object = + FPDFPageObj_NewTextObj(document(), "Arial", 12.0f); + ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(text_object)); + EXPECT_TRUE(FPDFPage_InsertObject(page.get(), text_object)); + ASSERT_EQ(2, FPDFPage_CountObjects(page.get())); + EXPECT_FALSE(FPDFPageObj_GetRenderedStrokePattern(document(), text_object)); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedStrokePatternNoPattern) { + ASSERT_TRUE(OpenDocument("rectangles.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path_object)); + + // The stroke color is a plain color, not a pattern. + EXPECT_FALSE(FPDFPageObj_GetRenderedStrokePattern(document(), path_object)); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedFillPattern) { + ASSERT_TRUE(OpenDocument("pattern_fill.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path_object)); + + ScopedFPDFBitmap bitmap( + FPDFPageObj_GetRenderedFillPattern(document(), path_object)); + ASSERT_TRUE(bitmap); + // This is the case when a colored pattern has a color of its own. + CompareBitmapWithExpectationSuffix(bitmap.get(), "pattern_fill"); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedFillPatternUncolored) { + ASSERT_TRUE(OpenDocument("pattern_fill_uncolored.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path_object)); + + ScopedFPDFBitmap bitmap( + FPDFPageObj_GetRenderedFillPattern(document(), path_object)); + ASSERT_TRUE(bitmap); + // This is the case when an uncolored pattern has no color of its own. + CompareBitmapWithExpectationSuffix(bitmap.get(), "pattern_fill_uncolored"); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedFillPatternBadParams) { + ASSERT_TRUE(OpenDocument("pattern_fill.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_TRUE(path_object); + + // Simple bad parameters testing. + EXPECT_FALSE(FPDFPageObj_GetRenderedFillPattern(nullptr, nullptr)); + EXPECT_FALSE(FPDFPageObj_GetRenderedFillPattern(document(), nullptr)); + EXPECT_FALSE(FPDFPageObj_GetRenderedFillPattern(nullptr, path_object)); + + // A page object that is not a path has no fill pattern, either. + FPDF_PAGEOBJECT text_object = + FPDFPageObj_NewTextObj(document(), "Arial", 12.0f); + ASSERT_EQ(FPDF_PAGEOBJ_TEXT, FPDFPageObj_GetType(text_object)); + EXPECT_TRUE(FPDFPage_InsertObject(page.get(), text_object)); + ASSERT_EQ(2, FPDFPage_CountObjects(page.get())); + EXPECT_FALSE(FPDFPageObj_GetRenderedFillPattern(document(), text_object)); +} + +TEST_F(FPDFEditEmbedderTest, GetRenderedFillPatternNoPattern) { + ASSERT_TRUE(OpenDocument("rectangles.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + FPDF_PAGEOBJECT path_object = FPDFPage_GetObject(page.get(), 0); + ASSERT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path_object)); + + // The fill color is a plain color, not a pattern. + EXPECT_FALSE(FPDFPageObj_GetRenderedFillPattern(document(), path_object)); +} + TEST_F(FPDFEditEmbedderTest, GetRenderedBitmapForRotatedImage) { ScopedFPDFDocument doc(FPDF_CreateNewDocument()); ScopedFPDFPage page(FPDFPage_New(doc.get(), 0, 100, 100)); diff --git a/fpdfsdk/fpdf_editimg_embeddertest.cpp b/fpdfsdk/fpdf_editimg_embeddertest.cpp index cbd24e2c62..cf51ed7090 100644 --- a/fpdfsdk/fpdf_editimg_embeddertest.cpp +++ b/fpdfsdk/fpdf_editimg_embeddertest.cpp @@ -80,7 +80,12 @@ TEST_F(PDFEditImgTest, NewImageObjLoadJpeg) { const char kPagePath[] = "mona_lisa_page"; { ScopedFPDFBitmap image_bitmap(FPDFImageObj_GetBitmap(image.get())); +#if defined(PDF_ENABLE_RUST_JPEG) + CompareBitmapWithExpectationSuffix(image_bitmap.get(), kImagePath, + kFuzzyDiffOptions); +#else CompareBitmap(image_bitmap.get(), kImagePath); +#endif } FPDFImageObj_SetMatrix(image.get(), kImageWidth, 0, 0, kImageHeight, 0, 0); @@ -88,11 +93,20 @@ TEST_F(PDFEditImgTest, NewImageObjLoadJpeg) { FPDFPage_GenerateContent(page.get()); { ScopedFPDFBitmap page_bitmap = RenderPage(page.get()); +#if defined(PDF_ENABLE_RUST_JPEG) + CompareBitmapWithExpectationSuffix(page_bitmap.get(), kPagePath, + kFuzzyDiffOptions); +#else CompareBitmap(page_bitmap.get(), kPagePath); +#endif } ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); +#if defined(PDF_ENABLE_RUST_JPEG) + VerifySavedDocumentWithExpectationSuffix(kPagePath, kFuzzyDiffOptions); +#else VerifySavedDocument(kPagePath); +#endif } TEST_F(PDFEditImgTest, NewImageObjLoadJpegInline) { @@ -116,7 +130,12 @@ TEST_F(PDFEditImgTest, NewImageObjLoadJpegInline) { const char kPagePath[] = "mona_lisa_page"; { ScopedFPDFBitmap image_bitmap(FPDFImageObj_GetBitmap(image.get())); +#if defined(PDF_ENABLE_RUST_JPEG) + CompareBitmapWithExpectationSuffix(image_bitmap.get(), kImagePath, + kFuzzyDiffOptions); +#else CompareBitmap(image_bitmap.get(), kImagePath); +#endif } FPDFImageObj_SetMatrix(image.get(), kImageWidth, 0, 0, kImageHeight, 0, 0); @@ -124,11 +143,20 @@ TEST_F(PDFEditImgTest, NewImageObjLoadJpegInline) { FPDFPage_GenerateContent(page.get()); { ScopedFPDFBitmap page_bitmap = RenderPage(page.get()); +#if defined(PDF_ENABLE_RUST_JPEG) + CompareBitmapWithExpectationSuffix(page_bitmap.get(), kPagePath, + kFuzzyDiffOptions); +#else CompareBitmap(page_bitmap.get(), kPagePath); +#endif } ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); +#if defined(PDF_ENABLE_RUST_JPEG) + VerifySavedDocumentWithExpectationSuffix(kPagePath, kFuzzyDiffOptions); +#else VerifySavedDocument(kPagePath); +#endif } TEST_F(PDFEditImgTest, SetBitmap) { diff --git a/fpdfsdk/fpdf_editpage.cpp b/fpdfsdk/fpdf_editpage.cpp index 9661e6dde5..bc4f281a97 100644 --- a/fpdfsdk/fpdf_editpage.cpp +++ b/fpdfsdk/fpdf_editpage.cpp @@ -26,6 +26,7 @@ #include "core/fpdfapi/page/cpdf_pathobject.h" #include "core/fpdfapi/page/cpdf_shadingobject.h" #include "core/fpdfapi/page/cpdf_textobject.h" +#include "core/fpdfapi/page/cpdf_tilingpattern.h" #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_document.h" @@ -33,6 +34,8 @@ #include "core/fpdfapi/parser/cpdf_number.h" #include "core/fpdfapi/parser/cpdf_string.h" #include "core/fpdfapi/render/cpdf_docrenderdata.h" +#include "core/fpdfapi/render/cpdf_renderoptions.h" +#include "core/fpdfapi/render/cpdf_rendertiling.h" #include "core/fpdfdoc/cpdf_annot.h" #include "core/fpdfdoc/cpdf_annotlist.h" #include "core/fxcrt/compiler_specific.h" @@ -44,6 +47,7 @@ #include "core/fxcrt/span_util.h" #include "core/fxcrt/stl_util.h" #include "core/fxcrt/unowned_ptr.h" +#include "core/fxge/dib/cfx_dibitmap.h" #include "fpdfsdk/cpdfsdk_helpers.h" #include "public/epdf_edit.h" #include "public/fpdf_formfill.h" @@ -202,6 +206,101 @@ ByteString FormatPDFDate(time_t current_time, const tm& local_time) { return date; } +FPDF_BITMAP RenderTilingPatternToBitmap(CPDF_Pattern* pattern, + CPDF_Document* doc, + CPDF_PageObject* page_obj, + bool use_stroke_color) { + CPDF_TilingPattern* tiling_pattern = pattern->AsTilingPattern(); + if (!tiling_pattern) { + return nullptr; + } + const std::unique_ptr pattern_form = + tiling_pattern->Load(page_obj); + if (!pattern_form) { + return nullptr; + } + const FX_RECT rect = tiling_pattern->bbox().GetOuterRect(); + if (rect.IsEmpty() || !rect.Valid()) { + return nullptr; + } + + RetainPtr cell_bitmap = CPDF_RenderTiling::DrawPatternBitmap( + doc, /*pCache=*/nullptr, tiling_pattern, pattern_form.get(), CFX_Matrix(), + rect.Width(), rect.Height(), CPDF_RenderOptions::Options()); + if (!cell_bitmap) { + return nullptr; + } + + if (tiling_pattern->colored()) { + ValidateBitmapPremultiplyState(cell_bitmap); + + // Caller takes ownership. + return FPDFBitmapFromCFXDIBitmap(cell_bitmap.Leak()); + } + + // An uncolored pattern gets its color from the stroke/fill color operands. + const CPDF_ColorState& color_state = page_obj->color_state(); + const FX_COLORREF colorref = + use_stroke_color ? color_state.GetStrokeColorRef() + : color_state.GetFillColorRef(); + // 0xFFFFFFFF is the unusable color, see FX_COLORREF. + if (colorref == 0xFFFFFFFF) { + return nullptr; + } + + auto result_bitmap = pdfium::MakeRetain(); + if (!result_bitmap->Create(rect.Width(), rect.Height(), + FXDIB_Format::kBgra)) { + return nullptr; + } + const CPDF_GeneralState& general_state = page_obj->general_state(); + const float float_alpha = use_stroke_color + ? general_state.GetStrokeAlpha() + : general_state.GetFillAlpha(); + const int alpha = static_cast(float_alpha * 255); + if (!result_bitmap->CompositeMask( + /*dest_left=*/0, /*dest_top=*/0, rect.Width(), rect.Height(), + cell_bitmap, AlphaAndColorRefToArgb(alpha, colorref), + /*src_left=*/0, /*src_top=*/0, BlendMode::kNormal)) { + return nullptr; + } + + ValidateBitmapPremultiplyState(result_bitmap); + + // Caller takes ownership. + return FPDFBitmapFromCFXDIBitmap(result_bitmap.Leak()); +} + +FPDF_BITMAP GetRenderedPattern(FPDF_DOCUMENT document, + FPDF_PAGEOBJECT page_object, + bool use_stroke_color) { + CPDF_Document* doc = CPDFDocumentFromFPDFDocument(document); + if (!doc) { + return nullptr; + } + + CPDF_PageObject* object = CPDFPageObjectFromFPDFPageObject(page_object); + if (!object) { + return nullptr; + } + + const CPDF_ColorState& color_state = object->color_state(); + const CPDF_Color* color = use_stroke_color + ? color_state.GetStrokeColor() + : color_state.GetFillColor(); + if (!color || !color->IsPattern()) { + return nullptr; + } + + RetainPtr pattern = color->GetPattern(); + if (!pattern) { + return nullptr; + } + + return RenderTilingPatternToBitmap(pattern.Get(), doc, object, + use_stroke_color); +} + } // namespace FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV FPDF_CreateNewDocument() { @@ -1246,6 +1345,18 @@ FPDFPageObj_SetDashArray(FPDF_PAGEOBJECT page_object, return true; } +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV +FPDFPageObj_GetRenderedStrokePattern(FPDF_DOCUMENT document, + FPDF_PAGEOBJECT page_object) { + return GetRenderedPattern(document, page_object, /*use_stroke_color=*/true); +} + +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV +FPDFPageObj_GetRenderedFillPattern(FPDF_DOCUMENT document, + FPDF_PAGEOBJECT page_object) { + return GetRenderedPattern(document, page_object, /*use_stroke_color=*/false); +} + FPDF_EXPORT int FPDF_CALLCONV FPDFFormObj_CountObjects(FPDF_PAGEOBJECT form_object) { const auto* pObjectList = CPDFPageObjHolderFromFPDFFormObject(form_object); diff --git a/fpdfsdk/fpdf_edittext.cpp b/fpdfsdk/fpdf_edittext.cpp index d8071d3565..52e49a728a 100644 --- a/fpdfsdk/fpdf_edittext.cpp +++ b/fpdfsdk/fpdf_edittext.cpp @@ -97,7 +97,7 @@ RetainPtr CreateCompositeFontDict(CPDF_Document* doc, auto font_dict = doc->NewIndirect(); font_dict->SetNewFor("Type", "Font"); font_dict->SetNewFor("Subtype", "Type0"); - // TODO(npm): Get the correct encoding, if it's not identity. + // TODO(crbug.com/567626894): Get the correct encoding, if it's not identity. ByteString encoding = "Identity-H"; font_dict->SetNewFor("Encoding", encoding); font_dict->SetNewFor( @@ -115,8 +115,8 @@ RetainPtr CreateCidFontDict(CPDF_Document* doc, : "CIDFontType2"); cid_font_dict->SetNewFor("BaseFont", name); - // TODO(npm): Maybe use FT_Get_CID_Registry_Ordering_Supplement to get the - // CIDSystemInfo + // TODO(crbug.com/567626894): Maybe use + // FT_Get_CID_Registry_Ordering_Supplement to get the CIDSystemInfo auto cid_system_info_dict = doc->NewIndirect(); cid_system_info_dict->SetNewFor("Registry", "Adobe"); cid_system_info_dict->SetNewFor("Ordering", "Identity"); @@ -148,27 +148,28 @@ RetainPtr LoadFontDesc(CPDF_Document* doc, flags |= pdfium::kFontStyleForceBold; } - // TODO(npm): How do I know if a font is symbolic, script, allcap, smallcap? + // TODO(crbug.com/567626894): How do I know if a font is symbolic, script, + // allcap, smallcap? flags |= pdfium::kFontStyleNonSymbolic; font_descriptor_dict->SetNewFor("Flags", flags); FX_RECT bbox = font->GetBBox().value_or(FX_RECT()); font_descriptor_dict->SetRectFor("FontBBox", CFX_FloatRect(bbox)); - // TODO(npm): calculate italic angle correctly + // TODO(crbug.com/567626894): calculate italic angle correctly font_descriptor_dict->SetNewFor("ItalicAngle", font->IsItalic() ? -12 : 0); font_descriptor_dict->SetNewFor("Ascent", font->GetAscent()); font_descriptor_dict->SetNewFor("Descent", font->GetDescent()); - // TODO(npm): calculate the capheight, stemV correctly + // TODO(crbug.com/567626894): calculate the capheight, stemV correctly font_descriptor_dict->SetNewFor("CapHeight", font->GetAscent()); font_descriptor_dict->SetNewFor("StemV", font->IsBold() ? 120 : 70); auto stream = doc->NewIndirect(font_data); - // TODO(npm): Lengths for Type1 fonts. + // TODO(crbug.com/567626894): Lengths for Type1 fonts. if (font_type == FPDF_FONT_TRUETYPE) { stream->GetMutableDict()->SetNewFor( "Length1", pdfium::checked_cast(font_data.size())); @@ -275,7 +276,7 @@ RetainPtr LoadCompositeFont(CPDF_Document* doc, cid_font_dict->SetNewFor( "W", doc, doc->AddIndirectObject(CreateWidthsArray(widths))); - // TODO(npm): Support vertical writing + // TODO(crbug.com/567626894): Support vertical writing CreateDescendantFontsArray(doc, font_dict.Get(), cid_font_dict->GetObjNum()); @@ -456,9 +457,9 @@ FPDF_EXPORT FPDF_FONT FPDF_CALLCONV FPDFText_LoadFont(FPDF_DOCUMENT document, auto span = UNSAFE_BUFFERS(pdfium::span(data, size)); auto pFont = std::make_unique(); - // TODO(npm): Maybe use FT_Get_X11_Font_Format to check format? Otherwise, we - // are allowing giving any font that can be loaded on freetype and setting it - // as any font type. + // TODO(crbug.com/567623562): Maybe use FT_Get_X11_Font_Format to check + // format? Otherwise, we are allowing giving any font that can be loaded on + // freetype and setting it as any font type. if (!pFont->LoadFaceZeroFromSpan(span, /*force_vertical=*/false, /*object_tag=*/0)) { return nullptr; diff --git a/fpdfsdk/fpdf_flatten.cpp b/fpdfsdk/fpdf_flatten.cpp index 293ddde71e..d64347c592 100644 --- a/fpdfsdk/fpdf_flatten.cpp +++ b/fpdfsdk/fpdf_flatten.cpp @@ -21,7 +21,6 @@ #include "constants/font_encodings.h" #include "constants/form_fields.h" #include "constants/page_object.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" #include "core/fpdfapi/page/cpdf_page.h" #include "core/fpdfapi/page/cpdf_pageobject.h" #include "core/fpdfapi/parser/cpdf_array.h" @@ -36,6 +35,7 @@ #include "core/fpdfdoc/cpdf_annot.h" #include "core/fxcrt/containers/contains.h" #include "core/fxcrt/fx_string_wrappers.h" +#include "core/fxcrt/stream_write_utils.h" #include "fpdfsdk/cpdfsdk_helpers.h" enum FPDF_TYPE { MAX, MIN }; diff --git a/fpdfsdk/fpdf_formfill.cpp b/fpdfsdk/fpdf_formfill.cpp index 451cdd55be..5e01cde1a7 100644 --- a/fpdfsdk/fpdf_formfill.cpp +++ b/fpdfsdk/fpdf_formfill.cpp @@ -236,7 +236,7 @@ void FFLCommon(FPDF_FORMHANDLE hHandle, CHECK(holder); } else { #if defined(PDF_USE_SKIA) - if (!CFX_GEModule::Get()->UseSkiaRenderer()) { + if (!CFX_GEModule::IsSkiaRenderer()) { return; } @@ -850,6 +850,12 @@ FPDF_RemoveFormFieldHighlight(FPDF_FORMHANDLE hHandle) { FPDF_EXPORT void FPDF_CALLCONV FORM_OnAfterLoadPage(FPDF_PAGE page, FPDF_FORMHANDLE hHandle) { + CPDFSDK_FormFillEnvironment* pFormFillEnv = + CPDFSDKFormFillEnvironmentFromFPDFFormHandle(hHandle); + if (!pFormFillEnv || pFormFillEnv->InPassiveFFICallback()) { + return; + } + if (CPDFSDK_PageView* pPageView = FormHandleToPageView(hHandle, page)) { pPageView->SetValid(true); } @@ -859,7 +865,7 @@ FPDF_EXPORT void FPDF_CALLCONV FORM_OnBeforeClosePage(FPDF_PAGE page, FPDF_FORMHANDLE hHandle) { CPDFSDK_FormFillEnvironment* pFormFillEnv = CPDFSDKFormFillEnvironmentFromFPDFFormHandle(hHandle); - if (!pFormFillEnv) { + if (!pFormFillEnv || pFormFillEnv->InPassiveFFICallback()) { return; } @@ -880,7 +886,11 @@ FPDF_EXPORT void FPDF_CALLCONV FORM_DoDocumentJSAction(FPDF_FORMHANDLE hHandle) { CPDFSDK_FormFillEnvironment* pFormFillEnv = CPDFSDKFormFillEnvironmentFromFPDFFormHandle(hHandle); - if (pFormFillEnv && pFormFillEnv->IsJSPlatformPresent()) { + if (!pFormFillEnv || pFormFillEnv->InPassiveFFICallback()) { + return; + } + + if (pFormFillEnv->IsJSPlatformPresent()) { pFormFillEnv->ProcJavascriptAction(); } } @@ -889,16 +899,18 @@ FPDF_EXPORT void FPDF_CALLCONV FORM_DoDocumentOpenAction(FPDF_FORMHANDLE hHandle) { CPDFSDK_FormFillEnvironment* pFormFillEnv = CPDFSDKFormFillEnvironmentFromFPDFFormHandle(hHandle); - if (pFormFillEnv) { - pFormFillEnv->ProcOpenAction(); + if (!pFormFillEnv || pFormFillEnv->InPassiveFFICallback()) { + return; } + + pFormFillEnv->ProcOpenAction(); } FPDF_EXPORT void FPDF_CALLCONV FORM_DoDocumentAAction(FPDF_FORMHANDLE hHandle, int aaType) { CPDFSDK_FormFillEnvironment* pFormFillEnv = CPDFSDKFormFillEnvironmentFromFPDFFormHandle(hHandle); - if (!pFormFillEnv) { + if (!pFormFillEnv || pFormFillEnv->InPassiveFFICallback()) { return; } @@ -920,7 +932,7 @@ FPDF_EXPORT void FPDF_CALLCONV FORM_DoPageAAction(FPDF_PAGE page, int aaType) { CPDFSDK_FormFillEnvironment* pFormFillEnv = CPDFSDKFormFillEnvironmentFromFPDFFormHandle(hHandle); - if (!pFormFillEnv) { + if (!pFormFillEnv || pFormFillEnv->InPassiveFFICallback()) { return; } diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp index 55ce265927..284d00bf27 100644 --- a/fpdfsdk/fpdf_formfill_embeddertest.cpp +++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp @@ -618,11 +618,21 @@ TEST_F(FPDFFormFillEmbedderTest, FirstTest) { EXPECT_CALL(mock, DoURIAction(_)).Times(0); EXPECT_CALL(mock, DoURIActionWithKeyboardModifier(_, _, _)).Times(0); EXPECT_CALL(mock, DoGoToAction(_, _, _, _, _)).Times(0); + EXPECT_CALL(mock, ExecuteNamedAction(_)).Times(0); ASSERT_TRUE(OpenDocument("hello_world.pdf")); ScopedPage page = LoadScopedPage(0); EXPECT_TRUE(page); } +TEST_F(FPDFFormFillEmbedderTest, OpenActionNamedAction) { + auto& mock = SetOwnedDelegate(); + EXPECT_CALL(mock, ExecuteNamedAction(StrEq("Print"))); + ASSERT_TRUE(OpenDocument("named_action.pdf")); + ScopedPage page = LoadScopedPage(0); + EXPECT_TRUE(page); + DoOpenActions(); +} + TEST_F(FPDFFormFillEmbedderTest, Bug487928) { auto& delegate = SetOwnedDelegate(); @@ -1425,8 +1435,8 @@ TEST_F(FPDFFormFillEmbedderTest, Bug1281) { ScopedFPDFBitmap bitmap_reverse_byte_order = RenderLoadedPageWithFlags(page.get(), FPDF_REVERSE_BYTE_ORDER); - CompareBitmapWithFuzzyExpectationSuffix(bitmap_reverse_byte_order.get(), - "bug_890322_reverse"); + CompareBitmapWithExpectationSuffix(bitmap_reverse_byte_order.get(), + "bug_890322_reverse", kFuzzyDiffOptions); } TEST_F(FPDFFormFillEmbedderTest, Bug1302455RenderOnly) { @@ -2067,6 +2077,29 @@ TEST_F(FPDFFormFillTextFormEmbedderTest, DeleteTextFieldEntireSelection) { EXPECT_EQ(Selection(), ""); } +TEST_F(FPDFFormFillTextFormEmbedderTest, + ControlCharacterChordsDoNotClearSelection) { + // Regression test for https://crbug.com/528342021. A modifier chord may + // produce a control character even when the modifier is not the platform + // shortcut key, e.g. Ctrl-C on Apple platforms. Such characters must not be + // typed into the field, where they would replace (and effectively clear) + // the current selection. + TypeTextIntoTextField(3, RegularFormBegin()); + SelectAllTextAtPoint(RegularFormBegin()); + EXPECT_EQ(FocusedFieldText(), "ABC"); + EXPECT_EQ(Selection(), "ABC"); + + // ETX (0x03), as produced by Ctrl-C. This chord is not the platform + // shortcut on Apple platforms. + FORM_OnChar(form_handle(), page(), 0x03, FWL_EVENTFLAG_ControlKey); + EXPECT_EQ(FocusedFieldText(), "ABC"); + + // The same character as produced by Cmd-C, which is not the platform + // shortcut on non-Apple platforms. + FORM_OnChar(form_handle(), page(), 0x03, FWL_EVENTFLAG_MetaKey); + EXPECT_EQ(FocusedFieldText(), "ABC"); +} + TEST_F(FPDFFormFillTextFormEmbedderTest, DeleteTextFieldSelectionMiddle) { // Select middle section of text. TypeTextIntoTextField(12, RegularFormBegin()); diff --git a/fpdfsdk/fpdf_progressive.cpp b/fpdfsdk/fpdf_progressive.cpp index 0a52bfe4c5..a0555b59fb 100644 --- a/fpdfsdk/fpdf_progressive.cpp +++ b/fpdfsdk/fpdf_progressive.cpp @@ -91,7 +91,7 @@ FPDF_RenderPageBitmapWithColorScheme_Start(FPDF_BITMAP bitmap, context->return_premultiplied_ = pBitmap->IsPremultiplied(); #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { pBitmap->PreMultiply(); } #endif @@ -100,8 +100,7 @@ FPDF_RenderPageBitmapWithColorScheme_Start(FPDF_BITMAP bitmap, pBitmap, !!(flags & FPDF_REVERSE_BYTE_ORDER)); if (!device) { #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer() && - !context->return_premultiplied_) { + if (CFX_GEModule::IsSkiaRenderer() && !context->return_premultiplied_) { pBitmap->UnPreMultiply(); } #endif // defined(PDF_USE_SKIA) @@ -116,8 +115,7 @@ FPDF_RenderPageBitmapWithColorScheme_Start(FPDF_BITMAP bitmap, if (!context->renderer_) { #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer() && - !context->return_premultiplied_) { + if (CFX_GEModule::IsSkiaRenderer() && !context->return_premultiplied_) { pBitmap->UnPreMultiply(); } #endif // defined(PDF_USE_SKIA) @@ -136,8 +134,7 @@ FPDF_RenderPageBitmapWithColorScheme_Start(FPDF_BITMAP bitmap, } #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer() && - !context->return_premultiplied_) { + if (CFX_GEModule::IsSkiaRenderer() && !context->return_premultiplied_) { pBitmap->UnPreMultiply(); } #endif // defined(PDF_USE_SKIA) @@ -186,8 +183,7 @@ FPDF_EXPORT int FPDF_CALLCONV FPDF_RenderPage_Continue(FPDF_PAGE page, } #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer() && - !context->return_premultiplied_) { + if (CFX_GEModule::IsSkiaRenderer() && !context->return_premultiplied_) { context->device_->GetBitmap()->UnPreMultiply(); } #endif // defined(PDF_USE_SKIA) diff --git a/fpdfsdk/fpdf_save_embeddertest.cpp b/fpdfsdk/fpdf_save_embeddertest.cpp index f6c6993925..105484fdb3 100644 --- a/fpdfsdk/fpdf_save_embeddertest.cpp +++ b/fpdfsdk/fpdf_save_embeddertest.cpp @@ -332,11 +332,18 @@ class FPDFSaveWithFontSubsetEmbedderTest : public FPDFSaveEmbedderTest { // "Goodbye, world!\r\n" 'G', 'o', 'o', 'd', 'b', 'y', 'e', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\r', '\n', - // "这是第一句。" - 0x8FD9, 0x662F, 0x7B2C, 0x2F00, 0x53E5, 0x3002}; + // "这是第一句。", where U+F906 is a compatibility ideograph for U+53E5. + // TODO(crbug.com/554790604): The CMap generated for the subset font maps + // the last two charcodes twice, so which unicode text extraction returns + // is decided by the tie-break rather than by the font. U+4E00 is the + // better answer of the two for the fourth character, U+53E5 for the + // fifth; no single tie-break rule produces both. + 0x8FD9, 0x662F, 0x7B2C, 0x4E00, 0xF906, 0x3002}; static constexpr std::array kExpectedSecondTextAdded = { - // "Hello again." - '\r', '\n', 'H', 'e', 'l', 'l', 'o', ' ', 'a', 'g', 'a', 'i', 'n', '.'}; + // "Hello again.", where the space is U+00A0 for the same reason as + // above: this font's generated CMap also maps that charcode twice. + '\r', '\n', 'H', 'e', 'l', 'l', 'o', + 0x00A0, 'a', 'g', 'a', 'i', 'n', '.'}; ScopedFPDFFont LoadTestFont(const std::string& font_path) { std::vector font_data = GetFileContents(font_path.c_str()); diff --git a/fpdfsdk/fpdf_structtree.cpp b/fpdfsdk/fpdf_structtree.cpp index 96455e726f..def28b9d4c 100644 --- a/fpdfsdk/fpdf_structtree.cpp +++ b/fpdfsdk/fpdf_structtree.cpp @@ -261,6 +261,21 @@ FPDF_StructElement_GetMarkedContentID(FPDF_STRUCTELEMENT struct_element) { return p && p->IsNumber() ? p->GetInteger() : -1; } +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDF_StructElement_GetRawType(FPDF_STRUCTELEMENT struct_element, + FPDF_WCHAR* buffer, + size_t buflen) { + CPDF_StructElement* elem = + CPDFStructElementFromFPDFStructElement(struct_element); + if (!elem) { + return 0; + } + // SAFETY: required from caller. + return UNSAFE_BUFFERS(WideStringToBuffer( + WideString::FromUTF8(elem->GetRawType().AsStringView()), buffer, + pdfium::checked_cast(buflen))); +} + FPDF_EXPORT unsigned long FPDF_CALLCONV FPDF_StructElement_GetType(FPDF_STRUCTELEMENT struct_element, void* buffer, diff --git a/fpdfsdk/fpdf_structtree_embeddertest.cpp b/fpdfsdk/fpdf_structtree_embeddertest.cpp index bd6cc8d781..ae94b0eec7 100644 --- a/fpdfsdk/fpdf_structtree_embeddertest.cpp +++ b/fpdfsdk/fpdf_structtree_embeddertest.cpp @@ -573,6 +573,74 @@ TEST_F(FPDFStructTreeEmbedderTest, GetParent) { } } +TEST_F(FPDFStructTreeEmbedderTest, GetRawTypeValue) { + ASSERT_TRUE(OpenDocument("raw_type_text.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + { + ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get())); + ASSERT_TRUE(struct_tree); + ASSERT_EQ(4, FPDF_StructTree_CountChildren(struct_tree.get())); + + FPDF_WCHAR buffer[18]; + ASSERT_EQ(0U, + FPDF_StructElement_GetRawType(nullptr, buffer, sizeof(buffer))); + ASSERT_EQ(0U, FPDF_StructElement_GetRawType(nullptr, nullptr, 0)); + + // The raw type (/S) value is valid. + FPDF_STRUCTELEMENT element1 = + FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0); + ASSERT_TRUE(element1); + + ASSERT_EQ(18U, FPDF_StructElement_GetRawType(element1, nullptr, 0)); + + // Deliberately pass in a buffer size too small to make sure `buffer` + // remains untouched. + std::ranges::fill(buffer, 0xabcd); + ASSERT_EQ(18U, FPDF_StructElement_GetRawType(element1, buffer, 1)); + for (const auto b : buffer) { + EXPECT_EQ(0xabcd, b); + } + + ASSERT_EQ(18U, + FPDF_StructElement_GetRawType(element1, buffer, sizeof(buffer))); + EXPECT_EQ(L"Workbook", GetPlatformWString(buffer)); + + // The type value is a number. + FPDF_STRUCTELEMENT element2 = + FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 1); + ASSERT_TRUE(element2); + + // For this 2 digit number, 0 is returned. + ASSERT_EQ(0U, + FPDF_StructElement_GetRawType(element2, buffer, sizeof(buffer))); + + // There is no type value. + FPDF_STRUCTELEMENT element3 = + FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 2); + ASSERT_TRUE(element3); + + std::ranges::fill(buffer, 0xabcd); + ASSERT_EQ(0U, FPDF_StructElement_GetRawType(element3, nullptr, 0)); + ASSERT_EQ(0U, + FPDF_StructElement_GetRawType(element3, buffer, sizeof(buffer))); + for (const auto b : buffer) { + EXPECT_EQ(0xabcd, b); + } + + // The type value is the same as one of the types returned by GetType(). + FPDF_STRUCTELEMENT element4 = + FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 3); + ASSERT_TRUE(element4); + + ASSERT_EQ(10U, FPDF_StructElement_GetRawType(element4, nullptr, 0)); + ASSERT_EQ(10U, + FPDF_StructElement_GetRawType(element4, buffer, sizeof(buffer))); + EXPECT_EQ(L"Part", GetPlatformWString(buffer)); + } +} + TEST_F(FPDFStructTreeEmbedderTest, GetTitle) { ASSERT_TRUE(OpenDocument("tagged_alt_text.pdf")); ScopedPage page = LoadScopedPage(0); diff --git a/fpdfsdk/fpdf_text_embeddertest.cpp b/fpdfsdk/fpdf_text_embeddertest.cpp index c991d02e0b..697b0803d6 100644 --- a/fpdfsdk/fpdf_text_embeddertest.cpp +++ b/fpdfsdk/fpdf_text_embeddertest.cpp @@ -2507,13 +2507,10 @@ TEST_F(FPDFTextEmbedderTest, Bug491516663) { TEST_F(FPDFTextEmbedderTest, Bug554790604) { // The document's ToUnicode CMap is a catch-all bfrange followed by overrides - // for individual codes. Per Adobe TN #5014 the later entries should win, so - // the page should read as U+00F3, U+00BA, U+20AC, U+00ED, U+00BA, 'X', 'A', - // 'B', U+00E9, U+00EA. The lowest mapping wins instead, so every code whose - // target is above it keeps the range's identity mapping, and 0x9B becomes an - // unprintable C1 control character. + // for individual codes. Per Adobe TN #5014 the later entries win, so the page + // reads as accented characters rather than the range's identity mapping. static constexpr std::array kExpectedChars = { - 'A', 'B', 'C', 'U', 0x009b, 'X', 'A', 'B', 'p', 'q', 0}; + 0x00f3, 0x00ba, 0x20ac, 0x00ed, 0x00ba, 'X', 'A', 'B', 0x00e9, 0x00ea, 0}; ASSERT_TRUE(OpenDocument("bug_554790604.pdf")); ScopedPage page = LoadScopedPage(0); @@ -2555,10 +2552,11 @@ TEST_F(FPDFTextEmbedderTest, ActualTextRtl) { // שלום (logical order): 0x05e9, 0x05dc, 0x05d5, 0x05dd, '\r', '\n', // Predominantly RTL case: - // שלום (logical order): - 0x05e9, 0x05dc, 0x05d5, 0x05dd, ' ', 'H', 'a', ' ', + // TODO(crbug.com/525087036): The two RTL segments should trade places. // מים (logical order): - 0x05de, 0x05d9, 0x05dd, '\r', '\n', + 0x05de, 0x05d9, 0x05dd, ' ', 'H', 'a', ' ', + // שלום (logical order): + 0x05e9, 0x05dc, 0x05d5, 0x05dd, '\r', '\n', // Tie-Breaker case: 'H', 'e', ' ', // שלום (logical order): @@ -2589,9 +2587,8 @@ TEST_F(FPDFTextEmbedderTest, ActualTextRtl) { // מים (logical order): 0x05de, 0x05d9, 0x05dd, '\r', '\n', // Literal RTL text followed by RTL /ActualText case: - // TODO(crbug.com/525087036): Should be in logical order. - // םולש (/ActualText): - 0x05dd, 0x05d5, 0x05dc, 0x05e9, + // שלום (/ActualText, logical order): + 0x05e9, 0x05dc, 0x05d5, 0x05dd, // בן (literal text): 0x05d1, 0x05df, '\0'}); static constexpr int kExpectedTextSize = std::size(kExpectedText); @@ -2602,3 +2599,23 @@ TEST_F(FPDFTextEmbedderTest, ActualTextRtl) { EXPECT_THAT(pdfium::span(buffer).first(), ElementsAreArray(kExpectedText)); } + +TEST_F(FPDFTextEmbedderTest, Arabic) { + ASSERT_TRUE(OpenDocument("arabic.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + ScopedFPDFTextPage text_page(FPDFText_LoadPage(page.get())); + ASSERT_TRUE(text_page); + + static constexpr auto kExpectedText = std::to_array( + {0x0627, 0x0644, 0x0628, 0x062d, 0x0631, ' ', 0x0627, 0x0644, 0x0623, + 0x0632, 0x0631, 0x0642, '\0'}); + static constexpr int kExpectedTextSize = std::size(kExpectedText); + + unsigned short buffer[256] = {}; + EXPECT_EQ(kExpectedTextSize, + FPDFText_GetText(text_page.get(), 0, std::size(buffer), buffer)); + EXPECT_THAT(pdfium::span(buffer).first(), + ElementsAreArray(kExpectedText)); +} diff --git a/fpdfsdk/fpdf_thumbnail_embeddertest.cpp b/fpdfsdk/fpdf_thumbnail_embeddertest.cpp index 18f3522271..d5bd1a9488 100644 --- a/fpdfsdk/fpdf_thumbnail_embeddertest.cpp +++ b/fpdfsdk/fpdf_thumbnail_embeddertest.cpp @@ -168,7 +168,12 @@ TEST_F(FPDFThumbnailEmbedderTest, GetThumbnailAsBitmapFromPage) { ScopedFPDFBitmap thumb_bitmap(FPDFPage_GetThumbnailAsBitmap(page.get())); EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(thumb_bitmap.get())); +#if defined(PDF_ENABLE_RUST_JPEG) + CompareBitmapWithExpectationSuffix(thumb_bitmap.get(), "simple_thumbnail0", + kFuzzyDiffOptions); +#else CompareBitmap(thumb_bitmap.get(), "simple_thumbnail0"); +#endif } { @@ -178,7 +183,12 @@ TEST_F(FPDFThumbnailEmbedderTest, GetThumbnailAsBitmapFromPage) { ScopedFPDFBitmap thumb_bitmap(FPDFPage_GetThumbnailAsBitmap(page.get())); EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(thumb_bitmap.get())); +#if defined(PDF_ENABLE_RUST_JPEG) + CompareBitmapWithExpectationSuffix(thumb_bitmap.get(), "simple_thumbnail1", + kFuzzyDiffOptions); +#else CompareBitmap(thumb_bitmap.get(), "simple_thumbnail1"); +#endif } } @@ -238,7 +248,12 @@ TEST_F(FPDFThumbnailEmbedderTest, GetThumbnailDoesNotAlterPage) { ScopedFPDFBitmap thumb_bitmap(FPDFPage_GetThumbnailAsBitmap(page.get())); EXPECT_EQ(FPDFBitmap_BGR, FPDFBitmap_GetFormat(thumb_bitmap.get())); +#if defined(PDF_ENABLE_RUST_JPEG) + CompareBitmapWithExpectationSuffix(thumb_bitmap.get(), "simple_thumbnail0", + kFuzzyDiffOptions); +#else CompareBitmap(thumb_bitmap.get(), "simple_thumbnail0"); +#endif // Get the raw data again unsigned long new_raw_size = diff --git a/fpdfsdk/fpdf_transformpage.cpp b/fpdfsdk/fpdf_transformpage.cpp index 0ce0bb5d95..bac53b588e 100644 --- a/fpdfsdk/fpdf_transformpage.cpp +++ b/fpdfsdk/fpdf_transformpage.cpp @@ -10,7 +10,6 @@ #include #include "constants/page_object.h" -#include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" #include "core/fpdfapi/page/cpdf_clippath.h" #include "core/fpdfapi/page/cpdf_page.h" #include "core/fpdfapi/page/cpdf_pageobject.h" @@ -25,6 +24,7 @@ #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/span.h" #include "core/fxcrt/stl_util.h" +#include "core/fxcrt/stream_write_utils.h" #include "core/fxge/cfx_fillrenderoptions.h" #include "core/fxge/cfx_path.h" #include "fpdfsdk/cpdfsdk_helpers.h" diff --git a/fpdfsdk/fpdf_view.cpp b/fpdfsdk/fpdf_view.cpp index 348b0e265c..0d3ec19a0f 100644 --- a/fpdfsdk/fpdf_view.cpp +++ b/fpdfsdk/fpdf_view.cpp @@ -241,15 +241,15 @@ FPDF_InitLibraryWithConfig(const FPDF_LIBRARY_CONFIG* config) { CFX_FontMgr::FontBackend backend = CFX_FontMgr::FontBackend::kFreeType; if (config && config->version >= 5) { -#if defined(PDF_USE_SKIA) +#if defined(PDF_ENABLE_FONTATIONS) CHECK(config->m_FontLibraryType == FPDF_FONTBACKENDTYPE_FREETYPE || config->m_FontLibraryType == FPDF_FONTBACKENDTYPE_FONTATIONS); if (config->m_FontLibraryType == FPDF_FONTBACKENDTYPE_FONTATIONS) { - CHECK_EQ(renderer_type, CFX_GEModule::RendererType::kSkia); backend = CFX_FontMgr::FontBackend::kFontations; } #else - // AGG-only builds should always use `FPDF_FONTBACKENDTYPE_FREETYPE`. + // Builds without Fontations should always use + // `FPDF_FONTBACKENDTYPE_FREETYPE`. CHECK_EQ(config->m_FontLibraryType, FPDF_FONTBACKENDTYPE_FREETYPE); #endif } @@ -282,8 +282,13 @@ FPDF_InitLibraryWithConfig(const FPDF_LIBRARY_CONFIG* config) { if (config && config->version >= 2) { void* platform = config->version >= 3 ? config->m_pPlatform : nullptr; + const bool isolate_per_document = + config->version >= 7 && config->m_IsolatePerDocument; + if (isolate_per_document) { + CHECK(!config->m_pIsolate); + } IJS_Runtime::Initialize(config->m_v8EmbedderSlot, config->m_pIsolate, - platform); + platform, isolate_per_document); } g_bLibraryInitialized = true; } @@ -1165,7 +1170,7 @@ FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_RenderPage(HDC dc, return false; } #if defined(PDF_USE_SKIA) - if (!CFX_GEModule::Get()->UseSkiaRenderer()) { + if (!CFX_GEModule::IsSkiaRenderer()) { // Not needed by Skia. Call it for AGG to preserve pre-existing behavior. pBitmap->Clear(0x00ffffff); } @@ -1738,8 +1743,8 @@ FPDF_EXPORT int FPDF_CALLCONV FPDFBitmap_GetFormat(FPDF_BITMAP bitmap) { return FPDFBitmap_BGRA; #if defined(PDF_USE_SKIA) case FXDIB_Format::kBgraPremul: - return CFX_GEModule::Get()->UseSkiaRenderer() ? FPDFBitmap_BGRA_Premul - : FPDFBitmap_Unknown; + return CFX_GEModule::IsSkiaRenderer() ? FPDFBitmap_BGRA_Premul + : FPDFBitmap_Unknown; #endif default: return FPDFBitmap_Unknown; @@ -2268,11 +2273,7 @@ FPDF_EXPORT const char* FPDF_CALLCONV FPDF_GetRecommendedV8Flags() { } FPDF_EXPORT void* FPDF_CALLCONV FPDF_GetArrayBufferAllocatorSharedInstance() { - // Deliberately leaked. This allocator is used outside of the library - // initialization / destruction lifecycle, and the caller does not take - // ownership of the object. Thus there is no existing way to delete this. - static auto* s_allocator = new CFX_V8ArrayBufferAllocator(); - return s_allocator; + return CFX_V8ArrayBufferAllocator::GetSharedInstance(); } #endif // PDF_ENABLE_V8 diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c index b51a810876..d67d4fbb45 100644 --- a/fpdfsdk/fpdf_view_c_api_test.c +++ b/fpdfsdk/fpdf_view_c_api_test.c @@ -108,6 +108,7 @@ int CheckPDFiumCApi() { CHK(FPDFPage_RemoveAnnot); // fpdf_attachment.h + CHK(FPDFAttachment_GetAFRelationship); CHK(FPDFAttachment_GetDescription); CHK(FPDFAttachment_GetFile); CHK(FPDFAttachment_GetName); @@ -231,6 +232,8 @@ int CheckPDFiumCApi() { CHK(FPDFPageObj_GetMark); CHK(FPDFPageObj_GetMarkedContentID); CHK(FPDFPageObj_GetMatrix); + CHK(FPDFPageObj_GetRenderedFillPattern); + CHK(FPDFPageObj_GetRenderedStrokePattern); CHK(FPDFPageObj_GetRotatedBounds); CHK(FPDFPageObj_GetStrokeColor); CHK(FPDFPageObj_GetStrokeWidth); @@ -416,6 +419,7 @@ int CheckPDFiumCApi() { CHK(FPDF_StructElement_GetMarkedContentIdCount); CHK(FPDF_StructElement_GetObjType); CHK(FPDF_StructElement_GetParent); + CHK(FPDF_StructElement_GetRawType); CHK(FPDF_StructElement_GetStringAttribute); CHK(FPDF_StructElement_GetTitle); CHK(FPDF_StructElement_GetType); diff --git a/fpdfsdk/fpdf_view_embeddertest.cpp b/fpdfsdk/fpdf_view_embeddertest.cpp index f7b44ba3a7..ea9ff04064 100644 --- a/fpdfsdk/fpdf_view_embeddertest.cpp +++ b/fpdfsdk/fpdf_view_embeddertest.cpp @@ -302,11 +302,9 @@ class FPDFViewEmbedderTest : public EmbedderTest { std::string_view expectation_png_name, bool fuzzy = false) { RenderPageToBitmap(page, bitmap); - if (fuzzy) { - CompareBitmapWithFuzzyExpectationSuffix(bitmap, expectation_png_name); - } else { - CompareBitmapWithExpectationSuffix(bitmap, expectation_png_name); - } + CompareBitmapWithExpectationSuffix( + bitmap, expectation_png_name, + fuzzy ? kFuzzyDiffOptions : kExactDiffOptions); } }; @@ -1595,7 +1593,7 @@ TEST_F(FPDFViewEmbedderTest, RenderManyRectanglesWithAndWithoutExternalMemory) { pdfium::kManyRectanglesPng); #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { TestRenderPageBitmapWithInternalMemory(page.get(), FPDFBitmap_BGRA_Premul, pdfium::kManyRectanglesPng); TestRenderPageBitmapWithInternalMemoryAndStride( @@ -1643,13 +1641,13 @@ TEST_F(FPDFViewEmbedderTest, RenderHelloWorldWithFlags) { } // Deliberately disabled because this test case renders a large bitmap, which is -// very slow for debug builds. -#if defined(NDEBUG) +// very slow for debug and AddressSanitizer builds. +#if defined(NDEBUG) && !defined(ADDRESS_SANITIZER) #define MAYBE_LargeImageDoesNotRenderBlank LargeImageDoesNotRenderBlank #else #define MAYBE_LargeImageDoesNotRenderBlank DISABLED_LargeImageDoesNotRenderBlank #endif -TEST_F(FPDFViewEmbedderTest, LargeImageDoesNotRenderBlank) { +TEST_F(FPDFViewEmbedderTest, MAYBE_LargeImageDoesNotRenderBlank) { static constexpr char kFilename[] = "bug_1646"; ASSERT_TRUE(OpenDocument("bug_1646.pdf")); @@ -1971,7 +1969,7 @@ TEST_F(FPDFViewEmbedderTest, RenderXfaPage) { #if defined(PDF_USE_SKIA) TEST_F(FPDFViewEmbedderTest, RenderPageToSkp) { - if (!CFX_GEModule::Get()->UseSkiaRenderer()) { + if (!CFX_GEModule::IsSkiaRenderer()) { GTEST_SKIP() << "FPDF_RenderPageSkp() only makes sense with Skia"; } @@ -1984,7 +1982,7 @@ TEST_F(FPDFViewEmbedderTest, RenderPageToSkp) { } TEST_F(FPDFViewEmbedderTest, RenderXfaPageToSkp) { - if (!CFX_GEModule::Get()->UseSkiaRenderer()) { + if (!CFX_GEModule::IsSkiaRenderer()) { GTEST_SKIP() << "FPDF_RenderPageSkp() only makes sense with Skia"; } @@ -2045,7 +2043,34 @@ TEST_F(FPDFViewEmbedderTest, Bug2087) { } #endif // defined(PDF_USE_SKIA) +TEST_F(FPDFViewEmbedderTest, BadIsolatePerDocumentConfig) { + FPDF_DestroyLibrary(); + + const FPDF_LIBRARY_CONFIG kBadConfig = { + .version = 7, + .m_pUserFontPaths = nullptr, + .m_pIsolate = reinterpret_cast(1), + .m_v8EmbedderSlot = 0, + .m_pPlatform = nullptr, + .m_RendererType = FPDF_RENDERERTYPE_AGG, + .m_FontLibraryType = FPDF_FONTBACKENDTYPE_FREETYPE, + .m_BrotliEnabled = false, + .m_IsolatePerDocument = true, + }; + EXPECT_DEATH_IF_SUPPORTED(FPDF_InitLibraryWithConfig(&kBadConfig), ""); + + EmbedderTestEnvironment::GetInstance()->TearDown(); + EmbedderTestEnvironment::GetInstance()->SetUp(); +} + TEST_F(FPDFViewEmbedderTest, NoSmoothTextItalicOverlappingGlyphs) { + if (EmbedderTestEnvironment::GetInstance()->fontations()) { + // Monochrome 1-bit text rendering (FPDF_RENDER_NO_SMOOTHTEXT) produces + // binary 0/255 pixel transitions at glyph boundaries where subpixel contour + // differences cross pixel centers, which cannot be fuzzy matched. + // TODO(crbug.com/42271123): fix as part of full fontations support. + GTEST_SKIP() << "Fontations subpixel monochrome variance"; + } ASSERT_TRUE(OpenDocument("bug_1919.pdf")); ScopedPage page = LoadScopedPage(0); ASSERT_TRUE(page); @@ -2138,7 +2163,7 @@ TEST_F(FPDFViewEmbedderTest, BitmapBGRAPremulFormat) { ScopedFPDFBitmap bitmap( FPDFBitmap_CreateEx(kWidth, kHeight, FPDFBitmap_BGRA_Premul, nullptr, 0)); #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { ASSERT_TRUE(bitmap); EXPECT_EQ(FPDFBitmap_BGRA_Premul, FPDFBitmap_GetFormat(bitmap.get())); return; diff --git a/fpdfsdk/fpdfxfa/BUILD.gn b/fpdfsdk/fpdfxfa/BUILD.gn index 64b18aa367..1c202f1dc4 100644 --- a/fpdfsdk/fpdfxfa/BUILD.gn +++ b/fpdfsdk/fpdfxfa/BUILD.gn @@ -46,6 +46,7 @@ pdfium_embeddertest_source_set("embeddertests") { sources = [ "cpdfxfa_context_embeddertest.cpp", "cpdfxfa_docenvironment_embeddertest.cpp", + "cpdfxfa_formfillenv_embeddertest.cpp", ] configs = [ "//v8:external_startup_data" ] deps = [ diff --git a/fpdfsdk/fpdfxfa/DEPS b/fpdfsdk/fpdfxfa/DEPS index 5ba75f2f2b..115554c644 100644 --- a/fpdfsdk/fpdfxfa/DEPS +++ b/fpdfsdk/fpdfxfa/DEPS @@ -1,5 +1,6 @@ include_rules = [ '+fxjs', '+fxbarcode', + '+v8', '+xfa/fgas/font', ] diff --git a/fpdfsdk/fpdfxfa/cpdfxfa_context.cpp b/fpdfsdk/fpdfxfa/cpdfxfa_context.cpp index e9ce2bb674..75d723a40b 100644 --- a/fpdfsdk/fpdfxfa/cpdfxfa_context.cpp +++ b/fpdfsdk/fpdfxfa/cpdfxfa_context.cpp @@ -23,14 +23,19 @@ #include "core/fxcrt/xml/cfx_xmldocument.h" #include "core/fxcrt/xml/cfx_xmlparser.h" #include "fpdfsdk/cpdfsdk_formfillenvironment.h" +#include "fpdfsdk/cpdfsdk_helpers.h" #include "fpdfsdk/cpdfsdk_pageview.h" #include "fpdfsdk/fpdfxfa/cpdfxfa_docenvironment.h" #include "fpdfsdk/fpdfxfa/cpdfxfa_page.h" #include "fxbarcode/BC_Library.h" +#include "fxjs/cfxjs_engine.h" #include "fxjs/cjs_runtime.h" #include "fxjs/ijs_runtime.h" #include "public/fpdf_formfill.h" +#include "public/fpdfview.h" #include "v8/include/cppgc/allocation.h" +#include "v8/include/v8-cppgc.h" +#include "v8/include/v8-isolate.h" #include "xfa/fgas/font/cfgas_gemodule.h" #include "xfa/fxfa/cxfa_eventparam.h" #include "xfa/fxfa/cxfa_ffapp.h" @@ -110,10 +115,21 @@ void CPDFXFA_ModuleDestroy() { CPDFXFA_Context::CPDFXFA_Context(CPDF_Document* pPDFDoc) : pdfdoc_(pPDFDoc), - doc_env_(std::make_unique(this)), - gc_heap_(FXGC_CreateHeap()) { + doc_env_(std::make_unique(this)) { DCHECK(pdfdoc_); + if (IJS_Runtime::IsIsolatePerDocument()) { + v8::Isolate::CreateParams params; + params.array_buffer_allocator = static_cast( + FPDF_GetArrayBufferAllocatorSharedInstance()); + params.cpp_heap = FXGC_CreateCppHeap(nullptr).release(); + isolate_.reset(v8::Isolate::New(params)); + CFXJS_PerIsolateData::GetOrCreate(isolate_.get()); + gc_heap_ = FXGC_CreateHeap(isolate_.get()); + } else { + gc_heap_ = FXGC_CreateHeap(nullptr); + } + // There might not be a heap when JS not initialized. if (gc_heap_) { xfaapp_ = cppgc::MakeGarbageCollected( @@ -123,16 +139,28 @@ CPDFXFA_Context::CPDFXFA_Context(CPDF_Document* pPDFDoc) CPDFXFA_Context::~CPDFXFA_Context() { load_status_ = LoadStatus::kClosing; + if (xfadoc_ && xfadoc_->GetXFADoc()) { + xfadoc_->GetXFADoc()->ClearLayoutData(); + xfadoc_view_.Clear(); + xfadoc_.Clear(); + xfaapp_.Clear(); + FXGC_ForceGarbageCollection(gc_heap_.get()); + } if (form_fill_env_) { - form_fill_env_->ClearAllFocusedAnnots(); + form_fill_env_->OnDetachFromDocument(); } } void CPDFXFA_Context::SetFormFillEnv( CPDFSDK_FormFillEnvironment* pFormFillEnv) { - // The layout data can have pointers back into the script context. That - // context will be different if the form fill environment closes, so, force - // the layout data to clear. + // The layout data can have pointers back into the script context, which + // will be different if the form fill environment closes, so the XFA object + // graph must go. Clearing the layout data destroys that script context, + // whose cppgc::Persistent members otherwise root the very graph being + // discarded, leaving the collection below nothing to do. All of it has to + // happen here, while the outgoing environment and its CJS_Runtime are + // still alive for the finalizers to reach; waiting until context teardown + // comes far too late because the environment may be long gone by then. if (xfadoc_ && xfadoc_->GetXFADoc()) { xfadoc_->GetXFADoc()->ClearLayoutData(); xfadoc_view_.Clear(); @@ -141,6 +169,10 @@ void CPDFXFA_Context::SetFormFillEnv( FXGC_ForceGarbageCollection(gc_heap_.get()); } form_fill_env_.Reset(pFormFillEnv); + if (form_fill_env_ && gc_heap_ && !xfaapp_) { + xfaapp_ = cppgc::MakeGarbageCollected( + gc_heap_->GetAllocationHandle(), this); + } } bool CPDFXFA_Context::LoadXFADoc() { @@ -192,7 +224,12 @@ bool CPDFXFA_Context::LoadXFADoc() { xfadoc_view_ = xfadoc_->CreateDocView(); if (xfadoc_view_->StartLayout() < 0) { + // Drop every root before collecting: the script context holds the graph + // alive, and the AutoNullers above do not fire until this function + // returns, so the collection would otherwise find nothing to do. xfadoc_->GetXFADoc()->ClearLayoutData(); + xfadoc_view_.Clear(); + xfadoc_.Clear(); FXGC_ForceGarbageCollection(gc_heap_.get()); FXSYS_SetLastError(FPDF_ERR_XFALAYOUT); return false; @@ -423,7 +460,7 @@ CFX_Timer::HandlerIface* CPDFXFA_Context::GetTimerHandler() const { return form_fill_env_ ? form_fill_env_->GetTimerHandler() : nullptr; } -cppgc::Heap* CPDFXFA_Context::GetGCHeap() const { +FXGC_Heap* CPDFXFA_Context::GetGCHeap() const { return gc_heap_.get(); } diff --git a/fpdfsdk/fpdfxfa/cpdfxfa_context.h b/fpdfsdk/fpdfxfa/cpdfxfa_context.h index 2434b4502f..f3f1b7acb2 100644 --- a/fpdfsdk/fpdfxfa/cpdfxfa_context.h +++ b/fpdfsdk/fpdfxfa/cpdfxfa_context.h @@ -19,6 +19,7 @@ #include "core/fxcrt/unowned_ptr.h" #include "fpdfsdk/cpdfsdk_formfillenvironment.h" #include "fpdfsdk/fpdfxfa/cpdfxfa_page.h" +#include "fxjs/cfx_isolate_wrapper.h" #include "fxjs/gc/heap.h" #include "v8/include/cppgc/persistent.h" #include "xfa/fxfa/cxfa_ffapp.h" @@ -55,6 +56,7 @@ class CPDFXFA_Context final : public CPDF_Document::Extension, } CPDF_Document* GetPDFDoc() const { return pdfdoc_; } + v8::Isolate* GetIsolate() const { return isolate_.get(); } CFX_XMLDocument* GetXMLDoc() { return xml_.get(); } CXFA_FFDoc* GetXFADoc() { return xfadoc_; } CXFA_FFDocView* GetXFADocView() const { return xfadoc_view_.Get(); } @@ -101,7 +103,7 @@ class CPDFXFA_Context final : public CPDF_Document::Extension, const WideString& wsData, const WideString& wsEncode) override; CFX_Timer::HandlerIface* GetTimerHandler() const override; - cppgc::Heap* GetGCHeap() const override; + FXGC_Heap* GetGCHeap() const override; bool SaveDatasetsPackage(const RetainPtr& pStream); bool SaveFormPackage(const RetainPtr& pStream); @@ -124,13 +126,18 @@ class CPDFXFA_Context final : public CPDF_Document::Extension, ObservedPtr form_fill_env_; std::vector> xfa_page_list_; - // Can't outlive |form_fill_env_|. + // Can't outlive `form_fill_env_`. std::unique_ptr doc_env_; + // `gc_heap_` must outlive `isolate_`: destroying `isolate_` terminates its + // attached CppHeap and runs finalizers on any remaining XFA objects, which + // hold UnownedPtr back to `gc_heap_`. In standalone mode, + // `isolate_` is null and ~FXGC_Heap() collects before `gc_heap_` dies. FXGCScopedHeap gc_heap_; - cppgc::Persistent xfaapp_; // can't outlive |gc_heap_| - cppgc::Persistent xfadoc_; // Can't outlive |gc_heap_| - cppgc::Persistent xfadoc_view_; // Can't outlive |gc_heap_| + std::unique_ptr isolate_; + cppgc::Persistent xfaapp_; // can't outlive `gc_heap_` + cppgc::Persistent xfadoc_; // Can't outlive `gc_heap_` + cppgc::Persistent xfadoc_view_; // Can't outlive `gc_heap_` }; #endif // FPDFSDK_FPDFXFA_CPDFXFA_CONTEXT_H_ diff --git a/fpdfsdk/fpdfxfa/cpdfxfa_context_embeddertest.cpp b/fpdfsdk/fpdfxfa/cpdfxfa_context_embeddertest.cpp index 73fc27a61f..251ad38757 100644 --- a/fpdfsdk/fpdfxfa/cpdfxfa_context_embeddertest.cpp +++ b/fpdfsdk/fpdfxfa/cpdfxfa_context_embeddertest.cpp @@ -5,16 +5,95 @@ #include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" #include "fpdfsdk/cpdfsdk_helpers.h" +#include "fxjs/ijs_runtime.h" +#include "public/fpdfview.h" +#include "testing/embedder_test_environment.h" #include "testing/gtest/include/gtest/gtest.h" +#include "testing/v8_test_environment.h" #include "testing/xfa_js_embedder_test.h" class CPDFXFAContextEmbedderTest : public XFAJSEmbedderTest {}; -// Should not crash. +// Should not crash. Uses external unattached isolate from test environment. TEST_F(CPDFXFAContextEmbedderTest, HasHeap) { + ASSERT_FALSE(IJS_Runtime::IsIsolatePerDocument()); ASSERT_TRUE(OpenDocument("simple_xfa.pdf")); CPDF_Document* pDocument = CPDFDocumentFromFPDFDocument(document()); auto* context = static_cast(pDocument->GetExtension()); - EXPECT_TRUE(context->GetGCHeap()); + ASSERT_TRUE(context); + EXPECT_FALSE(context->GetIsolate()); + ASSERT_TRUE(context->GetGCHeap()); + EXPECT_FALSE(context->GetGCHeap()->GetAttachedHeap()); + EXPECT_TRUE(context->GetGCHeap()->GetStandaloneHeap()); +} + +TEST_F(CPDFXFAContextEmbedderTest, UnattachedPDFiumCreatedIsolate) { + FPDF_DestroyLibrary(); + + const FPDF_LIBRARY_CONFIG kConfig = { + .version = 7, + .m_pUserFontPaths = nullptr, + .m_pIsolate = nullptr, + .m_v8EmbedderSlot = 0, + .m_pPlatform = V8TestEnvironment::GetInstance()->platform(), + .m_RendererType = FPDF_RENDERERTYPE_AGG, + .m_FontLibraryType = FPDF_FONTBACKENDTYPE_FREETYPE, + .m_BrotliEnabled = false, + .m_IsolatePerDocument = false, + }; + FPDF_InitLibraryWithConfig(&kConfig); + ASSERT_FALSE(IJS_Runtime::IsIsolatePerDocument()); + + ASSERT_TRUE(OpenDocument("simple_xfa.pdf")); + + CPDF_Document* pDocument = CPDFDocumentFromFPDFDocument(document()); + auto* context = static_cast(pDocument->GetExtension()); + ASSERT_TRUE(context); + EXPECT_FALSE(context->GetIsolate()); + ASSERT_TRUE(context->GetGCHeap()); + EXPECT_FALSE(context->GetGCHeap()->GetAttachedHeap()); + EXPECT_TRUE(context->GetGCHeap()->GetStandaloneHeap()); + + CloseDocument(); + + FPDF_DestroyLibrary(); + + EmbedderTestEnvironment::GetInstance()->TearDown(); + EmbedderTestEnvironment::GetInstance()->SetUp(); +} + +TEST_F(CPDFXFAContextEmbedderTest, IsolatePerDocument) { + FPDF_DestroyLibrary(); + + const FPDF_LIBRARY_CONFIG kConfig = { + .version = 7, + .m_pUserFontPaths = nullptr, + .m_pIsolate = nullptr, + .m_v8EmbedderSlot = 0, + .m_pPlatform = V8TestEnvironment::GetInstance()->platform(), + .m_RendererType = FPDF_RENDERERTYPE_AGG, + .m_FontLibraryType = FPDF_FONTBACKENDTYPE_FREETYPE, + .m_BrotliEnabled = false, + .m_IsolatePerDocument = true, + }; + FPDF_InitLibraryWithConfig(&kConfig); + ASSERT_TRUE(IJS_Runtime::IsIsolatePerDocument()); + + ASSERT_TRUE(OpenDocument("simple_xfa.pdf")); + + CPDF_Document* pDocument = CPDFDocumentFromFPDFDocument(document()); + auto* context = static_cast(pDocument->GetExtension()); + ASSERT_TRUE(context); + EXPECT_TRUE(context->GetIsolate()); + ASSERT_TRUE(context->GetGCHeap()); + EXPECT_TRUE(context->GetGCHeap()->GetAttachedHeap()); + EXPECT_FALSE(context->GetGCHeap()->GetStandaloneHeap()); + + CloseDocument(); + + FPDF_DestroyLibrary(); + + EmbedderTestEnvironment::GetInstance()->TearDown(); + EmbedderTestEnvironment::GetInstance()->SetUp(); } diff --git a/fpdfsdk/fpdfxfa/cpdfxfa_formfillenv_embeddertest.cpp b/fpdfsdk/fpdfxfa/cpdfxfa_formfillenv_embeddertest.cpp new file mode 100644 index 0000000000..26a5008a0e --- /dev/null +++ b/fpdfsdk/fpdfxfa/cpdfxfa_formfillenv_embeddertest.cpp @@ -0,0 +1,66 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "fxjs/ijs_runtime.h" +#include "public/fpdf_formfill.h" +#include "public/fpdfview.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/utils/path_service.h" +#include "testing/xfa_js_embedder_test.h" + +class CPDFXFAFormFillEnvEmbedderTest : public XFAJSEmbedderTest {}; + +TEST_F(CPDFXFAFormFillEnvEmbedderTest, ReinitFormFillEnvironment) { + ASSERT_FALSE(IJS_Runtime::IsIsolatePerDocument()); + + std::string file_path = PathService::GetTestFilePath("simple_xfa.pdf"); + ScopedFPDFDocument doc(FPDF_LoadDocument(file_path.c_str(), "")); + ASSERT_TRUE(doc); + + IPDF_JSPLATFORM platform = {}; + platform.version = 3; + + FPDF_FORMFILLINFO formfillinfo = {}; + formfillinfo.version = 2; + formfillinfo.m_pJsPlatform = &platform; + + ScopedFPDFFormHandle form( + FPDFDOC_InitFormFillEnvironment(doc.get(), &formfillinfo)); + ASSERT_TRUE(form); + EXPECT_TRUE(FPDF_LoadXFA(doc.get())); + + form.reset(); + + form.reset(FPDFDOC_InitFormFillEnvironment(doc.get(), &formfillinfo)); + ASSERT_TRUE(form); + EXPECT_TRUE(FPDF_LoadXFA(doc.get())); + + ScopedFPDFPage page(FPDF_LoadPage(doc.get(), 0)); + EXPECT_TRUE(page); +} + +TEST_F(CPDFXFAFormFillEnvEmbedderTest, CloseDocumentBeforeExit) { + ASSERT_FALSE(IJS_Runtime::IsIsolatePerDocument()); + + std::string file_path = PathService::GetTestFilePath("simple_xfa.pdf"); + ScopedFPDFDocument doc(FPDF_LoadDocument(file_path.c_str(), "")); + ASSERT_TRUE(doc); + + IPDF_JSPLATFORM platform = {}; + platform.version = 3; + + FPDF_FORMFILLINFO formfillinfo = {}; + formfillinfo.version = 2; + formfillinfo.m_pJsPlatform = &platform; + + ScopedFPDFFormHandle form( + FPDFDOC_InitFormFillEnvironment(doc.get(), &formfillinfo)); + ASSERT_TRUE(form); + EXPECT_TRUE(FPDF_LoadXFA(doc.get())); + + doc.reset(); + form.reset(); +} diff --git a/fpdfsdk/pwl/cpwl_edit.cpp b/fpdfsdk/pwl/cpwl_edit.cpp index d6fa576d5d..67d60eef21 100644 --- a/fpdfsdk/pwl/cpwl_edit.cpp +++ b/fpdfsdk/pwl/cpwl_edit.cpp @@ -596,6 +596,14 @@ bool CPWL_Edit::OnCharInternal(uint16_t nChar, Mask nFlag) { return false; } + // A modifier chord may produce a control character even when the modifier + // is not the platform shortcut key, e.g. Ctrl-C on Apple platforms, where + // the shortcut key is Meta. Never type such characters, as that would + // silently replace the current selection. See https://crbug.com/528342021. + if (nChar < 32 && (IsCTRLKeyDown(nFlag) || IsMETAKeyDown(nFlag))) { + return false; + } + if (IsReadOnly()) { return true; } diff --git a/fxbarcode/common/BC_CommonBitMatrix.cpp b/fxbarcode/common/BC_CommonBitMatrix.cpp index 0630d6fbc2..6260c01bdc 100644 --- a/fxbarcode/common/BC_CommonBitMatrix.cpp +++ b/fxbarcode/common/BC_CommonBitMatrix.cpp @@ -24,9 +24,10 @@ #include "core/fxcrt/check_op.h" #include "core/fxcrt/fixed_size_data_vector.h" +#include "core/fxcrt/fx_ceil_div.h" CBC_CommonBitMatrix::CBC_CommonBitMatrix(size_t width, size_t height) - : height_(height), row_size_((width + 31) >> 5) { + : height_(height), row_size_(fxcrt::CeilDiv(width, 32)) { static constexpr int32_t kMaxBits = 1024 * 1024 * 1024; // 1 Gb. CHECK_LT(row_size_, kMaxBits / height_); bits_ = FixedSizeDataVector::Zeroed(row_size_ * height_); diff --git a/fxbarcode/qrcode/BC_QRCoderBitVector.cpp b/fxbarcode/qrcode/BC_QRCoderBitVector.cpp index 76a386e37b..9de5c42950 100644 --- a/fxbarcode/qrcode/BC_QRCoderBitVector.cpp +++ b/fxbarcode/qrcode/BC_QRCoderBitVector.cpp @@ -24,6 +24,7 @@ #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" +#include "core/fxcrt/fx_ceil_div.h" #include "core/fxcrt/fx_system.h" CBC_QRCoderBitVector::CBC_QRCoderBitVector() = default; @@ -37,7 +38,7 @@ int32_t CBC_QRCoderBitVector::At(size_t index) const { } size_t CBC_QRCoderBitVector::sizeInBytes() const { - return (size_in_bits_ + 7) >> 3; + return fxcrt::CeilDiv(size_in_bits_, 8); } size_t CBC_QRCoderBitVector::Size() const { diff --git a/fxjs/BUILD.gn b/fxjs/BUILD.gn index 8062138eed..1f6c858b66 100644 --- a/fxjs/BUILD.gn +++ b/fxjs/BUILD.gn @@ -229,11 +229,11 @@ if (pdf_enable_v8) { "gc/heap.h", ] configs += [ "../:pdfium_strict_config" ] - deps = [ + deps = [ "//v8" ] + public_deps = [ "../core/fxcrt", - "//v8:v8_libplatform", + "//v8:cppgc", ] - public_deps = [ "//v8:cppgc" ] } } } @@ -247,6 +247,7 @@ if (pdf_enable_v8) { "cjs_publicmethods_unittest.cpp", "cjs_util_unittest.cpp", "fx_date_helpers_unittest.cpp", + "ijs_runtime_unittest.cpp", ] configs = [ "//v8:external_startup_data" ] deps = [ diff --git a/fxjs/cfx_globaldata_unittest.cpp b/fxjs/cfx_globaldata_unittest.cpp index a3ab452e9e..122adf81ea 100644 --- a/fxjs/cfx_globaldata_unittest.cpp +++ b/fxjs/cfx_globaldata_unittest.cpp @@ -20,7 +20,7 @@ class TestDelegate : public CFX_GlobalData::Delegate { ~TestDelegate() override = default; bool StoreBuffer(pdfium::span buffer) override { - last_buffer_ = DataVector(buffer.begin(), buffer.end()); + last_buffer_ = ToDataVector(buffer); return true; } std::optional> LoadBuffer() override { diff --git a/fxjs/cfx_isolate_wrapper.cpp b/fxjs/cfx_isolate_wrapper.cpp index 08670f6000..23acd25586 100644 --- a/fxjs/cfx_isolate_wrapper.cpp +++ b/fxjs/cfx_isolate_wrapper.cpp @@ -6,6 +6,7 @@ #include "fxjs/cfx_isolate_wrapper.h" +#include "fxjs/cfxjs_engine.h" #include "fxjs/fxv8.h" #include "v8/include/v8-isolate.h" @@ -136,5 +137,8 @@ v8::Local CFX_IsolateWrapper::ToArrayReentrant( } void CFX_V8IsolateDeleter::operator()(v8::Isolate* ptr) { - ptr->Dispose(); + if (ptr) { + CFXJS_PerIsolateData::Release(ptr); + ptr->Dispose(); + } } diff --git a/fxjs/cfx_v8_array_buffer_allocator.cpp b/fxjs/cfx_v8_array_buffer_allocator.cpp index 8bd24ce9e7..dd9f708737 100644 --- a/fxjs/cfx_v8_array_buffer_allocator.cpp +++ b/fxjs/cfx_v8_array_buffer_allocator.cpp @@ -8,6 +8,12 @@ #include "core/fxcrt/fx_memory.h" +// static +CFX_V8ArrayBufferAllocator* CFX_V8ArrayBufferAllocator::GetSharedInstance() { + static auto* s_allocator = new CFX_V8ArrayBufferAllocator(); + return s_allocator; +} + CFX_V8ArrayBufferAllocator::CFX_V8ArrayBufferAllocator() = default; CFX_V8ArrayBufferAllocator::~CFX_V8ArrayBufferAllocator() = default; diff --git a/fxjs/cfx_v8_array_buffer_allocator.h b/fxjs/cfx_v8_array_buffer_allocator.h index 38bb2bbd6e..9cd055c7fc 100644 --- a/fxjs/cfx_v8_array_buffer_allocator.h +++ b/fxjs/cfx_v8_array_buffer_allocator.h @@ -17,6 +17,11 @@ class CFX_V8ArrayBufferAllocator final : public v8::ArrayBuffer::Allocator { public: static const size_t kMaxAllowedBytes = 0x10000000; + // Deliberately leaked. This allocator is used outside of the library + // initialization / destruction lifecycle, and the caller does not take + // ownership of the object. Thus there is no existing way to delete this. + static CFX_V8ArrayBufferAllocator* GetSharedInstance(); + CFX_V8ArrayBufferAllocator(); CFX_V8ArrayBufferAllocator(const CFX_V8ArrayBufferAllocator&) = delete; CFX_V8ArrayBufferAllocator(CFX_V8ArrayBufferAllocator&&) = delete; diff --git a/fxjs/cfxjs_engine.cpp b/fxjs/cfxjs_engine.cpp index 7b83bb8577..7fa7d49fc7 100644 --- a/fxjs/cfxjs_engine.cpp +++ b/fxjs/cfxjs_engine.cpp @@ -16,6 +16,7 @@ #include "fxjs/cfx_v8_array_buffer_allocator.h" #include "fxjs/cjs_object.h" #include "fxjs/fxv8.h" +#include "fxjs/ijs_runtime.h" #include "fxjs/xfa/cfxjse_runtimedata.h" #include "v8/include/v8-context.h" #include "v8/include/v8-exception.h" @@ -29,10 +30,6 @@ namespace { unsigned int g_embedderDataSlot = 1u; v8::Isolate* g_isolate = nullptr; -size_t g_isolate_ref_count = 0; -CFX_V8ArrayBufferAllocator* g_arrayBufferAllocator = nullptr; -v8::Global* g_DefaultGlobalObjectTemplate = nullptr; - // Only the address matters, values are for humans debugging. ASLR should // ensure that these values are unlikely to arise otherwise. Keep these @@ -58,45 +55,45 @@ std::pair GetLineAndColumnFromError(v8::Local message, } // namespace // static -void CFXJS_PerObjectData::SetNewDataInObject(uint32_t nObjDefnID, - v8::Local pObj) { - if (pObj->InternalFieldCount() == 2) { - pObj->SetAlignedPointerInInternalField( +void CFXJS_PerObjectData::SetNewDataInObject(uint32_t obj_definition_id, + v8::Local obj) { + if (obj->InternalFieldCount() == 2) { + obj->SetAlignedPointerInInternalField( 0, GetAlignedPointerForPerObjectDataTag(), fxv8::kPDFiumSentinelTag); - pObj->SetAlignedPointerInInternalField( - 1, new CFXJS_PerObjectData(nObjDefnID), fxv8::kFXJSPerObjectDataTag); + obj->SetAlignedPointerInInternalField( + 1, new CFXJS_PerObjectData(obj_definition_id), + fxv8::kFXJSPerObjectDataTag); } } // static CFXJS_PerObjectData* CFXJS_PerObjectData::GetFromObject( - v8::Local pObj) { - if (pObj.IsEmpty()) { + v8::Local obj) { + if (obj.IsEmpty()) { return nullptr; } - if (!HasInternalFields(pObj)) { + if (!HasInternalFields(obj)) { return nullptr; } - return ExtractFromObject(pObj); + return ExtractFromObject(obj); } // static -bool CFXJS_PerObjectData::HasInternalFields(v8::Local pObj) { - return pObj->InternalFieldCount() == 2 && - pObj->GetAlignedPointerFromInternalField(0, - fxv8::kPDFiumSentinelTag) == +bool CFXJS_PerObjectData::HasInternalFields(v8::Local obj) { + return obj->InternalFieldCount() == 2 && + obj->GetAlignedPointerFromInternalField(0, fxv8::kPDFiumSentinelTag) == GetAlignedPointerForPerObjectDataTag(); } // static CFXJS_PerObjectData* CFXJS_PerObjectData::ExtractFromObject( - v8::Local pObj) { + v8::Local obj) { return static_cast( - pObj->GetAlignedPointerFromInternalField(1, fxv8::kFXJSPerObjectDataTag)); + obj->GetAlignedPointerFromInternalField(1, fxv8::kFXJSPerObjectDataTag)); } -CFXJS_PerObjectData::CFXJS_PerObjectData(uint32_t nObjDefnID) - : obj_defn_id_(nObjDefnID) {} +CFXJS_PerObjectData::CFXJS_PerObjectData(uint32_t obj_definition_id) + : obj_definition_id_(obj_definition_id) {} CFXJS_PerObjectData::~CFXJS_PerObjectData() = default; @@ -161,22 +158,22 @@ class V8TemplateMap { class CFXJS_ObjDefinition { public: CFXJS_ObjDefinition(v8::Isolate* isolate, - const char* sObjName, - FXJSOBJTYPE eObjType, - CFXJS_Engine::Constructor pConstructor, - CFXJS_Engine::Destructor pDestructor) - : obj_name_(sObjName), - obj_type_(eObjType), - constructor_(pConstructor), - destructor_(pDestructor), + const char* obj_name, + FXJSOBJTYPE obj_type, + CFXJS_Engine::Constructor constructor, + CFXJS_Engine::Destructor destructor) + : obj_name_(obj_name), + obj_type_(obj_type), + constructor_(constructor), + destructor_(destructor), isolate_(isolate) { v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); v8::Local fn = v8::FunctionTemplate::New(isolate); fn->InstanceTemplate()->SetInternalFieldCount(2); fn->InstanceTemplate()->SetImmutableProto(); - fn->SetCallHandler(CallHandler, v8::Number::New(isolate, eObjType)); - if (eObjType == FXJSOBJTYPE_GLOBAL) { + fn->SetCallHandler(CallHandler, v8::Number::New(isolate, obj_type)); + if (obj_type == FXJSOBJTYPE_GLOBAL) { fn->InstanceTemplate()->Set(v8::Symbol::GetToStringTag(isolate), fxv8::NewStringHelper(isolate, "global")); } @@ -206,31 +203,32 @@ class CFXJS_ObjDefinition { const char* GetObjName() const { return obj_name_; } v8::Isolate* GetIsolate() const { return isolate_; } - void DefineConst(const char* sConstName, v8::Local pDefault) { - GetInstanceTemplate()->Set(GetIsolate(), sConstName, pDefault); + void DefineConst(const char* const_name, v8::Local default_value) { + GetInstanceTemplate()->Set(GetIsolate(), const_name, default_value); } - void DefineProperty(v8::Local sPropName, - v8::AccessorNameGetterCallback pPropGet, - v8::AccessorNameSetterCallbackV2 pPropPut) { - GetInstanceTemplate()->SetNativeDataProperty(sPropName, pPropGet, pPropPut); + void DefineProperty(v8::Local prop_name, + v8::AccessorNameGetterCallback prop_getter, + v8::AccessorNameSetterCallbackV2 prop_setter) { + GetInstanceTemplate()->SetNativeDataProperty(prop_name, prop_getter, + prop_setter); } - void DefineMethod(v8::Local sMethodName, - v8::FunctionCallback pMethodCall) { + void DefineMethod(v8::Local method_name, + v8::FunctionCallback method_callback) { v8::Local fun = v8::FunctionTemplate::New( - GetIsolate(), pMethodCall, v8::Local(), GetSignature()); + GetIsolate(), method_callback, v8::Local(), GetSignature()); fun->RemovePrototype(); - GetInstanceTemplate()->Set(sMethodName, fun, v8::ReadOnly); + GetInstanceTemplate()->Set(method_name, fun, v8::ReadOnly); } - void DefineAllProperties(v8::NamedPropertyQueryCallback pPropQurey, - v8::NamedPropertyGetterCallback pPropGet, - v8::NamedPropertySetterCallbackV2 pPropPut, - v8::NamedPropertyDeleterCallback pPropDel, - v8::NamedPropertyEnumeratorCallback pPropEnum) { + void DefineAllProperties(v8::NamedPropertyQueryCallback prop_query, + v8::NamedPropertyGetterCallback prop_getter, + v8::NamedPropertySetterCallbackV2 prop_setter, + v8::NamedPropertyDeleterCallback prop_deleter, + v8::NamedPropertyEnumeratorCallback prop_enumer) { GetInstanceTemplate()->SetHandler(v8::NamedPropertyHandlerConfiguration( - pPropGet, pPropPut, pPropQurey, pPropDel, pPropEnum, + prop_getter, prop_setter, prop_query, prop_deleter, prop_enumer, v8::Local(), v8::PropertyHandlerFlags::kOnlyInterceptStrings)); } @@ -247,9 +245,9 @@ class CFXJS_ObjDefinition { return scope.Escape(signature_.Get(GetIsolate())); } - void RunConstructor(CFXJS_Engine* pEngine, v8::Local obj) { + void RunConstructor(CFXJS_Engine* engine, v8::Local obj) { if (constructor_) { - constructor_(pEngine, obj); + constructor_(engine, obj); } } @@ -270,23 +268,15 @@ class CFXJS_ObjDefinition { }; static v8::Local GetGlobalObjectTemplate( - v8::Isolate* pIsolate) { - CFXJS_PerIsolateData* pIsolateData = CFXJS_PerIsolateData::Get(pIsolate); - for (uint32_t i = 1; i <= pIsolateData->CurrentMaxObjDefinitionID(); ++i) { - CFXJS_ObjDefinition* pObjDef = pIsolateData->ObjDefinitionForID(i); - if (pObjDef->GetObjType() == FXJSOBJTYPE_GLOBAL) { - return pObjDef->GetInstanceTemplate(); + v8::Isolate* isolate) { + CFXJS_PerIsolateData* isolate_data = CFXJS_PerIsolateData::Get(isolate); + for (uint32_t i = 1; i < kMaxFxObjDefinitions; ++i) { + CFXJS_ObjDefinition* obj_defn = isolate_data->ObjDefinitionForID(i); + if (obj_defn && obj_defn->GetObjType() == FXJSOBJTYPE_GLOBAL) { + return obj_defn->GetInstanceTemplate(); } } - if (!g_DefaultGlobalObjectTemplate) { - v8::Local hGlobalTemplate = - v8::ObjectTemplate::New(pIsolate); - hGlobalTemplate->Set(v8::Symbol::GetToStringTag(pIsolate), - fxv8::NewStringHelper(pIsolate, "global")); - g_DefaultGlobalObjectTemplate = - new v8::Global(pIsolate, hGlobalTemplate); - } - return g_DefaultGlobalObjectTemplate->Get(pIsolate); + return isolate_data->GetOrCreateDefaultGlobalObjectTemplate(isolate); } void V8TemplateMapTraits::Dispose(v8::Isolate* isolate, @@ -297,15 +287,15 @@ void V8TemplateMapTraits::Dispose(v8::Isolate* isolate, return; } uint32_t id = CFXJS_Engine::GetObjDefnID(obj); - if (id == 0) { + if (id == kNotAnFxObject) { return; } - CFXJS_PerIsolateData* pIsolateData = CFXJS_PerIsolateData::Get(isolate); - CFXJS_ObjDefinition* pObjDef = pIsolateData->ObjDefinitionForID(id); - if (!pObjDef) { + CFXJS_PerIsolateData* isolate_data = CFXJS_PerIsolateData::Get(isolate); + CFXJS_ObjDefinition* obj_defn = isolate_data->ObjDefinitionForID(id); + if (!obj_defn) { return; } - pObjDef->RunDestructor(obj); + obj_defn->RunDestructor(obj); CFXJS_Engine::FreePerObjectData(obj); } @@ -316,29 +306,25 @@ void V8TemplateMapTraits::DisposeWeak( V8TemplateMapTraits::MapType* V8TemplateMapTraits::MapFromWeakCallbackInfo( const v8::WeakCallbackInfo& info) { - auto* pIsolateData = CFXJS_PerIsolateData::Get(info.GetIsolate()); - V8TemplateMap* pObjsMap = pIsolateData->GetDynamicObjsMap(); - return pObjsMap ? pObjsMap->GetMap() : nullptr; + auto* isolate_data = CFXJS_PerIsolateData::Get(info.GetIsolate()); + V8TemplateMap* objs_map = isolate_data->GetDynamicObjsMap(); + return objs_map ? objs_map->GetMap() : nullptr; } -void FXJS_Initialize(unsigned int embedderDataSlot, v8::Isolate* pIsolate) { +void FXJS_Initialize(unsigned int embedder_data_slot, v8::Isolate* isolate) { if (g_isolate) { - DCHECK_EQ(g_embedderDataSlot, embedderDataSlot); - DCHECK_EQ(g_isolate, pIsolate); + DCHECK_EQ(g_embedderDataSlot, embedder_data_slot); + DCHECK_EQ(g_isolate, isolate); return; } - g_embedderDataSlot = embedderDataSlot; - g_isolate = pIsolate; + g_embedderDataSlot = embedder_data_slot; + g_isolate = isolate; } void FXJS_Release() { - DCHECK(!g_isolate || g_isolate_ref_count == 0); - delete g_DefaultGlobalObjectTemplate; - g_DefaultGlobalObjectTemplate = nullptr; + DCHECK(!g_isolate || !CFXJS_PerIsolateData::Get(g_isolate) || + CFXJS_PerIsolateData::Get(g_isolate)->engine_ref_count() == 0); g_isolate = nullptr; - - delete g_arrayBufferAllocator; - g_arrayBufferAllocator = nullptr; } bool FXJS_GetIsolate(v8::Isolate** pResultIsolate) { @@ -347,182 +333,227 @@ bool FXJS_GetIsolate(v8::Isolate** pResultIsolate) { return false; } // Provide backwards compatibility when no external isolate. - if (!g_arrayBufferAllocator) { - g_arrayBufferAllocator = new CFX_V8ArrayBufferAllocator(); - } v8::Isolate::CreateParams params; - params.array_buffer_allocator = g_arrayBufferAllocator; + params.array_buffer_allocator = + CFX_V8ArrayBufferAllocator::GetSharedInstance(); *pResultIsolate = v8::Isolate::New(params); return true; } size_t FXJS_GlobalIsolateRefCount() { - return g_isolate_ref_count; + if (!g_isolate) { + return 0; + } + auto* isolate_data = CFXJS_PerIsolateData::Get(g_isolate); + return isolate_data ? isolate_data->engine_ref_count() : 0; } // static -void CFXJS_PerIsolateData::SetUp(v8::Isolate* pIsolate) { - if (!pIsolate->GetData(g_embedderDataSlot)) { - pIsolate->SetData(g_embedderDataSlot, new CFXJS_PerIsolateData(pIsolate)); +CFXJS_PerIsolateData* CFXJS_PerIsolateData::GetOrCreate(v8::Isolate* isolate) { + auto* result = Get(isolate); + if (result) { + return result; } + result = new CFXJS_PerIsolateData(isolate); + isolate->SetData(g_embedderDataSlot, result); + return result; } // static -CFXJS_PerIsolateData* CFXJS_PerIsolateData::Get(v8::Isolate* pIsolate) { +CFXJS_PerIsolateData* CFXJS_PerIsolateData::Get(v8::Isolate* isolate) { auto* result = - static_cast(pIsolate->GetData(g_embedderDataSlot)); + static_cast(isolate->GetData(g_embedderDataSlot)); + if (!result) { + return nullptr; + } CHECK(result->tag_ == kPerIsolateDataTag); return result; } -CFXJS_PerIsolateData::CFXJS_PerIsolateData(v8::Isolate* pIsolate) +// static +void CFXJS_PerIsolateData::Release(v8::Isolate* isolate) { + if (!isolate) { + return; + } + auto* isolate_data = Get(isolate); + if (!isolate_data) { + return; + } + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + delete isolate_data; + isolate->SetData(g_embedderDataSlot, nullptr); +} + +CFXJS_PerIsolateData::CFXJS_PerIsolateData(v8::Isolate* isolate) : tag_(kPerIsolateDataTag), - dynamic_objs_map_(std::make_unique(pIsolate)) {} + dynamic_objs_map_(std::make_unique(isolate)) {} CFXJS_PerIsolateData::~CFXJS_PerIsolateData() = default; -uint32_t CFXJS_PerIsolateData::CurrentMaxObjDefinitionID() const { - return fxcrt::CollectionSize(object_defn_array_); -} - CFXJS_ObjDefinition* CFXJS_PerIsolateData::ObjDefinitionForID( uint32_t id) const { - return id > 0 && id <= CurrentMaxObjDefinitionID() - ? object_defn_array_[id - 1].get() - : nullptr; + return id < object_defn_array_.size() ? object_defn_array_[id].get() + : nullptr; } -uint32_t CFXJS_PerIsolateData::AssignIDForObjDefinition( - std::unique_ptr pDefn) { - object_defn_array_.push_back(std::move(pDefn)); - return CurrentMaxObjDefinitionID(); +bool CFXJS_PerIsolateData::HasObjectDefinitions() const { + return !!object_defn_array_[1]; +} + +void CFXJS_PerIsolateData::InstallObjDefinitionForID( + uint32_t id, + std::unique_ptr defn) { + CHECK_NE(id, kNotAnFxObject); + CHECK(!object_defn_array_[id]); + object_defn_array_[id] = std::move(defn); +} + +v8::Local +CFXJS_PerIsolateData::GetOrCreateDefaultGlobalObjectTemplate( + v8::Isolate* isolate) { + if (default_global_object_template_.IsEmpty()) { + v8::Local hGlobalTemplate = + v8::ObjectTemplate::New(isolate); + hGlobalTemplate->Set(v8::Symbol::GetToStringTag(isolate), + fxv8::NewStringHelper(isolate, "global")); + default_global_object_template_.Reset(isolate, hGlobalTemplate); + } + return default_global_object_template_.Get(isolate); } CFXJS_Engine::CFXJS_Engine() : CFX_IsolateWrapper(nullptr) {} -CFXJS_Engine::CFXJS_Engine(v8::Isolate* pIsolate) - : CFX_IsolateWrapper(pIsolate) {} +CFXJS_Engine::CFXJS_Engine(v8::Isolate* isolate) + : CFX_IsolateWrapper(isolate) {} CFXJS_Engine::~CFXJS_Engine() = default; // static -uint32_t CFXJS_Engine::GetObjDefnID(v8::Local pObj) { - CFXJS_PerObjectData* pData = CFXJS_PerObjectData::GetFromObject(pObj); - return pData ? pData->GetObjDefnID() : 0; +uint32_t CFXJS_Engine::GetObjDefnID(v8::Local obj) { + CFXJS_PerObjectData* data = CFXJS_PerObjectData::GetFromObject(obj); + return data ? data->GetObjDefnID() : kNotAnFxObject; } // static -void CFXJS_Engine::SetBinding(v8::Local pObj, - std::unique_ptr p) { - CFXJS_PerObjectData* pPerObjectData = - CFXJS_PerObjectData::GetFromObject(pObj); - if (pPerObjectData) { - pPerObjectData->SetBinding(std::move(p)); +void CFXJS_Engine::SetBinding( + v8::Local obj, + std::unique_ptr binding) { + CFXJS_PerObjectData* data = CFXJS_PerObjectData::GetFromObject(obj); + if (data) { + data->SetBinding(std::move(binding)); } } // static -void CFXJS_Engine::FreePerObjectData(v8::Local pObj) { - CFXJS_PerObjectData* pData = CFXJS_PerObjectData::GetFromObject(pObj); - pObj->SetAlignedPointerInInternalField(0, nullptr, fxv8::kPDFiumSentinelTag); - pObj->SetAlignedPointerInInternalField(1, nullptr, - fxv8::kFXJSPerObjectDataTag); - delete pData; -} - -uint32_t CFXJS_Engine::DefineObj(const char* sObjName, - FXJSOBJTYPE eObjType, - CFXJS_Engine::Constructor pConstructor, - CFXJS_Engine::Destructor pDestructor) { +void CFXJS_Engine::FreePerObjectData(v8::Local obj) { + CFXJS_PerObjectData* data = CFXJS_PerObjectData::GetFromObject(obj); + if (!data) { + return; + } + obj->SetAlignedPointerInInternalField(0, nullptr, fxv8::kPDFiumSentinelTag); + obj->SetAlignedPointerInInternalField(1, nullptr, + fxv8::kFXJSPerObjectDataTag); + delete data; +} + +void CFXJS_Engine::DefineObj(uint32_t obj_definition_id, + const char* obj_name, + FXJSOBJTYPE obj_type, + CFXJS_Engine::Constructor constructor, + CFXJS_Engine::Destructor destructor) { v8::Isolate::Scope isolate_scope(GetIsolate()); v8::HandleScope handle_scope(GetIsolate()); - CFXJS_PerIsolateData::SetUp(GetIsolate()); - CFXJS_PerIsolateData* pIsolateData = CFXJS_PerIsolateData::Get(GetIsolate()); - return pIsolateData->AssignIDForObjDefinition( - std::make_unique(GetIsolate(), sObjName, eObjType, - pConstructor, pDestructor)); + CFXJS_PerIsolateData* isolate_data = + CFXJS_PerIsolateData::GetOrCreate(GetIsolate()); + isolate_data->InstallObjDefinitionForID( + obj_definition_id, + std::make_unique(GetIsolate(), obj_name, obj_type, + constructor, destructor)); } -void CFXJS_Engine::DefineObjMethod(uint32_t nObjDefnID, - const char* sMethodName, - v8::FunctionCallback pMethodCall) { +void CFXJS_Engine::DefineObjMethod(uint32_t obj_definition_id, + const char* method_name, + v8::FunctionCallback method_callback) { v8::Isolate::Scope isolate_scope(GetIsolate()); v8::HandleScope handle_scope(GetIsolate()); - CFXJS_PerIsolateData* pIsolateData = CFXJS_PerIsolateData::Get(GetIsolate()); - CFXJS_ObjDefinition* pObjDef = pIsolateData->ObjDefinitionForID(nObjDefnID); - pObjDef->DefineMethod(NewString(sMethodName), pMethodCall); + CFXJS_PerIsolateData* isolate_data = CFXJS_PerIsolateData::Get(GetIsolate()); + CFXJS_ObjDefinition* obj_defn = + isolate_data->ObjDefinitionForID(obj_definition_id); + obj_defn->DefineMethod(NewString(method_name), method_callback); } void CFXJS_Engine::DefineObjProperty( - uint32_t nObjDefnID, - const char* sPropName, - v8::AccessorNameGetterCallback pPropGet, - v8::AccessorNameSetterCallbackV2 pPropPut) { + uint32_t obj_definition_id, + const char* prop_name, + v8::AccessorNameGetterCallback prop_getter, + v8::AccessorNameSetterCallbackV2 prop_setter) { v8::Isolate::Scope isolate_scope(GetIsolate()); v8::HandleScope handle_scope(GetIsolate()); - CFXJS_PerIsolateData* pIsolateData = CFXJS_PerIsolateData::Get(GetIsolate()); - CFXJS_ObjDefinition* pObjDef = pIsolateData->ObjDefinitionForID(nObjDefnID); - pObjDef->DefineProperty(NewString(sPropName), pPropGet, pPropPut); + CFXJS_PerIsolateData* isolate_data = CFXJS_PerIsolateData::Get(GetIsolate()); + CFXJS_ObjDefinition* obj_defn = + isolate_data->ObjDefinitionForID(obj_definition_id); + obj_defn->DefineProperty(NewString(prop_name), prop_getter, prop_setter); } void CFXJS_Engine::DefineObjAllProperties( - uint32_t nObjDefnID, - v8::NamedPropertyQueryCallback pPropQurey, - v8::NamedPropertyGetterCallback pPropGet, - v8::NamedPropertySetterCallbackV2 pPropPut, - v8::NamedPropertyDeleterCallback pPropDel, - v8::NamedPropertyEnumeratorCallback pPropEnum) { + uint32_t obj_definition_id, + v8::NamedPropertyQueryCallback prop_query, + v8::NamedPropertyGetterCallback prop_getter, + v8::NamedPropertySetterCallbackV2 prop_setter, + v8::NamedPropertyDeleterCallback prop_deleter, + v8::NamedPropertyEnumeratorCallback prop_enumer) { v8::Isolate::Scope isolate_scope(GetIsolate()); v8::HandleScope handle_scope(GetIsolate()); - CFXJS_PerIsolateData* pIsolateData = CFXJS_PerIsolateData::Get(GetIsolate()); - CFXJS_ObjDefinition* pObjDef = pIsolateData->ObjDefinitionForID(nObjDefnID); - pObjDef->DefineAllProperties(pPropQurey, pPropGet, pPropPut, pPropDel, - pPropEnum); + CFXJS_PerIsolateData* isolate_data = CFXJS_PerIsolateData::Get(GetIsolate()); + CFXJS_ObjDefinition* obj_defn = + isolate_data->ObjDefinitionForID(obj_definition_id); + obj_defn->DefineAllProperties(prop_query, prop_getter, prop_setter, + prop_deleter, prop_enumer); } -void CFXJS_Engine::DefineObjConst(uint32_t nObjDefnID, - const char* sConstName, - v8::Local pDefault) { +void CFXJS_Engine::DefineObjConst(uint32_t obj_definition_id, + const char* const_name, + v8::Local default_value) { v8::Isolate::Scope isolate_scope(GetIsolate()); v8::HandleScope handle_scope(GetIsolate()); - CFXJS_PerIsolateData* pIsolateData = CFXJS_PerIsolateData::Get(GetIsolate()); - CFXJS_ObjDefinition* pObjDef = pIsolateData->ObjDefinitionForID(nObjDefnID); - pObjDef->DefineConst(sConstName, pDefault); + CFXJS_PerIsolateData* isolate_data = CFXJS_PerIsolateData::Get(GetIsolate()); + CFXJS_ObjDefinition* obj_defn = + isolate_data->ObjDefinitionForID(obj_definition_id); + obj_defn->DefineConst(const_name, default_value); } -void CFXJS_Engine::DefineGlobalMethod(const char* sMethodName, - v8::FunctionCallback pMethodCall) { +void CFXJS_Engine::DefineGlobalMethod(const char* method_name, + v8::FunctionCallback method_callback) { v8::Isolate::Scope isolate_scope(GetIsolate()); v8::HandleScope handle_scope(GetIsolate()); v8::Local fun = - v8::FunctionTemplate::New(GetIsolate(), pMethodCall); + v8::FunctionTemplate::New(GetIsolate(), method_callback); fun->RemovePrototype(); GetGlobalObjectTemplate(GetIsolate()) - ->Set(NewString(sMethodName), fun, v8::ReadOnly); + ->Set(NewString(method_name), fun, v8::ReadOnly); } -void CFXJS_Engine::DefineGlobalConst(const wchar_t* sConstName, - v8::FunctionCallback pConstGetter) { +void CFXJS_Engine::DefineGlobalConst(const wchar_t* const_name, + v8::FunctionCallback getter_callback) { v8::Isolate::Scope isolate_scope(GetIsolate()); v8::HandleScope handle_scope(GetIsolate()); v8::Local fun = - v8::FunctionTemplate::New(GetIsolate(), pConstGetter); + v8::FunctionTemplate::New(GetIsolate(), getter_callback); fun->RemovePrototype(); GetGlobalObjectTemplate(GetIsolate()) - ->SetAccessorProperty(NewString(sConstName), fun); + ->SetAccessorProperty(NewString(const_name), fun); } void CFXJS_Engine::InitializeEngine() { - if (GetIsolate() == g_isolate) { - ++g_isolate_ref_count; - } - v8::Isolate::Scope isolate_scope(GetIsolate()); v8::HandleScope handle_scope(GetIsolate()); // This has to happen before we call GetGlobalObjectTemplate because that // method gets the PerIsolateData from GetIsolate(). - CFXJS_PerIsolateData::SetUp(GetIsolate()); + CFXJS_PerIsolateData* isolate_data = + CFXJS_PerIsolateData::GetOrCreate(GetIsolate()); + isolate_data->IncrementEngineRefCount(); v8::Local v8Context = v8::Context::New( GetIsolate(), nullptr, GetGlobalObjectTemplate(GetIsolate())); @@ -538,20 +569,20 @@ void CFXJS_Engine::InitializeEngine() { } v8::Context::Scope context_scope(v8Context); - CFXJS_PerIsolateData* pIsolateData = CFXJS_PerIsolateData::Get(GetIsolate()); - uint32_t maxID = pIsolateData->CurrentMaxObjDefinitionID(); - static_objects_.resize(maxID + 1); - for (uint32_t i = 1; i <= maxID; ++i) { - CFXJS_ObjDefinition* pObjDef = pIsolateData->ObjDefinitionForID(i); - if (pObjDef->GetObjType() == FXJSOBJTYPE_GLOBAL) { + for (uint32_t i = 1; i < kMaxFxObjDefinitions; ++i) { + CFXJS_ObjDefinition* obj_defn = isolate_data->ObjDefinitionForID(i); + if (!obj_defn) { + continue; + } + if (obj_defn->GetObjType() == FXJSOBJTYPE_GLOBAL) { CFXJS_PerObjectData::SetNewDataInObject(i, pThis); - pObjDef->RunConstructor(this, pThis); + obj_defn->RunConstructor(this, pThis); - } else if (pObjDef->GetObjType() == FXJSOBJTYPE_STATIC) { - v8::Local pObjName = NewString(pObjDef->GetObjName()); + } else if (obj_defn->GetObjType() == FXJSOBJTYPE_STATIC) { + v8::Local obj_name = NewString(obj_defn->GetObjName()); v8::Local obj = NewFXJSBoundObject(i, FXJSOBJTYPE_STATIC); if (!obj.IsEmpty()) { - v8Context->Global()->Set(v8Context, pObjName, obj).FromJust(); + v8Context->Global()->Set(v8Context, obj_name, obj).FromJust(); static_objects_[i] = v8::Global(GetIsolate(), obj); } } @@ -565,35 +596,44 @@ void CFXJS_Engine::ReleaseEngine() { v8::HandleScope handle_scope(GetIsolate()); v8::Local context = GetV8Context(); v8::Context::Scope context_scope(context); - CFXJS_PerIsolateData* pIsolateData = CFXJS_PerIsolateData::Get(GetIsolate()); - if (!pIsolateData) { + CFXJS_PerIsolateData* isolate_data = CFXJS_PerIsolateData::Get(GetIsolate()); + if (!isolate_data) { return; } const_arrays_.clear(); - for (uint32_t i = 1; i <= pIsolateData->CurrentMaxObjDefinitionID(); ++i) { - CFXJS_ObjDefinition* pObjDef = pIsolateData->ObjDefinitionForID(i); - v8::Local pObj; - if (pObjDef->GetObjType() == FXJSOBJTYPE_GLOBAL) { - pObj = context->Global(); + for (uint32_t i = 1; i < kMaxFxObjDefinitions; ++i) { + CFXJS_ObjDefinition* obj_defn = isolate_data->ObjDefinitionForID(i); + if (!obj_defn) { + continue; + } + v8::Local obj; + if (obj_defn->GetObjType() == FXJSOBJTYPE_GLOBAL) { + if (GetObjDefnID(context->Global()) == i) { + obj = context->Global(); + } } else if (!static_objects_[i].IsEmpty()) { - pObj = v8::Local::New(GetIsolate(), static_objects_[i]); + obj = v8::Local::New(GetIsolate(), static_objects_[i]); static_objects_[i].Reset(); } - if (!pObj.IsEmpty()) { - pObjDef->RunDestructor(pObj); - FreePerObjectData(pObj); + if (!obj.IsEmpty()) { + obj_defn->RunDestructor(obj); + FreePerObjectData(obj); } } v8_context_.Reset(); - if (GetIsolate() == g_isolate && --g_isolate_ref_count > 0) { + if (isolate_data->DecrementEngineRefCount() > 0) { + return; + } + + if (IJS_Runtime::IsIsolatePerDocument()) { return; } - delete pIsolateData; + delete isolate_data; GetIsolate()->SetData(g_embedderDataSlot, nullptr); } @@ -621,32 +661,33 @@ std::optional CFXJS_Engine::Execute( return std::nullopt; } -v8::Local CFXJS_Engine::NewFXJSBoundObject(uint32_t nObjDefnID, - FXJSOBJTYPE type) { +v8::Local CFXJS_Engine::NewFXJSBoundObject( + uint32_t obj_definition_id, + FXJSOBJTYPE obj_type) { v8::Isolate::Scope isolate_scope(GetIsolate()); v8::Local context = GetIsolate()->GetCurrentContext(); - CFXJS_PerIsolateData* pData = CFXJS_PerIsolateData::Get(GetIsolate()); - if (!pData) { + CFXJS_PerIsolateData* data = CFXJS_PerIsolateData::Get(GetIsolate()); + if (!data) { return v8::Local(); } - CFXJS_ObjDefinition* pObjDef = pData->ObjDefinitionForID(nObjDefnID); - if (!pObjDef) { + CFXJS_ObjDefinition* obj_defn = data->ObjDefinitionForID(obj_definition_id); + if (!obj_defn) { return v8::Local(); } v8::Local obj; - if (!pObjDef->GetInstanceTemplate()->NewInstance(context).ToLocal(&obj)) { + if (!obj_defn->GetInstanceTemplate()->NewInstance(context).ToLocal(&obj)) { return v8::Local(); } - CFXJS_PerObjectData::SetNewDataInObject(nObjDefnID, obj); - pObjDef->RunConstructor(this, obj); - if (type == FXJSOBJTYPE_DYNAMIC) { - auto* pIsolateData = CFXJS_PerIsolateData::Get(GetIsolate()); - V8TemplateMap* pObjsMap = pIsolateData->GetDynamicObjsMap(); - if (pObjsMap) { - pObjsMap->SetAndMakeWeak(obj); + CFXJS_PerObjectData::SetNewDataInObject(obj_definition_id, obj); + obj_defn->RunConstructor(this, obj); + if (obj_type == FXJSOBJTYPE_DYNAMIC) { + auto* isolate_data = CFXJS_PerIsolateData::Get(GetIsolate()); + V8TemplateMap* objs_map = isolate_data->GetDynamicObjsMap(); + if (objs_map) { + objs_map->SetAndMakeWeak(obj); } } return obj; @@ -673,10 +714,10 @@ v8::Local CFXJS_Engine::GetV8Context() { // static CFXJS_PerObjectData::Binding* CFXJS_Engine::GetBinding( - v8::Isolate* pIsolate, - v8::Local pObj) { - auto* pData = CFXJS_PerObjectData::GetFromObject(pObj); - return pData ? pData->GetBinding() : nullptr; + v8::Isolate* isolate, + v8::Local obj) { + auto* data = CFXJS_PerObjectData::GetFromObject(obj); + return data ? data->GetBinding() : nullptr; } v8::Local CFXJS_Engine::GetConstArray(const WideString& name) { diff --git a/fxjs/cfxjs_engine.h b/fxjs/cfxjs_engine.h index bcdd0c4b2f..bcfe8160cc 100644 --- a/fxjs/cfxjs_engine.h +++ b/fxjs/cfxjs_engine.h @@ -14,11 +14,12 @@ #ifndef FXJS_CFXJS_ENGINE_H_ #define FXJS_CFXJS_ENGINE_H_ +#include #include +#include #include #include #include -#include #include "core/fxcrt/widestring.h" #include "fxjs/cfx_isolate_wrapper.h" @@ -37,6 +38,11 @@ enum FXJSOBJTYPE { FXJSOBJTYPE_GLOBAL, // The global object itself (may only appear once). }; +// Defined here to avoid circular include dependencies with js_define.h. The +// maximum must be consistent with constants in js_define.h. +inline constexpr uint32_t kNotAnFxObject = 0; +inline constexpr size_t kMaxFxObjDefinitions = 21; + class CFXJS_PerIsolateData { public: // Hook for XFA's data, when present. @@ -47,25 +53,42 @@ class CFXJS_PerIsolateData { ~CFXJS_PerIsolateData(); - static void SetUp(v8::Isolate* pIsolate); - static CFXJS_PerIsolateData* Get(v8::Isolate* pIsolate); + static CFXJS_PerIsolateData* GetOrCreate(v8::Isolate* isolate); + static CFXJS_PerIsolateData* Get(v8::Isolate* isolate); + static void Release(v8::Isolate* isolate); - uint32_t CurrentMaxObjDefinitionID() const; CFXJS_ObjDefinition* ObjDefinitionForID(uint32_t id) const; - uint32_t AssignIDForObjDefinition(std::unique_ptr pDefn); + bool HasObjectDefinitions() const; + void InstallObjDefinitionForID(uint32_t id, + std::unique_ptr defn); V8TemplateMap* GetDynamicObjsMap() { return dynamic_objs_map_.get(); } ExtensionIface* GetExtension() { return extension_.get(); } void SetExtension(std::unique_ptr extension) { extension_ = std::move(extension); } + v8::Local GetOrCreateDefaultGlobalObjectTemplate( + v8::Isolate* isolate); + + size_t engine_ref_count() const { return engine_ref_count_; } + size_t IncrementEngineRefCount() { + CHECK_LT(engine_ref_count_, std::numeric_limits::max()); + return ++engine_ref_count_; + } + size_t DecrementEngineRefCount() { + CHECK_GT(engine_ref_count_, 0u); + return --engine_ref_count_; + } private: - explicit CFXJS_PerIsolateData(v8::Isolate* pIsolate); + explicit CFXJS_PerIsolateData(v8::Isolate* isolate); + size_t engine_ref_count_ = 0; const wchar_t* const tag_; // Raw, always a literal. - std::vector> object_defn_array_; + std::array, kMaxFxObjDefinitions> + object_defn_array_; std::unique_ptr dynamic_objs_map_; std::unique_ptr extension_; + v8::Global default_global_object_template_; }; class CFXJS_PerObjectData { @@ -76,27 +99,29 @@ class CFXJS_PerObjectData { virtual ~Binding() = default; }; - static void SetNewDataInObject(uint32_t nObjDefnID, - v8::Local pObj); - static CFXJS_PerObjectData* GetFromObject(v8::Local pObj); + static void SetNewDataInObject(uint32_t obj_definition_id, + v8::Local obj); + static CFXJS_PerObjectData* GetFromObject(v8::Local obj); ~CFXJS_PerObjectData(); - uint32_t GetObjDefnID() const { return obj_defn_id_; } + uint32_t GetObjDefnID() const { return obj_definition_id_; } Binding* GetBinding() { return binding_.get(); } - void SetBinding(std::unique_ptr p) { binding_ = std::move(p); } + void SetBinding(std::unique_ptr binding) { + binding_ = std::move(binding); + } private: - explicit CFXJS_PerObjectData(uint32_t nObjDefnID); + explicit CFXJS_PerObjectData(uint32_t obj_definition_id); - static bool HasInternalFields(v8::Local pObj); - static CFXJS_PerObjectData* ExtractFromObject(v8::Local pObj); + static bool HasInternalFields(v8::Local obj); + static CFXJS_PerObjectData* ExtractFromObject(v8::Local obj); - const uint32_t obj_defn_id_; + const uint32_t obj_definition_id_; std::unique_ptr binding_; }; -void FXJS_Initialize(unsigned int embedderDataSlot, v8::Isolate* pIsolate); +void FXJS_Initialize(unsigned int embedder_data_slot, v8::Isolate* isolate); void FXJS_Release(); // Gets the global isolate set by FXJS_Initialize(), or makes a new one each @@ -109,46 +134,46 @@ size_t FXJS_GlobalIsolateRefCount(); class CFXJS_Engine : public CFX_IsolateWrapper { public: - explicit CFXJS_Engine(v8::Isolate* pIsolate); + explicit CFXJS_Engine(v8::Isolate* isolate); ~CFXJS_Engine() override; using Constructor = std::function obj)>; using Destructor = std::function obj)>; - static uint32_t GetObjDefnID(v8::Local pObj); - static CFXJS_PerObjectData::Binding* GetBinding(v8::Isolate* pIsolate, - v8::Local pObj); - static void SetBinding(v8::Local pObj, - std::unique_ptr p); - static void FreePerObjectData(v8::Local pObj); - - // Always returns a valid (i.e. non-zero), newly-created objDefnID. - uint32_t DefineObj(const char* sObjName, - FXJSOBJTYPE eObjType, - Constructor pConstructor, - Destructor pDestructor); - - void DefineObjMethod(uint32_t nObjDefnID, - const char* sMethodName, - v8::FunctionCallback pMethodCall); - void DefineObjProperty(uint32_t nObjDefnID, - const char* sPropName, - v8::AccessorNameGetterCallback pPropGet, - v8::AccessorNameSetterCallbackV2 pPropPut); - void DefineObjAllProperties(uint32_t nObjDefnID, - v8::NamedPropertyQueryCallback pPropQurey, - v8::NamedPropertyGetterCallback pPropGet, - v8::NamedPropertySetterCallbackV2 pPropPut, - v8::NamedPropertyDeleterCallback pPropDel, - v8::NamedPropertyEnumeratorCallback pPropEnum); - void DefineObjConst(uint32_t nObjDefnID, - const char* sConstName, - v8::Local pDefault); - void DefineGlobalMethod(const char* sMethodName, - v8::FunctionCallback pMethodCall); - void DefineGlobalConst(const wchar_t* sConstName, - v8::FunctionCallback pConstGetter); + static uint32_t GetObjDefnID(v8::Local obj); + static CFXJS_PerObjectData::Binding* GetBinding(v8::Isolate* isolate, + v8::Local obj); + static void SetBinding(v8::Local obj, + std::unique_ptr binding); + static void FreePerObjectData(v8::Local obj); + + void DefineObj(uint32_t obj_definition_id, + const char* obj_name, + FXJSOBJTYPE obj_type, + Constructor constructor, + Destructor destructor); + + void DefineObjMethod(uint32_t obj_definition_id, + const char* method_name, + v8::FunctionCallback method_callback); + void DefineObjProperty(uint32_t obj_definition_id, + const char* prop_name, + v8::AccessorNameGetterCallback prop_getter, + v8::AccessorNameSetterCallbackV2 prop_setter); + void DefineObjAllProperties(uint32_t obj_definition_id, + v8::NamedPropertyQueryCallback prop_query, + v8::NamedPropertyGetterCallback prop_getter, + v8::NamedPropertySetterCallbackV2 prop_setter, + v8::NamedPropertyDeleterCallback prop_deleter, + v8::NamedPropertyEnumeratorCallback prop_enumer); + void DefineObjConst(uint32_t obj_definition_id, + const char* const_name, + v8::Local default_value); + void DefineGlobalMethod(const char* method_name, + v8::FunctionCallback method_callback); + void DefineGlobalConst(const wchar_t* const_name, + v8::FunctionCallback getter_callback); // Called after FXJS_Define* calls made. void InitializeEngine(); @@ -158,8 +183,8 @@ class CFXJS_Engine : public CFX_IsolateWrapper { std::optional Execute(const WideString& script); v8::Local GetThisObj(); - v8::Local NewFXJSBoundObject(uint32_t nObjDefnID, - FXJSOBJTYPE type); + v8::Local NewFXJSBoundObject(uint32_t obj_definition_id, + FXJSOBJTYPE obj_type); void Error(const WideString& message); v8::Local GetV8Context(); @@ -172,7 +197,7 @@ class CFXJS_Engine : public CFX_IsolateWrapper { private: v8::Global v8_context_; - std::vector> static_objects_; + std::array, kMaxFxObjDefinitions> static_objects_; std::map> const_arrays_; }; diff --git a/fxjs/cfxjs_engine_unittest.cpp b/fxjs/cfxjs_engine_unittest.cpp index 6ceeab965d..25b8ae5de4 100644 --- a/fxjs/cfxjs_engine_unittest.cpp +++ b/fxjs/cfxjs_engine_unittest.cpp @@ -48,7 +48,7 @@ TEST_F(FXJSEngineUnitTest, GC) { // Object: 1 engine()->DefineObj( - "perm", FXJSOBJTYPE_DYNAMIC, + 1, "perm", FXJSOBJTYPE_DYNAMIC, [](CFXJS_Engine* pEngine, v8::Local obj) { pEngine->SetBinding( obj, std::make_unique(obj, pEngine->GetIsolate())); @@ -61,7 +61,7 @@ TEST_F(FXJSEngineUnitTest, GC) { // Object: 2 engine()->DefineObj( - "temp", FXJSOBJTYPE_DYNAMIC, + 2, "temp", FXJSOBJTYPE_DYNAMIC, [](CFXJS_Engine* pEngine, v8::Local obj) { pEngine->SetBinding( obj, std::make_unique(obj, pEngine->GetIsolate())); diff --git a/fxjs/cjs_annot.cpp b/fxjs/cjs_annot.cpp index 4251e62916..079b095481 100644 --- a/fxjs/cjs_annot.cpp +++ b/fxjs/cjs_annot.cpp @@ -17,20 +17,13 @@ const JSPropertySpec CJS_Annot::PropertySpecs[] = { {"name", get_name_static, set_name_static}, {"type", get_type_static, set_type_static}}; -uint32_t CJS_Annot::ObjDefnID = 0; - const char CJS_Annot::kName[] = "Annot"; -// static -uint32_t CJS_Annot::GetObjDefnID() { - return ObjDefnID; -} - // static void CJS_Annot::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj(CJS_Annot::kName, FXJSOBJTYPE_DYNAMIC, - JSConstructor, JSDestructor); - DefineProps(pEngine, ObjDefnID, PropertySpecs); + pEngine->DefineObj(kObjDefnId, CJS_Annot::kName, FXJSOBJTYPE_DYNAMIC, + JSConstructor, JSDestructor); + DefineProps(pEngine, kObjDefnId, PropertySpecs); } CJS_Annot::CJS_Annot(v8::Local pObject, CJS_Runtime* pRuntime) diff --git a/fxjs/cjs_annot.h b/fxjs/cjs_annot.h index 97ee5f36cf..605d1334e4 100644 --- a/fxjs/cjs_annot.h +++ b/fxjs/cjs_annot.h @@ -13,7 +13,7 @@ class CJS_Annot final : public CJS_Object { public: - static uint32_t GetObjDefnID(); + static constexpr uint32_t kObjDefnId = kJSAnnotObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Annot(v8::Local pObject, CJS_Runtime* pRuntime); @@ -26,7 +26,6 @@ class CJS_Annot final : public CJS_Object { JS_STATIC_PROP(type, type, CJS_Annot) private: - static uint32_t ObjDefnID; static const char kName[]; static const JSPropertySpec PropertySpecs[]; diff --git a/fxjs/cjs_app.cpp b/fxjs/cjs_app.cpp index 793b6442f0..f56b06625c 100644 --- a/fxjs/cjs_app.cpp +++ b/fxjs/cjs_app.cpp @@ -74,21 +74,14 @@ const JSMethodSpec CJS_App::MethodSpecs[] = { {"setInterval", setInterval_static}, {"setTimeOut", setTimeOut_static}}; -uint32_t CJS_App::ObjDefnID = 0; - const char CJS_App::kName[] = "app"; -// static -uint32_t CJS_App::GetObjDefnID() { - return ObjDefnID; -} - // static void CJS_App::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj(CJS_App::kName, FXJSOBJTYPE_STATIC, - JSConstructor, JSDestructor); - DefineProps(pEngine, ObjDefnID, PropertySpecs); - DefineMethods(pEngine, ObjDefnID, MethodSpecs); + pEngine->DefineObj(kObjDefnId, CJS_App::kName, FXJSOBJTYPE_STATIC, + JSConstructor, JSDestructor); + DefineProps(pEngine, kObjDefnId, PropertySpecs); + DefineMethods(pEngine, kObjDefnId, MethodSpecs); } CJS_App::CJS_App(v8::Local pObject, CJS_Runtime* pRuntime) @@ -331,7 +324,7 @@ CJS_Result CJS_App::setInterval(CJS_Runtime* pRuntime, timers_.insert(std::move(timerRef)); v8::Local pRetObj = pRuntime->NewFXJSBoundObject( - CJS_TimerObj::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + CJS_TimerObj::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pRetObj.IsEmpty()) { return CJS_Result::Failure(JSMessage::kBadObjectError); } @@ -363,7 +356,7 @@ CJS_Result CJS_App::setTimeOut(CJS_Runtime* pRuntime, timers_.insert(std::move(timerRef)); v8::Local pRetObj = pRuntime->NewFXJSBoundObject( - CJS_TimerObj::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + CJS_TimerObj::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pRetObj.IsEmpty()) { return CJS_Result::Failure(JSMessage::kBadObjectError); } diff --git a/fxjs/cjs_app.h b/fxjs/cjs_app.h index 1c65689cfe..54887d0288 100644 --- a/fxjs/cjs_app.h +++ b/fxjs/cjs_app.h @@ -19,7 +19,7 @@ class GlobalTimer; class CJS_App final : public CJS_Object { public: - static uint32_t GetObjDefnID(); + static constexpr uint32_t kObjDefnId = kJSAppObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_App(v8::Local pObject, CJS_Runtime* pRuntime); @@ -66,7 +66,6 @@ class CJS_App final : public CJS_Object { JS_STATIC_METHOD(setTimeOut, CJS_App) private: - static uint32_t ObjDefnID; static const char kName[]; static const JSPropertySpec PropertySpecs[]; static const JSMethodSpec MethodSpecs[]; diff --git a/fxjs/cjs_border.cpp b/fxjs/cjs_border.cpp index b8edd09ca6..7cc4393520 100644 --- a/fxjs/cjs_border.cpp +++ b/fxjs/cjs_border.cpp @@ -6,6 +6,8 @@ #include "fxjs/cjs_border.h" +#include "fxjs/js_define.h" + const JSConstSpec CJS_Border::ConstSpecs[] = { {"s", JSConstSpec::String, 0, "solid"}, {"b", JSConstSpec::String, 0, "beveled"}, @@ -13,11 +15,9 @@ const JSConstSpec CJS_Border::ConstSpecs[] = { {"i", JSConstSpec::String, 0, "inset"}, {"u", JSConstSpec::String, 0, "underline"}}; -uint32_t CJS_Border::ObjDefnID = 0; - // static void CJS_Border::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = - pEngine->DefineObj("border", FXJSOBJTYPE_STATIC, nullptr, nullptr); - DefineConsts(pEngine, ObjDefnID, ConstSpecs); + pEngine->DefineObj(kObjDefnId, "border", FXJSOBJTYPE_STATIC, nullptr, + nullptr); + DefineConsts(pEngine, kObjDefnId, ConstSpecs); } diff --git a/fxjs/cjs_border.h b/fxjs/cjs_border.h index 29a9251489..0127ec8828 100644 --- a/fxjs/cjs_border.h +++ b/fxjs/cjs_border.h @@ -8,15 +8,16 @@ #define FXJS_CJS_BORDER_H_ #include "fxjs/cjs_object.h" +#include "fxjs/js_define.h" class CJS_Border final : public CJS_Object { public: + static constexpr uint32_t kObjDefnId = kJSBorderObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Border() = delete; private: - static uint32_t ObjDefnID; static const JSConstSpec ConstSpecs[]; }; diff --git a/fxjs/cjs_color.cpp b/fxjs/cjs_color.cpp index daaefae4b0..b39440d826 100644 --- a/fxjs/cjs_color.cpp +++ b/fxjs/cjs_color.cpp @@ -34,20 +34,14 @@ const JSPropertySpec CJS_Color::PropertySpecs[] = { const JSMethodSpec CJS_Color::MethodSpecs[] = {{"convert", convert_static}, {"equal", equal_static}}; -uint32_t CJS_Color::ObjDefnID = 0; const char CJS_Color::kName[] = "color"; -// static -uint32_t CJS_Color::GetObjDefnID() { - return ObjDefnID; -} - // static void CJS_Color::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj(CJS_Color::kName, FXJSOBJTYPE_STATIC, - JSConstructor, JSDestructor); - DefineProps(pEngine, ObjDefnID, PropertySpecs); - DefineMethods(pEngine, ObjDefnID, MethodSpecs); + pEngine->DefineObj(kObjDefnId, CJS_Color::kName, FXJSOBJTYPE_STATIC, + JSConstructor, JSDestructor); + DefineProps(pEngine, kObjDefnId, PropertySpecs); + DefineMethods(pEngine, kObjDefnId, MethodSpecs); } // static diff --git a/fxjs/cjs_color.h b/fxjs/cjs_color.h index 0eb6247af6..744fde13e4 100644 --- a/fxjs/cjs_color.h +++ b/fxjs/cjs_color.h @@ -14,7 +14,7 @@ class CJS_Color final : public CJS_Object { public: - static uint32_t GetObjDefnID(); + static constexpr uint32_t kObjDefnId = kJSColorObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); static v8::Local ConvertPWLColorToArray(CJS_Runtime* pRuntime, const CFX_Color& color); @@ -41,7 +41,6 @@ class CJS_Color final : public CJS_Object { JS_STATIC_METHOD(equal, CJS_Color) private: - static uint32_t ObjDefnID; static const char kName[]; static const JSPropertySpec PropertySpecs[]; static const JSMethodSpec MethodSpecs[]; diff --git a/fxjs/cjs_console.cpp b/fxjs/cjs_console.cpp index db20281e44..9fe8e3edc8 100644 --- a/fxjs/cjs_console.cpp +++ b/fxjs/cjs_console.cpp @@ -16,19 +16,13 @@ const JSMethodSpec CJS_Console::MethodSpecs[] = {{"clear", clear_static}, {"println", println_static}, {"show", show_static}}; -uint32_t CJS_Console::ObjDefnID = 0; const char CJS_Console::kName[] = "console"; -// static -uint32_t CJS_Console::GetObjDefnID() { - return ObjDefnID; -} - // static void CJS_Console::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj(CJS_Console::kName, FXJSOBJTYPE_STATIC, - JSConstructor, JSDestructor); - DefineMethods(pEngine, ObjDefnID, MethodSpecs); + pEngine->DefineObj(kObjDefnId, CJS_Console::kName, FXJSOBJTYPE_STATIC, + JSConstructor, JSDestructor); + DefineMethods(pEngine, kObjDefnId, MethodSpecs); } CJS_Console::CJS_Console(v8::Local pObject, CJS_Runtime* pRuntime) diff --git a/fxjs/cjs_console.h b/fxjs/cjs_console.h index 610c3ae125..b93df0975b 100644 --- a/fxjs/cjs_console.h +++ b/fxjs/cjs_console.h @@ -13,7 +13,7 @@ class CJS_Console final : public CJS_Object { public: - static uint32_t GetObjDefnID(); + static constexpr uint32_t kObjDefnId = kJSConsoleObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Console(v8::Local pObject, CJS_Runtime* pRuntime); @@ -25,7 +25,6 @@ class CJS_Console final : public CJS_Object { JS_STATIC_METHOD(show, CJS_Console) private: - static uint32_t ObjDefnID; static const char kName[]; static const JSMethodSpec MethodSpecs[]; diff --git a/fxjs/cjs_display.cpp b/fxjs/cjs_display.cpp index 4d15d150c4..28c4a9d355 100644 --- a/fxjs/cjs_display.cpp +++ b/fxjs/cjs_display.cpp @@ -6,17 +6,17 @@ #include "fxjs/cjs_display.h" +#include "fxjs/js_define.h" + const JSConstSpec CJS_Display::ConstSpecs[] = { {"visible", JSConstSpec::Number, 0, nullptr}, {"hidden", JSConstSpec::Number, 1, nullptr}, {"noPrint", JSConstSpec::Number, 2, nullptr}, {"noView", JSConstSpec::Number, 3, nullptr}}; -uint32_t CJS_Display::ObjDefnID = 0; - // static void CJS_Display::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = - pEngine->DefineObj("display", FXJSOBJTYPE_STATIC, nullptr, nullptr); - DefineConsts(pEngine, ObjDefnID, ConstSpecs); + pEngine->DefineObj(kObjDefnId, "display", FXJSOBJTYPE_STATIC, nullptr, + nullptr); + DefineConsts(pEngine, kObjDefnId, ConstSpecs); } diff --git a/fxjs/cjs_display.h b/fxjs/cjs_display.h index 7de536855e..51f6767bbe 100644 --- a/fxjs/cjs_display.h +++ b/fxjs/cjs_display.h @@ -8,15 +8,16 @@ #define FXJS_CJS_DISPLAY_H_ #include "fxjs/cjs_object.h" +#include "fxjs/js_define.h" class CJS_Display final : public CJS_Object { public: + static constexpr uint32_t kObjDefnId = kJSDisplayObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Display() = delete; private: - static uint32_t ObjDefnID; static const JSConstSpec ConstSpecs[]; }; diff --git a/fxjs/cjs_document.cpp b/fxjs/cjs_document.cpp index 2197eb4508..ee73eac1c9 100644 --- a/fxjs/cjs_document.cpp +++ b/fxjs/cjs_document.cpp @@ -116,20 +116,14 @@ const JSMethodSpec CJS_Document::MethodSpecs[] = { {"submitForm", submitForm_static}, {"syncAnnotScan", syncAnnotScan_static}}; -uint32_t CJS_Document::ObjDefnID = 0; const char CJS_Document::kName[] = "Document"; -// static -uint32_t CJS_Document::GetObjDefnID() { - return ObjDefnID; -} - // static void CJS_Document::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj(CJS_Document::kName, FXJSOBJTYPE_GLOBAL, - JSConstructor, JSDestructor); - DefineProps(pEngine, ObjDefnID, PropertySpecs); - DefineMethods(pEngine, ObjDefnID, MethodSpecs); + pEngine->DefineObj(kObjDefnId, CJS_Document::kName, FXJSOBJTYPE_GLOBAL, + JSConstructor, JSDestructor); + DefineProps(pEngine, kObjDefnId, PropertySpecs); + DefineMethods(pEngine, kObjDefnId, MethodSpecs); } CJS_Document::CJS_Document(v8::Local pObject, CJS_Runtime* pRuntime) @@ -268,8 +262,8 @@ CJS_Result CJS_Document::getField(CJS_Runtime* pRuntime, return CJS_Result::Success(pRuntime->NewUndefined()); } - v8::Local pFieldObj = pRuntime->NewFXJSBoundObject( - CJS_Field::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + v8::Local pFieldObj = + pRuntime->NewFXJSBoundObject(CJS_Field::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pFieldObj.IsEmpty()) { return CJS_Result::Failure(JSMessage::kBadObjectError); } @@ -1102,8 +1096,8 @@ CJS_Result CJS_Document::getAnnot(CJS_Runtime* pRuntime, return CJS_Result::Failure(JSMessage::kBadObjectError); } - v8::Local pObj = pRuntime->NewFXJSBoundObject( - CJS_Annot::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + v8::Local pObj = + pRuntime->NewFXJSBoundObject(CJS_Annot::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pObj.IsEmpty()) { return CJS_Result::Failure(JSMessage::kBadObjectError); } @@ -1147,7 +1141,7 @@ CJS_Result CJS_Document::getAnnots(CJS_Runtime* pRuntime, } v8::Local pObj = pRuntime->NewFXJSBoundObject( - CJS_Annot::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + CJS_Annot::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pObj.IsEmpty()) { return CJS_Result::Failure(JSMessage::kBadObjectError); } @@ -1215,8 +1209,8 @@ CJS_Result CJS_Document::get_icons(CJS_Runtime* pRuntime) { v8::Local Icons = pRuntime->NewArray(); int i = 0; for (const auto& name : icon_names_) { - v8::Local pObj = pRuntime->NewFXJSBoundObject( - CJS_Icon::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + v8::Local pObj = + pRuntime->NewFXJSBoundObject(CJS_Icon::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pObj.IsEmpty()) { return CJS_Result::Failure(JSMessage::kBadObjectError); } @@ -1249,8 +1243,8 @@ CJS_Result CJS_Document::getIcon(CJS_Runtime* pRuntime, return CJS_Result::Failure(JSMessage::kBadObjectError); } - v8::Local pObj = pRuntime->NewFXJSBoundObject( - CJS_Icon::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + v8::Local pObj = + pRuntime->NewFXJSBoundObject(CJS_Icon::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pObj.IsEmpty()) { return CJS_Result::Failure(JSMessage::kBadObjectError); } diff --git a/fxjs/cjs_document.h b/fxjs/cjs_document.h index 3e09ea4565..dea5343400 100644 --- a/fxjs/cjs_document.h +++ b/fxjs/cjs_document.h @@ -21,7 +21,7 @@ struct CJS_DelayData; class CJS_Document final : public CJS_Object, public Observable { public: - static uint32_t GetObjDefnID(); + static constexpr uint32_t kObjDefnId = kJSDocumentObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Document(v8::Local pObject, CJS_Runtime* pRuntime); @@ -111,7 +111,6 @@ class CJS_Document final : public CJS_Object, public Observable { JS_STATIC_METHOD(syncAnnotScan, CJS_Document) private: - static uint32_t ObjDefnID; static const char kName[]; static const JSPropertySpec PropertySpecs[]; static const JSMethodSpec MethodSpecs[]; diff --git a/fxjs/cjs_event.cpp b/fxjs/cjs_event.cpp index 62e7e6ee9f..7c94620d54 100644 --- a/fxjs/cjs_event.cpp +++ b/fxjs/cjs_event.cpp @@ -33,19 +33,13 @@ const JSPropertySpec CJS_Event::PropertySpecs[] = { {"value", get_value_static, set_value_static}, {"willCommit", get_will_commit_static, set_will_commit_static}}; -uint32_t CJS_Event::ObjDefnID = 0; const char CJS_Event::kName[] = "event"; -// static -uint32_t CJS_Event::GetObjDefnID() { - return ObjDefnID; -} - // static void CJS_Event::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj(CJS_Event::kName, FXJSOBJTYPE_STATIC, - JSConstructor, JSDestructor); - DefineProps(pEngine, ObjDefnID, PropertySpecs); + pEngine->DefineObj(kObjDefnId, CJS_Event::kName, FXJSOBJTYPE_STATIC, + JSConstructor, JSDestructor); + DefineProps(pEngine, kObjDefnId, PropertySpecs); } CJS_Event::CJS_Event(v8::Local pObject, CJS_Runtime* pRuntime) diff --git a/fxjs/cjs_event.h b/fxjs/cjs_event.h index fc9bec2df4..fbfedc7b9d 100644 --- a/fxjs/cjs_event.h +++ b/fxjs/cjs_event.h @@ -12,7 +12,7 @@ class CJS_Event final : public CJS_Object { public: - static uint32_t GetObjDefnID(); + static constexpr uint32_t kObjDefnId = kJSEventObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Event(v8::Local pObject, CJS_Runtime* pRuntime); @@ -40,7 +40,6 @@ class CJS_Event final : public CJS_Object { JS_STATIC_PROP(willCommit, will_commit, CJS_Event) private: - static uint32_t ObjDefnID; static const char kName[]; static const JSPropertySpec PropertySpecs[]; diff --git a/fxjs/cjs_event_context.cpp b/fxjs/cjs_event_context.cpp index bc32522c5b..f1bd3c7d92 100644 --- a/fxjs/cjs_event_context.cpp +++ b/fxjs/cjs_event_context.cpp @@ -56,13 +56,13 @@ std::optional CJS_EventContext::RunScript( CJS_Field* CJS_EventContext::SourceField() { v8::Local pDocObj = runtime_->NewFXJSBoundObject( - CJS_Document::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + CJS_Document::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pDocObj.IsEmpty()) { return nullptr; } - v8::Local pFieldObj = runtime_->NewFXJSBoundObject( - CJS_Field::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + v8::Local pFieldObj = + runtime_->NewFXJSBoundObject(CJS_Field::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pFieldObj.IsEmpty()) { return nullptr; } @@ -80,13 +80,13 @@ CJS_Field* CJS_EventContext::SourceField() { CJS_Field* CJS_EventContext::TargetField() { v8::Local pDocObj = runtime_->NewFXJSBoundObject( - CJS_Document::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + CJS_Document::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pDocObj.IsEmpty()) { return nullptr; } - v8::Local pFieldObj = runtime_->NewFXJSBoundObject( - CJS_Field::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + v8::Local pFieldObj = + runtime_->NewFXJSBoundObject(CJS_Field::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pFieldObj.IsEmpty()) { return nullptr; } diff --git a/fxjs/cjs_field.cpp b/fxjs/cjs_field.cpp index 6d1180d264..4f0cb5e2ab 100644 --- a/fxjs/cjs_field.cpp +++ b/fxjs/cjs_field.cpp @@ -636,20 +636,14 @@ const JSMethodSpec CJS_Field::MethodSpecs[] = { {"signatureSign", signatureSign_static}, {"signatureValidate", signatureValidate_static}}; -uint32_t CJS_Field::ObjDefnID = 0; const char CJS_Field::kName[] = "Field"; -// static -uint32_t CJS_Field::GetObjDefnID() { - return ObjDefnID; -} - // static void CJS_Field::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj(CJS_Field::kName, FXJSOBJTYPE_DYNAMIC, - JSConstructor, JSDestructor); - DefineProps(pEngine, ObjDefnID, PropertySpecs); - DefineMethods(pEngine, ObjDefnID, MethodSpecs); + pEngine->DefineObj(kObjDefnId, CJS_Field::kName, FXJSOBJTYPE_DYNAMIC, + JSConstructor, JSDestructor); + DefineProps(pEngine, kObjDefnId, PropertySpecs); + DefineMethods(pEngine, kObjDefnId, MethodSpecs); } CJS_Field::CJS_Field(v8::Local pObject, CJS_Runtime* pRuntime) @@ -2483,8 +2477,8 @@ CJS_Result CJS_Field::buttonGetIcon(CJS_Runtime* pRuntime, return CJS_Result::Failure(JSMessage::kBadObjectError); } - v8::Local pObj = pRuntime->NewFXJSBoundObject( - CJS_Icon::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + v8::Local pObj = + pRuntime->NewFXJSBoundObject(CJS_Icon::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pObj.IsEmpty()) { return CJS_Result::Failure(JSMessage::kBadObjectError); } @@ -2609,7 +2603,7 @@ CJS_Result CJS_Field::getArray(CJS_Runtime* pRuntime, int j = 0; for (const auto& pStr : swSort) { v8::Local pObj = pRuntime->NewFXJSBoundObject( - CJS_Field::GetObjDefnID(), FXJSOBJTYPE_DYNAMIC); + CJS_Field::kObjDefnId, FXJSOBJTYPE_DYNAMIC); if (pObj.IsEmpty()) { return CJS_Result::Failure(JSMessage::kBadObjectError); } diff --git a/fxjs/cjs_field.h b/fxjs/cjs_field.h index 07c67ad020..ba6dc843e0 100644 --- a/fxjs/cjs_field.h +++ b/fxjs/cjs_field.h @@ -30,7 +30,7 @@ enum FIELD_PROP { class CJS_Field final : public CJS_Object { public: - static uint32_t GetObjDefnID(); + static constexpr uint32_t kObjDefnId = kJSFieldObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); static void DoDelay(CPDFSDK_FormFillEnvironment* pFormFillEnv, CJS_DelayData* pData); @@ -124,7 +124,6 @@ class CJS_Field final : public CJS_Object { CJS_Result set_text_color(CJS_Runtime* pRuntime, v8::Local vp); private: - static uint32_t ObjDefnID; static const char kName[]; static const JSPropertySpec PropertySpecs[]; static const JSMethodSpec MethodSpecs[]; diff --git a/fxjs/cjs_font.cpp b/fxjs/cjs_font.cpp index fcf69ac029..702ca642b6 100644 --- a/fxjs/cjs_font.cpp +++ b/fxjs/cjs_font.cpp @@ -6,6 +6,8 @@ #include "fxjs/cjs_font.h" +#include "fxjs/js_define.h" + const JSConstSpec CJS_Font::ConstSpecs[] = { {"Times", JSConstSpec::String, 0, "Times-Roman"}, {"TimesB", JSConstSpec::String, 0, "Times-Bold"}, @@ -22,10 +24,8 @@ const JSConstSpec CJS_Font::ConstSpecs[] = { {"Symbol", JSConstSpec::String, 0, "Symbol"}, {"ZapfD", JSConstSpec::String, 0, "ZapfDingbats"}}; -uint32_t CJS_Font::ObjDefnID = 0; - // static void CJS_Font::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj("font", FXJSOBJTYPE_STATIC, nullptr, nullptr); - DefineConsts(pEngine, ObjDefnID, ConstSpecs); + pEngine->DefineObj(kObjDefnId, "font", FXJSOBJTYPE_STATIC, nullptr, nullptr); + DefineConsts(pEngine, kObjDefnId, ConstSpecs); } diff --git a/fxjs/cjs_font.h b/fxjs/cjs_font.h index a4ecca164c..69cd9a2d92 100644 --- a/fxjs/cjs_font.h +++ b/fxjs/cjs_font.h @@ -8,15 +8,16 @@ #define FXJS_CJS_FONT_H_ #include "fxjs/cjs_object.h" +#include "fxjs/js_define.h" class CJS_Font final : public CJS_Object { public: + static constexpr uint32_t kObjDefnId = kJSFontObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Font() = delete; private: - static uint32_t ObjDefnID; static const JSConstSpec ConstSpecs[]; }; diff --git a/fxjs/cjs_global.cpp b/fxjs/cjs_global.cpp index 6ae91afb7c..e23b8d4455 100644 --- a/fxjs/cjs_global.cpp +++ b/fxjs/cjs_global.cpp @@ -21,6 +21,7 @@ #include "fxjs/fxv8.h" #include "fxjs/js_define.h" #include "fxjs/js_resources.h" +#include "v8/include/v8-container.h" #include "v8/include/v8-isolate.h" namespace { @@ -40,8 +41,6 @@ CJS_Global::JSGlobalData::~JSGlobalData() = default; const JSMethodSpec CJS_Global::MethodSpecs[] = { {"setPersistent", setPersistent_static}}; -uint32_t CJS_Global::ObjDefnID = 0; - // static void CJS_Global::setPersistent_static( const v8::FunctionCallbackInfo& info) { @@ -157,21 +156,16 @@ void CJS_Global::enumprop_static( // static void CJS_Global::DefineAllProperties(CFXJS_Engine* pEngine) { pEngine->DefineObjAllProperties( - ObjDefnID, CJS_Global::queryprop_static, CJS_Global::getprop_static, + kObjDefnId, CJS_Global::queryprop_static, CJS_Global::getprop_static, CJS_Global::putprop_static, CJS_Global::delprop_static, CJS_Global::enumprop_static); } -// static -uint32_t CJS_Global::GetObjDefnID() { - return ObjDefnID; -} - // static void CJS_Global::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj("global", FXJSOBJTYPE_STATIC, - JSConstructor, JSDestructor); - DefineMethods(pEngine, ObjDefnID, MethodSpecs); + pEngine->DefineObj(kObjDefnId, "global", FXJSOBJTYPE_STATIC, + JSConstructor, JSDestructor); + DefineMethods(pEngine, kObjDefnId, MethodSpecs); DefineAllProperties(pEngine); } @@ -265,17 +259,16 @@ CJS_Result CJS_Global::SetProperty(CJS_Runtime* pRuntime, void CJS_Global::EnumProperties( CJS_Runtime* pRuntime, const v8::PropertyCallbackInfo& info) { - v8::Local result = pRuntime->NewArray(); - int idx = 0; + v8::LocalVector names(pRuntime->GetIsolate()); + names.reserve(map_global_.size()); for (const auto& it : map_global_) { if (it.second->bDeleted) { continue; } - v8::Local name = pRuntime->NewString(it.first.AsStringView()); - pRuntime->PutArrayElementReentrant(result, idx, name); - ++idx; + names.push_back(pRuntime->NewString(it.first.AsStringView())); } - info.GetReturnValue().Set(result); + info.GetReturnValue().Set( + v8::Array::New(pRuntime->GetIsolate(), names.data(), names.size())); } CJS_Result CJS_Global::setPersistent( diff --git a/fxjs/cjs_global.h b/fxjs/cjs_global.h index 83b9ec259d..acc0580044 100644 --- a/fxjs/cjs_global.h +++ b/fxjs/cjs_global.h @@ -16,6 +16,7 @@ #include "fxjs/cfx_keyvalue.h" #include "fxjs/cjs_object.h" #include "fxjs/cjs_result.h" +#include "fxjs/js_define.h" class CFX_GlobalData; @@ -31,7 +32,7 @@ class CFX_GlobalData; class CJS_Global final : public CJS_Object { public: - static uint32_t GetObjDefnID(); + static constexpr uint32_t kObjDefnId = kJSGlobalObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); static void DefineAllProperties(CFXJS_Engine* pEngine); @@ -67,7 +68,6 @@ class CJS_Global final : public CJS_Object { bool bDeleted = false; }; - static uint32_t ObjDefnID; static const JSMethodSpec MethodSpecs[]; void UpdateGlobalPersistentVariables(); diff --git a/fxjs/cjs_highlight.cpp b/fxjs/cjs_highlight.cpp index 174685d9cc..dd1e1d0f9e 100644 --- a/fxjs/cjs_highlight.cpp +++ b/fxjs/cjs_highlight.cpp @@ -6,17 +6,17 @@ #include "fxjs/cjs_highlight.h" +#include "fxjs/js_define.h" + const JSConstSpec CJS_Highlight::ConstSpecs[] = { {"n", JSConstSpec::String, 0, "none"}, {"i", JSConstSpec::String, 0, "invert"}, {"p", JSConstSpec::String, 0, "push"}, {"o", JSConstSpec::String, 0, "outline"}}; -uint32_t CJS_Highlight::ObjDefnID = 0; - // static void CJS_Highlight::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = - pEngine->DefineObj("highlight", FXJSOBJTYPE_STATIC, nullptr, nullptr); - DefineConsts(pEngine, ObjDefnID, ConstSpecs); + pEngine->DefineObj(kObjDefnId, "highlight", FXJSOBJTYPE_STATIC, nullptr, + nullptr); + DefineConsts(pEngine, kObjDefnId, ConstSpecs); } diff --git a/fxjs/cjs_highlight.h b/fxjs/cjs_highlight.h index ad6881189d..b96fa494ec 100644 --- a/fxjs/cjs_highlight.h +++ b/fxjs/cjs_highlight.h @@ -8,15 +8,16 @@ #define FXJS_CJS_HIGHLIGHT_H_ #include "fxjs/cjs_object.h" +#include "fxjs/js_define.h" class CJS_Highlight final : public CJS_Object { public: + static constexpr uint32_t kObjDefnId = kJSHighlightObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Highlight() = delete; private: - static uint32_t ObjDefnID; static const JSConstSpec ConstSpecs[]; }; diff --git a/fxjs/cjs_icon.cpp b/fxjs/cjs_icon.cpp index 18393b4bcb..1d2ebe0d64 100644 --- a/fxjs/cjs_icon.cpp +++ b/fxjs/cjs_icon.cpp @@ -9,19 +9,13 @@ const JSPropertySpec CJS_Icon::PropertySpecs[] = { {"name", get_name_static, set_name_static}}; -uint32_t CJS_Icon::ObjDefnID = 0; const char CJS_Icon::kName[] = "Icon"; -// static -uint32_t CJS_Icon::GetObjDefnID() { - return ObjDefnID; -} - // static void CJS_Icon::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj(CJS_Icon::kName, FXJSOBJTYPE_DYNAMIC, - JSConstructor, JSDestructor); - DefineProps(pEngine, ObjDefnID, PropertySpecs); + pEngine->DefineObj(kObjDefnId, CJS_Icon::kName, FXJSOBJTYPE_DYNAMIC, + JSConstructor, JSDestructor); + DefineProps(pEngine, kObjDefnId, PropertySpecs); } CJS_Icon::CJS_Icon(v8::Local pObject, CJS_Runtime* pRuntime) diff --git a/fxjs/cjs_icon.h b/fxjs/cjs_icon.h index 7be9186136..3fc048f675 100644 --- a/fxjs/cjs_icon.h +++ b/fxjs/cjs_icon.h @@ -12,7 +12,7 @@ class CJS_Icon final : public CJS_Object { public: - static uint32_t GetObjDefnID(); + static constexpr uint32_t kObjDefnId = kJSIconObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Icon(v8::Local pObject, CJS_Runtime* pRuntime); @@ -24,7 +24,6 @@ class CJS_Icon final : public CJS_Object { JS_STATIC_PROP(name, name, CJS_Icon) private: - static uint32_t ObjDefnID; static const char kName[]; static const JSPropertySpec PropertySpecs[]; diff --git a/fxjs/cjs_position.cpp b/fxjs/cjs_position.cpp index 7c40525dbc..d64b4aa526 100644 --- a/fxjs/cjs_position.cpp +++ b/fxjs/cjs_position.cpp @@ -6,6 +6,8 @@ #include "fxjs/cjs_position.h" +#include "fxjs/js_define.h" + const JSConstSpec CJS_Position::ConstSpecs[] = { {"textOnly", JSConstSpec::Number, 0, nullptr}, {"iconOnly", JSConstSpec::Number, 1, nullptr}, @@ -15,11 +17,9 @@ const JSConstSpec CJS_Position::ConstSpecs[] = { {"textIconH", JSConstSpec::Number, 5, nullptr}, {"overlay", JSConstSpec::Number, 6, nullptr}}; -uint32_t CJS_Position::ObjDefnID = 0; - // static void CJS_Position::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = - pEngine->DefineObj("position", FXJSOBJTYPE_STATIC, nullptr, nullptr); - DefineConsts(pEngine, ObjDefnID, ConstSpecs); + pEngine->DefineObj(kObjDefnId, "position", FXJSOBJTYPE_STATIC, nullptr, + nullptr); + DefineConsts(pEngine, kObjDefnId, ConstSpecs); } diff --git a/fxjs/cjs_position.h b/fxjs/cjs_position.h index 14097a9713..e683dbda34 100644 --- a/fxjs/cjs_position.h +++ b/fxjs/cjs_position.h @@ -8,15 +8,16 @@ #define FXJS_CJS_POSITION_H_ #include "fxjs/cjs_object.h" +#include "fxjs/js_define.h" class CJS_Position final : public CJS_Object { public: + static constexpr uint32_t kObjDefnId = kJSPositionObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Position() = delete; private: - static uint32_t ObjDefnID; static const JSConstSpec ConstSpecs[]; }; diff --git a/fxjs/cjs_publicmethods_embeddertest.cpp b/fxjs/cjs_publicmethods_embeddertest.cpp index 1aa5c98cb0..3c99d765f3 100644 --- a/fxjs/cjs_publicmethods_embeddertest.cpp +++ b/fxjs/cjs_publicmethods_embeddertest.cpp @@ -65,12 +65,12 @@ TEST_F(CJSPublicMethodsEmbedderTest, ParseDateUsingFormat) { EXPECT_DOUBLE_EQ(504835200000.0, date); EXPECT_FALSE(bWrongFormat); - // 2085, the other '85. + // 1985 with two-digit year. bWrongFormat = false; date = CJS_PublicMethods::ParseDateUsingFormat(isolate(), L"311285", L"ddmmyy", &bWrongFormat); date = RoundDownDate(date); - EXPECT_DOUBLE_EQ(3660595200000.0, date); + EXPECT_DOUBLE_EQ(504835200000.0, date); EXPECT_FALSE(bWrongFormat); // 1995 @@ -81,12 +81,12 @@ TEST_F(CJSPublicMethodsEmbedderTest, ParseDateUsingFormat) { EXPECT_DOUBLE_EQ(791596800000.0, date); EXPECT_FALSE(bWrongFormat); - // 2095, the other '95. + // 1995 with two-digit year. bWrongFormat = false; date = CJS_PublicMethods::ParseDateUsingFormat(isolate(), L"010295", L"ddmmyy", &bWrongFormat); date = RoundDownDate(date); - EXPECT_DOUBLE_EQ(3947356800000.0, date); + EXPECT_DOUBLE_EQ(791596800000.0, date); EXPECT_FALSE(bWrongFormat); // 2005 @@ -97,7 +97,7 @@ TEST_F(CJSPublicMethodsEmbedderTest, ParseDateUsingFormat) { EXPECT_DOUBLE_EQ(1107216000000.0, date); EXPECT_FALSE(bWrongFormat); - // 2005 + // 2005 with two-digit year. bWrongFormat = false; date = CJS_PublicMethods::ParseDateUsingFormat(isolate(), L"010205", L"ddmmyy", &bWrongFormat); diff --git a/fxjs/cjs_runtime.cpp b/fxjs/cjs_runtime.cpp index 941d378bd8..75c55fa339 100644 --- a/fxjs/cjs_runtime.cpp +++ b/fxjs/cjs_runtime.cpp @@ -46,25 +46,37 @@ CJS_Runtime::CJS_Runtime(CPDFSDK_FormFillEnvironment* pFormFillEnv) : form_fill_env_(pFormFillEnv) { - v8::Isolate* pIsolate = nullptr; - IPDF_JSPLATFORM* pPlatform = form_fill_env_->GetJSPlatform(); - if (pPlatform->version <= 2) { - // Backwards compatibility - JS now initialized earlier in more modern - // JSPLATFORM versions. - unsigned int embedderDataSlot = 0; - v8::Isolate* pExternalIsolate = nullptr; - if (pPlatform->version == 2) { - pExternalIsolate = static_cast(pPlatform->m_isolate); - embedderDataSlot = pPlatform->m_v8EmbedderSlot; + v8::Isolate* pIsolate = form_fill_env_->GetIsolate(); + const bool isolate_from_env = pIsolate != nullptr; + if (!pIsolate) { + IPDF_JSPLATFORM* pPlatform = form_fill_env_->GetJSPlatform(); + if (pPlatform->version <= 2) { + // Backwards compatibility - JS now initialized earlier in more modern + // JSPLATFORM versions. + unsigned int embedderDataSlot = 0; + v8::Isolate* pExternalIsolate = nullptr; + if (pPlatform->version == 2) { + pExternalIsolate = static_cast(pPlatform->m_isolate); + embedderDataSlot = pPlatform->m_v8EmbedderSlot; + } + FXJS_Initialize(embedderDataSlot, pExternalIsolate); } - FXJS_Initialize(embedderDataSlot, pExternalIsolate); + isolate_managed_ = FXJS_GetIsolate(&pIsolate); } - isolate_managed_ = FXJS_GetIsolate(&pIsolate); SetIsolate(pIsolate); v8::Isolate::Scope isolate_scope(pIsolate); v8::HandleScope handle_scope(pIsolate); - if (isolate_managed_ || FXJS_GlobalIsolateRefCount() == 0) { + bool needs_define_js_objects; + if (isolate_from_env) { + auto* isolate_data = CFXJS_PerIsolateData::Get(pIsolate); + needs_define_js_objects = + !isolate_data || !isolate_data->HasObjectDefinitions(); + } else { + needs_define_js_objects = + isolate_managed_ || FXJS_GlobalIsolateRefCount() == 0; + } + if (needs_define_js_objects) { DefineJSObjects(); } @@ -87,43 +99,27 @@ void CJS_Runtime::DefineJSObjects() { v8::Local context = v8::Context::New(GetIsolate()); v8::Context::Scope context_scope(context); - // The call order determines the "ObjDefID" assigned to each class. - // ObjDefIDs 0 - 2 CJS_Border::DefineJSObjects(this); CJS_Display::DefineJSObjects(this); CJS_Font::DefineJSObjects(this); - - // ObjDefIDs 3 - 5 CJS_Highlight::DefineJSObjects(this); CJS_Position::DefineJSObjects(this); CJS_ScaleHow::DefineJSObjects(this); - - // ObjDefIDs 6 - 8 CJS_ScaleWhen::DefineJSObjects(this); CJS_Style::DefineJSObjects(this); CJS_Zoomtype::DefineJSObjects(this); - - // ObjDefIDs 9 - 11 CJS_App::DefineJSObjects(this); CJS_Color::DefineJSObjects(this); CJS_Console::DefineJSObjects(this); - - // ObjDefIDs 12 - 14 CJS_Document::DefineJSObjects(this); CJS_Event::DefineJSObjects(this); CJS_Field::DefineJSObjects(this); - - // ObjDefIDs 15 - 17 CJS_Global::DefineJSObjects(this); CJS_Icon::DefineJSObjects(this); CJS_Util::DefineJSObjects(this); - - // ObjDefIDs 18 - 20 (these can't fail, return void). CJS_PublicMethods::DefineJSObjects(this); CJS_GlobalConsts::DefineJSObjects(this); CJS_GlobalArrays::DefineJSObjects(this); - - // ObjDefIDs 21 - 22. CJS_TimerObj::DefineJSObjects(this); CJS_Annot::DefineJSObjects(this); } diff --git a/fxjs/cjs_scalehow.cpp b/fxjs/cjs_scalehow.cpp index b64da9fbec..ddd89715bc 100644 --- a/fxjs/cjs_scalehow.cpp +++ b/fxjs/cjs_scalehow.cpp @@ -6,15 +6,15 @@ #include "fxjs/cjs_scalehow.h" +#include "fxjs/js_define.h" + const JSConstSpec CJS_ScaleHow::ConstSpecs[] = { {"proportional", JSConstSpec::Number, 0, 0}, {"anamorphic", JSConstSpec::Number, 1, 0}}; -uint32_t CJS_ScaleHow::ObjDefnID = 0; - // static void CJS_ScaleHow::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = - pEngine->DefineObj("scaleHow", FXJSOBJTYPE_STATIC, nullptr, nullptr); - DefineConsts(pEngine, ObjDefnID, ConstSpecs); + pEngine->DefineObj(kObjDefnId, "scaleHow", FXJSOBJTYPE_STATIC, nullptr, + nullptr); + DefineConsts(pEngine, kObjDefnId, ConstSpecs); } diff --git a/fxjs/cjs_scalehow.h b/fxjs/cjs_scalehow.h index b5ab3e584c..16977c318e 100644 --- a/fxjs/cjs_scalehow.h +++ b/fxjs/cjs_scalehow.h @@ -8,15 +8,16 @@ #define FXJS_CJS_SCALEHOW_H_ #include "fxjs/cjs_object.h" +#include "fxjs/js_define.h" class CJS_ScaleHow final : public CJS_Object { public: + static constexpr uint32_t kObjDefnId = kJSScaleHowObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_ScaleHow() = delete; private: - static uint32_t ObjDefnID; static const JSConstSpec ConstSpecs[]; }; diff --git a/fxjs/cjs_scalewhen.cpp b/fxjs/cjs_scalewhen.cpp index c562277827..614e82cbd1 100644 --- a/fxjs/cjs_scalewhen.cpp +++ b/fxjs/cjs_scalewhen.cpp @@ -6,17 +6,17 @@ #include "fxjs/cjs_scalewhen.h" +#include "fxjs/js_define.h" + const JSConstSpec CJS_ScaleWhen::ConstSpecs[] = { {"always", JSConstSpec::Number, 0, nullptr}, {"never", JSConstSpec::Number, 1, nullptr}, {"tooBig", JSConstSpec::Number, 2, nullptr}, {"tooSmall", JSConstSpec::Number, 3, nullptr}}; -uint32_t CJS_ScaleWhen::ObjDefnID = 0; - // static void CJS_ScaleWhen::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = - pEngine->DefineObj("scaleWhen", FXJSOBJTYPE_STATIC, nullptr, nullptr); - DefineConsts(pEngine, ObjDefnID, ConstSpecs); + pEngine->DefineObj(kObjDefnId, "scaleWhen", FXJSOBJTYPE_STATIC, nullptr, + nullptr); + DefineConsts(pEngine, kObjDefnId, ConstSpecs); } diff --git a/fxjs/cjs_scalewhen.h b/fxjs/cjs_scalewhen.h index 43ab5418a2..004f476567 100644 --- a/fxjs/cjs_scalewhen.h +++ b/fxjs/cjs_scalewhen.h @@ -8,15 +8,16 @@ #define FXJS_CJS_SCALEWHEN_H_ #include "fxjs/cjs_object.h" +#include "fxjs/js_define.h" class CJS_ScaleWhen final : public CJS_Object { public: + static constexpr uint32_t kObjDefnId = kJSScaleWhenObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_ScaleWhen() = delete; private: - static uint32_t ObjDefnID; static const JSConstSpec ConstSpecs[]; }; diff --git a/fxjs/cjs_style.cpp b/fxjs/cjs_style.cpp index a43fa7dd07..b789af1036 100644 --- a/fxjs/cjs_style.cpp +++ b/fxjs/cjs_style.cpp @@ -6,6 +6,8 @@ #include "fxjs/cjs_style.h" +#include "fxjs/js_define.h" + const JSConstSpec CJS_Style::ConstSpecs[] = { {"ch", JSConstSpec::String, 0, "check"}, {"cr", JSConstSpec::String, 0, "cross"}, @@ -14,10 +16,8 @@ const JSConstSpec CJS_Style::ConstSpecs[] = { {"st", JSConstSpec::String, 0, "star"}, {"sq", JSConstSpec::String, 0, "square"}}; -uint32_t CJS_Style::ObjDefnID = 0; - // static void CJS_Style::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj("style", FXJSOBJTYPE_STATIC, nullptr, nullptr); - DefineConsts(pEngine, ObjDefnID, ConstSpecs); + pEngine->DefineObj(kObjDefnId, "style", FXJSOBJTYPE_STATIC, nullptr, nullptr); + DefineConsts(pEngine, kObjDefnId, ConstSpecs); } diff --git a/fxjs/cjs_style.h b/fxjs/cjs_style.h index 02449a66c5..6217de40ad 100644 --- a/fxjs/cjs_style.h +++ b/fxjs/cjs_style.h @@ -8,15 +8,16 @@ #define FXJS_CJS_STYLE_H_ #include "fxjs/cjs_object.h" +#include "fxjs/js_define.h" class CJS_Style final : public CJS_Object { public: + static constexpr uint32_t kObjDefnId = kJSStyleObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Style() = delete; private: - static uint32_t ObjDefnID; static const JSConstSpec ConstSpecs[]; }; diff --git a/fxjs/cjs_timerobj.cpp b/fxjs/cjs_timerobj.cpp index 42b8a92500..f2a29f044a 100644 --- a/fxjs/cjs_timerobj.cpp +++ b/fxjs/cjs_timerobj.cpp @@ -9,17 +9,10 @@ #include "fxjs/global_timer.h" #include "fxjs/js_define.h" -uint32_t CJS_TimerObj::ObjDefnID = 0; - -// static -uint32_t CJS_TimerObj::GetObjDefnID() { - return ObjDefnID; -} - // static void CJS_TimerObj::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj("TimerObj", FXJSOBJTYPE_DYNAMIC, - JSConstructor, JSDestructor); + pEngine->DefineObj(kObjDefnId, "TimerObj", FXJSOBJTYPE_DYNAMIC, + JSConstructor, JSDestructor); } CJS_TimerObj::CJS_TimerObj(v8::Local pObject, CJS_Runtime* pRuntime) diff --git a/fxjs/cjs_timerobj.h b/fxjs/cjs_timerobj.h index 0e86392574..c67d310172 100644 --- a/fxjs/cjs_timerobj.h +++ b/fxjs/cjs_timerobj.h @@ -8,12 +8,13 @@ #define FXJS_CJS_TIMEROBJ_H_ #include "fxjs/cjs_object.h" +#include "fxjs/js_define.h" class GlobalTimer; class CJS_TimerObj final : public CJS_Object { public: - static uint32_t GetObjDefnID(); + static constexpr uint32_t kObjDefnId = kJSTimerObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_TimerObj(v8::Local pObject, CJS_Runtime* pRuntime); @@ -23,7 +24,6 @@ class CJS_TimerObj final : public CJS_Object { int GetTimerID() const { return timer_id_; } private: - static uint32_t ObjDefnID; int timer_id_ = 0; // Weak reference to GlobalTimer through global map. }; diff --git a/fxjs/cjs_util.cpp b/fxjs/cjs_util.cpp index 439cc55dcd..2e79260fa9 100644 --- a/fxjs/cjs_util.cpp +++ b/fxjs/cjs_util.cpp @@ -81,19 +81,13 @@ const JSMethodSpec CJS_Util::MethodSpecs[] = { {"scand", scand_static}, {"byteToChar", byteToChar_static}}; -uint32_t CJS_Util::ObjDefnID = 0; const char CJS_Util::kName[] = "util"; -// static -uint32_t CJS_Util::GetObjDefnID() { - return ObjDefnID; -} - // static void CJS_Util::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = pEngine->DefineObj(CJS_Util::kName, FXJSOBJTYPE_STATIC, - JSConstructor, JSDestructor); - DefineMethods(pEngine, ObjDefnID, MethodSpecs); + pEngine->DefineObj(kObjDefnId, CJS_Util::kName, FXJSOBJTYPE_STATIC, + JSConstructor, JSDestructor); + DefineMethods(pEngine, kObjDefnId, MethodSpecs); } CJS_Util::CJS_Util(v8::Local pObject, CJS_Runtime* pRuntime) @@ -271,6 +265,7 @@ CJS_Result CJS_Util::printd(CJS_Runtime* pRuntime, time.tm_hour = hour; time.tm_min = min; time.tm_sec = sec; + time.tm_wday = FX_GetDayOfWeekFromTime(date); wchar_t buf[64] = {}; UNSAFE_TODO(FXSYS_wcsftime(buf, 64, cFormat.c_str(), &time)); diff --git a/fxjs/cjs_util.h b/fxjs/cjs_util.h index 10e202ce28..25f2372954 100644 --- a/fxjs/cjs_util.h +++ b/fxjs/cjs_util.h @@ -22,7 +22,7 @@ class CJS_Util final : public CJS_Object { kString = 2, }; - static uint32_t GetObjDefnID(); + static constexpr uint32_t kObjDefnId = kJSUtilObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Util(v8::Local pObject, CJS_Runtime* pRuntime); @@ -48,7 +48,6 @@ class CJS_Util final : public CJS_Object { JS_STATIC_METHOD(byteToChar, CJS_Util) private: - static uint32_t ObjDefnID; static const char kName[]; static const JSMethodSpec MethodSpecs[]; diff --git a/fxjs/cjs_zoomtype.cpp b/fxjs/cjs_zoomtype.cpp index b978468f6e..5259b97c8f 100644 --- a/fxjs/cjs_zoomtype.cpp +++ b/fxjs/cjs_zoomtype.cpp @@ -6,6 +6,8 @@ #include "fxjs/cjs_zoomtype.h" +#include "fxjs/js_define.h" + const JSConstSpec CJS_Zoomtype::ConstSpecs[] = { {"none", JSConstSpec::String, 0, "NoVary"}, {"fitP", JSConstSpec::String, 0, "FitPage"}, @@ -15,11 +17,9 @@ const JSConstSpec CJS_Zoomtype::ConstSpecs[] = { {"pref", JSConstSpec::String, 0, "Preferred"}, {"refW", JSConstSpec::String, 0, "ReflowWidth"}}; -uint32_t CJS_Zoomtype::ObjDefnID = 0; - // static void CJS_Zoomtype::DefineJSObjects(CFXJS_Engine* pEngine) { - ObjDefnID = - pEngine->DefineObj("zoomtype", FXJSOBJTYPE_STATIC, nullptr, nullptr); - DefineConsts(pEngine, ObjDefnID, ConstSpecs); + pEngine->DefineObj(kObjDefnId, "zoomtype", FXJSOBJTYPE_STATIC, nullptr, + nullptr); + DefineConsts(pEngine, kObjDefnId, ConstSpecs); } diff --git a/fxjs/cjs_zoomtype.h b/fxjs/cjs_zoomtype.h index 2efd49ac97..6697c59c51 100644 --- a/fxjs/cjs_zoomtype.h +++ b/fxjs/cjs_zoomtype.h @@ -8,15 +8,16 @@ #define FXJS_CJS_ZOOMTYPE_H_ #include "fxjs/cjs_object.h" +#include "fxjs/js_define.h" class CJS_Zoomtype final : public CJS_Object { public: + static constexpr uint32_t kObjDefnId = kJSZoomtypeObjDefnID; static void DefineJSObjects(CFXJS_Engine* pEngine); CJS_Zoomtype() = delete; private: - static uint32_t ObjDefnID; static const JSConstSpec ConstSpecs[]; }; diff --git a/fxjs/fx_date_helpers.cpp b/fxjs/fx_date_helpers.cpp index 8085f96927..c7d55a6d9b 100644 --- a/fxjs/fx_date_helpers.cpp +++ b/fxjs/fx_date_helpers.cpp @@ -68,7 +68,7 @@ int GetDaylightSavingTA(double d) { } bool IsLeapYear(int year) { - return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 != 0)); + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } int DayFromYear(int y) { @@ -217,6 +217,11 @@ int FX_GetDayFromTime(double dt) { return DateFromTime(dt); } +int FX_GetDayOfWeekFromTime(double dt) { + // 1970-01-01 was Thursday (day 4 in 0-based Sunday-indexed week). + return static_cast(Mod(Day(dt) + 4, 7)); +} + int FX_GetHourFromTime(double dt) { return (int)Mod(floor(dt / (60 * 60 * 1000)), 24); } @@ -315,39 +320,39 @@ int FX_ParseStringInteger(const WideString& str, ConversionStatus FX_ParseDateUsingFormat(const WideString& value, const WideString& format, double* result) { - double dt = FX_GetDateTime(); + const double dt = FX_GetDateTime(); if (format.IsEmpty() || value.IsEmpty()) { *result = dt; return ConversionStatus::kSuccess; } - int nYear = FX_GetYearFromTime(dt); - int nMonth = FX_GetMonthFromTime(dt) + 1; - int nDay = FX_GetDayFromTime(dt); - int nHour = FX_GetHourFromTime(dt); - int nMin = FX_GetMinFromTime(dt); - int nSec = FX_GetSecFromTime(dt); - int nYearSub = 99; // nYear - 2000; - bool bPm = false; - bool bExit = false; - bool bBadFormat = false; - size_t i = 0; - size_t j = 0; - - while (i < format.GetLength()) { - if (bExit) { + int year = FX_GetYearFromTime(dt); + int month = FX_GetMonthFromTime(dt) + 1; + int day = FX_GetDayFromTime(dt); + int hour = FX_GetHourFromTime(dt); + int minute = FX_GetMinFromTime(dt); + int second = FX_GetSecFromTime(dt); + enum class AmPm { kNone, kAm, kPm }; + AmPm am_pm = AmPm::kNone; + bool exit_loop = false; + bool bad_format = false; + size_t format_idx = 0; + size_t value_idx = 0; + + while (format_idx < format.GetLength()) { + if (exit_loop) { break; } - wchar_t c = format[i]; - switch (c) { + const wchar_t format_char = format[format_idx]; + switch (format_char) { case ':': case '.': case '-': case '\\': case '/': - i++; - j++; + ++format_idx; + ++value_idx; break; case 'y': @@ -358,218 +363,200 @@ ConversionStatus FX_ParseDateUsingFormat(const WideString& value, case 'M': case 's': case 't': { - size_t oldj = j; - size_t nSkip = 0; - size_t remaining = format.GetLength() - i - 1; + const size_t old_value_idx = value_idx; + size_t chars_to_skip = 0; + size_t token_len = 1; + while (format_idx + token_len < format.GetLength() && + format[format_idx + token_len] == format_char) { + ++token_len; + } - if (remaining == 0 || format[i + 1] != c) { - switch (c) { + if (token_len <= 2) { + switch (format_char) { case 'y': - i++; - j++; - break; - case 'm': - nMonth = FX_ParseStringInteger(value, j, &nSkip, 2); - i++; - j += nSkip; - break; - case 'd': - nDay = FX_ParseStringInteger(value, j, &nSkip, 2); - i++; - j += nSkip; - break; - case 'H': - nHour = FX_ParseStringInteger(value, j, &nSkip, 2); - i++; - j += nSkip; - break; - case 'h': - nHour = FX_ParseStringInteger(value, j, &nSkip, 2); - i++; - j += nSkip; - break; - case 'M': - nMin = FX_ParseStringInteger(value, j, &nSkip, 2); - i++; - j += nSkip; - break; - case 's': - nSec = FX_ParseStringInteger(value, j, &nSkip, 2); - i++; - j += nSkip; - break; - case 't': - bPm = (j < value.GetLength() && value[j] == 'p'); - i++; - j++; - break; - } - } else if (remaining == 1 || format[i + 2] != c) { - switch (c) { - case 'y': - nYear = FX_ParseStringInteger(value, j, &nSkip, 2); - i += 2; - j += nSkip; + if (token_len == 1) { + ++value_idx; + } else { + year = + FX_ParseStringInteger(value, value_idx, &chars_to_skip, 2); + value_idx += chars_to_skip; + } break; case 'm': - nMonth = FX_ParseStringInteger(value, j, &nSkip, 2); - i += 2; - j += nSkip; + month = + FX_ParseStringInteger(value, value_idx, &chars_to_skip, 2); + value_idx += chars_to_skip; break; case 'd': - nDay = FX_ParseStringInteger(value, j, &nSkip, 2); - i += 2; - j += nSkip; + day = FX_ParseStringInteger(value, value_idx, &chars_to_skip, 2); + value_idx += chars_to_skip; break; case 'H': - nHour = FX_ParseStringInteger(value, j, &nSkip, 2); - i += 2; - j += nSkip; - break; case 'h': - nHour = FX_ParseStringInteger(value, j, &nSkip, 2); - i += 2; - j += nSkip; + hour = FX_ParseStringInteger(value, value_idx, &chars_to_skip, 2); + value_idx += chars_to_skip; break; case 'M': - nMin = FX_ParseStringInteger(value, j, &nSkip, 2); - i += 2; - j += nSkip; + minute = + FX_ParseStringInteger(value, value_idx, &chars_to_skip, 2); + value_idx += chars_to_skip; break; case 's': - nSec = FX_ParseStringInteger(value, j, &nSkip, 2); - i += 2; - j += nSkip; + second = + FX_ParseStringInteger(value, value_idx, &chars_to_skip, 2); + value_idx += chars_to_skip; break; - case 't': - bPm = (j + 1 < value.GetLength() && value[j] == 'p' && - value[j + 1] == 'm'); - i += 2; - j += 2; + case 't': { + if (value_idx + token_len <= value.GetLength()) { + const WideStringView marker = + value.AsStringView().Substr(value_idx, token_len); + if (marker.EqualsASCIINoCase(token_len == 1 ? "p" : "pm")) { + am_pm = AmPm::kPm; + value_idx += token_len; + } else if (marker.EqualsASCIINoCase(token_len == 1 ? "a" + : "am")) { + am_pm = AmPm::kAm; + value_idx += token_len; + } + } break; + } } - } else if (remaining == 2 || format[i + 3] != c) { - switch (c) { + format_idx += token_len; + } else if (token_len == 3) { + switch (format_char) { case 'm': { - bool bFind = false; - nSkip = FindSubWordLength(value, j); - if (nSkip == KMonthAbbreviationLength) { - WideString sMonth = value.Substr(j, KMonthAbbreviationLength); + bool found = false; + chars_to_skip = FindSubWordLength(value, value_idx); + if (chars_to_skip == KMonthAbbreviationLength) { + const WideString month_str = + value.Substr(value_idx, KMonthAbbreviationLength); for (size_t m = 0; m < std::size(kMonths); ++m) { - if (sMonth.EqualsASCIINoCase(kMonths[m])) { - nMonth = static_cast(m) + 1; - i += 3; - j += nSkip; - bFind = true; + if (month_str.EqualsASCIINoCase(kMonths[m])) { + month = static_cast(m) + 1; + format_idx += 3; + value_idx += chars_to_skip; + found = true; break; } } } - if (!bFind) { - nMonth = FX_ParseStringInteger(value, j, &nSkip, 3); - i += 3; - j += nSkip; + if (!found) { + month = + FX_ParseStringInteger(value, value_idx, &chars_to_skip, 3); + format_idx += 3; + value_idx += chars_to_skip; } - } break; + break; + } case 'y': break; default: - i += 3; - j += 3; + format_idx += 3; + value_idx += 3; break; } - } else if (remaining == 3 || format[i + 4] != c) { - switch (c) { + } else if (token_len == 4) { + switch (format_char) { case 'y': - nYear = FX_ParseStringInteger(value, j, &nSkip, 4); - j += nSkip; - i += 4; + year = FX_ParseStringInteger(value, value_idx, &chars_to_skip, 4); + format_idx += 4; + value_idx += chars_to_skip; break; case 'm': { - bool bFind = false; - nSkip = FindSubWordLength(value, j); - if (nSkip <= kLongestFullMonthLength) { - WideString sMonth = value.Substr(j, nSkip); - sMonth.MakeLower(); + bool found = false; + chars_to_skip = FindSubWordLength(value, value_idx); + if (chars_to_skip <= kLongestFullMonthLength) { + WideString month_str = value.Substr(value_idx, chars_to_skip); + month_str.MakeLower(); for (size_t m = 0; m < std::size(kFullMonths); ++m) { - auto sFullMonths = WideString::FromASCII(kFullMonths[m]); - sFullMonths.MakeLower(); - if (sFullMonths.Contains(sMonth.AsStringView())) { - nMonth = static_cast(m) + 1; - i += 4; - j += nSkip; - bFind = true; + auto full_month = WideString::FromASCII(kFullMonths[m]); + full_month.MakeLower(); + if (full_month.Contains(month_str.AsStringView())) { + month = static_cast(m) + 1; + format_idx += 4; + value_idx += chars_to_skip; + found = true; break; } } } - if (!bFind) { - nMonth = FX_ParseStringInteger(value, j, &nSkip, 4); - i += 4; - j += nSkip; + if (!found) { + month = + FX_ParseStringInteger(value, value_idx, &chars_to_skip, 4); + format_idx += 4; + value_idx += chars_to_skip; } - } break; + break; + } default: - i += 4; - j += 4; + format_idx += 4; + value_idx += 4; break; } } else { - if (j >= value.GetLength() || format[i] != value[j]) { - bBadFormat = true; - bExit = true; + if (value_idx >= value.GetLength() || + format[format_idx] != value[value_idx]) { + bad_format = true; + exit_loop = true; } - i++; - j++; + ++format_idx; + ++value_idx; } - if (oldj == j) { - bBadFormat = true; - bExit = true; + if (old_value_idx == value_idx) { + bad_format = true; + exit_loop = true; } break; } default: - if (value.GetLength() <= j) { - bExit = true; - } else if (format[i] != value[j]) { - bBadFormat = true; - bExit = true; + if (value.GetLength() <= value_idx) { + exit_loop = true; + } else if (format[format_idx] != value[value_idx]) { + bad_format = true; + exit_loop = true; } - i++; - j++; + ++format_idx; + ++value_idx; break; } } - if (bBadFormat) { + if (bad_format) { return ConversionStatus::kBadFormat; } - if (bPm) { - nHour += 12; + // Adjust 12-hour clock (1-12) to 24-hour clock (0-23). + // Evaluated after the format loop because 't' can precede 'h' in format + // strings. + if (am_pm != AmPm::kNone) { + hour = (hour % 12) + (am_pm == AmPm::kPm ? 12 : 0); } - if (nYear >= 0 && nYear <= nYearSub) { - nYear += 2000; + // Resolves two-digit year ambiguity using Acrobat's date horizon heuristic: + // < 50 is assumed in the 21st century (+2000), >= 50 in the 20th century + // (+1900). + if (year >= 0 && year < 100) { + year += year < 50 ? 2000 : 1900; } - if (!FX_IsValidMonth(nMonth) || !FX_IsValidDay(nDay) || - !FX_IsValid24Hour(nHour) || !FX_IsValidMinute(nMin) || - !FX_IsValidSecond(nSec)) { + if (!FX_IsValidMonth(month) || !FX_IsValidDay(day) || + !FX_IsValid24Hour(hour) || !FX_IsValidMinute(minute) || + !FX_IsValidSecond(second)) { return ConversionStatus::kBadDate; } - dt = FX_MakeDate(FX_MakeDay(nYear, nMonth - 1, nDay), - FX_MakeTime(nHour, nMin, nSec, 0)); - if (isnan(dt)) { + const double parsed_dt = FX_MakeDate(FX_MakeDay(year, month - 1, day), + FX_MakeTime(hour, minute, second, 0)); + if (isnan(parsed_dt)) { return ConversionStatus::kBadDate; } - *result = dt; + *result = parsed_dt; return ConversionStatus::kSuccess; } diff --git a/fxjs/fx_date_helpers.h b/fxjs/fx_date_helpers.h index a3fc55d309..3e096ce53b 100644 --- a/fxjs/fx_date_helpers.h +++ b/fxjs/fx_date_helpers.h @@ -24,6 +24,7 @@ double FX_GetDateTime(); int FX_GetYearFromTime(double dt); int FX_GetMonthFromTime(double dt); int FX_GetDayFromTime(double dt); +int FX_GetDayOfWeekFromTime(double dt); int FX_GetHourFromTime(double dt); int FX_GetMinFromTime(double dt); int FX_GetSecFromTime(double dt); @@ -50,6 +51,7 @@ ConversionStatus FX_ParseDateUsingFormat(const WideString& value, using fxjs::FX_GetDateTime; using fxjs::FX_GetDayFromTime; +using fxjs::FX_GetDayOfWeekFromTime; using fxjs::FX_GetHourFromTime; using fxjs::FX_GetMinFromTime; using fxjs::FX_GetMonthFromTime; diff --git a/fxjs/fx_date_helpers_unittest.cpp b/fxjs/fx_date_helpers_unittest.cpp index d8376c9b6c..9a4efc4724 100644 --- a/fxjs/fx_date_helpers_unittest.cpp +++ b/fxjs/fx_date_helpers_unittest.cpp @@ -31,6 +31,16 @@ TEST(FXDateHelperTest, GetYearFromTime) { // 1972 is a leap year, so there should be an extra day. {3 * 365.0 * kMilliSecondsInADay, 1972}, {(3 * 365.0 + 1) * kMilliSecondsInADay, 1973}, + // 1900 is not a leap year. + // There are 17 leap days between 1900 and 1970. + {-(70 * 365.0 + 17) * kMilliSecondsInADay, 1900}, + {-(69 * 365.0 + 17 + 0.1) * kMilliSecondsInADay, 1900}, + {-(69 * 365.0 + 17) * kMilliSecondsInADay, 1901}, + // 2000 is a leap year. + // There are 7 leap days between 1970 and 2000. + {(30 * 365.0 + 7) * kMilliSecondsInADay, 2000}, + {(31 * 365.0 + 7) * kMilliSecondsInADay, 2000}, + {(31 * 365.0 + 8) * kMilliSecondsInADay, 2001}, }; for (const auto& test : kTests) { @@ -102,6 +112,15 @@ TEST(FXDateHelperTest, GetMonthFromTime) { {(2 * 365.0 + 305) * kMilliSecondsInADay, 10}, {(2 * 365.0 + 334) * kMilliSecondsInADay, 10}, {(2 * 365.0 + 335) * kMilliSecondsInADay, 11}, + // 1900 is not a leap year. + // There are 17 leap days between 1900 and 1970. + {(-(70 * 365.0 + 17) + 58) * kMilliSecondsInADay, 1}, + {(-(70 * 365.0 + 17) + 59) * kMilliSecondsInADay, 2}, + // 2000 is a leap year. + // There are 7 leap days between 1970 and 2000. + {(30 * 365.0 + 7 + 58) * kMilliSecondsInADay, 1}, + {(30 * 365.0 + 7 + 59) * kMilliSecondsInADay, 1}, + {(30 * 365.0 + 7 + 60) * kMilliSecondsInADay, 2}, }; for (const auto& test : kTests) { @@ -110,6 +129,62 @@ TEST(FXDateHelperTest, GetMonthFromTime) { } } +TEST(FXDateHelperTest, GetDayFromTime) { + static constexpr struct { + double time_ms; + int expected_day; + } kTests[] = { + // 1900 is not a leap year. + // There are 17 leap days between 1900 and 1970. + {(-(70 * 365.0 + 17) + 58) * kMilliSecondsInADay, 28}, + {(-(70 * 365.0 + 17) + 59) * kMilliSecondsInADay, 1}, + // 1970 is not a leap year. + {0, 1}, + {30 * kMilliSecondsInADay, 31}, + {31 * kMilliSecondsInADay, 1}, + // 1972 is a leap year. + {(2 * 365.0 + 58) * kMilliSecondsInADay, 28}, + {(2 * 365.0 + 59) * kMilliSecondsInADay, 29}, + {(2 * 365.0 + 60) * kMilliSecondsInADay, 1}, + // 2000 is a leap year. + // There are 7 leap days between 1970 and 2000. + {(30 * 365.0 + 7 + 58) * kMilliSecondsInADay, 28}, + {(30 * 365.0 + 7 + 59) * kMilliSecondsInADay, 29}, + {(30 * 365.0 + 7 + 60) * kMilliSecondsInADay, 1}, + }; + + for (const auto& test : kTests) { + EXPECT_EQ(test.expected_day, FX_GetDayFromTime(test.time_ms)) + << test.time_ms; + } +} + +TEST(FXDateHelperTest, GetDayOfWeekFromTime) { + static constexpr struct { + double time_ms; + int expected_day_of_week; // 0 = Sunday, 1 = Monday, ..., 6 = Saturday. + } kTests[] = { + {0, 4}, // 1970-01-01 is Thursday. + {1 * kMilliSecondsInADay, 5}, // 1970-01-02 is Friday. + {2 * kMilliSecondsInADay, 6}, // 1970-01-03 is Saturday. + {3 * kMilliSecondsInADay, 0}, // 1970-01-04 is Sunday. + {4 * kMilliSecondsInADay, 1}, // 1970-01-05 is Monday. + {5 * kMilliSecondsInADay, 2}, // 1970-01-06 is Tuesday. + {6 * kMilliSecondsInADay, 3}, // 1970-01-07 is Wednesday. + // Negative timestamps before 1970. + {-1 * kMilliSecondsInADay, 3}, // 1969-12-31 is Wednesday. + {-2 * kMilliSecondsInADay, 2}, // 1969-12-30 is Tuesday. + {-3 * kMilliSecondsInADay, 1}, // 1969-12-29 is Monday. + {-4 * kMilliSecondsInADay, 0}, // 1969-12-28 is Sunday. + {-5 * kMilliSecondsInADay, 6}, // 1969-12-27 is Saturday. + }; + + for (const auto& test : kTests) { + EXPECT_EQ(test.expected_day_of_week, FX_GetDayOfWeekFromTime(test.time_ms)) + << test.time_ms; + } +} + using FXDateHelperFakeTimeTest = FakeTimeTest; TEST_F(FXDateHelperFakeTimeTest, ParseDateUsingFormatWithEmptyParams) { @@ -142,14 +217,95 @@ TEST_F(FXDateHelperFakeTimeTest, ParseDateUsingFormatForValidMonthDay) { EXPECT_EQ(ConversionStatus::kSuccess, FX_ParseDateUsingFormat(L"11/12/2000", L"mm/dd/yyyy", &result)); - EXPECT_DOUBLE_EQ(973'955'121'000, result); + EXPECT_DOUBLE_EQ(974'041'521'000, result); EXPECT_EQ(ConversionStatus::kSuccess, FX_ParseDateUsingFormat(L"11/12/2000", L"m/d/yyyy", &result)); - EXPECT_DOUBLE_EQ(973'955'121'000, result); + EXPECT_DOUBLE_EQ(974'041'521'000, result); EXPECT_EQ(ConversionStatus::kSuccess, FX_ParseDateUsingFormat(L"11-12-2000", L"m-d-yyyy", &result)); - EXPECT_DOUBLE_EQ(973'955'121'000, result); + EXPECT_DOUBLE_EQ(974'041'521'000, result); EXPECT_EQ(ConversionStatus::kSuccess, FX_ParseDateUsingFormat(L"12-11-2000", L"d-m-yyyy", &result)); - EXPECT_DOUBLE_EQ(973'955'121'000, result); + EXPECT_DOUBLE_EQ(974'041'521'000, result); + + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"02/29/2000", L"mm/dd/yyyy", &result)); + EXPECT_DOUBLE_EQ(951'836'721'000, result); +} + +TEST_F(FXDateHelperFakeTimeTest, ParseDateUsingFormatFor12HourTime) { + double result = 0.0; + const double kBaseDay = FX_MakeDay(2020, 3, 23); + + // 12:30:00 PM is 12:30. + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 12:30:00 pm", + L"yyyy/mm/dd hh:MM:ss tt", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(12, 30, 0, 0)), result); + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 12:30:00 PM", + L"yyyy/mm/dd hh:MM:ss tt", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(12, 30, 0, 0)), result); + + // 12:30:00 AM is 00:30. + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 12:30:00 am", + L"yyyy/mm/dd hh:MM:ss tt", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(0, 30, 0, 0)), result); + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 12:30:00 AM", + L"yyyy/mm/dd hh:MM:ss tt", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(0, 30, 0, 0)), result); + + // 1:30:00 PM is 13:30. + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 1:30:00 pm", + L"yyyy/mm/dd h:MM:ss tt", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(13, 30, 0, 0)), result); + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 1:30:00 PM", + L"yyyy/mm/dd h:MM:ss tt", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(13, 30, 0, 0)), result); + + // 1:30:00 AM is 01:30. + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 1:30:00 am", + L"yyyy/mm/dd h:MM:ss tt", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(1, 30, 0, 0)), result); + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 1:30:00 AM", + L"yyyy/mm/dd h:MM:ss tt", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(1, 30, 0, 0)), result); + + // Single 't' marker. + // 12:30:00 P is 12:30. + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 12:30:00 p", + L"yyyy/mm/dd hh:MM:ss t", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(12, 30, 0, 0)), result); + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 12:30:00 P", + L"yyyy/mm/dd hh:MM:ss t", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(12, 30, 0, 0)), result); + + // 12:30:00 A is 00:30. + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 12:30:00 a", + L"yyyy/mm/dd hh:MM:ss t", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(0, 30, 0, 0)), result); + EXPECT_EQ(ConversionStatus::kSuccess, + FX_ParseDateUsingFormat(L"2020/04/23 12:30:00 A", + L"yyyy/mm/dd hh:MM:ss t", &result)); + EXPECT_DOUBLE_EQ(FX_MakeDate(kBaseDay, FX_MakeTime(0, 30, 0, 0)), result); + + // Malformed AM/PM markers should return kBadFormat. + EXPECT_EQ(ConversionStatus::kBadFormat, + FX_ParseDateUsingFormat(L"2020/04/23 1:30:00 xm", + L"yyyy/mm/dd h:MM:ss tt", &result)); + EXPECT_EQ(ConversionStatus::kBadFormat, + FX_ParseDateUsingFormat(L"2020/04/23 1:30:00 px", + L"yyyy/mm/dd h:MM:ss tt", &result)); + EXPECT_EQ(ConversionStatus::kBadFormat, + FX_ParseDateUsingFormat(L"2020/04/23 1:30:00 x", + L"yyyy/mm/dd h:MM:ss t", &result)); } diff --git a/fxjs/gc/heap.cpp b/fxjs/gc/heap.cpp index 96b813c3df..0a7bfddef8 100644 --- a/fxjs/gc/heap.cpp +++ b/fxjs/gc/heap.cpp @@ -4,12 +4,17 @@ #include "fxjs/gc/heap.h" +#include #include +#include #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_system.h" #include "v8/include/cppgc/heap.h" +#include "v8/include/v8-cppgc.h" +#include "v8/include/v8-isolate.h" +#include "v8/include/v8-platform.h" namespace { @@ -59,7 +64,7 @@ class CFXGC_Platform final : public cppgc::Platform { void FXGC_Initialize(v8::Platform* platform, v8::Isolate* isolate) { if (platform) { - DCHECK(!g_platform); + CHECK(!g_platform); g_platform = platform; g_isolate = isolate; } @@ -72,7 +77,51 @@ void FXGC_Release() { } } -FXGCScopedHeap FXGC_CreateHeap() { +FXGC_Heap::FXGC_Heap(std::unique_ptr heap) + : heap_(std::move(heap)) {} + +FXGC_Heap::FXGC_Heap(v8::Isolate* isolate) + : heap_(isolate->GetCppHeap()), isolate_(isolate) { + CHECK(GetAttachedHeap()); +} + +FXGC_Heap::~FXGC_Heap() { + if (cppgc::Heap* heap = GetStandaloneHeap()) { + CHECK_GT(g_platform_ref_count, 0u); + --g_platform_ref_count; + FXGC_ForceGarbageCollection(heap); + } +} + +cppgc::AllocationHandle& FXGC_Heap::GetAllocationHandle() { + return std::visit( + [](const auto& heap) -> cppgc::AllocationHandle& { + return heap->GetAllocationHandle(); + }, + heap_); +} + +cppgc::HeapHandle& FXGC_Heap::GetHeapHandle() { + return std::visit( + [](const auto& heap) -> cppgc::HeapHandle& { + return heap->GetHeapHandle(); + }, + heap_); +} + +std::unique_ptr FXGC_CreateCppHeap(v8::Platform* platform) { + v8::Platform* p = platform ? platform : g_platform; + if (!p) { + return nullptr; + } + v8::CppHeapCreateParams params({}); + params.marking_support = cppgc::Heap::MarkingType::kAtomic; + params.sweeping_support = + cppgc::Heap::SweepingType::kIncrementalAndConcurrent; + return v8::CppHeap::Create(p, params); +} + +FXGCScopedHeap FXGC_CreateHeap(v8::Isolate* isolate) { // If XFA is included at compile-time, but JS is disabled at run-time, // we may still attempt to build a CPDFXFA_Context which will want a // heap. But we can't make one because JS is disabled. @@ -81,6 +130,10 @@ FXGCScopedHeap FXGC_CreateHeap() { return nullptr; } + if (isolate) { + return std::make_unique(isolate); + } + ++g_platform_ref_count; auto heap = cppgc::Heap::Create( std::make_shared(), @@ -90,19 +143,31 @@ FXGCScopedHeap FXGC_CreateHeap() { cppgc::Heap::MarkingType::kAtomic, cppgc::Heap::SweepingType::kIncrementalAndConcurrent, {}}); - return FXGCScopedHeap(heap.release()); + return std::make_unique(std::move(heap)); } -void FXGC_ForceGarbageCollection(cppgc::Heap* heap) { - heap->ForceGarbageCollectionSlow("FXGC", "ForceGarbageCollection", - cppgc::Heap::StackState::kNoHeapPointers); +void FXGC_ForceGarbageCollection(FXGC_Heap* heap) { + if (!heap) { + return; + } + if (v8::CppHeap* attached_heap = heap->GetAttachedHeap()) { + v8::Isolate::Scope isolate_scope(heap->GetIsolate()); + FXGC_ForceGarbageCollection(attached_heap); + return; + } + FXGC_ForceGarbageCollection(heap->GetStandaloneHeap()); } -void FXGCHeapDeleter::operator()(cppgc::Heap* heap) { - DCHECK(heap); - DCHECK_GT(g_platform_ref_count, 0); - --g_platform_ref_count; +void FXGC_ForceGarbageCollection(cppgc::Heap* heap) { + if (heap) { + heap->ForceGarbageCollectionSlow("FXGC", "ForceGarbageCollection", + cppgc::Heap::StackState::kNoHeapPointers); + } +} - FXGC_ForceGarbageCollection(heap); - delete heap; +void FXGC_ForceGarbageCollection(v8::CppHeap* cpp_heap) { + if (cpp_heap) { + cpp_heap->CollectGarbageForTesting( + cppgc::EmbedderStackState::kNoHeapPointers); + } } diff --git a/fxjs/gc/heap.h b/fxjs/gc/heap.h index 1e2bf61590..70653cae00 100644 --- a/fxjs/gc/heap.h +++ b/fxjs/gc/heap.h @@ -6,28 +6,61 @@ #define FXJS_GC_HEAP_H_ #include +#include +#include "core/fxcrt/unowned_ptr_exclusion.h" #include "v8/include/cppgc/allocation.h" namespace cppgc { class Heap; +class HeapHandle; } // namespace cppgc namespace v8 { +class CppHeap; class Isolate; class Platform; } // namespace v8 -struct FXGCHeapDeleter { - void operator()(cppgc::Heap* heap); +// Abstracts between two different kinds of garbage-collected heaps: +// those associated with V8 (v8::CppHeap) and standalone (cppgc::Heap). +class FXGC_Heap { + public: + explicit FXGC_Heap(std::unique_ptr heap); + explicit FXGC_Heap(v8::Isolate* isolate); + FXGC_Heap(const FXGC_Heap&) = delete; + FXGC_Heap& operator=(const FXGC_Heap&) = delete; + ~FXGC_Heap(); + + cppgc::AllocationHandle& GetAllocationHandle(); + cppgc::HeapHandle& GetHeapHandle(); + + cppgc::Heap* GetStandaloneHeap() const { + auto* standalone = std::get_if>(&heap_); + return standalone ? standalone->get() : nullptr; + } + v8::CppHeap* GetAttachedHeap() const { + auto* attached = std::get_if(&heap_); + return attached ? *attached : nullptr; + } + v8::Isolate* GetIsolate() const { return isolate_; } + + private: + // Non-owning pointers; outlived by this wrapper because GC objects finalized + // during isolate/heap teardown hold UnownedPtrs back to this FXGC_Heap. + std::variant, v8::CppHeap*> const heap_; + UNOWNED_PTR_EXCLUSION v8::Isolate* const isolate_ = nullptr; }; -using FXGCScopedHeap = std::unique_ptr; +using FXGCScopedHeap = std::unique_ptr; void FXGC_Initialize(v8::Platform* platform, v8::Isolate* isolate); void FXGC_Release(); -FXGCScopedHeap FXGC_CreateHeap(); +FXGCScopedHeap FXGC_CreateHeap(v8::Isolate* isolate); +std::unique_ptr FXGC_CreateCppHeap(v8::Platform* platform); +void FXGC_ForceGarbageCollection(FXGC_Heap* heap); void FXGC_ForceGarbageCollection(cppgc::Heap* heap); +void FXGC_ForceGarbageCollection(v8::CppHeap* cpp_heap); #define CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED \ template \ diff --git a/fxjs/gc/heap_unittest.cpp b/fxjs/gc/heap_unittest.cpp index 126dad6d37..737b75877f 100644 --- a/fxjs/gc/heap_unittest.cpp +++ b/fxjs/gc/heap_unittest.cpp @@ -75,13 +75,13 @@ class HeapUnitTest : public FXGCUnitTest { }; TEST_F(HeapUnitTest, SeveralHeaps) { - FXGCScopedHeap heap1 = FXGC_CreateHeap(); + FXGCScopedHeap heap1 = FXGC_CreateHeap(nullptr); EXPECT_TRUE(heap1); - FXGCScopedHeap heap2 = FXGC_CreateHeap(); + FXGCScopedHeap heap2 = FXGC_CreateHeap(nullptr); EXPECT_TRUE(heap2); - FXGCScopedHeap heap3 = FXGC_CreateHeap(); + FXGCScopedHeap heap3 = FXGC_CreateHeap(nullptr); EXPECT_TRUE(heap3); // Test manually destroying the heap. @@ -92,7 +92,7 @@ TEST_F(HeapUnitTest, SeveralHeaps) { } TEST_F(HeapUnitTest, NoReferences) { - FXGCScopedHeap heap1 = FXGC_CreateHeap(); + FXGCScopedHeap heap1 = FXGC_CreateHeap(nullptr); ASSERT_TRUE(heap1); { auto holder = std::make_unique( @@ -109,7 +109,7 @@ TEST_F(HeapUnitTest, NoReferences) { } TEST_F(HeapUnitTest, HasReferences) { - FXGCScopedHeap heap1 = FXGC_CreateHeap(); + FXGCScopedHeap heap1 = FXGC_CreateHeap(nullptr); ASSERT_TRUE(heap1); { auto holder = std::make_unique( @@ -129,7 +129,7 @@ TEST_F(HeapUnitTest, HasReferences) { // TODO(tsepez): enable when CPPGC fixes this segv. TEST_F(HeapUnitTest, DISABLED_DeleteHeapHasReferences) { - FXGCScopedHeap heap1 = FXGC_CreateHeap(); + FXGCScopedHeap heap1 = FXGC_CreateHeap(nullptr); ASSERT_TRUE(heap1); { auto holder = std::make_unique( @@ -150,7 +150,7 @@ TEST_F(HeapUnitTest, DISABLED_DeleteHeapHasReferences) { } TEST_F(HeapUnitTest, DeleteHeapNoReferences) { - FXGCScopedHeap heap1 = FXGC_CreateHeap(); + FXGCScopedHeap heap1 = FXGC_CreateHeap(nullptr); ASSERT_TRUE(heap1); { auto holder = std::make_unique( diff --git a/fxjs/ijs_runtime.cpp b/fxjs/ijs_runtime.cpp index 560fcb371f..b6cfecb009 100644 --- a/fxjs/ijs_runtime.cpp +++ b/fxjs/ijs_runtime.cpp @@ -4,6 +4,7 @@ #include "fxjs/ijs_runtime.h" +#include "core/fxcrt/epdf_tls.h" #include "fxjs/cjs_runtimestub.h" #ifdef PDF_ENABLE_V8 @@ -16,6 +17,14 @@ #endif // PDF_ENABLE_XFA #endif // PDF_ENABLE_V8 +namespace { + +// EmbedPDF(thread-confined-globals): library start-up and shutdown run on every +// worker thread (rule per-thread-init), and each writes this flag. +EPDF_TLS bool g_isolate_per_document = false; + +} // namespace + IJS_Runtime::ScopedEventContext::ScopedEventContext(IJS_Runtime* pRuntime) : runtime_(pRuntime), context_(pRuntime->NewEventContext()) {} @@ -24,7 +33,11 @@ IJS_Runtime::ScopedEventContext::~ScopedEventContext() { } // static -void IJS_Runtime::Initialize(unsigned int slot, void* isolate, void* platform) { +void IJS_Runtime::Initialize(unsigned int slot, + void* isolate, + void* platform, + bool isolate_per_document) { + g_isolate_per_document = isolate_per_document; #ifdef PDF_ENABLE_V8 GlobalTimer::InitializeGlobals(); FXJS_Initialize(slot, static_cast(isolate)); @@ -44,6 +57,12 @@ void IJS_Runtime::Destroy() { FXJS_Release(); GlobalTimer::DestroyGlobals(); #endif // PDF_ENABLE_V8 + g_isolate_per_document = false; +} + +// static +bool IJS_Runtime::IsIsolatePerDocument() { + return g_isolate_per_document; } // static diff --git a/fxjs/ijs_runtime.h b/fxjs/ijs_runtime.h index 52e15c8d4c..c0c66a003a 100644 --- a/fxjs/ijs_runtime.h +++ b/fxjs/ijs_runtime.h @@ -46,10 +46,14 @@ class IJS_Runtime { UnownedPtr context_; }; - static void Initialize(unsigned int slot, void* isolate, void* platform); + static void Initialize(unsigned int slot, + void* isolate, + void* platform, + bool isolate_per_document); static void Destroy(); static std::unique_ptr Create( CPDFSDK_FormFillEnvironment* pFormFillEnv); + static bool IsIsolatePerDocument(); virtual ~IJS_Runtime(); diff --git a/fxjs/ijs_runtime_unittest.cpp b/fxjs/ijs_runtime_unittest.cpp new file mode 100644 index 0000000000..d1813e42c3 --- /dev/null +++ b/fxjs/ijs_runtime_unittest.cpp @@ -0,0 +1,31 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "fxjs/ijs_runtime.h" + +#include "testing/gtest/include/gtest/gtest.h" + +class IJSRuntimeTest : public testing::Test { + public: + void TearDown() override { + IJS_Runtime::Destroy(); + testing::Test::TearDown(); + } +}; + +TEST_F(IJSRuntimeTest, IsolatePerDocument) { + EXPECT_FALSE(IJS_Runtime::IsIsolatePerDocument()); + + IJS_Runtime::Initialize(0, nullptr, nullptr, /*isolate_per_document=*/true); + EXPECT_TRUE(IJS_Runtime::IsIsolatePerDocument()); + + IJS_Runtime::Destroy(); + EXPECT_FALSE(IJS_Runtime::IsIsolatePerDocument()); + + IJS_Runtime::Initialize(0, nullptr, nullptr, /*isolate_per_document=*/false); + EXPECT_FALSE(IJS_Runtime::IsIsolatePerDocument()); + + IJS_Runtime::Destroy(); + EXPECT_FALSE(IJS_Runtime::IsIsolatePerDocument()); +} diff --git a/fxjs/js_define.h b/fxjs/js_define.h index a6de74471e..dcb33f3808 100644 --- a/fxjs/js_define.h +++ b/fxjs/js_define.h @@ -35,6 +35,31 @@ v8::LocalVector ExpandKeywordParams( bool IsExpandedParamKnown(v8::Local value); +// Object definition IDs for FXJS classes. +// The sequence matches the registration order in +// CJS_Runtime::DefineJSObjects(). +inline constexpr uint32_t kJSBorderObjDefnID = 1; +inline constexpr uint32_t kJSDisplayObjDefnID = 2; +inline constexpr uint32_t kJSFontObjDefnID = 3; +inline constexpr uint32_t kJSHighlightObjDefnID = 4; +inline constexpr uint32_t kJSPositionObjDefnID = 5; +inline constexpr uint32_t kJSScaleHowObjDefnID = 6; +inline constexpr uint32_t kJSScaleWhenObjDefnID = 7; +inline constexpr uint32_t kJSStyleObjDefnID = 8; +inline constexpr uint32_t kJSZoomtypeObjDefnID = 9; +inline constexpr uint32_t kJSAppObjDefnID = 10; +inline constexpr uint32_t kJSColorObjDefnID = 11; +inline constexpr uint32_t kJSConsoleObjDefnID = 12; +inline constexpr uint32_t kJSDocumentObjDefnID = 13; +inline constexpr uint32_t kJSEventObjDefnID = 14; +inline constexpr uint32_t kJSFieldObjDefnID = 15; +inline constexpr uint32_t kJSGlobalObjDefnID = 16; +inline constexpr uint32_t kJSIconObjDefnID = 17; +inline constexpr uint32_t kJSUtilObjDefnID = 18; +inline constexpr uint32_t kJSTimerObjDefnID = 19; +inline constexpr uint32_t kJSAnnotObjDefnID = 20; +static_assert(kJSAnnotObjDefnID + 1 == kMaxFxObjDefinitions); + // All JS classes have a name, an object defintion ID, and the ability to // register themselves with FXJS_V8. We never make a BASE class on its own // because it can't really do anything. @@ -57,7 +82,7 @@ UnownedPtr JSGetObject(v8::Local obj) { if (!pData) { return nullptr; } - if (pData->GetObjDefnID() != C::GetObjDefnID()) { + if (pData->GetObjDefnID() != C::kObjDefnId) { return nullptr; } CFXJS_PerObjectData::Binding* pBinding = pData->GetBinding(); diff --git a/fxjs/xfa/cfxjse_engine.cpp b/fxjs/xfa/cfxjse_engine.cpp index b47dbfc975..521026f130 100644 --- a/fxjs/xfa/cfxjse_engine.cpp +++ b/fxjs/xfa/cfxjse_engine.cpp @@ -143,11 +143,10 @@ CXFA_Object* CFXJSE_Engine::ToObject(CFXJSE_HostObject* pHostObj) { return pJSObject ? pJSObject->GetXFAObject() : nullptr; } -CFXJSE_Engine::CFXJSE_Engine(CXFA_Document* document, CJS_Runtime* fxjs_runtime) - : CFX_IsolateWrapper(fxjs_runtime->GetIsolate()), - subordinate_runtime_(fxjs_runtime), +CFXJSE_Engine::CFXJSE_Engine(CXFA_Document* document, v8::Isolate* isolate) + : CFX_IsolateWrapper(isolate), document_(document), - js_context_(CFXJSE_Context::Create(fxjs_runtime->GetIsolate(), + js_context_(CFXJSE_Context::Create(isolate, &kGlobalClassDescriptor, document->GetRoot()->JSObject(), nullptr)), @@ -192,6 +191,17 @@ CFXJSE_Engine::EventParamScope::~EventParamScope() { engine_->event_param_ = prev_event_param_; } +// The runtime belongs to the form fill environment, which may depart before +// this object does, so look it up rather than holding it. +CJS_Runtime* CFXJSE_Engine::GetCJSRuntime() const { + CXFA_FFNotify* notify = document_ ? document_->GetNotify() : nullptr; + if (!notify) { + return nullptr; + } + IJS_Runtime* runtime = notify->GetFFDoc()->GetIJSRuntime(); + return runtime ? runtime->AsCJSRuntime() : nullptr; +} + CFXJSE_Context::ExecutionResult CFXJSE_Engine::RunScript( CXFA_Script::Type eScriptType, WideStringView wsScript, @@ -225,7 +235,12 @@ CFXJSE_Context::ExecutionResult CFXJSE_Engine::RunScript( pThisBinding = GetOrCreateJSBindingFromMap(pThisObject); } - IJS_Runtime::ScopedEventContext ctx(subordinate_runtime_); + CJS_Runtime* runtime = GetCJSRuntime(); + if (!runtime) { + return CFXJSE_Context::ExecutionResult(); + } + + IJS_Runtime::ScopedEventContext ctx(runtime); return js_context_->ExecuteScript(btScript.AsStringView(), pThisBinding); } @@ -309,13 +324,7 @@ void CFXJSE_Engine::GlobalPropertySetter(v8::Isolate* pIsolate, pObject, szPropName); return; } - CXFA_FFNotify* pNotify = doc->GetNotify(); - if (!pNotify) { - return; - } - - CXFA_FFDoc* hDoc = pNotify->GetFFDoc(); - auto* pCJSRuntime = static_cast(hDoc->GetIJSRuntime()); + CJS_Runtime* pCJSRuntime = pScriptContext->GetCJSRuntime(); if (!pCJSRuntime) { return; } @@ -380,13 +389,7 @@ v8::Local CFXJSE_Engine::GlobalPropertyGetter( return pValue; } - CXFA_FFNotify* pNotify = doc->GetNotify(); - if (!pNotify) { - return pValue; - } - - CXFA_FFDoc* hDoc = pNotify->GetFFDoc(); - auto* pCJSRuntime = static_cast(hDoc->GetIJSRuntime()); + CJS_Runtime* pCJSRuntime = pScriptContext->GetCJSRuntime(); if (!pCJSRuntime) { return pValue; } @@ -477,13 +480,7 @@ v8::Local CFXJSE_Engine::NormalPropertyGetter( return pReturnValue; } - CXFA_FFNotify* pNotify = pObject->GetDocument()->GetNotify(); - if (!pNotify) { - return pReturnValue; - } - - CXFA_FFDoc* hDoc = pNotify->GetFFDoc(); - auto* pCJSRuntime = static_cast(hDoc->GetIJSRuntime()); + CJS_Runtime* pCJSRuntime = pScriptContext->GetCJSRuntime(); if (!pCJSRuntime) { return pReturnValue; } diff --git a/fxjs/xfa/cfxjse_engine.h b/fxjs/xfa/cfxjse_engine.h index 04401e99ac..fafc729af0 100644 --- a/fxjs/xfa/cfxjse_engine.h +++ b/fxjs/xfa/cfxjse_engine.h @@ -110,7 +110,7 @@ class CFXJSE_Engine final : public CFX_IsolateWrapper { ByteStringView szPropName, bool bQueryIn); - CFXJSE_Engine(CXFA_Document* document, CJS_Runtime* fxjs_runtime); + CFXJSE_Engine(CXFA_Document* document, v8::Isolate* isolate); ~CFXJSE_Engine() override; class EventParamScope { @@ -171,6 +171,7 @@ class CFXJSE_Engine final : public CFX_IsolateWrapper { CFXJSE_Context* GetJseContextForTest() const { return GetJseContext(); } private: + CJS_Runtime* GetCJSRuntime() const; CFXJSE_Context* GetJseContext() const { return js_context_.get(); } CFXJSE_Context* CreateVariablesContext(CXFA_Script* pScriptNode, CXFA_Node* pSubform); @@ -195,7 +196,6 @@ class CFXJSE_Engine final : public CFX_IsolateWrapper { v8::Local pValue); void RunVariablesScript(CXFA_Script* pScriptNode); - UnownedPtr const subordinate_runtime_; cppgc::WeakPersistent const document_; std::unique_ptr js_context_; UnownedPtr js_class_; diff --git a/fxjs/xfa/cfxjse_formcalc_context.cpp b/fxjs/xfa/cfxjse_formcalc_context.cpp index 503b6746b4..b4ab540a5f 100644 --- a/fxjs/xfa/cfxjse_formcalc_context.cpp +++ b/fxjs/xfa/cfxjse_formcalc_context.cpp @@ -5087,7 +5087,7 @@ ByteString CFXJSE_FormCalcContext::GenerateSomExpression(ByteStringView bsName, } std::optional CFXJSE_FormCalcContext::Translate( - cppgc::Heap* pHeap, + FXGC_Heap* pHeap, WideStringView wsFormcalc) { if (wsFormcalc.IsEmpty()) { return WideTextBuffer(); diff --git a/fxjs/xfa/cfxjse_formcalc_context.h b/fxjs/xfa/cfxjse_formcalc_context.h index 1fcd18a5e1..9d82d137f1 100644 --- a/fxjs/xfa/cfxjse_formcalc_context.h +++ b/fxjs/xfa/cfxjse_formcalc_context.h @@ -22,10 +22,7 @@ class CFXJSE_Context; class CXFA_Document; - -namespace cppgc { -class Heap; -} // namespace cppgc +class FXGC_Heap; class CFXJSE_FormCalcContext final : public CFXJSE_HostObject { public: @@ -268,7 +265,7 @@ class CFXJSE_FormCalcContext final : public CFXJSE_HostObject { static void concat_fm_object(CFXJSE_HostObject* pThis, const v8::FunctionCallbackInfo& info); - static std::optional Translate(cppgc::Heap* pHeap, + static std::optional Translate(FXGC_Heap* pHeap, WideStringView wsFormcalc); v8::Local GlobalPropertyGetter(); diff --git a/fxjs/xfa/cfxjse_resolveprocessor.cpp b/fxjs/xfa/cfxjse_resolveprocessor.cpp index 8af230bd2e..b74733d6e4 100644 --- a/fxjs/xfa/cfxjse_resolveprocessor.cpp +++ b/fxjs/xfa/cfxjse_resolveprocessor.cpp @@ -12,6 +12,7 @@ #include "core/fxcrt/check_op.h" #include "core/fxcrt/containers/contains.h" +#include "core/fxcrt/containers/to_vector.h" #include "core/fxcrt/fx_extension.h" #include "fxjs/fxv8.h" #include "fxjs/xfa/cfxjse_engine.h" @@ -121,8 +122,7 @@ bool CFXJSE_ResolveProcessor::ResolveAnyChild(v8::Isolate* pIsolate, std::vector siblings = pChild->GetSiblings(bClassName); nodes.insert(nodes.end(), siblings.begin(), siblings.end()); - rnd.result_.objects = - std::vector>(nodes.begin(), nodes.end()); + rnd.result_.objects = pdfium::ToVector>(nodes); FilterCondition(pIsolate, wsCondition, &rnd); return !rnd.result_.objects.empty(); } diff --git a/fxjs/xfa/cfxjse_runtimedata.cpp b/fxjs/xfa/cfxjse_runtimedata.cpp index 0d4126ad1b..8b9ca82e04 100644 --- a/fxjs/xfa/cfxjse_runtimedata.cpp +++ b/fxjs/xfa/cfxjse_runtimedata.cpp @@ -47,8 +47,7 @@ std::unique_ptr CFXJSE_RuntimeData::Create( } CFXJSE_RuntimeData* CFXJSE_RuntimeData::Get(v8::Isolate* pIsolate) { - CFXJS_PerIsolateData::SetUp(pIsolate); - CFXJS_PerIsolateData* pData = CFXJS_PerIsolateData::Get(pIsolate); + CFXJS_PerIsolateData* pData = CFXJS_PerIsolateData::GetOrCreate(pIsolate); if (!pData->GetExtension()) { pData->SetExtension(CFXJSE_RuntimeData::Create(pIsolate)); } diff --git a/fxjs/xfa/cjx_object.cpp b/fxjs/xfa/cjx_object.cpp index 499d771e14..f4d813d3f9 100644 --- a/fxjs/xfa/cjx_object.cpp +++ b/fxjs/xfa/cjx_object.cpp @@ -1037,7 +1037,7 @@ void CJX_Object::OnChanged(XFA_Attribute eAttr, bool bScriptModify) { GetXFANode()->SendAttributeChangeMessage(eAttr, bScriptModify); } -CJX_Object::CalcData* CJX_Object::GetOrCreateCalcData(cppgc::Heap* heap) { +CJX_Object::CalcData* CJX_Object::GetOrCreateCalcData(FXGC_Heap* heap) { if (!calc_data_) { calc_data_ = cppgc::MakeGarbageCollected(heap->GetAllocationHandle()); diff --git a/fxjs/xfa/cjx_object.h b/fxjs/xfa/cjx_object.h index 624d347f76..d3f57e62c7 100644 --- a/fxjs/xfa/cjx_object.h +++ b/fxjs/xfa/cjx_object.h @@ -205,7 +205,7 @@ class CJX_Object : public cppgc::GarbageCollected, void MergeAllData(CXFA_Object* pDstObj); CalcData* GetCalcData() const { return calc_data_; } - CalcData* GetOrCreateCalcData(cppgc::Heap* heap); + CalcData* GetOrCreateCalcData(FXGC_Heap* heap); void TakeCalcDataFrom(CJX_Object* that); void ThrowInvalidPropertyException(v8::Isolate* pIsolate) const; diff --git a/patches/embedpdf-runtime/win/build.patch b/patches/embedpdf-runtime/win/build.patch index aecd8d2423..69926b32de 100644 --- a/patches/embedpdf-runtime/win/build.patch +++ b/patches/embedpdf-runtime/win/build.patch @@ -11,3 +11,43 @@ index 7dbd8a17e..361f74f85 100644 depsformat = "msvc" outputs = [ "$object_subdir/{{source_name_part}}.res" ] description = "RC {{output}}" +diff --git a/vs_toolchain.py b/vs_toolchain.py +--- a/vs_toolchain.py ++++ b/vs_toolchain.py +@@ -60,7 +60,8 @@ + # Make sure any version numbers in the documentation match the code. + # + TOOLCHAIN_HASH = '3bfcb536c8' +-SDK_VERSION = '10.0.28000.0' ++# EmbedPDF: CI builds with the runner's own Visual Studio and Windows SDK. ++SDK_VERSION = os.environ.get('EMBEDPDF_WIN_SDK_VERSION', '10.0.28000.0') + + # Visual Studio versions are listed in descending order of priority. + # The first version is assumed by this script to be the one that is packaged, +diff --git a/toolchain/win/setup_toolchain.py b/toolchain/win/setup_toolchain.py +--- a/toolchain/win/setup_toolchain.py ++++ b/toolchain/win/setup_toolchain.py +@@ -22,7 +22,8 @@ + import gn_helpers + + SCRIPT_DIR = os.path.dirname(__file__) +-SDK_VERSION = '10.0.28000.0' ++# EmbedPDF: CI builds with the runner's own Visual Studio and Windows SDK. ++SDK_VERSION = os.environ.get('EMBEDPDF_WIN_SDK_VERSION', '10.0.28000.0') + MSVC_DIR = re.compile('^.*/VC/Tools/MSVC/[^/]+/include$') + WINDOWS_KITS_DIR = re.compile(r'^(.*/Windows Kits/\d+/Include/[^/]+)/.*') + +diff --git a/config/win/BUILD.gn b/config/win/BUILD.gn +--- a/config/win/BUILD.gn ++++ b/config/win/BUILD.gn +@@ -305,7 +305,9 @@ + # manually override this config for their compiles. + config("winver") { + defines = [ +- "NTDDI_VERSION=NTDDI_WIN11_BR", ++ # EmbedPDF: NTDDI_WIN11_BR needs SDK 10.0.28000, which CI runners lack; target ++ # the version older SDKs know too (what S6's build/ targeted). ++ "NTDDI_VERSION=NTDDI_WIN11_GE", + + # We can't say `=_WIN32_WINNT_WIN10` here because some files do + # `#if WINVER < 0x0600` without including windows.h before, diff --git a/pdfium.gni b/pdfium.gni index bbeda7f787..71cebb8eeb 100644 --- a/pdfium.gni +++ b/pdfium.gni @@ -56,17 +56,13 @@ declare_args() { # Note that Fontations requires Skia and Rust support. pdf_enable_fontations = pdf_enable_fontations_override - # Build PDFium with code to compare FT/Skia/Fontations results in - # parallel. Currently flags several spots, so must be false except when - # developing locally. - pdf_enable_skia_typeface_checks = false - # Build PDFium with Rust-based BMP decoder. If enabled, it ensures that PDFium # doesn't depend on its native C++ BMP decoder. Note that # `pdf_enable_rust_bmp` requires Skia and Rust support. pdf_enable_rust_bmp = pdf_enable_rust_bmp_override - # Build PDFium with Skia/Rust-based JPEG decoder. + # Build PDFium with Rust-based JPEG decoder. If enabled, it ensures that + # PDFium doesn't depend on `libjpeg`. pdf_enable_rust_jpeg = pdf_enable_rust_jpeg_override # Build PDFium with Rust-based PNG decoder and encoder. @@ -114,16 +110,16 @@ assert(pdf_use_agg || pdf_use_skia, "At least one of pdf_use_agg or pdf_use_skia must be true") assert( - !pdf_enable_fontations || (enable_rust && enable_rust_cxx && pdf_use_skia), - "pdf_enable_fontations=true requires enable_rust=true, enable_rust_cxx=true, and pdf_use_skia=true") + !pdf_enable_fontations || (enable_rust && enable_rust_cxx), + "pdf_enable_fontations=true requires enable_rust=true and enable_rust_cxx=true") assert( !pdf_enable_rust_bmp || (enable_rust && enable_rust_cxx), "pdf_enable_rust_bmp=true requires enable_rust=true and enable_rust_cxx=true") assert( - !pdf_enable_rust_jpeg || (enable_rust && enable_rust_cxx && pdf_use_skia), - "pdf_enable_rust_jpeg=true requires enable_rust=true, enable_rust_cxx=true, and pdf_use_skia=true") + !pdf_enable_rust_jpeg || (enable_rust && enable_rust_cxx), + "pdf_enable_rust_jpeg=true requires enable_rust=true and enable_rust_cxx=true") assert( !pdf_enable_rust_png || (enable_rust && enable_rust_cxx), diff --git a/public/fpdf_attachment.h b/public/fpdf_attachment.h index 64728f61c3..58908ca00d 100644 --- a/public/fpdf_attachment.h +++ b/public/fpdf_attachment.h @@ -220,6 +220,26 @@ FPDFAttachment_GetSubtype(FPDF_ATTACHMENT attachment, FPDF_WCHAR* buffer, unsigned long buflen); +// Experimental API. +// Get the associated file relationship string (the /AFRelationship entry in +// the file specification dictionary of the embedded file |attachment|; PDF +// 2.0, ISO 32000-2:2020, Table 43), e.g. "Source", "Data", "Alternative", +// "Supplement" or "Unspecified". |buffer| is only modified if |buflen| is +// longer than the length of the relationship name. If the /AFRelationship +// entry is absent or is not a name, an empty string is copied to |buffer| and +// the return value is 2. On other errors, |buffer| is not modified and the +// return value is 0. +// +// attachment - handle to an attachment. +// buffer - buffer for holding the relationship name encoded in UTF-16LE. +// buflen - length of the buffer in bytes. +// +// Returns the length of the relationship name in bytes. +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFAttachment_GetAFRelationship(FPDF_ATTACHMENT attachment, + FPDF_WCHAR* buffer, + unsigned long buflen); + #ifdef __cplusplus } // extern "C" #endif // __cplusplus diff --git a/public/fpdf_doc.h b/public/fpdf_doc.h index 6d551c1ce2..a609ca9618 100644 --- a/public/fpdf_doc.h +++ b/public/fpdf_doc.h @@ -425,7 +425,10 @@ FPDF_GetFileIdentifier(FPDF_DOCUMENT document, // buffer - a buffer for the tag. May be NULL. // buflen - the length of the buffer, in bytes. May be 0. // -// Returns the number of bytes in the tag, including trailing zeros. +// Returns the number of bytes in the tag, including trailing zeros, on success. +// Returns 0 if |document| or |tag| is NULL. Returns 0 if |document| does not +// have an /Info dictionary. Returns 2 for an empty string if |tag| is not in +// the /Info dictionary, // // The |buffer| is always encoded in UTF-16LE. The |buffer| is followed by two // bytes of zeros indicating the end of the string. If |buflen| is less than diff --git a/public/fpdf_edit.h b/public/fpdf_edit.h index 2dbd60db58..77fa19c81e 100644 --- a/public/fpdf_edit.h +++ b/public/fpdf_edit.h @@ -1190,6 +1190,38 @@ FPDFPageObj_SetDashArray(FPDF_PAGEOBJECT page_object, size_t dash_count, float phase); +// Experimental API. +// Get a bitmap rasterization of the stroke pattern of |page_object|. +// To render correctly, the caller must provide the |document| associated with +// |page_object|. The returned bitmap will be owned by the caller, and +// FPDFBitmap_Destroy() must be called on the returned bitmap when it is no +// longer needed. +// +// document - handle to the document containing |page_object|. +// page_object - handle to a page object. +// +// Returns the bitmap, or NULL if the stroke is not a tiling pattern or on +// failure. +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV +FPDFPageObj_GetRenderedStrokePattern(FPDF_DOCUMENT document, + FPDF_PAGEOBJECT page_object); + +// Experimental API. +// Get a bitmap rasterization of the fill pattern of |page_object|. +// To render correctly, the caller must provide the |document| associated with +// |page_object|. The returned bitmap will be owned by the caller, and +// FPDFBitmap_Destroy() must be called on the returned bitmap when it is no +// longer needed. +// +// document - handle to the document containing |page_object|. +// page_object - handle to a page object. +// +// Returns the bitmap, or NULL if the fill is not a tiling pattern or on +// failure. +FPDF_EXPORT FPDF_BITMAP FPDF_CALLCONV +FPDFPageObj_GetRenderedFillPattern(FPDF_DOCUMENT document, + FPDF_PAGEOBJECT page_object); + // Get number of segments inside |path|. // // path - handle to a path. diff --git a/public/fpdf_structtree.h b/public/fpdf_structtree.h index c9823a6348..3c933b3d49 100644 --- a/public/fpdf_structtree.h +++ b/public/fpdf_structtree.h @@ -203,6 +203,27 @@ FPDF_StructElement_GetStringAttribute(FPDF_STRUCTELEMENT struct_element, FPDF_EXPORT int FPDF_CALLCONV FPDF_StructElement_GetMarkedContentID(FPDF_STRUCTELEMENT struct_element); +// Experimental API. +// Function: FPDF_StructElement_GetRawType +// Get the raw value of the /S entry for a given element. +// Parameters: +// struct_element - Handle to the struct element. +// buffer - A FPDF_WCHAR buffer for output text. May be NULL. +// buflen - The length of the buffer. May be 0. +// Return value: +// The number of bytes in the text, including the terminating NUL +// character. The number of bytes is returned regardless of the +// |buffer| and |buflen| parameters. +// Comments: +// Regardless of the platform, the |buffer| is always in UTF-16LE +// encoding. The string is terminated by a UTF16 NUL character. If +// |buflen| is less than the required length, or |buffer| is NULL, +// |buffer| will not be modified. +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDF_StructElement_GetRawType(FPDF_STRUCTELEMENT struct_element, + FPDF_WCHAR* buffer, + size_t buflen); + // Function: FPDF_StructElement_GetType // Get the type (/S) for a given element. // Parameters: diff --git a/public/fpdfview.h b/public/fpdfview.h index 1175868ae6..70061ae4c4 100644 --- a/public/fpdfview.h +++ b/public/fpdfview.h @@ -292,8 +292,7 @@ typedef struct FPDF_LIBRARY_CONFIG_ { // Version 5 - Experimental. - // Explicit specification of font library to use when |m_RendererType| is set - // to |FPDF_RENDERERTYPE_SKIA|. + // Explicit specification of font library to use. // |m_FontLibraryType| must be a valid value for |FPDF_LIBRARY_CONFIG| // versions of this level or higher, or else the initialization will fail with // an immediate crash. @@ -308,6 +307,12 @@ typedef struct FPDF_LIBRARY_CONFIG_ { // is enabled. For setting the bool to true to have an effect, PDFium must be // built with |PDF_ENABLE_BROTLI|. FPDF_BOOL m_BrotliEnabled; + + // Version 7 - Experimental. + + // Boolean to specify whether PDFium creates a dedicated v8::Isolate for each + // document. When true, |m_pIsolate| must be NULL. + FPDF_BOOL m_IsolatePerDocument; } FPDF_LIBRARY_CONFIG; // Function: FPDF_InitLibraryWithConfig diff --git a/scripts/embedpdf-runtime/globals-allowlist.txt b/scripts/embedpdf-runtime/globals-allowlist.txt index 88184a159c..202ba46c6f 100644 --- a/scripts/embedpdf-runtime/globals-allowlist.txt +++ b/scripts/embedpdf-runtime/globals-allowlist.txt @@ -10,6 +10,7 @@ thread-local | *::g_registry | The registered-font registry. thread-local | *::g_stock_colorspaces | Stock colour spaces. thread-local | *::g_opcodes | The content parser's opcode table. thread-local | *::g_CurrentRecursionDepth | Render recursion depth. +thread-local | *::g_isolate_per_document | Library start-up and shutdown write it on every worker thread. thread-local | CPDF_SyntaxParser::s_CurrentRecursionDepth | Parser recursion depth: the guard against deeply nested PDFs. thread-local | *::g_bLibraryInitialized | Whether this thread initialized PDFium. thread-local | *::g_last_error | FPDF_GetLastError. diff --git a/skia/BUILD.gn b/skia/BUILD.gn index e465a3cc53..a7d439c3a5 100644 --- a/skia/BUILD.gn +++ b/skia/BUILD.gn @@ -202,13 +202,6 @@ config("skia_config") { defines += [ "SK_CODEC_DECODES_BMP_WITH_RUST" ] } - if (pdf_enable_rust_jpeg) { - defines += [ - "SK_CODEC_DECODES_JPEG", - "SK_CODEC_DECODES_JPEG_WITH_RUST", - ] - } - if (pdf_enable_rust_png) { defines += [ "SK_CODEC_DECODES_PNG", @@ -581,15 +574,6 @@ component("skia") { sources += skia_encode_png_base sources += skia_encode_public sources += skia_encode_srcs - sources += [ - "//third_party/skia/src/codec/SkJpegCodec.cpp", - "//third_party/skia/src/codec/SkJpegDecoderMgr.cpp", - "//third_party/skia/src/codec/SkJpegMetadataDecoderImpl.cpp", - "//third_party/skia/src/codec/SkJpegMultiPicture.cpp", - "//third_party/skia/src/codec/SkJpegSegmentScan.cpp", - "//third_party/skia/src/codec/SkJpegSourceMgr.cpp", - "//third_party/skia/src/codec/SkJpegUtility.cpp", - ] if (pdf_enable_rust_png) { sources += skia_codec_rust_png @@ -606,7 +590,6 @@ component("skia") { sources += skia_encode_png_public deps += [ "../third_party:png" ] } - deps += [ "../third_party:jpeg" ] } # Template for things that are logically part of :skia, but need to be split out diff --git a/skia/OWNERS b/skia/OWNERS index f914cc888b..87064dc8e2 100644 --- a/skia/OWNERS +++ b/skia/OWNERS @@ -1 +1 @@ -kjlubick@google.com +fmalita@google.com diff --git a/testing/BUILD.gn b/testing/BUILD.gn index 11331b1260..3bbb09d823 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -104,6 +104,7 @@ source_set("pixel_diff_utils") { "utils/pixel_diff_util.cpp", "utils/pixel_diff_util.h", ] + public_deps = [ "../core/fxcrt" ] configs += [ "../:pdfium_strict_config" ] } diff --git a/testing/SUPPRESSIONS b/testing/SUPPRESSIONS index d4bded8977..7574ff3064 100644 --- a/testing/SUPPRESSIONS +++ b/testing/SUPPRESSIONS @@ -2,833 +2,1618 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # -# List of tests to be skipped, one per line. -# There are five space-separated columns per line +# List of expectations for tests that do not match their expected images, +# one per line. +# There are seven space-separated columns per line # Each column (except column 0) can contain a comma-separated list of values. # # Column 0: test file name # Column 1: platform: *, win, mac, mac_arm, mac_x86, linux # Column 2: v8 support: *, nov8, v8 # Column 3: xfa support: *, noxfa, xfa -# Column 4: rendering support: *, agg, gdi, skia +# Column 4: rendering support: *, agg, gdi, gdi_agg, gdi_skia, skia +# Column 5: font engine: *, freetype, fontations +# Column 6: action, one of: +# diff - the output differs in a way that no tolerance can bridge, so +# the test still runs but never fails, however far off it lands +# blank - skip the image comparison entirely +# fuzzy - compare with a tolerance, either the defaults or the explicit +# fuzzy=,, (e.g. fuzzy=3,0.05,15.0) +# skip - the test is expected to crash, so do not run it at all +# +# Apart from "skip", every action excuses mismatching output and nothing +# else. A crash, an ASan report, a CHECK failure or a timeout is reported +# as a failure no matter what is listed here, even when the test managed +# to write output that matches. # # All columns on a line must match, but filenames may be repeated on subsequent # lines to suppress more cases. Within each column, any one of the # comma-separated values must match in order for the column to "match". # The filenames and keywords are case-sensitive. -# One exception is the column 1, where "mac" is a shorthand that matches either -# "mac_arm" or "mac_x86". +# One exception is column 1, where "mac" is a shorthand that matches either +# "mac_arm" or "mac_x86". Similarly, "gdi" in column 4 is a shorthand that +# matches either "gdi_agg" or "gdi_skia". # # Try to keep the file alphabetized within each category of test. # # Corpus tests # -12.pdf mac * * agg -1_1_textbox.pdf * * * * -1_2_typewriter.pdf * * * * -1_3_callout.pdf * * * * -1_matrix.pdf mac * * agg -1m_diff_lsjdf.pdf mac * * agg -1m_same_xxxx.pdf mac * * agg -2_11_stamp3.pdf mac * * agg -2_6_textbox.pdf * * * * -2_color_calrgb.pdf mac * * agg -2_color_indexed.pdf mac * * agg -3_4_textbox.pdf * * * * -3_interpolate_image.pdf mac * * agg -3bigpreview.pdf mac * * agg -4_35.pdf mac * * agg -4_39.pdf mac * * agg -5.1.pdf mac * * agg -5.2.pdf * * * * -5.5_simple_font.pdf mac * * agg -8.2_name_dest_f_dest.pdf mac * * agg -8.2_outline.pdf mac * * agg -8.3_presentation.pdf mac * * agg -FRC_10_8.2.2__T8.3_original_file.pdf * * * * -FRC_11_8.2.2__T8.3_first_last_exchange.pdf * * * * -FRC_12_8.2.2__T8.3_first_outline_obj_ID.pdf * * * * -FRC_13_8.2.2__T8.3_Count_edit300.pdf * * * * -FRC_14_8.2.2__T8.3_Count_edit0.pdf * * * * -FRC_15_8.2.2__T8.3_Count_edit1.pdf * * * * -FRC_16_8.2.2__T8.3_Count_edit_1.pdf * * * * -FRC_1_8.2.2__T8.3_First_empty.pdf * * * * -FRC_2_8.2.2__T8.3_Last_empty.pdf * * * * -FRC_3.5_P__3616_Password_1.pdf * * * * -FRC_3_8.2.2_Type_empty.pdf * * * * -FRC_4_8.2.2__T8.3_Count_empty.pdf * * * * -FRC_5_8.2.2__T8.3_First_remove.pdf * * * * -FRC_6_8.2.2__T8.3_Last_remove.pdf * * * * -FRC_7_8.2.2_Type_remove.pdf * * * * -FRC_8.4.1_Annotations_M.pdf * * * * -FRC_8.4.1_Annotations_NM.pdf * * * * -FRC_8.5_Page_C_SubmitForm.pdf * * * * -FRC_8.5_Page_PI_ResetForm_Phantom.pdf * * * * -FRC_8.5_Widget_F.pdf * nov8 * * -FRC_8_8.2.2__T8.3_Count_remove.pdf * * * * -FRC_9_8.2.2__T8.3_remove_first_item.pdf * * * * -action.pdf * * * * -action_execute_a_menu_item.pdf mac * * agg -action_hide_show_form.pdf mac * * agg -action_on_focus.pdf mac * * agg -action_open_a_file.pdf mac * * agg -action_pdf_save_close.pdf mac * * agg -action_reset.pdf mac * * agg -action_run_javascript.pdf mac * * agg -action_submit_a_form.pdf mac * * agg -all_trigger_alert.pdf * * * * -all_trigger_mailmsg.pdf * * * * -all_trigger_print.pdf * * * * -all_trigger_run_js_lunchurl.pdf mac * * agg -all_trigger_run_js_maildoc.pdf mac * * agg -annotation_highlight_author_content.pdf mac * * agg -annotation_highlight_long_content.pdf mac * * agg -annotation_highlight_no_author.pdf mac * * agg -app_launchurl.pdf mac * * agg -appstoredescription3.1_en_updated.pdf mac * * agg -bookmark.pdf * * * * -bookmarkgetcolor.pdf mac * * agg -bug_0_length_line.pdf mac * * agg +12.pdf mac * * agg * diff +12.pdf * * * * fontations fuzzy=10,0.1,15 +1_1_textbox.pdf * * * * * diff +1_2_typewriter.pdf * * * * * diff +1_3_callout.pdf * * * * * diff +1_matrix.pdf mac * * agg * diff +1_matrix.pdf * * * * fontations fuzzy=10,0.1,15 +1m_diff_lsjdf.pdf mac * * agg * diff +1m_diff_lsjdf.pdf * * * * fontations fuzzy=10,0.1,15 +1m_same_xxxx.pdf mac * * agg * diff +2_11_stamp3.pdf mac * * agg * diff +2_11_stamp3.pdf * * * * fontations fuzzy=10,0.1,15 +2_11_stamp3.pdf mac * * skia * fuzzy +2_6_textbox.pdf * * * * * diff +2_color_calrgb.pdf mac * * agg * diff +2_color_calrgb.pdf * * * * fontations fuzzy=10,0.1,15 +2_color_indexed.pdf mac * * agg * diff +2_color_indexed.pdf * * * * fontations fuzzy=10,0.1,15 +3_4_textbox.pdf * * * * * diff +3_interpolate_image.pdf mac * * agg * diff +3_interpolate_image.pdf * * * * fontations fuzzy=10,0.1,15 +3_interpolate_image.pdf mac * * skia * fuzzy +3bigpreview.pdf mac * * agg * diff +3bigpreview.pdf * * * * * fuzzy=21,0.15,10.0 +4_35.pdf mac * * agg * diff +4_35.pdf * * * * fontations fuzzy=10,0.1,15 +4_39.pdf mac * * agg * diff +4_39.pdf * * * * fontations fuzzy=10,0.1,15 +5.1.pdf mac * * agg * diff +5.1.pdf * * * * fontations fuzzy=10,0.1,15 +5.2.pdf * * * * * diff +5.5_simple_font.pdf mac * * agg * diff +5.5_simple_font.pdf * * * * fontations diff +8.2_name_dest_f_dest.pdf mac * * agg * diff +8.2_name_dest_f_dest.pdf * * * * fontations fuzzy=10,0.1,15 +8.2_outline.pdf mac * * agg * diff +8.2_outline.pdf * * * * fontations fuzzy=10,0.1,15 +8.3_presentation.pdf mac * * agg * diff +8.3_presentation.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_10_8.2.2__T8.3_original_file.pdf * * * * * diff +FRC_11_8.2.2__T8.3_first_last_exchange.pdf * * * * * diff +FRC_12_8.2.2__T8.3_first_outline_obj_ID.pdf * * * * * diff +FRC_13_8.2.2__T8.3_Count_edit300.pdf * * * * * diff +FRC_14_8.2.2__T8.3_Count_edit0.pdf * * * * * diff +FRC_15_8.2.2__T8.3_Count_edit1.pdf * * * * * diff +FRC_16_8.2.2__T8.3_Count_edit_1.pdf * * * * * diff +FRC_1_8.2.2__T8.3_First_empty.pdf * * * * * diff +FRC_2_8.2.2__T8.3_Last_empty.pdf * * * * * diff +FRC_3.5_P__3616_Password_1.pdf * * * * * diff +FRC_3_8.2.2_Type_empty.pdf * * * * * diff +FRC_4_8.2.2__T8.3_Count_empty.pdf * * * * * diff +FRC_5_8.2.2__T8.3_First_remove.pdf * * * * * diff +FRC_6_8.2.2__T8.3_Last_remove.pdf * * * * * diff +FRC_7_8.2.2_Type_remove.pdf * * * * * diff +FRC_8.4.1_Annotations_M.pdf * * * * * diff +FRC_8.4.1_Annotations_NM.pdf * * * * * diff +FRC_8.5_Page_C_SubmitForm.pdf * * * * * diff +FRC_8.5_Page_PI_ResetForm_Phantom.pdf * * * * * diff +FRC_8.5_Widget_F.pdf * nov8 * * * diff +FRC_8.5_Widget_F.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8_8.2.2__T8.3_Count_remove.pdf * * * * * diff +FRC_9_8.2.2__T8.3_remove_first_item.pdf * * * * * diff +action.pdf * * * * * diff +action_execute_a_menu_item.pdf mac * * agg * diff +action_execute_a_menu_item.pdf * * * * fontations fuzzy=10,0.1,15 +action_execute_a_menu_item.pdf mac * * skia * fuzzy +action_hide_show_form.pdf mac * * agg * diff +action_hide_show_form.pdf * * * * fontations fuzzy=10,0.1,15 +action_hide_show_form.pdf mac * * skia * fuzzy +action_on_focus.pdf mac * * agg * diff +action_on_focus.pdf * * * * fontations fuzzy=10,0.1,15 +action_open_a_file.pdf mac * * agg * diff +action_open_a_file.pdf * * * * fontations fuzzy=10,0.1,15 +action_pdf_save_close.pdf mac * * agg * diff +action_pdf_save_close.pdf * * * * fontations fuzzy=10,0.1,15 +action_reset.pdf mac * * agg * diff +action_reset.pdf * * * * fontations fuzzy=10,0.1,15 +action_reset.pdf mac * * skia * fuzzy +action_run_javascript.pdf mac * * agg * diff +action_run_javascript.pdf * * * * fontations fuzzy=10,0.1,15 +action_submit_a_form.pdf mac * * agg * diff +action_submit_a_form.pdf * * * * fontations fuzzy=10,0.1,15 +all_trigger_alert.pdf * * * * * diff +all_trigger_mailmsg.pdf * * * * * diff +all_trigger_print.pdf * * * * * diff +all_trigger_run_js_lunchurl.pdf mac * * agg * diff +all_trigger_run_js_lunchurl.pdf * * * * fontations fuzzy=10,0.1,15 +all_trigger_run_js_lunchurl.pdf mac * * skia * fuzzy +all_trigger_run_js_maildoc.pdf mac * * agg * diff +all_trigger_run_js_maildoc.pdf * * * * fontations fuzzy=10,0.1,15 +all_trigger_run_js_maildoc.pdf mac * * skia * fuzzy +annotation_highlight_author_content.pdf mac * * agg * diff +annotation_highlight_author_content.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_highlight_author_content.pdf mac * * skia * fuzzy +annotation_highlight_long_content.pdf mac * * agg * diff +annotation_highlight_long_content.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_highlight_long_content.pdf mac * * skia * fuzzy +annotation_highlight_no_author.pdf mac * * agg * diff +annotation_highlight_no_author.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_highlight_no_author.pdf mac * * skia * fuzzy +app_launchurl.pdf mac * * agg * diff +app_launchurl.pdf * * * * fontations fuzzy=10,0.1,15 +appstoredescription3.1_en_updated.pdf mac * * agg * diff +appstoredescription3.1_en_updated.pdf * * * * fontations fuzzy=10,0.1,15 +bookmark.pdf * * * * * diff +bookmarkgetcolor.pdf mac * * agg * diff +bookmarkgetcolor.pdf * * * * fontations diff +bug_0_length_line.pdf mac * * agg * diff +bug_0_length_line.pdf * * * * fontations fuzzy=10,0.1,15 # TODO(crbug.com/42270825): Remove after associated bug is fixed -bug_0_length_line.pdf * * * skia - -bug_0_width_line.pdf mac * * agg -bug_440132.pdf mac * * agg -bug_white_space.pdf mac * * agg -calcorderindex_test.pdf * * * * -calculate_average.pdf mac * * agg -calculate_order.pdf * * * * -calculate_sum_a_b_c.pdf mac * * agg -calculate_validate.pdf mac * * agg -calculate_validate.pdf * nov8 * * -ch_1.pdf * * * * -check_box.pdf * * * * -color.pdf mac * * agg -colorspace.pdf mac * * agg -colorspace_test1.pdf mac * * agg -combo_box.pdf * * * * -combo_box_format.pdf mac * * agg -date.pdf mac * * agg -edit_transform.pdf mac * * agg -en_contact.pdf mac * * agg -en_diy.pdf mac * * agg -en_foxit.pdf mac * * agg -en_fqa2.pdf mac * * agg -en_introduce.pdf mac * * agg -en_tem.pdf mac * * agg -en_uicase_.pdf mac * * agg -event.change.pdf mac * * agg -event.changeex.pdf mac * * agg -event.keydown.pdf mac * * agg -event.keydown_1_.pdf mac * * agg -event.type_name.pdf mac * * agg -event.value.pdf mac * * agg -event_change.pdf mac * * agg -example_001.pdf mac * * agg -example_002.pdf mac * * agg -example_003.pdf mac * * agg -example_004.pdf mac * * agg -example_005.pdf mac * * agg -example_006.pdf mac * * agg -example_007.pdf mac * * agg -example_008.pdf mac * * agg -example_009.pdf mac * * agg -example_010.pdf mac * * agg -example_011.pdf mac * * agg -example_012.pdf mac * * agg -example_013.pdf mac * * agg -example_014.pdf mac * * agg -example_015.pdf mac * * agg -example_016.pdf mac * * agg -example_017.pdf mac * * agg -example_018.pdf mac * * agg -example_019.pdf mac * * agg -example_020.pdf mac * * agg -example_021.pdf mac * * agg -example_022.pdf mac * * agg -example_023.pdf mac * * agg -example_024.pdf mac * * agg -example_025.pdf mac * * agg -example_026.pdf mac * * agg -example_027.pdf mac * * agg -example_028.pdf mac * * agg -example_029.pdf mac * * agg -example_030.pdf mac * * agg -example_031.pdf mac * * agg -example_032.pdf mac * * agg -example_033.pdf mac * * agg -example_034.pdf mac * * agg -example_035.pdf mac * * agg -example_036.pdf mac * * agg -example_037.pdf mac * * agg -example_038.pdf mac * * agg -example_039.pdf mac * * agg -example_040.pdf mac * * agg -example_041.pdf mac * * agg -example_042.pdf mac * * agg -example_043.pdf mac * * agg -example_044.pdf mac * * agg -example_045.pdf mac * * agg -example_046.pdf mac * * agg -example_047.pdf mac * * agg -example_048.pdf mac * * agg -example_049.pdf mac * * agg -example_050.pdf mac * * agg -example_051.pdf mac * * agg -example_052.pdf mac * * agg -example_053.pdf mac * * agg -example_054.pdf mac * * agg -example_055.pdf mac * * agg -example_056.pdf mac * * agg -example_057.pdf mac * * agg -example_058.pdf mac * * agg -example_059.pdf mac * * agg -example_060.pdf mac * * agg -example_061.pdf mac * * agg -example_062.pdf mac * * agg -example_063.pdf mac * * agg -example_064.pdf mac * * agg -example_065.pdf mac * * agg -fillform.pdf * * * * -form_action_trigger.pdf * * * * -form_button_sign_url.pdf * * * * -form_combo_sign_url.pdf * * * * -form_combobox0.pdf * * * * -form_combobox_actioin_goto.pdf * * * * -form_combobox_date.pdf mac * * agg -form_combobox_date.pdf * nov8 * * -form_combobox_date1.pdf * * * * -form_combobox_date2.pdf mac * * agg -form_combobox_date2.pdf * nov8 * * -form_combobox_importform.pdf * * * * -form_combobox_num.pdf mac * * agg -form_combobox_num.pdf * nov8 * * -form_combobox_per.pdf mac * * agg -form_combobox_per.pdf * nov8 * * -form_combobox_plus.pdf mac * * agg -form_combobox_plus.pdf * nov8 * * -form_combobox_product.pdf mac * * agg -form_combobox_product.pdf * nov8 * * -form_combobox_resetform.pdf * * * * -form_combobox_time.pdf mac * * agg -form_combobox_time.pdf * nov8 * * -form_list.pdf * * * * -form_list1.pdf * * * * -form_same.pdf mac * * agg -form_text_sign_url.pdf * * * * -format_combo_box.pdf mac * * agg -format_combo_box.pdf * nov8 * * -format_custom_format.pdf * nov8 * * -format_custom_keystroke.pdf * * * * -format_date.pdf * nov8 * * -format_number.pdf mac * * agg -format_percentage.pdf mac * * agg -format_special.pdf * nov8 * * -format_text_color.pdf mac * * agg -formfield.pdf * * * * -getarray.pdf mac * * agg +bug_0_length_line.pdf * * * skia * diff + +bug_0_width_line.pdf mac * * agg * diff +bug_0_width_line.pdf * * * * fontations fuzzy=10,0.1,15 +bug_440132.pdf mac * * agg * diff +bug_440132.pdf * * * * fontations fuzzy=10,0.1,15 +bug_white_space.pdf mac * * agg * diff +bug_white_space.pdf * * * * fontations fuzzy=10,0.1,15 +calcorderindex_test.pdf * * * * * diff +calculate_average.pdf mac * * agg * diff +calculate_average.pdf * * * * fontations fuzzy=10,0.1,15 +calculate_order.pdf * * * * * diff +calculate_sum_a_b_c.pdf mac * * agg * diff +calculate_sum_a_b_c.pdf * * * * fontations fuzzy=10,0.1,15 +calculate_validate.pdf mac * * agg * diff +calculate_validate.pdf * * * * fontations fuzzy=10,0.1,15 +calculate_validate.pdf * nov8 * * * diff +ch_1.pdf * * * * * diff +check_box.pdf * * * * * diff +color.pdf mac * * agg * diff +color.pdf * * * * fontations fuzzy=10,0.1,15 +colorspace.pdf mac * * agg * diff +colorspace.pdf * * * * fontations fuzzy=10,0.1,15 +colorspace.pdf mac * * skia * fuzzy +colorspace_test1.pdf mac * * agg * diff +colorspace_test1.pdf * * * * fontations fuzzy=10,0.1,15 +colorspace_test1.pdf mac * * skia * fuzzy +combo_box.pdf * * * * * diff +combo_box_format.pdf mac * * agg * diff +combo_box_format.pdf * * * * fontations fuzzy=10,0.1,15 +date.pdf mac * * agg * diff +date.pdf * * * * fontations fuzzy=10,0.1,15 +edit_transform.pdf mac * * agg * diff +edit_transform.pdf * * * * fontations fuzzy=10,0.1,15 +en_contact.pdf mac * * agg * diff +en_contact.pdf * * * * fontations fuzzy=10,0.1,15 +en_diy.pdf mac * * agg * diff +en_diy.pdf * * * * fontations diff +en_foxit.pdf mac * * agg * diff +en_foxit.pdf * * * * fontations fuzzy=10,0.1,15 +en_fqa2.pdf mac * * agg * diff +en_fqa2.pdf * * * * fontations fuzzy=10,0.1,15 +en_fqa2.pdf mac * * skia * fuzzy +en_introduce.pdf mac * * agg * diff +en_introduce.pdf * * * * fontations fuzzy=10,0.1,15 +en_tem.pdf mac * * agg * diff +en_tem.pdf * * * * fontations diff +en_tem.pdf * * * * * fuzzy +en_tem.pdf mac * * skia * fuzzy +en_uicase_.pdf mac * * agg * diff +en_uicase_.pdf * * * * fontations fuzzy=10,0.1,15 +en_uicase_.pdf mac * * skia * fuzzy +event.change.pdf mac * * agg * diff +event.change.pdf * * * * fontations fuzzy=10,0.1,15 +event.changeex.pdf mac * * agg * diff +event.changeex.pdf * * * * fontations fuzzy=10,0.1,15 +event.keydown.pdf mac * * agg * diff +event.keydown.pdf * * * * fontations fuzzy=10,0.1,15 +event.keydown_1_.pdf mac * * agg * diff +event.keydown_1_.pdf * * * * fontations fuzzy=10,0.1,15 +event.type_name.pdf mac * * agg * diff +event.type_name.pdf * * * * fontations fuzzy=10,0.1,15 +event.value.pdf mac * * agg * diff +event.value.pdf * * * * fontations fuzzy=10,0.1,15 +event_change.pdf mac * * agg * diff +event_change.pdf * * * * fontations fuzzy=10,0.1,15 +example_001.pdf mac * * agg * diff +example_001.pdf * * * * fontations diff +example_001.pdf * * * * * fuzzy +example_001.pdf mac * * skia * fuzzy +example_002.pdf mac * * agg * diff +example_002.pdf * * * * fontations fuzzy=10,0.1,15 +example_003.pdf mac * * agg * diff +example_003.pdf * * * * fontations diff +example_003.pdf * * * * * fuzzy +example_004.pdf mac * * agg * diff +example_004.pdf * * * * fontations diff +example_004.pdf * * * * * fuzzy +example_005.pdf mac * * agg * diff +example_005.pdf * * * * fontations diff +example_005.pdf * * * * * fuzzy +example_006.pdf mac * * agg * diff +example_006.pdf * * * * fontations diff +example_006.pdf * * * * * fuzzy +example_006.pdf mac * * skia * fuzzy +example_007.pdf mac * * agg * diff +example_007.pdf * * * * fontations diff +example_007.pdf * * * * * fuzzy +example_008.pdf mac * * agg * diff +example_008.pdf * * * * fontations diff +example_008.pdf * * * * * fuzzy +example_009.pdf mac * * agg * diff +example_009.pdf * * * * fontations diff +example_009.pdf * * * * * fuzzy=3,0.15,5.0 +example_010.pdf mac * * agg * diff +example_010.pdf * * * * fontations diff +example_010.pdf * * * * * fuzzy +example_010.pdf mac * * skia * fuzzy +example_011.pdf mac * * agg * diff +example_011.pdf * * * * fontations diff +example_011.pdf * * * * * fuzzy +example_012.pdf mac * * agg * diff +example_012.pdf * * * * fontations fuzzy=10,0.1,15 +example_013.pdf mac * * agg * diff +example_013.pdf * * * * fontations diff +example_013.pdf * * * * * fuzzy +example_014.pdf mac * * agg * diff +example_014.pdf * * * * fontations diff +example_014.pdf * * * * * fuzzy +example_015.pdf mac * * agg * diff +example_015.pdf * * * * fontations diff +example_015.pdf * * * * * fuzzy +example_016.pdf mac * * agg * diff +example_016.pdf * * * * fontations diff +example_016.pdf * * * * * fuzzy +example_017.pdf mac * * agg * diff +example_017.pdf * * * * fontations diff +example_017.pdf * * * * * fuzzy +example_018.pdf mac * * agg * diff +example_018.pdf * * * * fontations diff +example_018.pdf * * * * * fuzzy +example_018.pdf mac * * skia * fuzzy +example_019.pdf mac * * agg * diff +example_019.pdf * * * * fontations diff +example_019.pdf * * * * * fuzzy +example_020.pdf mac * * agg * diff +example_020.pdf * * * * fontations diff +example_020.pdf * * * * * fuzzy +example_021.pdf mac * * agg * diff +example_021.pdf * * * * fontations diff +example_021.pdf * * * * * fuzzy +example_022.pdf mac * * agg * diff +example_022.pdf * * * * fontations diff +example_022.pdf * * * * * fuzzy +example_023.pdf mac * * agg * diff +example_023.pdf * * * * fontations diff +example_023.pdf * * * * * fuzzy +example_024.pdf mac * * agg * diff +example_024.pdf * * * * fontations diff +example_024.pdf * * * * * fuzzy +example_025.pdf mac * * agg * diff +example_025.pdf * * * * fontations diff +example_025.pdf * * * * * fuzzy +example_025.pdf mac * * skia * fuzzy +example_026.pdf mac * * agg * diff +example_026.pdf * * * * * fuzzy=4,0.05,5.0 +example_027.pdf mac * * agg * diff +example_027.pdf * * * * fontations diff +example_027.pdf * * * * * fuzzy +example_028.pdf mac * * agg * diff +example_028.pdf * * * * fontations fuzzy=10,0.1,15 +example_029.pdf mac * * agg * diff +example_029.pdf * * * * fontations diff +example_029.pdf * * * * * fuzzy +example_030.pdf mac * * agg * diff +example_030.pdf * * * * fontations diff +example_030.pdf * * * * * fuzzy +example_031.pdf mac * * agg * diff +example_031.pdf * * * * fontations diff +example_031.pdf * * * * * fuzzy +example_032.pdf mac * * agg * diff +example_032.pdf * * * * fontations diff +example_032.pdf * * * * * fuzzy +example_033.pdf mac * * agg * diff +example_033.pdf * * * * fontations diff +example_033.pdf * * * * * fuzzy +example_034.pdf mac * * agg * diff +example_034.pdf * * * * * fuzzy=4,0.05,5.0 +example_035.pdf mac * * agg * diff +example_035.pdf * * * * fontations diff +example_035.pdf * * * * * fuzzy +example_036.pdf mac * * agg * diff +example_036.pdf * * * * fontations diff +example_036.pdf * * * * * fuzzy +example_037.pdf mac * * agg * diff +example_037.pdf * * * * fontations diff +example_037.pdf * * * * * fuzzy +example_038.pdf mac * * agg * diff +example_038.pdf * * * * fontations diff +example_038.pdf * * * * * fuzzy +example_039.pdf mac * * agg * diff +example_039.pdf * * * * fontations diff +example_039.pdf * * * * * fuzzy +example_039.pdf mac * * skia * fuzzy +example_040.pdf mac * * agg * diff +example_040.pdf * * * * fontations diff +example_040.pdf * * * * * fuzzy +example_040.pdf mac * * skia * fuzzy +example_041.pdf mac * * agg * diff +example_041.pdf * * * * fontations diff +example_041.pdf * * * * * fuzzy +example_042.pdf mac * * agg * diff +example_042.pdf * * * * fontations diff +example_042.pdf * * * * * fuzzy +example_043.pdf mac * * agg * diff +example_043.pdf * * * * fontations diff +example_043.pdf * * * * * fuzzy +example_044.pdf mac * * agg * diff +example_044.pdf * * * * fontations diff +example_044.pdf * * * * * fuzzy +example_045.pdf mac * * agg * diff +example_045.pdf * * * * fontations diff +example_045.pdf * * * * * fuzzy +example_046.pdf mac * * agg * diff +example_046.pdf * * * * fontations diff +example_046.pdf * * * * * fuzzy +example_047.pdf mac * * agg * diff +example_047.pdf * * * * fontations diff +example_047.pdf * * * * * fuzzy +example_048.pdf mac * * agg * diff +example_048.pdf * * * * fontations diff +example_048.pdf * * * * * fuzzy +example_049.pdf mac * * agg * diff +example_049.pdf * * * * fontations diff +example_049.pdf * * * * * fuzzy +example_050.pdf mac * * agg * diff +example_050.pdf * * * * fontations diff +example_050.pdf * * * * * fuzzy +example_051.pdf mac * * agg * diff +example_051.pdf * * * * * fuzzy=3,0.35,5.0 +example_052.pdf mac * * agg * diff +example_052.pdf * * * * fontations diff +example_052.pdf * * * * * fuzzy +example_053.pdf mac * * agg * diff +example_053.pdf * * * * fontations diff +example_053.pdf * * * * * fuzzy +example_054.pdf mac * * agg * diff +example_054.pdf * * * * fontations diff +example_054.pdf * * * * * fuzzy +example_055.pdf mac * * agg * diff +example_055.pdf * * * * fontations diff +example_055.pdf * * * * * fuzzy +example_056.pdf mac * * agg * diff +example_056.pdf * * * * fontations diff +example_056.pdf * * * * * fuzzy +example_057.pdf mac * * agg * diff +example_057.pdf * * * * fontations diff +example_057.pdf * * * * * fuzzy +example_057.pdf mac * * skia * fuzzy +example_058.pdf mac * * agg * diff +example_058.pdf * * * * fontations diff +example_058.pdf * * * * * fuzzy +example_058.pdf mac * * skia * fuzzy +example_059.pdf mac * * agg * diff +example_059.pdf * * * * fontations diff +example_059.pdf * * * * * fuzzy +example_060.pdf mac * * agg * diff +example_060.pdf * * * * fontations diff +example_060.pdf * * * * * fuzzy +example_060.pdf mac * * skia * fuzzy +example_061.pdf mac * * agg * diff +example_061.pdf * * * * fontations diff +example_061.pdf * * * * * fuzzy +example_062.pdf mac * * agg * diff +example_062.pdf * * * * * fuzzy=4,0.05,5.0 +example_063.pdf mac * * agg * diff +example_063.pdf * * * * fontations diff +example_063.pdf * * * * * fuzzy +example_063.pdf mac * * skia * fuzzy +example_064.pdf mac * * agg * diff +example_064.pdf * * * * fontations diff +example_064.pdf * * * * * fuzzy +example_064.pdf mac * * skia * fuzzy +example_065.pdf mac * * agg * diff +example_065.pdf * * * * fontations fuzzy=15,0.05,15 +example_065.pdf * * * * * fuzzy +fillform.pdf * * * * * diff +form_action_trigger.pdf * * * * * diff +form_button_sign_url.pdf * * * * * diff +form_combo_sign_url.pdf * * * * * diff +form_combobox0.pdf * * * * * diff +form_combobox_actioin_goto.pdf * * * * * diff +form_combobox_date.pdf mac * * agg * diff +form_combobox_date.pdf * * * * fontations fuzzy=10,0.1,15 +form_combobox_date.pdf * nov8 * * * diff +form_combobox_date1.pdf * * * * * diff +form_combobox_date2.pdf mac * * agg * diff +form_combobox_date2.pdf * * * * fontations fuzzy=10,0.1,15 +form_combobox_date2.pdf * nov8 * * * diff +form_combobox_importform.pdf * * * * * diff +form_combobox_num.pdf mac * * agg * diff +form_combobox_num.pdf * * * * fontations fuzzy=10,0.1,15 +form_combobox_num.pdf * nov8 * * * diff +form_combobox_per.pdf mac * * agg * diff +form_combobox_per.pdf * * * * fontations fuzzy=10,0.1,15 +form_combobox_per.pdf * nov8 * * * diff +form_combobox_plus.pdf mac * * agg * diff +form_combobox_plus.pdf * * * * fontations fuzzy=10,0.1,15 +form_combobox_plus.pdf * nov8 * * * diff +form_combobox_product.pdf mac * * agg * diff +form_combobox_product.pdf * * * * fontations fuzzy=10,0.1,15 +form_combobox_product.pdf * nov8 * * * diff +form_combobox_resetform.pdf * * * * * diff +form_combobox_time.pdf mac * * agg * diff +form_combobox_time.pdf * * * * fontations fuzzy=10,0.1,15 +form_combobox_time.pdf * nov8 * * * diff +form_list.pdf * * * * * diff +form_list1.pdf * * * * * diff +form_same.pdf mac * * agg * diff +form_same.pdf * * * * fontations fuzzy=10,0.1,15 +form_text_sign_url.pdf * * * * * diff +format_combo_box.pdf mac * * agg * diff +format_combo_box.pdf * * * * fontations fuzzy=10,0.1,15 +format_combo_box.pdf * nov8 * * * diff +format_custom_format.pdf * nov8 * agg * diff +format_custom_format.pdf * * * * fontations fuzzy=10,0.1,15 +format_custom_keystroke.pdf * * * * * diff +format_date.pdf * nov8 * * * diff +format_number.pdf mac * * agg * diff +format_number.pdf * * * * fontations fuzzy=10,0.1,15 +format_percentage.pdf mac * * agg * diff +format_percentage.pdf * * * * fontations fuzzy=10,0.1,15 +format_special.pdf * nov8 * * * diff +format_text_color.pdf mac * * agg * diff +format_text_color.pdf * * * * fontations fuzzy=10,0.1,15 +formfield.pdf * * * * * diff +getarray.pdf mac * * agg * diff +getarray.pdf * * * * fontations fuzzy=10,0.1,15 # TODO(crbug.com/42271468): Remove after associated bug is fixed. -gradient_many_stops.pdf * * * agg - -javascriptaction.pdf * * * * -jetman_std.pdf mac * * agg -jetman_std_fixed.pdf mac * * agg -js_calculate.pdf * * * * -list_box.pdf * * * * -negative.pdf mac * * agg -new_certify1.pdf mac * * agg -new_signature1.pdf mac * * agg -new_signature2.pdf mac * * agg -new_stamp4.pdf mac * * agg -new_stamp5.pdf mac * * agg -new_textmarkup1.pdf mac * * agg -new_textmarkup1_hidden.pdf mac * * agg -new_textmarkup2.pdf mac * * agg -new_textmarkup2_hidden.pdf mac * * agg -new_textmarkup4.pdf mac * * agg -new_textmarkup4_hidden.pdf mac * * agg -new_textmarkup5.pdf mac * * agg -new_textmarkup5_hidden.pdf mac * * agg -new_textmarkup6.pdf mac * * agg -new_textmarkup7.pdf mac * * agg -new_textmarkup7_hidden.pdf mac * * agg -new_textmarkup8.pdf mac * * agg -new_textmarkup8_hidden.pdf mac * * agg -number.pdf * * * * -octest.pdf mac * * agg -open_a_weblink.pdf mac * * agg -path_10_jd.pdf mac * * agg -path_5_pattern.pdf mac * * agg -path_6_graphics4.5.5.pdf mac * * agg -path_7.pdf mac * * agg -path_9.pdf mac * * agg -percentage.pdf mac * * agg -push_button.pdf * * * * -quick_start_guide.pdf mac * * agg -radio_button.pdf * * * * -run_custom_validate_script.pdf * * * * -show_1.pdf mac * * agg -signature.pdf * * * * -signature_4.pdf * * * * -simplified_field_notation.pdf mac * * agg -special.pdf mac * * agg -submit_form.pdf mac * * agg -test_app_beep.pdf * * * * -test_control.pdf * * * * -text_field.pdf * * * * -text_field_font_input_decimal_point.pdf mac * * agg -text_field_multiline_line_spacing.pdf mac * * agg -thread_action.pdf mac * * agg -time.pdf mac * * agg -transformation.pdf mac * * agg -transparent.pdf mac * * agg -whats_new_in_v3.0.pdf mac * * agg -widget_javascript.pdf mac * * agg +gradient_many_stops.pdf * * * agg * diff + +javascriptaction.pdf * * * * * diff +jetman_std.pdf mac * * agg * diff +jetman_std.pdf * * * * fontations fuzzy=10,0.1,15 +jetman_std_fixed.pdf mac * * agg * diff +jetman_std_fixed.pdf * * * * fontations fuzzy=10,0.1,15 +js_calculate.pdf * * * * * diff +list_box.pdf * * * * * diff +negative.pdf mac * * agg * diff +negative.pdf * * * * fontations fuzzy=10,0.1,15 +new_certify1.pdf mac * * agg * diff +new_certify1.pdf * * * * fontations fuzzy=10,0.1,15 +new_signature1.pdf mac * * agg * diff +new_signature1.pdf * * * * fontations fuzzy=10,0.1,15 +new_signature2.pdf mac * * agg * diff +new_signature2.pdf * * * * fontations fuzzy=10,0.1,15 +new_stamp4.pdf mac * * agg * diff +new_stamp4.pdf * * * * fontations fuzzy=10,0.1,15 +new_stamp5.pdf mac * * agg * diff +new_stamp5.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup1.pdf mac * * agg * diff +new_textmarkup1.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup1_hidden.pdf mac * * agg * diff +new_textmarkup1_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup2.pdf mac * * agg * diff +new_textmarkup2.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup2.pdf mac * * skia * fuzzy +new_textmarkup2_hidden.pdf mac * * agg * diff +new_textmarkup2_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup4.pdf mac * * agg * diff +new_textmarkup4.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup4_hidden.pdf mac * * agg * diff +new_textmarkup4_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup5.pdf mac * * agg * diff +new_textmarkup5.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup5_hidden.pdf mac * * agg * diff +new_textmarkup5_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup6.pdf mac * * agg * diff +new_textmarkup6.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup7.pdf mac * * agg * diff +new_textmarkup7.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup7_hidden.pdf mac * * agg * diff +new_textmarkup7_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup8.pdf mac * * agg * diff +new_textmarkup8.pdf * * * * fontations fuzzy=10,0.1,15 +new_textmarkup8.pdf mac * * skia * fuzzy +new_textmarkup8_hidden.pdf mac * * agg * diff +new_textmarkup8_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +number.pdf * * * * * diff +octest.pdf mac * * agg * diff +octest.pdf * * * * fontations fuzzy=10,0.1,15 +open_a_weblink.pdf mac * * agg * diff +open_a_weblink.pdf * * * * fontations fuzzy=10,0.1,15 +path_10_jd.pdf mac * * agg * diff +path_10_jd.pdf * * * * fontations fuzzy=10,0.1,15 +path_5_pattern.pdf mac * * agg * diff +path_5_pattern.pdf * * * * fontations fuzzy=10,0.1,15 +path_5_pattern.pdf mac * * skia * fuzzy +path_6_graphics4.5.5.pdf mac * * agg * diff +path_6_graphics4.5.5.pdf * * * * fontations fuzzy=10,0.1,15 +path_7.pdf mac * * agg * diff +path_7.pdf * * * * fontations diff +path_9.pdf mac * * agg * diff +path_9.pdf * * * * fontations diff +path_9.pdf mac_x86 * * skia * fuzzy +percentage.pdf mac * * agg * diff +percentage.pdf * * * * fontations fuzzy=10,0.1,15 +push_button.pdf * * * * * diff +quick_start_guide.pdf mac * * agg * diff +quick_start_guide.pdf * * * * fontations diff +quick_start_guide.pdf * * * * * fuzzy=4,0.05,5.0 +radio_button.pdf * * * * * diff +run_custom_validate_script.pdf * * * * * diff +show_1.pdf mac * * agg * diff +show_1.pdf * * * * fontations fuzzy=10,0.1,15 +signature.pdf * * * * * diff +signature_4.pdf * * * * * diff +simplified_field_notation.pdf mac * * agg * diff +simplified_field_notation.pdf * * * * fontations fuzzy=10,0.1,15 +special.pdf mac * * agg * diff +special.pdf * * * * fontations fuzzy=10,0.1,15 +submit_form.pdf mac * * agg * diff +submit_form.pdf * * * * fontations fuzzy=10,0.1,15 +test_app_beep.pdf * * * * * diff +test_control.pdf * * * * * diff +text_field.pdf * * * * * diff +text_field_font_input_decimal_point.pdf mac * * agg * diff +text_field_font_input_decimal_point.pdf * * * * fontations fuzzy=10,0.1,15 +text_field_multiline_line_spacing.pdf mac * * agg * diff +text_field_multiline_line_spacing.pdf * * * * fontations fuzzy=10,0.1,15 +thread_action.pdf mac * * agg * diff +thread_action.pdf * * * * fontations fuzzy=10,0.1,15 +time.pdf mac * * agg * diff +time.pdf * * * * fontations fuzzy=10,0.1,15 +transformation.pdf mac * * agg * diff +transformation.pdf * * * * fontations fuzzy=10,0.1,15 +transparent.pdf mac * * agg * diff +whats_new_in_v3.0.pdf mac * * agg * diff +whats_new_in_v3.0.pdf * * * * fontations fuzzy=10,0.1,15 +whats_new_in_v3.0.pdf mac * * skia * fuzzy +widget_javascript.pdf mac * * agg * diff +widget_javascript.pdf * * * * fontations fuzzy=10,0.1,15 # TODO(crbug.com/42270997): Remove after associated bug is fixed -xfermodes2.pdf * * * agg +xfermodes2.pdf * * * agg * diff +xfermodes2.pdf * * * skia * fuzzy -zh_file1.pdf mac * * agg -zh_function_list.pdf mac * * agg -zh_shared_document.pdf mac * * agg +zh_file1.pdf mac * * agg * diff +zh_file1.pdf * * * * fontations fuzzy=10,0.1,15 +zh_function_list.pdf mac * * agg * diff +zh_function_list.pdf * * * * fontations fuzzy=10,0.1,15 +zh_function_list.pdf mac * * skia * fuzzy +zh_shared_document.pdf mac * * agg * diff +zh_shared_document.pdf * * * * fontations diff # TODO(hnakashima): These might never have been run. Go over them and fix. -FRC_8.5_E&X.pdf * * * * -FRC_8.5_O&PO_GoToE.pdf * * * * -FRC_8.5_OpenAction&O_URI.pdf * * * * -FRC_8.5_PC&C_GoToE_T_T.pdf * * * * -FRC_8.5_PC_GoToE_T_R&P&A.pdf * * * * -FRC_8.5_PO_GoToE_T_R&N.pdf * * * * +FRC_8.5_E&X.pdf * * * * * diff +FRC_8.5_O&PO_GoToE.pdf * * * * * diff +FRC_8.5_OpenAction&O_URI.pdf * * * * * diff +FRC_8.5_PC&C_GoToE_T_T.pdf * * * * * diff +FRC_8.5_PC_GoToE_T_R&P&A.pdf * * * * * diff +FRC_8.5_PO_GoToE_T_R&N.pdf * * * * * diff # xfa_specific -Choose.pdf * * * * -data_binding.pdf * * * * -# TODO(npm): Add proper evt for MouseEvents. -MouseEvents_enter.pdf * * * * -MouseEvents_exit.pdf * * * * -Oneof3.pdf * * * * -Sum.pdf * * * * -Test_CheckBox.pdf * * * * +Choose.pdf * * * * * diff +data_binding.pdf * * * * * diff +# TODO(crbug.com/567626109): Add proper evt for MouseEvents. +MouseEvents_enter.pdf * * * * * diff +MouseEvents_exit.pdf * * * * * diff +Oneof3.pdf * * * * * diff +Sum.pdf * * * * * diff +Test_CheckBox.pdf * * * * * diff # TODO(crbug.com/459586268): Remove this entry once macOS support for Intel # hardware goes away. The test expectation is just different enough from the ARM # rendering that fuzzy matching is not enough for it to match. -2_color_tiling.pdf mac_x86 * * skia -2_uncolor_tiling.pdf mac_x86 * * skia -FRC_10_8.2.4_View_C.pdf mac_x86 * * skia -Test_DateField_locale_zh_HK.pdf mac_x86 * * skia -TimeField.pdf mac_x86 * * skia -annotation_circle_hidden.pdf mac_x86 * * skia -annotation_ellipse_hidden.pdf mac_x86 * * skia -annotation_highlight.pdf mac_x86 * * skia -annotation_highlight_empty_content.pdf mac_x86 * * skia -annotation_highlight_hidden.pdf mac_x86 * * skia -annotation_highlight_no_author_no_content.pdf mac_x86 * * skia -annotation_highlight_no_content.pdf mac_x86 * * skia -annotation_highlight_opacity.pdf mac_x86 * * skia -annotation_ink_hidden.pdf mac_x86 * * skia -annotation_polygon.pdf mac_x86 * * skia -annotation_square_fill_negative_border.pdf mac_x86 * * skia -annotation_square_hidden.pdf mac_x86 * * skia -annotation_squiggly_hidden.pdf mac_x86 * * skia -annotation_stamp.pdf mac_x86 * * skia -annotation_strikeout_hidden.pdf mac_x86 * * skia -annotation_strikeout_opacity.pdf mac_x86 * * skia -annotation_underline.pdf mac_x86 * * skia -annotation_underline_hidden.pdf mac_x86 * * skia -annotation_underline_opacity.pdf mac_x86 * * skia -bug_668762.pdf mac_x86 * * skia -en_14_foxit_products.pdf mac_x86 * * skia -en_fqa.pdf mac_x86 * * skia -example_042.pdf mac_x86 * * skia -font_1_embedded_font_en_feature.pdf mac_x86 * * skia -font_2_embedded_font_en_size14.pdf mac_x86 * * skia -transparent1.pdf mac_x86 * * skia -xfermodes.pdf mac_x86 * * skia -xfermodes2.pdf mac_x86 * * skia -xfermodes3.pdf mac_x86 * * skia +2_color_calrgb.pdf mac_x86 * * skia * diff +2_color_tiling.pdf mac_x86 * * skia * diff +2_color_tiling.pdf * * * * fontations fuzzy=10,0.1,15 +2_uncolor_tiling.pdf mac_x86 * * skia * diff +3bigpreview.pdf mac_x86 * * skia * diff +FRC_4.5.3_DeviceRGB_rg.pdf mac_x86 * * skia * diff +FRC_4.5.4_CalGray_gamma.pdf mac_x86 * * skia * diff +FRC_4.5.4_CalGray_whitepoint.pdf mac_x86 * * skia * diff +FRC_4.5.4_CalRGB_gamma.pdf mac_x86 * * skia * diff +FRC_4.5.4_CalRGB_matrix.pdf mac_x86 * * skia * diff +FRC_4.5.4_CalRGB_whitepoint.pdf mac_x86 * * skia * diff +FRC_4.5.4_Lab.pdf mac_x86 * * skia * diff +FRC_4.5.4_RI_Relative.pdf mac_x86 * * skia * diff +FRC_10_8.2.4_View_C.pdf mac_x86 * * skia * diff +FRC_10_8.2.4_View_C.pdf * * * * fontations fuzzy=10,0.1,15 +Test_DateField_locale_zh_HK.pdf mac_x86 * * skia * diff +Test_DateField_locale_zh_HK.pdf * * * * fontations fuzzy=10,0.1,15 +TimeField.pdf mac_x86 * * skia * diff +annotation_circle_hidden.pdf mac_x86 * * skia * diff +annotation_circle_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_ellipse_hidden.pdf mac_x86 * * skia * diff +annotation_ellipse_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_highlight.pdf mac_x86 * * skia * diff +annotation_highlight.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_highlight_empty_content.pdf mac_x86 * * skia * diff +annotation_highlight_empty_content.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_highlight_hidden.pdf mac_x86 * * skia * diff +annotation_highlight_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_highlight_no_author_no_content.pdf mac_x86 * * skia * diff +annotation_highlight_no_author_no_content.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_highlight_no_content.pdf mac_x86 * * skia * diff +annotation_highlight_opacity.pdf mac_x86 * * skia * diff +annotation_highlight_opacity.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_ink_hidden.pdf mac_x86 * * skia * diff +annotation_ink_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_polygon.pdf mac_x86 * * skia * diff +annotation_polygon.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_square_fill_negative_border.pdf mac_x86 * * skia * diff +annotation_square_fill_negative_border.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_square_hidden.pdf mac_x86 * * skia * diff +annotation_square_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_squiggly_hidden.pdf mac_x86 * * skia * diff +annotation_squiggly_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_stamp.pdf mac_x86 * * skia * diff +annotation_stamp.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_strikeout_hidden.pdf mac_x86 * * skia * diff +annotation_strikeout_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_strikeout_opacity.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_strikeout_opacity.pdf mac * * skia * fuzzy +annotation_underline.pdf mac_x86 * * skia * diff +annotation_underline.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_underline_hidden.pdf mac_x86 * * skia * diff +annotation_underline_hidden.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_underline_opacity.pdf mac_x86 * * skia * diff +annotation_underline_opacity.pdf * * * * fontations fuzzy=10,0.1,15 +bug_668762.pdf mac_x86 * * skia * diff +bug_668762.pdf * * * * fontations fuzzy=10,0.1,15 +edit_transform.pdf mac_x86 * * skia * diff +en_14_foxit_products.pdf mac_x86 * * skia * diff +en_14_foxit_products.pdf * * * * fontations fuzzy=10,0.1,15 +en_contact.pdf mac_x86 * * skia * diff +en_foxit.pdf mac_x86 * * skia * diff +en_fqa.pdf mac_x86 * * skia * diff +en_fqa.pdf * * * * fontations fuzzy=10,0.1,15 +example_002.pdf mac_x86 * * skia * diff +example_004.pdf mac_x86 * * skia * diff +example_028.pdf mac_x86 * * skia * diff +example_055.pdf mac_x86 * * skia * diff +font_1_embedded_font_en_feature.pdf mac_x86 * * skia * diff +font_1_embedded_font_en_feature.pdf * * * * fontations fuzzy=10,0.1,15 +font_2_embedded_font_en_size14.pdf mac_x86 * * skia * diff +font_2_embedded_font_en_size14.pdf * * * * fontations fuzzy=10,0.1,15 +new_stamp4.pdf mac_x86 * * skia * diff +octest.pdf mac_x86 * * skia * diff +path_5_pattern.pdf mac_x86 * * skia * diff +path_6_graphics4.5.5.pdf mac_x86 * * skia * diff +path_7.pdf mac_x86 * * skia * diff +quick_start_guide.pdf mac_x86 * * skia * diff +transparent1.pdf mac_x86 * * skia * diff +xfermodes.pdf mac_x86 * * skia * diff +xfermodes.pdf * * * skia * fuzzy +xfermodes2.pdf mac_x86 * * skia * diff +xfermodes3.pdf * * * skia * fuzzy +zh_function_list.pdf mac_x86 * * skia * diff # GDI # TODO(crbug.com/42271066): Remove after associated bug is fixed -1.pdf * * * gdi -123.pdf * * * gdi -1_image.pdf * * * gdi -3_interpolate_image.pdf * * * gdi -3bigpreview.pdf * * * gdi -4_3.pdf * * * gdi -4_36.pdf * * * gdi -FRC_10_8.2.4_View_C.pdf * * * gdi -FRC_8.4.1_Annotations_Type.pdf * * * gdi -FRC_8.4.3_Border_Stypes_W_different_values.pdf * * * gdi -FRC_8.5_Screen_Img_D_Launch.pdf * * * gdi -FRC_8.5_URI_IsMap.pdf * * * gdi -bug_691967.pdf * * * gdi -bug_86459.pdf * * * gdi -bug_880920.pdf * * * gdi -ch_7_0.pdf * * * gdi -ch_7_1.pdf * * * gdi -ch_7_4.pdf * * * gdi -ch_7_5.pdf * * * gdi -ch_7_6.pdf * * * gdi -ch_7_7.pdf * * * gdi -ch_7_8.pdf * * * gdi -en_tem.pdf * * * gdi -example_001.pdf * * * gdi -example_003.pdf * * * gdi -example_004.pdf * * * gdi -example_005.pdf * * * gdi -example_006.pdf * * * gdi -example_007.pdf * * * gdi -example_008.pdf * * * gdi -example_009.pdf * * * gdi -example_010.pdf * * * gdi -example_011.pdf * * * gdi -example_013.pdf * * * gdi -example_015.pdf * * * gdi -example_016.pdf * * * gdi -example_017.pdf * * * gdi -example_018.pdf * * * gdi -example_019.pdf * * * gdi -example_020.pdf * * * gdi -example_021.pdf * * * gdi -example_022.pdf * * * gdi -example_023.pdf * * * gdi -example_024.pdf * * * gdi -example_025.pdf * * * gdi -example_026.pdf * * * gdi -example_027.pdf * * * gdi -example_029.pdf * * * gdi -example_030.pdf * * * gdi -example_031.pdf * * * gdi -example_032.pdf * * * gdi -example_033.pdf * * * gdi -example_034.pdf * * * gdi -example_035.pdf * * * gdi -example_036.pdf * * * gdi -example_037.pdf * * * gdi -example_038.pdf * * * gdi -example_039.pdf * * * gdi -example_040.pdf * * * gdi -example_041.pdf * * * gdi -example_042.pdf * * * gdi -example_043.pdf * * * gdi -example_044.pdf * * * gdi -example_045.pdf * * * gdi -example_046.pdf * * * gdi -example_047.pdf * * * gdi -example_048.pdf * * * gdi -example_049.pdf * * * gdi -example_050.pdf * * * gdi -example_051.pdf * * * gdi -example_052.pdf * * * gdi -example_053.pdf * * * gdi -example_055.pdf * * * gdi -example_056.pdf * * * gdi -example_057.pdf * * * gdi -example_058.pdf * * * gdi -example_059.pdf * * * gdi -example_060.pdf * * * gdi -example_061.pdf * * * gdi -example_062.pdf * * * gdi -example_063.pdf * * * gdi -example_064.pdf * * * gdi -example_065.pdf * * * gdi -image_8bit_devicergb_dctdecode.pdf * * * gdi -image_bmp.pdf * * * gdi -image_jpg.pdf * * * gdi -image_png.pdf * * * gdi -image_tif.pdf * * * gdi -mask_array.pdf * * * gdi -new_stamp2.pdf * * * gdi -quick_start_guide.pdf * * * gdi -whats_new_in_v3.0.pdf * * * gdi +1.pdf * * * gdi * diff +1.pdf * * * * * fuzzy +1.pdf mac * * skia * fuzzy +123.pdf * * * gdi * diff +123.pdf mac * * skia * fuzzy +1_image.pdf * * * gdi * diff +1_image.pdf mac * * skia * fuzzy +3_interpolate_image.pdf * * * gdi * diff +3bigpreview.pdf * * * gdi * diff +4_3.pdf * * * gdi * diff +4_3.pdf mac * * skia * fuzzy +4_36.pdf * * * gdi * diff +4_36.pdf mac * * skia * fuzzy +FRC_10_8.2.4_View_C.pdf * * * gdi * diff +FRC_8.4.1_Annotations_Type.pdf * * * gdi * diff +FRC_8.4.1_Annotations_Type.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_W_different_values.pdf * * * gdi * diff +FRC_8.4.3_Border_Stypes_W_different_values.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Screen_Img_D_Launch.pdf * * * gdi * diff +FRC_8.5_Screen_Img_D_Launch.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Screen_Img_D_Launch.pdf mac * * skia * fuzzy +FRC_8.5_URI_IsMap.pdf * * * gdi * diff +FRC_8.5_URI_IsMap.pdf * * * * * fuzzy +FRC_8.5_URI_IsMap.pdf mac * * skia * fuzzy +bug_691967.pdf * * * gdi_skia * diff +bug_691967.pdf * * * * * fuzzy +bug_691967.pdf mac * * skia * fuzzy +bug_86459.pdf * * * gdi * diff +bug_86459.pdf * * * * * fuzzy +bug_880920.pdf * * * gdi * diff +ch_7_0.pdf * * * gdi * diff +ch_7_0.pdf * * * * * fuzzy +ch_7_1.pdf * * * gdi * diff +ch_7_1.pdf * * * * * fuzzy +ch_7_4.pdf * * * gdi * diff +ch_7_4.pdf * * * * * fuzzy +ch_7_5.pdf * * * gdi * diff +ch_7_5.pdf * * * * * fuzzy +ch_7_6.pdf * * * gdi * diff +ch_7_6.pdf * * * * * fuzzy +ch_7_7.pdf * * * gdi * diff +ch_7_7.pdf * * * * * fuzzy +ch_7_8.pdf * * * gdi * diff +ch_7_8.pdf * * * * * fuzzy +en_tem.pdf * * * gdi * diff +example_001.pdf * * * gdi * diff +example_003.pdf * * * gdi * diff +example_004.pdf * * * gdi * diff +example_005.pdf * * * gdi * diff +example_006.pdf * * * gdi * diff +example_007.pdf * * * gdi * diff +example_008.pdf * * * gdi * diff +example_009.pdf * * * gdi * diff +example_010.pdf * * * gdi * diff +example_011.pdf * * * gdi * diff +example_013.pdf * * * gdi * diff +example_015.pdf * * * gdi * diff +example_016.pdf * * * gdi * diff +example_017.pdf * * * gdi * diff +example_018.pdf * * * gdi * diff +example_019.pdf * * * gdi * diff +example_020.pdf * * * gdi * diff +example_021.pdf * * * gdi * diff +example_022.pdf * * * gdi * diff +example_023.pdf * * * gdi * diff +example_024.pdf * * * gdi * diff +example_025.pdf * * * gdi * diff +example_026.pdf * * * gdi * diff +example_027.pdf * * * gdi * diff +example_029.pdf * * * gdi * diff +example_030.pdf * * * gdi * diff +example_031.pdf * * * gdi * diff +example_032.pdf * * * gdi * diff +example_033.pdf * * * gdi * diff +example_034.pdf * * * gdi * diff +example_035.pdf * * * gdi * diff +example_036.pdf * * * gdi * diff +example_037.pdf * * * gdi * diff +example_038.pdf * * * gdi * diff +example_039.pdf * * * gdi * diff +example_040.pdf * * * gdi * diff +example_041.pdf * * * gdi * diff +example_042.pdf * * * gdi * diff +example_043.pdf * * * gdi * diff +example_044.pdf * * * gdi * diff +example_045.pdf * * * gdi * diff +example_046.pdf * * * gdi * diff +example_047.pdf * * * gdi * diff +example_048.pdf * * * gdi * diff +example_049.pdf * * * gdi * diff +example_050.pdf * * * gdi * diff +example_051.pdf * * * gdi * diff +example_052.pdf * * * gdi * diff +example_053.pdf * * * gdi * diff +example_055.pdf * * * gdi * diff +example_056.pdf * * * gdi * diff +example_057.pdf * * * gdi * diff +example_058.pdf * * * gdi * diff +example_059.pdf * * * gdi * diff +example_060.pdf * * * gdi * diff +example_061.pdf * * * gdi * diff +example_062.pdf * * * gdi * diff +example_063.pdf * * * gdi * diff +example_064.pdf * * * gdi * diff +example_065.pdf * * * gdi * diff +image_8bit_devicergb_dctdecode.pdf * * * gdi * diff +image_8bit_devicergb_dctdecode.pdf * * * * * fuzzy +image_8bit_devicergb_dctdecode.pdf mac * * skia * fuzzy +image_bmp.pdf * * * gdi * diff +image_bmp.pdf * * * * * fuzzy +image_bmp.pdf mac * * skia * fuzzy +image_jpg.pdf * * * gdi * diff +image_jpg.pdf * * * * * fuzzy +image_jpg.pdf mac * * skia * fuzzy +image_png.pdf * * * gdi * diff +image_png.pdf mac * * skia * fuzzy +image_tif.pdf * * * gdi * diff +image_tif.pdf * * * * * fuzzy=10,0.05,10.0 +mask_array.pdf * * * gdi * diff +new_stamp2.pdf * * * gdi * diff +quick_start_guide.pdf * * * gdi * diff +whats_new_in_v3.0.pdf * * * gdi * diff # TODO(crbug.com/42271067): Remove after associated bug is fixed -Date_FormCale.pdf * * * gdi -FRC_3.5_P__3648_Password_1.pdf * * * gdi -FRC_8.4.1_Annotations_AP_N_R.D_.pdf * * * gdi -FRC_8.4.1_Annotations_AS_Off_.pdf * * * gdi -FRC_8.4.1_Annotations_AS_Yes_.pdf * * * gdi -FRC_8.5_Bl_Hide.pdf * * * gdi -FRC_8.5_E_GoTo_D.pdf * * * gdi -FRC_8.5_Fo_URI_Base.pdf * * * gdi -FRC_8.5_U_GoToR_NewWindow.pdf * * * gdi -FRC_8.5_U_GoToR_NewWindow_2.pdf * * * gdi -FRC_8.5_Widget_C.pdf * * * gdi -FRC_8.5_Widget_F.pdf * * * gdi -FRC_8.5_Widget_K.pdf * * * gdi -FRC_8.5_Widget_V.pdf * * * gdi -FRC_8.5_X_GoToR_D.pdf * * * gdi -Line_Stroke.pdf * * * gdi -Oneof1.pdf * * * gdi -Oneof2.pdf * * * gdi -PagePosition_any_rest.pdf * * * gdi -PagePosition_first.pdf * * * gdi -PagePosition_last.pdf * * * gdi -PagePosition_no_any.pdf * * * gdi -PagePosition_rest.pdf * * * gdi -PagePosition_rest_any.pdf * * * gdi -Test_DateField_locale_en_CA.pdf * * * gdi -Test_DateField_locale_en_GB.pdf * * * gdi -Test_DateField_locale_en_US.pdf * * * gdi -Test_DateField_locale_fr_CA.pdf * * * gdi -Test_DateField_locale_fr_FR.pdf * * * gdi -Test_DateField_locale_nl_NL.pdf * * * gdi -Test_DateField_locale_zh_CN.pdf * * * gdi -Test_DateField_locale_zh_HK.pdf * * * gdi -Test_Drop_downList.pdf * * * gdi -Test_NumericField.pdf * * * gdi -Test_PasswordField.pdf * * * gdi -Test_RadioButton.pdf * * * gdi -Test_ResetButton.pdf * * * gdi -Test_TextField.pdf * * * gdi -TimeField.pdf * * * gdi -action_execute_a_menu_item.pdf * * * gdi -action_hide_show_form.pdf * * * gdi -action_on_focus.pdf * * * gdi -action_reset.pdf * * * gdi -action_run_javascript.pdf * * * gdi -action_submit_a_form.pdf * * * gdi -all_trigger_browsefordoc.pdf * * * gdi -all_trigger_newdoc.pdf * * * gdi -all_trigger_run_js_lunchurl.pdf * * * gdi -all_trigger_run_js_maildoc.pdf * * * gdi -annotation_highlight_author_content.pdf * * * gdi -annotation_highlight_long_content.pdf * * * gdi -annotation_highlight_no_author.pdf * * * gdi -app_launchurl.pdf * * * gdi -bi.pdf * * * gdi -bug_434.pdf * * * gdi -bug_440132.pdf * * * gdi -calculate_average.pdf * * * gdi -calculate_sum_a_b_c.pdf * * * gdi -calculate_validate.pdf * * * gdi -check_box_no_color.pdf * * * gdi -combo_box_format.pdf * * * gdi -date.pdf * * * gdi -event.change.pdf * * * gdi -event.changeex.pdf * * * gdi -event.keydown.pdf * * * gdi -event.keydown_1_.pdf * * * gdi -event.shift.pdf * * * gdi -event.type_name.pdf * * * gdi -event.value.pdf * * * gdi -event_change.pdf * * * gdi -event_fieldfull.pdf * * * gdi -event_fieldfull_1_.pdf * * * gdi -example_014.pdf * * * gdi -example_054.pdf * * * gdi -form_button0.pdf * * * gdi -form_button1.pdf * * * gdi -form_button10.pdf * * * gdi -form_button2.pdf * * * gdi -form_button3.pdf * * * gdi -form_button4.pdf * * * gdi -form_button5.pdf * * * gdi -form_button7.pdf * * * gdi -form_button8.pdf * * * gdi -form_button9.pdf * * * gdi -form_checkbox.pdf * * * gdi -form_checkbox1.pdf * * * gdi -form_checkbox2.pdf * * * gdi -form_checkbox3.pdf * * * gdi -form_combobox_date.pdf * * * gdi -form_combobox_date2.pdf * * * gdi -form_combobox_num.pdf * * * gdi -form_combobox_per.pdf * * * gdi -form_combobox_plus.pdf * * * gdi -form_combobox_product.pdf * * * gdi -form_combobox_time.pdf * * * gdi -form_radio.pdf * * * gdi -form_same.pdf * * * gdi -form_textfield_focused_ltr.in * * * gdi -form_textfield_focused_rtl.in * * * gdi -form_textfield_selected_ltr.in * * * gdi -form_textfield_selected_rtl.in * * * gdi -format_alert_box.pdf * * * gdi -format_combo_box.pdf * * * gdi -format_custom_format.pdf * * * gdi -format_date.pdf * * * gdi -format_number.pdf * * * gdi -format_percentage.pdf * * * gdi -format_special.pdf * * * gdi -format_text_color.pdf * * * gdi -getarray.pdf * * * gdi -js_value_chack_box.pdf * * * gdi -negative.pdf * * * gdi -new_certify1.pdf * * * gdi -new_signature1.pdf * * * gdi -new_signature2.pdf * * * gdi -none.pdf * * * gdi -open_a_weblink.pdf * * * gdi -percentage.pdf * * * gdi -rotate.pdf * * * gdi -show_1.pdf * * * gdi -simplified_field_notation.pdf * * * gdi -special.pdf * * * gdi -submit_form.pdf * * * gdi -text_field_font_input_decimal_point.pdf * * * gdi -text_field_multiline_line_spacing.pdf * * * gdi -time.pdf * * * gdi -validate_alert_box.pdf * * * gdi -widget_javascript.pdf * * * gdi +Date_FormCale.pdf * * * gdi * diff +FRC_3.5_P__3648_Password_1.pdf * * * gdi * diff +FRC_3.5_P__3648_Password_1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.1_Annotations_AP_N_R.D_.pdf * * * gdi * diff +FRC_8.4.1_Annotations_AP_N_R.D_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.1_Annotations_AS_Off_.pdf * * * gdi * diff +FRC_8.4.1_Annotations_AS_Off_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.1_Annotations_AS_Yes_.pdf * * * gdi * diff +FRC_8.4.1_Annotations_AS_Yes_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Bl_Hide.pdf * * * gdi * diff +FRC_8.5_Bl_Hide.pdf * * * * fontations diff +FRC_8.5_E_GoTo_D.pdf * * * gdi * diff +FRC_8.5_E_GoTo_D.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Fo_URI_Base.pdf * * * gdi * diff +FRC_8.5_Fo_URI_Base.pdf * * * * fontations diff +FRC_8.5_U_GoToR_NewWindow.pdf * * * gdi * diff +FRC_8.5_U_GoToR_NewWindow.pdf * * * * fontations diff +FRC_8.5_U_GoToR_NewWindow_2.pdf * * * gdi * diff +FRC_8.5_U_GoToR_NewWindow_2.pdf * * * * fontations diff +FRC_8.5_Widget_C.pdf * * * gdi * diff +FRC_8.5_Widget_C.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Widget_F.pdf * * * gdi * diff +FRC_8.5_Widget_K.pdf * * * gdi * diff +FRC_8.5_Widget_K.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Widget_V.pdf * * * gdi * diff +FRC_8.5_Widget_V.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_X_GoToR_D.pdf * * * gdi * diff +FRC_8.5_X_GoToR_D.pdf * * * * fontations fuzzy=10,0.1,15 +Line_Stroke.pdf * * * gdi * diff +Oneof1.pdf * * * gdi * diff +Oneof2.pdf * * * gdi * diff +PagePosition_any_rest.pdf * * * gdi * diff +PagePosition_any_rest.pdf * * * * fontations fuzzy=10,0.1,15 +PagePosition_first.pdf * * * gdi * diff +PagePosition_first.pdf * * * * fontations diff +PagePosition_last.pdf * * * gdi * diff +PagePosition_last.pdf * * * * fontations diff +PagePosition_no_any.pdf * * * gdi * diff +PagePosition_no_any.pdf * * * * fontations fuzzy=10,0.1,15 +PagePosition_rest.pdf * * * gdi * diff +PagePosition_rest.pdf * * * * fontations fuzzy=10,0.1,15 +PagePosition_rest_any.pdf * * * gdi * diff +PagePosition_rest_any.pdf * * * * fontations fuzzy=10,0.1,15 +Test_DateField_locale_en_CA.pdf * * * gdi * diff +Test_DateField_locale_en_CA.pdf * * * * fontations fuzzy=10,0.1,15 +Test_DateField_locale_en_GB.pdf * * * gdi * diff +Test_DateField_locale_en_GB.pdf * * * * fontations fuzzy=10,0.1,15 +Test_DateField_locale_en_US.pdf * * * gdi * diff +Test_DateField_locale_en_US.pdf * * * * fontations fuzzy=10,0.1,15 +Test_DateField_locale_fr_CA.pdf * * * gdi * diff +Test_DateField_locale_fr_CA.pdf * * * * fontations fuzzy=10,0.1,15 +Test_DateField_locale_fr_FR.pdf * * * gdi * diff +Test_DateField_locale_fr_FR.pdf * * * * fontations fuzzy=10,0.1,15 +Test_DateField_locale_nl_NL.pdf * * * gdi * diff +Test_DateField_locale_nl_NL.pdf * * * * fontations fuzzy=10,0.1,15 +Test_DateField_locale_zh_CN.pdf * * * gdi * diff +Test_DateField_locale_zh_CN.pdf * * * * fontations fuzzy=10,0.1,15 +Test_DateField_locale_zh_HK.pdf * * * gdi * diff +Test_Drop_downList.pdf * * * gdi * diff +Test_Drop_downList.pdf * * * * fontations fuzzy=10,0.1,15 +Test_NumericField.pdf * * * gdi * diff +Test_NumericField.pdf * * * * fontations fuzzy=10,0.1,15 +Test_PasswordField.pdf * * * gdi * diff +Test_PasswordField.pdf * * * * fontations fuzzy=10,0.1,15 +Test_RadioButton.pdf * * * gdi * diff +Test_RadioButton.pdf * * * * fontations fuzzy=10,0.1,15 +Test_ResetButton.pdf * * * gdi * diff +Test_ResetButton.pdf * * * * fontations fuzzy=10,0.1,15 +Test_TextField.pdf * * * gdi * diff +TimeField.pdf * * * gdi * diff +action_execute_a_menu_item.pdf * * * gdi * diff +action_hide_show_form.pdf * * * gdi * diff +action_on_focus.pdf * * * gdi * diff +action_reset.pdf * * * gdi * diff +action_run_javascript.pdf * * * gdi * diff +action_submit_a_form.pdf * * * gdi * diff +all_trigger_browsefordoc.pdf * * * gdi * diff +all_trigger_browsefordoc.pdf linux,win * * * fontations diff +all_trigger_browsefordoc.pdf * * * * * fuzzy +all_trigger_browsefordoc.pdf mac * * skia * fuzzy +all_trigger_newdoc.pdf * * * gdi * diff +all_trigger_newdoc.pdf * * * * fontations fuzzy=10,0.1,15 +all_trigger_newdoc.pdf mac * * skia * fuzzy +all_trigger_run_js_lunchurl.pdf * * * gdi * diff +all_trigger_run_js_maildoc.pdf * * * gdi * diff +annotation_highlight_author_content.pdf * * * gdi * diff +annotation_highlight_long_content.pdf * * * gdi * diff +annotation_highlight_no_author.pdf * * * gdi * diff +app_launchurl.pdf * * * gdi * diff +bi.pdf * * * gdi * diff +bi.pdf mac * * skia * fuzzy +bug_434.pdf * * * gdi * diff +bug_440132.pdf * * * gdi * diff +calculate_average.pdf * * * gdi * diff +calculate_sum_a_b_c.pdf * * * gdi * diff +calculate_validate.pdf * * * gdi * diff +check_box_no_color.pdf * * * gdi * diff +combo_box_format.pdf * * * gdi * diff +date.pdf * * * gdi * diff +event.change.pdf * * * gdi * diff +event.changeex.pdf * * * gdi * diff +event.keydown.pdf * * * gdi * diff +event.keydown_1_.pdf * * * gdi * diff +event.shift.pdf * * * gdi * diff +event.type_name.pdf * * * gdi * diff +event.value.pdf * * * gdi * diff +event_change.pdf * * * gdi * diff +event_fieldfull.pdf * * * gdi * diff +event_fieldfull_1_.pdf * * * gdi * diff +example_014.pdf * * * gdi * diff +example_054.pdf * * * gdi * diff +form_button0.pdf * * * gdi * diff +form_button0.pdf mac * * skia * fuzzy +form_button1.pdf * * * gdi * diff +form_button1.pdf mac * * skia * fuzzy +form_button10.pdf * * * gdi * diff +form_button10.pdf mac * * skia * fuzzy +form_button2.pdf * * * gdi * diff +form_button2.pdf mac * * skia * fuzzy +form_button3.pdf * * * gdi * diff +form_button3.pdf mac * * skia * fuzzy +form_button4.pdf * * * gdi * diff +form_button4.pdf mac * * skia * fuzzy +form_button5.pdf * * * gdi * diff +form_button5.pdf mac * * skia * fuzzy +form_button7.pdf * * * gdi * diff +form_button7.pdf mac * * skia * fuzzy +form_button8.pdf * * * gdi * diff +form_button8.pdf mac * * skia * fuzzy +form_button9.pdf * * * gdi * diff +form_button9.pdf mac * * skia * fuzzy +form_checkbox.pdf * * * gdi * diff +form_checkbox1.pdf * * * gdi * diff +form_checkbox2.pdf * * * gdi * diff +form_checkbox3.pdf * * * gdi * diff +form_combobox_date.pdf * * * gdi * diff +form_combobox_date2.pdf * * * gdi * diff +form_combobox_num.pdf * * * gdi * diff +form_combobox_per.pdf * * * gdi * diff +form_combobox_plus.pdf * * * gdi * diff +form_combobox_product.pdf * * * gdi * diff +form_combobox_time.pdf * * * gdi * diff +form_radio.pdf * * * gdi * diff +form_same.pdf * * * gdi * diff +form_textfield_focused_ltr.in * * * gdi * diff +form_textfield_focused_ltr.in * * * * fontations fuzzy=10,0.1,15 +form_textfield_focused_rtl.in * * * gdi * diff +form_textfield_focused_rtl.in * * * * fontations fuzzy=10,0.1,15 +form_textfield_selected_ltr.in * * * gdi * diff +form_textfield_selected_rtl.in * * * gdi * diff +format_alert_box.pdf * * * gdi * diff +format_combo_box.pdf * * * gdi * diff +format_custom_format.pdf * * * gdi * diff +format_date.pdf * * * gdi * diff +format_number.pdf * * * gdi * diff +format_percentage.pdf * * * gdi * diff +format_special.pdf * * * gdi * diff +format_text_color.pdf * * * gdi * diff +getarray.pdf * * * gdi * diff +js_value_chack_box.pdf * * * gdi * diff +negative.pdf * * * gdi * diff +new_certify1.pdf * * * gdi * diff +new_signature1.pdf * * * gdi * diff +new_signature2.pdf * * * gdi * diff +none.pdf * * * gdi * diff +open_a_weblink.pdf * * * gdi * diff +percentage.pdf * * * gdi * diff +rotate.pdf * * * gdi * diff +show_1.pdf * * * gdi * diff +simplified_field_notation.pdf * * * gdi * diff +special.pdf * * * gdi * diff +submit_form.pdf * * * gdi * diff +text_field_font_input_decimal_point.pdf * * * gdi * diff +text_field_multiline_line_spacing.pdf * * * gdi * diff +time.pdf * * * gdi * diff +validate_alert_box.pdf * * * gdi * diff +widget_javascript.pdf * * * gdi * diff # TODO(crbug.com/42271068): Remove after associated bug is fixed -1_10_watermark.pdf * * * gdi -1_matrix.pdf * * * gdi -2_color_tiling.pdf * * * gdi -2_shading_type_6_00.pdf * * * gdi -2_shading_type_6_001.pdf * * * gdi -2_uncolor_tiling.pdf * * * gdi -FRC_11_8.2.4_View_edit.pdf * * * gdi -FRC_12_8.2.4_View_remove_all.pdf * * * gdi -FRC_13_8.2.4_View_remove_value.pdf * * * gdi -FRC_14_8.2.4_Sort_remove_all.pdf * * * gdi -FRC_15_8.2.4_Sort_remove_value.pdf * * * gdi -FRC_1_8.2.4_Type_8.6_.pdf * * * gdi -FRC_2_8.2.4_Type_8.6__remove_value.pdf * * * gdi -FRC_3.5_AuthEvent_EFOpen.pdf * * * gdi -FRC_3.5_CFM_AESV2__EncryptMetadata_F.pdf * * * gdi -FRC_3.5_CF_EFF_StdCF_Strf_Stmf_Identity.pdf * * * gdi -FRC_3.5_CF_Strf_stmf_StdCF.pdf * * * gdi -FRC_3.5_EncryptMetadata_None.pdf * * * gdi -FRC_3.5_V_4_CFM_V2_.pdf * * * gdi -FRC_3.5_V_5_CFM_AESV3.pdf * * * gdi -FRC_3.5_v_1_length_40_Filter_standard.pdf * * * gdi -FRC_3.5_v_2_length_128_AuthEvent_DocOpen_.pdf * * * gdi -FRC_3_8.2.4_Type_8.6__edit_.pdf * * * gdi -FRC_4.5.5_Pattern_shading.pdf * * * gdi -FRC_4.5.5_Pattern_tiling.pdf * * * gdi -FRC_4_8.2.4_Schema_8.6__remove_all.pdf * * * gdi -FRC_5_8.2.4_Schema_8.6__remove_value.pdf * * * gdi -FRC_6_8.2.4_Schema_8.6__remove_obj.pdf * * * gdi -FRC_7_8.2.4_View_H.pdf * * * gdi -FRC_8_8.2.4_View_D.pdf * * * gdi -FRC_9_8.2.4_View_T.pdf * * * gdi -annotation_circle_fill_opacity.pdf * * * gdi -annotation_square_fill_opacity.pdf * * * gdi -annotation_square_fill_opacity_dash.pdf * * * gdi -bug_0_length_line.pdf * * * gdi -bug_883026.pdf * * * gdi -clipping_text.pdf * * * gdi -en_fqa.pdf * * * gdi -en_introduce.pdf * * * gdi -en_system.pdf * * * gdi -example_012.pdf * * * gdi -gradient_many_stops.pdf * * * gdi -group_xobject.pdf * * * gdi -image_gif.pdf * * * gdi -image_ico.pdf * * * gdi -new_pdfsign1.pdf * * * gdi -new_pdfsign2.pdf * * * gdi -new_pdfsign3.pdf * * * gdi -new_pdfsign4.pdf * * * gdi -new_stamp3.pdf * * * gdi -path_10_jd.pdf * * * gdi -path_5_pattern.pdf * * * gdi -path_7.pdf * * * gdi -path_9.pdf * * * gdi -transformation.pdf * * * gdi -transparent1.pdf * * * gdi -xfermodes.pdf * * * gdi -xfermodes2.pdf * * * gdi -xfermodes3.pdf * * * gdi +1_10_watermark.pdf * * * gdi_skia * diff +1_10_watermark.pdf * * * * fontations fuzzy=10,0.1,15 +1_10_watermark.pdf * * * skia * fuzzy +1_matrix.pdf * * * gdi * diff +2_color_tiling.pdf * * * gdi * diff +2_shading_type_6_00.pdf * * * gdi_skia * diff +2_shading_type_6_001.pdf * * * gdi_skia * diff +2_uncolor_tiling.pdf * * * gdi * diff +FRC_11_8.2.4_View_edit.pdf * * * gdi * diff +FRC_11_8.2.4_View_edit.pdf * * * * fontations diff +FRC_11_8.2.4_View_edit.pdf * * * * * fuzzy +FRC_11_8.2.4_View_edit.pdf mac * * skia * fuzzy +FRC_12_8.2.4_View_remove_all.pdf * * * gdi * diff +FRC_12_8.2.4_View_remove_all.pdf * * * * fontations diff +FRC_12_8.2.4_View_remove_all.pdf * * * * * fuzzy +FRC_12_8.2.4_View_remove_all.pdf mac * * skia * fuzzy +FRC_13_8.2.4_View_remove_value.pdf * * * gdi * diff +FRC_13_8.2.4_View_remove_value.pdf * * * * fontations diff +FRC_13_8.2.4_View_remove_value.pdf * * * * * fuzzy +FRC_13_8.2.4_View_remove_value.pdf mac * * skia * fuzzy +FRC_14_8.2.4_Sort_remove_all.pdf * * * gdi * diff +FRC_14_8.2.4_Sort_remove_all.pdf * * * * fontations diff +FRC_14_8.2.4_Sort_remove_all.pdf * * * * * fuzzy +FRC_14_8.2.4_Sort_remove_all.pdf mac * * skia * fuzzy +FRC_15_8.2.4_Sort_remove_value.pdf * * * gdi * diff +FRC_15_8.2.4_Sort_remove_value.pdf * * * * fontations diff +FRC_15_8.2.4_Sort_remove_value.pdf * * * * * fuzzy +FRC_15_8.2.4_Sort_remove_value.pdf mac * * skia * fuzzy +FRC_1_8.2.4_Type_8.6_.pdf * * * gdi * diff +FRC_1_8.2.4_Type_8.6_.pdf * * * * fontations diff +FRC_1_8.2.4_Type_8.6_.pdf * * * * * fuzzy +FRC_1_8.2.4_Type_8.6_.pdf mac * * skia * fuzzy +FRC_2_8.2.4_Type_8.6__remove_value.pdf * * * gdi * diff +FRC_2_8.2.4_Type_8.6__remove_value.pdf * * * * fontations diff +FRC_2_8.2.4_Type_8.6__remove_value.pdf * * * * * fuzzy +FRC_2_8.2.4_Type_8.6__remove_value.pdf mac * * skia * fuzzy +FRC_3.5_AuthEvent_EFOpen.pdf * * * gdi * diff +FRC_3.5_AuthEvent_EFOpen.pdf * * * * fontations diff +FRC_3.5_AuthEvent_EFOpen.pdf mac * * skia * fuzzy +FRC_3.5_CFM_AESV2__EncryptMetadata_F.pdf * * * gdi * diff +FRC_3.5_CFM_AESV2__EncryptMetadata_F.pdf * * * * fontations diff +FRC_3.5_CFM_AESV2__EncryptMetadata_F.pdf mac * * skia * fuzzy +FRC_3.5_CF_EFF_StdCF_Strf_Stmf_Identity.pdf * * * gdi * diff +FRC_3.5_CF_EFF_StdCF_Strf_Stmf_Identity.pdf * * * * fontations diff +FRC_3.5_CF_EFF_StdCF_Strf_Stmf_Identity.pdf mac * * skia * fuzzy +FRC_3.5_CF_Strf_stmf_StdCF.pdf * * * gdi * diff +FRC_3.5_CF_Strf_stmf_StdCF.pdf * * * * fontations diff +FRC_3.5_CF_Strf_stmf_StdCF.pdf mac * * skia * fuzzy +FRC_3.5_EncryptMetadata_None.pdf * * * gdi * diff +FRC_3.5_EncryptMetadata_None.pdf * * * * fontations diff +FRC_3.5_EncryptMetadata_None.pdf mac * * skia * fuzzy +FRC_3.5_V_4_CFM_V2_.pdf * * * gdi * diff +FRC_3.5_V_4_CFM_V2_.pdf * * * * fontations diff +FRC_3.5_V_4_CFM_V2_.pdf mac * * skia * fuzzy +FRC_3.5_V_5_CFM_AESV3.pdf * * * gdi * diff +FRC_3.5_V_5_CFM_AESV3.pdf * * * * fontations diff +FRC_3.5_V_5_CFM_AESV3.pdf mac * * skia * fuzzy +FRC_3.5_v_1_length_40_Filter_standard.pdf * * * gdi * diff +FRC_3.5_v_1_length_40_Filter_standard.pdf * * * * fontations diff +FRC_3.5_v_1_length_40_Filter_standard.pdf mac * * skia * fuzzy +FRC_3.5_v_2_length_128_AuthEvent_DocOpen_.pdf * * * gdi * diff +FRC_3.5_v_2_length_128_AuthEvent_DocOpen_.pdf * * * * fontations diff +FRC_3.5_v_2_length_128_AuthEvent_DocOpen_.pdf mac * * skia * fuzzy +FRC_3_8.2.4_Type_8.6__edit_.pdf * * * gdi * diff +FRC_3_8.2.4_Type_8.6__edit_.pdf * * * * fontations diff +FRC_3_8.2.4_Type_8.6__edit_.pdf * * * * * fuzzy +FRC_3_8.2.4_Type_8.6__edit_.pdf mac * * skia * fuzzy +FRC_4.5.5_Pattern_shading.pdf * * * gdi * diff +FRC_4.5.5_Pattern_shading.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.5_Pattern_shading.pdf mac * * skia * fuzzy +FRC_4.5.5_Pattern_tiling.pdf * * * gdi_skia * diff +FRC_4.5.5_Pattern_tiling.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4_8.2.4_Schema_8.6__remove_all.pdf * * * gdi * diff +FRC_4_8.2.4_Schema_8.6__remove_all.pdf * * * * fontations diff +FRC_4_8.2.4_Schema_8.6__remove_all.pdf * * * * * fuzzy +FRC_4_8.2.4_Schema_8.6__remove_all.pdf mac * * skia * fuzzy +FRC_5_8.2.4_Schema_8.6__remove_value.pdf * * * gdi * diff +FRC_5_8.2.4_Schema_8.6__remove_value.pdf * * * * fontations diff +FRC_5_8.2.4_Schema_8.6__remove_value.pdf * * * * * fuzzy +FRC_5_8.2.4_Schema_8.6__remove_value.pdf mac * * skia * fuzzy +FRC_6_8.2.4_Schema_8.6__remove_obj.pdf * * * gdi * diff +FRC_6_8.2.4_Schema_8.6__remove_obj.pdf * * * * fontations diff +FRC_6_8.2.4_Schema_8.6__remove_obj.pdf * * * * * fuzzy +FRC_6_8.2.4_Schema_8.6__remove_obj.pdf mac * * skia * fuzzy +FRC_7_8.2.4_View_H.pdf * * * gdi * diff +FRC_7_8.2.4_View_H.pdf * * * * fontations diff +FRC_7_8.2.4_View_H.pdf * * * * * fuzzy +FRC_7_8.2.4_View_H.pdf mac * * skia * fuzzy +FRC_8_8.2.4_View_D.pdf * * * gdi * diff +FRC_8_8.2.4_View_D.pdf * * * * fontations diff +FRC_8_8.2.4_View_D.pdf * * * * * fuzzy +FRC_8_8.2.4_View_D.pdf mac * * skia * fuzzy +FRC_9_8.2.4_View_T.pdf * * * gdi * diff +FRC_9_8.2.4_View_T.pdf * * * * fontations diff +FRC_9_8.2.4_View_T.pdf * * * * * fuzzy +FRC_9_8.2.4_View_T.pdf mac * * skia * fuzzy +annotation_circle_fill_opacity.pdf * * * gdi_skia * diff +annotation_circle_fill_opacity.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_circle_fill_opacity.pdf mac * * skia * fuzzy +annotation_square_fill_opacity.pdf * * * gdi_skia * diff +annotation_square_fill_opacity.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_square_fill_opacity.pdf mac * * skia * fuzzy +annotation_square_fill_opacity_dash.pdf * * * gdi_skia * diff +annotation_square_fill_opacity_dash.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_square_fill_opacity_dash.pdf mac * * skia * fuzzy +bug_0_length_line.pdf * * * gdi * diff +bug_883026.pdf * * * gdi_skia * diff +bug_883026.pdf * * * * fontations fuzzy=10,0.1,15 +clipping_text.pdf * * * gdi * diff +clipping_text.pdf * * * * fontations fuzzy=10,0.1,15 +en_fqa.pdf * * * gdi_skia * diff +en_introduce.pdf * * * gdi_skia * diff +en_system.pdf * * * gdi * diff +en_system.pdf mac * * skia * fuzzy +example_012.pdf * * * gdi * diff +gradient_many_stops.pdf * * * gdi * diff +group_xobject.pdf * * * gdi * diff +group_xobject.pdf * * * * fontations fuzzy=10,0.1,15 +image_gif.pdf * * * gdi_skia * diff +image_gif.pdf mac * * skia * fuzzy +image_ico.pdf * * * gdi_skia * diff +new_pdfsign1.pdf * * * gdi_skia * diff +new_pdfsign2.pdf * * * gdi_skia * diff +new_pdfsign3.pdf * * * gdi_skia * diff +new_pdfsign3.pdf * * * * * fuzzy +new_pdfsign4.pdf * * * gdi_skia * diff +new_stamp3.pdf * * * gdi_skia * diff +new_stamp3.pdf * * * * * fuzzy +new_stamp3.pdf mac * * skia * fuzzy +path_10_jd.pdf * * * gdi * diff +path_5_pattern.pdf * * * gdi * diff +path_7.pdf * * * gdi * diff +path_9.pdf * * * gdi * diff +transformation.pdf * * * gdi * diff +transparent1.pdf * * * gdi_skia * diff +xfermodes.pdf * * * gdi_skia * diff +xfermodes2.pdf * * * gdi * diff +xfermodes3.pdf * * * gdi_skia * diff # # JavaScript tests # -bug_679642.in * * noxfa * -bug_679643.in * * noxfa * -bug_735912.in * * noxfa * - -# JS tests in nov8 mode expect empty results. This one will -# not be empty as the callback is not js-based. -named_action.in * nov8 * * +bug_679642.in * * noxfa * * diff +bug_735912.in * * noxfa * * diff # xfa_specific # TODO(crbug.com/42270097): Remove after associated bug is fixed -resolve_nodes_1.pdf * * * * +resolve_nodes_1.pdf * * * * * diff # # Pixel tests # # TODO(crbug.com/475874968): Remove after the bug is fixed -alpha_composite.in * * * gdi +alpha_composite.in * * * gdi * diff # TODO(crbug.com/40643646): Remove after associated bug is fixed -bug_40643646.in * * * * +bug_40643646.in * * * * * diff # TODO(crbug.com/40721524): Remove after associated bug is fixed -bug_40721524.in * * * * +bug_40721524.in * * * * * diff # TODO(thestig): Remove after fixing inline image parsing. -bug_1236805.in * * * * +bug_1236805.in * * * * * diff # TODO(crbug.com/42270756): Remove after associated bug is fixed -bug_1258634.in * * * * +bug_1258634.in * * * * * diff # TODO(crbug.com/42270333): Remove after associated bug is fixed -bug_42270333.in * * * * +bug_42270333.in * * * * * diff # TODO(crbug.com/40860237): Remove after associated bug is fixed -bug_40860237.in mac * * agg +bug_40860237.in mac * * agg * diff # This test is intentionally suppressed on Windows. Normally, Windows provides # the Symbol font, so this PDF would render correctly. However, in the hermetic # test environment, there is no Symbol font and the rendering is incorrect. -bug_1442723.in win * * * +bug_1442723.in win * * * * diff # TODO(crbug.com/42270459): Remove after associated bug is fixed -bug_42270459_2.in * * * * +bug_42270459_2.in * * * * * diff # TODO(crbug.com/42270524): Remove after associated bug is fixed -bug_42270524.in * * * * +bug_42270524.in * * * * * diff # TODO(crbug.com/42270577): Remove after associated bug is fixed -bug_42270577.in * * * * +bug_42270577.in * * * * * diff # TODO(crbug.com/42270732): Remove after associated bug is fixed -bug_42270732.in * * * * +bug_42270732.in * * * * * diff # TODO(crbug.com/42270979): Remove after associated bug is fixed -bug_42270979_1.in * * * agg -bug_42270979_2.in * * * agg -bug_42270979_3.in * * * agg +bug_42270979_1.in * * * agg * diff +bug_42270979_2.in * * * agg * diff +bug_42270979_3.in * * * agg * diff # TODO(crbug.com/42270980): Remove after associated bug is fixed -bug_42270980.in * * * * +bug_42270980.in * * * * * diff # TODO(crbug.com/42271010): Remove after associated bug is fixed -bug_42271010.pdf * * * * +bug_42271010.pdf * * * * * diff # TODO(crbug.com/41010502): Remove after associated bug is fixed -bug_41010502_1.in * * * * +bug_41010502_1.in * * * * * diff # TODO(crbug.com/346766787): Remove after associated bug is fixed -bug_346766787.in * * * * +bug_346766787.in * * * * * diff # TODO(crbug.com/394767650): Remove after associated bug is fixed -bug_394767650.in * * * * +bug_394767650.in * * * * * diff # TODO(crbug.com/41153500): Remove after associated bug is fixed -bug_41153500.in * * * agg +bug_41153500.in * * * agg * diff # TODO(crbug.com/42271472): Remove after associated bug is fixed -bug_42271472.in * nov8 * * +bug_42271472.in * nov8 * * * diff # TODO(crbug.com/40040570, crbug.com/40040569): Remove after associated bug is # fixed -bug_40040570.in * * * skia +bug_40040570.in * * * skia * diff # TODO(crbug.com/41470360): Remove after associated bug is fixed -bug_41470360.in * * * agg +bug_41470360.in * * * agg * diff # TODO(crbug.com/42270756): Remove after associated bug is fixed -jpxdecode.in * * * * -jpxdecode_without_bitspercomponent.in * * * * -jpxdecode_without_colorspace.in * * * * +jpxdecode.in * * * * * diff +jpxdecode_without_bitspercomponent.in * * * * * diff +jpxdecode_without_colorspace.in * * * * * diff # TODO(crbug.com/40661120): Remove after associated bug is fixed -reset_button.in * * * * +reset_button.in * * * * * diff # TODO(crbug.com/479849565): Remove after associated bug is fixed -bug_479849565.in * * * agg +bug_479849565.in * * * agg * diff # xfa_specific # TODO(crbug.com/42270088): Remove after associated bug is fixed -bug_997412.in win * * * +bug_997412.in win * * * * diff # TODO(crbug.com/42270098): Remove after associated bug is fixed -standard_symbols.pdf * * * * +standard_symbols.pdf * * * * * diff # TODO(crbug.com/42270088): Remove after associated bug is fixed -xfa_example.in win * * * +xfa_example.in win * * * * diff # TODO(crbug.com/42270088): Remove after associated bug is fixed -xfa_textfield.in win * * * +xfa_textfield.in win * * * * diff +xfa_textfield.in mac * * skia * fuzzy # GDI # TODO(crbug.com/42271066): Remove after associated bug is fixed -bug_1258968.in * * * gdi -bug_1383708.in * * * gdi +bug_1258968.in * * * gdi * diff +bug_1258968.in * * * skia * fuzzy +bug_1383708.in * * * gdi * diff +bug_1383708.in mac * * skia * fuzzy # TODO(crbug.com/42271067): Remove after associated bug is fixed -barcode_test.pdf * * * gdi -bug_1072440.in * * * gdi -bug_113910.in * * * gdi -bug_1282.in * * * gdi -bug_1304714.in * * * gdi -bug_1337.in * * * gdi -bug_1372651.in * * * gdi -bug_477200528.in * * * gdi -bug_725389.in * * * gdi -bug_733528.in * * * gdi -bug_736695_1.in * * * gdi -bug_736695_2.in * * * gdi -bug_736695_3.in * * * gdi -bug_736695_4.in * * * gdi -bug_983137.in * * * gdi -checkbox_radiobutton.in * * * gdi -checkbox_radiobutton_hide.in * * * gdi -checkbox_radiobutton_reset.in * * * gdi -combobox_form.in * * * gdi -dynamic_list_box_allow_multiple_selection.pdf * * * gdi -dynamic_password_field_background_fill.pdf * * * gdi -dynamic_table_color_and_width.pdf * * * gdi -password.in * * * gdi -resolve_nodes_0.pdf * * * gdi -scrollable_widgets1.in * * * gdi -scrollable_widgets2.in * * * gdi -static_list_box_caption.pdf * * * gdi -static_password_field_rotate.pdf * * * gdi -text_form_custom_font.in * * * gdi -text_form_multiline.in * * * gdi -xfa_bmp_image.in * * * gdi -xfa_gif_image.in * * * gdi -xfa_jpg_image.in * * * gdi -xfa_node_caption.pdf * * * gdi -xfa_png_image.in * * * gdi -xfa_rectangle_node.in * * * gdi -xfa_tiff_image.in * * * gdi -xfa_tiff_lzw_image.in * * * gdi -xfa_tiff_packbits_image.in * * * gdi +barcode_test.pdf * * * gdi * diff +bug_1026918.in * * * gdi * diff +bug_1072440.in * * * gdi * diff +bug_113910.in * * * gdi * diff +bug_1282.in * * * gdi * diff +bug_1286970.in * * * gdi * diff +bug_1304714.in * * * gdi * diff +bug_1337.in * * * gdi * diff +bug_1372651.in * * * gdi * diff +bug_1372651.in * * * * fontations fuzzy=10,0.1,15 +bug_477200528.in * * * gdi * diff +bug_477200528.in * * * * fontations fuzzy=10,0.1,15 +bug_725389.in * * * gdi * diff +bug_725389.in * * * * fontations fuzzy=10,0.1,15 +bug_733528.in * * * gdi * diff +bug_733528.in * * * * fontations fuzzy=10,0.1,15 +bug_736695_1.in * * * gdi * diff +bug_736695_2.in * * * gdi * diff +bug_736695_2.in * * * * fontations fuzzy=10,0.1,15 +bug_736695_3.in * * * gdi * diff +bug_736695_4.in * * * gdi * diff +bug_983137.in * * * gdi * diff +checkbox_radiobutton.in * * * gdi * diff +checkbox_radiobutton_hide.in * * * gdi * diff +checkbox_radiobutton_reset.in * * * gdi * diff +combobox_form.in * * * gdi * diff +dynamic_list_box_allow_multiple_selection.pdf * * * gdi * diff +dynamic_password_field_background_fill.pdf * * * gdi * diff +dynamic_password_field_background_fill.pdf mac * * skia * fuzzy +dynamic_table_color_and_width.pdf * * * gdi * diff +password.in * * * gdi * diff +resolve_nodes_0.pdf * * * gdi * diff +resolve_nodes_0.pdf * * * * * fuzzy +scrollable_widgets1.in * * * gdi * diff +scrollable_widgets2.in * * * gdi * diff +scrollable_widgets2.in * * * * fontations fuzzy=10,0.1,15 +static_list_box_caption.pdf * * * gdi * diff +static_password_field_rotate.pdf * * * gdi * diff +text_form_custom_font.in * * * gdi * diff +text_form_multiline.in * * * gdi * diff +text_form_multiline.in * * * * fontations fuzzy=10,0.1,15 +xfa_bmp_image.in * * * gdi * diff +xfa_gif_image.in * * * gdi * diff +xfa_jpg_image.in * * * gdi * diff +xfa_node_caption.pdf * * * gdi * diff +xfa_node_caption.pdf * * * * fontations fuzzy=10,0.1,15 +xfa_node_caption.pdf mac * * skia * fuzzy +xfa_png_image.in * * * gdi * diff +xfa_rectangle_node.in * * * gdi * diff +xfa_tiff_image.in * * * gdi * diff +xfa_tiff_lzw_image.in * * * gdi * diff +xfa_tiff_packbits_image.in * * * gdi * diff # TODO(crbug.com/42271068): Remove after associated bug is fixed -bug_1015233.in * * * gdi -bug_1099446.in * * * gdi -bug_1161.in * * * gdi -bug_1236.in * * * gdi -bug_1288_1.in * * * gdi -bug_1330.in * * * gdi -bug_1395648.in * * * gdi -bug_1396266.in * * * gdi -bug_1430333.in * * * gdi -bug_1822.in * * * gdi -bug_1847.in * * * gdi -bug_1949.in * * * gdi -bug_1995.in * * * gdi -bug_41153500.in * * * gdi -bug_42270979_1.in * * * gdi -bug_42270979_3.in * * * gdi -bug_632.in * * * gdi -bug_660850.in * * * gdi -long_dashed_line.in * * * gdi -matte.in * * * gdi +bug_1015233.in * * * gdi * diff +bug_1099446.in * * * gdi * diff +bug_1161.in * * * gdi * diff +bug_1236.in * * * gdi_skia * diff +bug_1288_1.in * * * gdi_skia * diff +bug_1330.in * * * gdi * diff +bug_1395648.in * * * gdi_skia * diff +bug_1396266.in * * * gdi_skia * diff +bug_1430333.in * * * gdi_skia * diff +bug_1822.in * * * gdi * diff +bug_1847.in * * * gdi_skia * diff +bug_1949.in * * * gdi_skia * diff +bug_1995.in * * * gdi_skia * diff +bug_41153500.in * * * gdi * diff +bug_42270979_1.in * * * gdi * diff +bug_42270979_3.in * * * gdi * diff +bug_632.in * * * gdi * diff +bug_632.in * * * * fontations fuzzy=10,0.1,15 +bug_660850.in * * * gdi * diff +long_dashed_line.in * * * gdi * diff +matte.in * * * gdi_skia * diff # TODO(crbug.com/361002664): Remove after associated bug is fixed -smask_blend.in * * * gdi +smask_blend.in * * * gdi * diff # TODO(crbug.com/459586268): Remove this entry once macOS support for Intel # hardware goes away. The test expectation is just different enough from the ARM # rendering that fuzzy matching is not enough for it to match. The difference # looks like it may be due to several rounding errors combining for a bigger # than normal pixel value difference. -bug_1430333.in mac_x86 * * skia -bug_1746.in mac_x86 * * skia +bug_1430333.in mac_x86 * * skia * diff +bug_1746.in mac_x86 * * skia * diff +# +# Merged from SUPPRESSIONS_IMAGE_DIFF and SUPPRESSIONS_EXACT_MATCHING; these +# name no test that appears above. +# +2_9_stamp2.pdf mac * * skia * fuzzy +2_9_stamp2.pdf * * * * fontations fuzzy=10,0.1,15 +2_halftone.pdf mac * * skia * fuzzy +3_image_imagemask.pdf mac * * skia * fuzzy +FRC_3.5_CF_Strf_stmf_DefaultCryptFilter.pdf * * * * * blank +FRC_3.5_EncryptMetadata_T.pdf * * * * * blank +FRC_3.5_Encrypt_is_damage.pdf * * * * * blank +FRC_3.5_Filter_PubSec_SubFilter_s5.pdf * * * * * blank +FRC_3.5_Filter_PubSec_Sub_SubFilter_s4.pdf * * * * * blank +FRC_8.4.1_Annotations_Border.pdf mac * * skia * fuzzy +FRC_8.4.1_Annotations_Border.pdf * * * * fontations fuzzy=10,0.1,15 +MouseEvents.pdf * * * * * blank +Oneof.pdf * * * * * blank +annotation_circle.pdf mac * * skia * fuzzy +annotation_circle.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_circle_dash.pdf mac * * skia * fuzzy +annotation_circle_dash.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_ellipse.pdf mac * * skia * fuzzy +annotation_ellipse.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_ellipse_fill.pdf mac * * skia * fuzzy +annotation_ellipse_fill.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_ellipse_fill_dash.pdf mac * * skia * fuzzy +annotation_ellipse_fill_dash.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_freetext.pdf mac * * skia * fuzzy +annotation_freetext.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_ink.pdf mac * * skia * fuzzy +annotation_ink.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_ink_dash.pdf mac * * skia * fuzzy +annotation_ink_dash.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_ink_dot.pdf mac * * skia * fuzzy +annotation_ink_dot.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_ink_multiple.pdf mac * * skia * fuzzy +annotation_ink_multiple.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_square.pdf mac * * skia * fuzzy +annotation_square.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_square_dash.pdf mac * * skia * fuzzy +annotation_square_dash.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_squiggly.pdf mac * * skia * fuzzy +annotation_squiggly.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_squiggly_opacity.pdf mac * * skia * fuzzy +annotation_squiggly_opacity.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_strikeout.pdf mac * * skia * fuzzy +annotation_strikeout.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_text.pdf mac * * skia * fuzzy +annotation_text.pdf * * * * fontations fuzzy=10,0.1,15 +bug_651304.pdf * * * * * blank +ch_9_android.pdf mac * * skia * fuzzy +ch_9_android.pdf * * * * fontations fuzzy=10,0.1,15 +en_uicase_2_.pdf mac * * skia * fuzzy +en_uicase_2_.pdf * * * * fontations fuzzy=10,0.1,15 +image_foxit.pdf * * * * * fuzzy=10,0.35,10.0 +image_transformer_other.in mac * * skia * fuzzy +outline.pdf * * * * * blank +radial_shading_point_at_border.in mac * * skia * fuzzy +radial_shading_point_at_border_no_extend.in mac * * skia * fuzzy +shade.in mac * * skia * fuzzy +shading_2_15.pdf mac * * skia * fuzzy +shading_2_17.pdf mac * * skia * fuzzy +shading_2_4.pdf mac * * skia * fuzzy +shading_2_7.pdf mac * * skia * fuzzy +shading_2_8.pdf mac * * skia * fuzzy +shading_2_9.pdf mac * * skia * fuzzy +zh_file_structure.pdf mac * * skia * fuzzy +zh_file_structure.pdf * * * * fontations diff +zh_page.pdf mac * * skia * fuzzy +zh_page.pdf * * * * fontations fuzzy=10,0.1,15 +zh_webdav.pdf mac * * skia * fuzzy +zh_webdav.pdf * * * * fontations fuzzy=10,0.1,15 +# +# Fontations, naming no test that appears above. +# +2_color_lab.pdf * * * * fontations diff +FRC_4.5.3_DeviceCMYK_K1.pdf * * * * fontations diff +FRC_4.5.3_DeviceRGB_RG1.pdf * * * * fontations diff +FRC_8.5_Catalog_OpenAction.pdf linux,win * * * fontations diff +FRC_8.5_Page_O_URI.pdf * * * * fontations diff +bug_1021762.in * * * * fontations fuzzy +bug_601362.in * * * * fontations diff +bug_736703.in * * * * fontations diff +bug_846.in * * * * fontations diff + +# +# Fontations fuzzy matching, naming no test that appears above. +# +2_8_stamp1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_10_8.2.2_Prev_remove_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_10_8.2.4__remove_ModDate_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_11_8.2.2_Prev_remove_obj.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_11_8.2.4__remove_ModDate_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_12_8.2.2__Next_remove_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_12_8.2.4__remove_ModDate_obj_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_13_8.2.2__Next_remove_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_13_8.2.4_remove_Size_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_14_8.2.2__Next_remove_obj.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_14_8.2.4_remove_Size_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_15_8.2.2__Next_exchange.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_15_8.2.4_remove_Size_obj.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_16_8.2.2__Next_add.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_16_8.2.4__remove_CompressedSize__value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_17_8.2.2__T8.4_First_remove_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_17_8.2.4__remove_CompressedSize__all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_18_8.2.2__T8.4_First_remove_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_18_8.2.4__remove_CompressedSize__obj.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_19_8.2.2__T8.4_First_remove_obj.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_19_8.2.4__remove_CreationDate_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_1_8.2.2__Title_edit.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_1_8.2.4__original.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_20_8.2.2__T8.4_First_remove_value2.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_20_8.2.4__remove_CreationDate_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_21_8.2.2__T8.4_First_remove_all2.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_21_8.2.4__remove_CreationDate_obj.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_22_8.2.2__T8.4_First_remove_obj2.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_22_8.2.4__remove_Order_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_23_8.2.2__T8.4_Last_remove_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_23_8.2.4__remove_Order_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_24_8.2.2__T8.4_Last_remove_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_24_8.2.4__remove_Order_obj.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_25_8.2.2__T8.4_Last_remove_obj.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_26_8.2.2__T8.4_Last_remove_value2.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_27_8.2.2__T8.4_Last_remove_all2.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_28_8.2.2__T8.4_Last_remove_obj2.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_29_8.2.2__T8.4_First_Last_exchange.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_2_8.2.2__Title_remove_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_2_8.2.4__add_type_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_3.5_P_4_Password_1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_3.5_P__1852_Password_1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_3.5_P__2584_Password_1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_3.5_P__2616_Password_1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_3.5_P__2880_Password_1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_3.5_P__3376_Password_1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_3.5_P__3392_Password_1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_3.5_P__3608_Password_1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_3.5_P__3900_Password_1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_31_8.2.2_add_dest_entry.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_32_8.2.2_A_Support_action.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_33_8.2.2_A_empty.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_34_8.2.2_A_remove.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_35_8.2.2_A_remove_A_item.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_36_8.2.2_Dest.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_37_8.2.2_Dest_empty.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_38_8.2.2_Dest_remove.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_39_8.2.2_C_edit.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_3_8.2.2__Title_remove_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_3_8.2.4__add_type_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.3_DeviceCMYK_k.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.3_DeviceGray_G1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.3_DeviceGray_g.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.3_DeviceRGB_rg.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_CalGray_gamma.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_CalGray_whitepoint.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_CalRGB_gamma.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_CalRGB_matrix.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_CalRGB_whitepoint.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_ICCBased.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_Lab.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_RI_Absolute.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_RI_Perceptual.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_RI_Relative.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_RI_Saturation.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.4_Separation.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.5_DeviceN.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4.5.5_Indexed.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_40_8.2.2_C_empty1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_41_8.2.2_C_empty2.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_42_8.2.2_C_empty3.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_43_8.2.2_C_empty.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_44_8.2.2_C_remove.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_45_8.2.2_F_edit_F_1_2_3.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_46_8.2.2_F_edit_F__1__2__3.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_47_8.2.2_F_edit_F_32__32.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_48_8.2.2_F_edit_F_100__100.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_49_8.2.2_F_empty.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4_8.2.2_Parent_edit2.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_4_8.2.4__remove_FileName_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_50_8.2.2_F_remove.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_51_8.2.2_T_8.4__Count_edit_count_100.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_52_8.2.2_T_8.4__Count_edit_count_0.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_53_8.2.2_T_8.4__Count_empty.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_54_8.2.2_T_8.4__Count_empty1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_55_8.2.2_T_8.4__Count_remove.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_56_8.2.2_C_edit_C__1.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_57_8.2.2_C_edit_C_2.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_58_8.2.2_Count_edit_count__2.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_5_8.2.2_Parent_remove_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_5_8.2.4__remove_FileName_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_6_8.2.2_Parent_remove_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_6_8.2.4__remove_FileName_obj.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_7_8.2.2_Parent_remove_obj.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_7_8.2.4__remove_Description_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.1_Annotations_AP_N_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.1_Annotations_AP_N_R_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.1_Annotations_C.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.1_Annotations_M_text_string_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.1_Annotations_P.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.1_Annotations_Rect.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.2_Annotation_Flags_F_Hidden.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.2_Annotation_Flags_F_Invisible.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.2_Annotation_Flags_F_Locked.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.2_Annotation_Flags_F_LockedContents.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.2_Annotation_Flags_F_NoRotate.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.2_Annotation_Flags_F_NoView.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.2_Annotation_Flags_F_NoZoom.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.2_Annotation_Flags_F_Print.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.2_Annotation_Flags_F_ReadOnly.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.2_Annotation_Flags_F_ToggleNoView.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_D_different_values.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_D_remove.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_I_different_values.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_I_remove.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_S_different_values_BE_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_S_different_values_BS_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_S_not_exist_BS_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_S_remove_BE_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_S_remove_BS_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_Type_add.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_Type_not_exist_default_.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.4.3_Border_Stypes_W_remove.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Catalog_WC.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_DP.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_DS.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Link_URL.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Outline_A.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Page_PV_Named_Phantom.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Screen_Fo_JavaScript.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_Screen_Rendition.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_WP.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8.5_WS.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8_8.2.2_Parent_edit.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_8_8.2.4__remove__Description_all.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_9_8.2.2_Prev_remove_value.pdf * * * * fontations fuzzy=10,0.1,15 +FRC_9_8.2.4__remove__Description_obj.pdf * * * * fontations fuzzy=10,0.1,15 +annotation_highlight_no_content.pdf * * * * fontations fuzzy=10,0.1,15 +bug_113910.in * * * * fontations fuzzy=10,0.1,15 +bug_1752.in * * * * fontations fuzzy=10,0.1,15 +bug_1772.in * * * * fontations fuzzy=10,0.1,15 +bug_1922.in * * * * fontations fuzzy=10,0.1,15 +bug_432208037.in * * * * fontations fuzzy=10,0.1,15 +bug_493126_endobj_bug.pdf * * * * fontations fuzzy=10,0.1,15 +bug_493126_endobj_bug_weirdWS.pdf * * * * fontations fuzzy=10,0.1,15 +bug_524043_1.in * * * * fontations fuzzy=10,0.1,15 +bug_524043_2.in * * * * fontations fuzzy=10,0.1,15 +bug_524043_3.in * * * * fontations fuzzy=10,0.1,15 +bug_524043_4.in * * * * fontations fuzzy=10,0.1,15 +bug_524043_5.in * * * * fontations fuzzy=10,0.1,15 +bug_524043_7.in * * * * fontations fuzzy=10,0.1,15 +bug_528103.in * * * * fontations fuzzy=10,0.1,15 +bug_543018_1.in * * * * fontations fuzzy=10,0.1,15 +bug_543018_2.in * * * * fontations fuzzy=10,0.1,15 +bug_551258_1.in * * * * fontations fuzzy=10,0.1,15 +bug_665467.in * * * * fontations fuzzy=10,0.1,15 +bug_736695_3.in * * * * fontations fuzzy=10,0.1,15 +bug_820345.in * * * * fontations fuzzy=10,0.1,15 +bug_845697.in * * * * fontations fuzzy=10,0.1,15 +bug_909762.in * * * * fontations fuzzy=10,0.1,15 +bug_925736.pdf * * * * fontations fuzzy=10,0.1,15 +ch_7_10.pdf * * * * fontations fuzzy=10,0.1,15 +ch_7_11.pdf * * * * fontations fuzzy=10,0.1,15 +ch_7_12.pdf * * * * fontations fuzzy=10,0.1,15 +ch_7_14.pdf * * * * fontations fuzzy=10,0.1,15 +ch_7_15.pdf * * * * fontations fuzzy=10,0.1,15 +font_size.in * * * * fontations fuzzy=10,0.1,15 +form_textfield_selected_ltr.in * * * * fontations fuzzy=10,0.1,15 +form_textfield_selected_rtl.in * * * * fontations fuzzy=10,0.1,15 +foxittext.pdf * * * * fontations fuzzy=10,0.1,15 +freetext_annotation_with_da.in * * * * fontations fuzzy=10,0.1,15 +freetext_annotation_without_da.pdf * * * * fontations fuzzy=10,0.1,15 +generation_numbers1.pdf * * * * fontations fuzzy=10,0.1,15 +generation_numbers2.pdf * * * * fontations fuzzy=10,0.1,15 +password.in * * * * fontations fuzzy=10,0.1,15 +scrollable_widgets1.in * * * * fontations fuzzy=10,0.1,15 diff --git a/testing/SUPPRESSIONS_EXACT_MATCHING b/testing/SUPPRESSIONS_EXACT_MATCHING deleted file mode 100644 index c68d2c011c..0000000000 --- a/testing/SUPPRESSIONS_EXACT_MATCHING +++ /dev/null @@ -1,184 +0,0 @@ -# Copyright 2023 The PDFium Authors -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. -# -# List of tests to use fuzzy instead of exact matching, one per line. -# There are five space-separated columns per line -# Each column (except column 0) can contain a comma-separated list of values. -# -# Column 0: test file name -# Column 1: platform: *, win, mac, mac_arm, mac_x86, linux -# Column 2: v8 support: *, nov8, v8 -# Column 3: xfa support: *, noxfa, xfa -# Column 4: rendering support: *, agg, gdi, skia -# -# All columns on a line must match, but filenames may be repeated on subsequent -# lines to suppress more cases. Within each column, any one of the -# comma-separated values must match in order for the column to "match". -# The filenames and keywords are case-sensitive. -# One exception is the column 1, where "mac" is a shorthand that matches either -# "mac_arm" or "mac_x86". -# -# Try to keep the file alphabetized within each category of test. - -# -# Pixel tests -# - -# Sensitive to transparency code changes. -bug_1258968.in * * * skia - -# TODO(crbug.com/459586268): Remove these entries once macOS support for Intel -# hardware goes away. Then rebase the test expectations as needed. -bug_1383708.in mac * * skia -dynamic_password_field_background_fill.pdf mac * * skia -image_transformer_other.in mac * * skia -radial_shading_point_at_border.in mac * * skia -radial_shading_point_at_border_no_extend.in mac * * skia -shade.in mac * * skia -xfa_textfield.in mac * * skia - -# -# Corpus tests -# - -# Sensitive to transparency code changes. -1_10_watermark.pdf * * * skia - -# Device-specific ColorBurn differences (see pdfium:1959). -xfermodes.pdf * * * skia -xfermodes2.pdf * * * skia -xfermodes3.pdf * * * skia - -# TODO(crbug.com/459586268): Remove these entries once macOS support for Intel -# hardware goes away. Then rebase the test expectations as needed. -1.pdf mac * * skia -123.pdf mac * * skia -1_image.pdf mac * * skia -2_11_stamp3.pdf mac * * skia -2_9_stamp2.pdf mac * * skia -2_halftone.pdf mac * * skia -3_image_imagemask.pdf mac * * skia -3_interpolate_image.pdf mac * * skia -3bigpreview.pdf mac * * skia -4_3.pdf mac * * skia -4_36.pdf mac * * skia -FRC_11_8.2.4_View_edit.pdf mac * * skia -FRC_12_8.2.4_View_remove_all.pdf mac * * skia -FRC_13_8.2.4_View_remove_value.pdf mac * * skia -FRC_14_8.2.4_Sort_remove_all.pdf mac * * skia -FRC_15_8.2.4_Sort_remove_value.pdf mac * * skia -FRC_1_8.2.4_Type_8.6_.pdf mac * * skia -FRC_2_8.2.4_Type_8.6__remove_value.pdf mac * * skia -FRC_3.5_AuthEvent_EFOpen.pdf mac * * skia -FRC_3.5_CFM_AESV2__EncryptMetadata_F.pdf mac * * skia -FRC_3.5_CF_EFF_StdCF_Strf_Stmf_Identity.pdf mac * * skia -FRC_3.5_CF_Strf_stmf_StdCF.pdf mac * * skia -FRC_3.5_EncryptMetadata_None.pdf mac * * skia -FRC_3.5_V_4_CFM_V2_.pdf mac * * skia -FRC_3.5_V_5_CFM_AESV3.pdf mac * * skia -FRC_3.5_v_1_length_40_Filter_standard.pdf mac * * skia -FRC_3.5_v_2_length_128_AuthEvent_DocOpen_.pdf mac * * skia -FRC_3_8.2.4_Type_8.6__edit_.pdf mac * * skia -FRC_4.5.5_Pattern_shading.pdf mac * * skia -FRC_4_8.2.4_Schema_8.6__remove_all.pdf mac * * skia -FRC_5_8.2.4_Schema_8.6__remove_value.pdf mac * * skia -FRC_6_8.2.4_Schema_8.6__remove_obj.pdf mac * * skia -FRC_7_8.2.4_View_H.pdf mac * * skia -FRC_8.4.1_Annotations_Border.pdf mac * * skia -FRC_8.5_Screen_Img_D_Launch.pdf mac * * skia -FRC_8.5_URI_IsMap.pdf mac * * skia -FRC_8_8.2.4_View_D.pdf mac * * skia -FRC_9_8.2.4_View_T.pdf mac * * skia -action_execute_a_menu_item.pdf mac * * skia -action_hide_show_form.pdf mac * * skia -action_reset.pdf mac * * skia -all_trigger_browsefordoc.pdf mac * * skia -all_trigger_newdoc.pdf mac * * skia -all_trigger_run_js_lunchurl.pdf mac * * skia -all_trigger_run_js_maildoc.pdf mac * * skia -annotation_circle.pdf mac * * skia -annotation_circle_dash.pdf mac * * skia -annotation_circle_fill_opacity.pdf mac * * skia -annotation_ellipse.pdf mac * * skia -annotation_ellipse_fill.pdf mac * * skia -annotation_ellipse_fill_dash.pdf mac * * skia -annotation_freetext.pdf mac * * skia -annotation_highlight_author_content.pdf mac * * skia -annotation_highlight_long_content.pdf mac * * skia -annotation_highlight_no_author.pdf mac * * skia -annotation_ink.pdf mac * * skia -annotation_ink_dash.pdf mac * * skia -annotation_ink_dot.pdf mac * * skia -annotation_ink_multiple.pdf mac * * skia -annotation_square.pdf mac * * skia -annotation_square_dash.pdf mac * * skia -annotation_square_fill_opacity.pdf mac * * skia -annotation_square_fill_opacity_dash.pdf mac * * skia -annotation_squiggly.pdf mac * * skia -annotation_squiggly_opacity.pdf mac * * skia -annotation_strikeout.pdf mac * * skia -annotation_strikeout_opacity.pdf mac * * skia -annotation_text.pdf mac * * skia -bi.pdf mac * * skia -bug_691967.pdf mac * * skia -ch_9_android.pdf mac * * skia -colorspace.pdf mac * * skia -colorspace_test1.pdf mac * * skia -en_fqa2.pdf mac * * skia -en_system.pdf mac * * skia -en_tem.pdf mac * * skia -en_uicase_.pdf mac * * skia -en_uicase_2_.pdf mac * * skia -example_001.pdf mac * * skia -example_006.pdf mac * * skia -example_009.pdf mac * * skia -example_010.pdf mac * * skia -example_018.pdf mac * * skia -example_025.pdf mac * * skia -example_026.pdf mac * * skia -example_034.pdf mac * * skia -example_039.pdf mac * * skia -example_040.pdf mac * * skia -example_051.pdf mac * * skia -example_057.pdf mac * * skia -example_058.pdf mac * * skia -example_060.pdf mac * * skia -example_062.pdf mac * * skia -example_063.pdf mac * * skia -example_064.pdf mac * * skia -form_button0.pdf mac * * skia -form_button1.pdf mac * * skia -form_button10.pdf mac * * skia -form_button2.pdf mac * * skia -form_button3.pdf mac * * skia -form_button4.pdf mac * * skia -form_button5.pdf mac * * skia -form_button7.pdf mac * * skia -form_button8.pdf mac * * skia -form_button9.pdf mac * * skia -image_8bit_devicergb_dctdecode.pdf mac * * skia -image_bmp.pdf mac * * skia -image_foxit.pdf mac * * skia -image_gif.pdf mac * * skia -image_jpg.pdf mac * * skia -image_png.pdf mac * * skia -image_tif.pdf mac * * skia -new_stamp3.pdf mac * * skia -new_textmarkup2.pdf mac * * skia -new_textmarkup8.pdf mac * * skia -path_5_pattern.pdf mac * * skia -path_9.pdf mac_x86 * * skia -quick_start_guide.pdf mac * * skia -shading_2_15.pdf mac * * skia -shading_2_17.pdf mac * * skia -shading_2_4.pdf mac * * skia -shading_2_7.pdf mac * * skia -shading_2_8.pdf mac * * skia -shading_2_9.pdf mac * * skia -whats_new_in_v3.0.pdf mac * * skia -xfa_node_caption.pdf mac * * skia -zh_file_structure.pdf mac * * skia -zh_function_list.pdf mac * * skia -zh_page.pdf mac * * skia -zh_webdav.pdf mac * * skia diff --git a/testing/SUPPRESSIONS_IMAGE_DIFF b/testing/SUPPRESSIONS_IMAGE_DIFF deleted file mode 100644 index b96afeebac..0000000000 --- a/testing/SUPPRESSIONS_IMAGE_DIFF +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2017 The PDFium Authors -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. -# -# List of tests to have image diff tests skipped, one per line. -# There are five space-separated columns per line -# Each column (except column 0) can contain a comma-separated list of values. -# -# Column 0: test file name -# Column 1: platform: *, win, mac, mac_arm, mac_x86, linux -# Column 2: v8 support: *, nov8, v8 -# Column 3: xfa support: *, noxfa, xfa -# Column 4: rendering support: *, agg, gdi, skia -# -# All columns on a line must match, but filenames may be repeated on subsequent -# lines to suppress more cases. Within each column, any one of the -# comma-separated values must match in order for the column to "match". -# The filenames and keywords are case-sensitive. -# One exception is the column 1, where "mac" is a shorthand that matches either -# "mac_arm" or "mac_x86". -# -# Try to keep the file alphabetized within each category of test. - -# -# Corpus tests -# -FRC_3.5_CF_Strf_stmf_DefaultCryptFilter.pdf * * * * -FRC_3.5_EncryptMetadata_T.pdf * * * * -FRC_3.5_Encrypt_is_damage.pdf * * * * -FRC_3.5_Filter_PubSec_SubFilter_s5.pdf * * * * -FRC_3.5_Filter_PubSec_Sub_SubFilter_s4.pdf * * * * -MouseEvents.pdf * * * * -Oneof.pdf * * * * -bug_651304.pdf * * * * -outline.pdf * * * * diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp index 2d2855ddc6..d1e4eed45e 100644 --- a/testing/embedder_test.cpp +++ b/testing/embedder_test.cpp @@ -15,6 +15,7 @@ #include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/containers/contains.h" +#include "core/fxcrt/containers/to_vector.h" #include "core/fxcrt/fx_memcpy_wrappers.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/notreached.h" @@ -114,6 +115,12 @@ void OnFocusChangeTrampoline(FPDF_FORMFILLINFO* info, return delegate->OnFocusChange(info, annot, page_index); } +void ExecuteNamedActionTrampoline(FPDF_FORMFILLINFO* info, + FPDF_BYTESTRING name) { + auto* delegate = static_cast(info)->GetDelegate(); + return delegate->ExecuteNamedAction(name); +} + void DoURIActionWithKeyboardModifierTrampoline(FPDF_FORMFILLINFO* info, FPDF_BYTESTRING uri, int modifiers) { @@ -152,8 +159,6 @@ 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, @@ -313,7 +318,7 @@ std::vector GetEmbedderTestExpectationsWithSuffixPath( std::string renderer = "_agg"; #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { renderer = "_skia"; } #endif @@ -368,49 +373,20 @@ DecodedPng DecodePngData(pdfium::span png_data) { int CompareBGRxBitmapToPng(pdfium::span 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(decoded_png.width); + const DiffOptions& options) { auto decoded_png_span32 = fxcrt::reinterpret_span( 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( - 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(total_squared_error) / - (3.0 * decoded_png.width * decoded_png.height); - if (mse > max_mean_squared_error) { - ++pixels_different; - } - } - return pixels_different; + auto bitmap_span32 = fxcrt::reinterpret_span(bitmap_span); + return CalculatePixelsDifferent( + decoded_png_span32, static_cast(decoded_png.width), bitmap_span32, + bitmap_stride / sizeof(uint32_t), decoded_png.width, decoded_png.height, + options); } int CompareGrayBitmapToPng(pdfium::span bitmap_span, size_t bitmap_stride, const DecodedPng& decoded_png, - int max_pixel_per_channel_delta, - double max_mean_squared_error) { + const DiffOptions& options) { const int width = decoded_png.width; const size_t dest_row_width = static_cast(width); const int height = decoded_png.height; @@ -428,18 +404,15 @@ int CompareGrayBitmapToPng(pdfium::span bitmap_span, dest_pixel.alpha = 255; } } - return CompareBGRxBitmapToPng(bgrx_buffer, bgrx_stride, decoded_png, - max_pixel_per_channel_delta, - max_mean_squared_error); + return CompareBGRxBitmapToPng(bgrx_buffer, bgrx_stride, decoded_png, options); } #ifdef PDF_USE_SKIA int CompareBGRxPremultBitmapToPng(pdfium::span bitmap_span, size_t bitmap_stride, const DecodedPng& decoded_png, - int max_pixel_per_channel_delta, - double max_mean_squared_error) { - std::vector bitmap_data(bitmap_span.begin(), bitmap_span.end()); + const DiffOptions& options) { + std::vector bitmap_data = pdfium::ToVector(bitmap_span); pdfium::span converted_bitmap_span{bitmap_data}; for (int h = 0; h < decoded_png.height; ++h) { @@ -451,16 +424,14 @@ int CompareBGRxPremultBitmapToPng(pdfium::span bitmap_span, } } return CompareBGRxBitmapToPng(bitmap_data, bitmap_stride, decoded_png, - max_pixel_per_channel_delta, - max_mean_squared_error); + options); } #endif // PDF_USE_SKIA int CompareBGRBitmapToPng(pdfium::span bitmap_span, size_t bitmap_stride, const DecodedPng& decoded_png, - int max_pixel_per_channel_delta, - double max_mean_squared_error) { + const DiffOptions& options) { const int width = decoded_png.width; const size_t dest_row_width = static_cast(width); const int height = decoded_png.height; @@ -479,9 +450,47 @@ int CompareBGRBitmapToPng(pdfium::span bitmap_span, dest_row[w].alpha = 255; } } - return CompareBGRxBitmapToPng(bgrx_buffer, bgrx_stride, decoded_png, - max_pixel_per_channel_delta, - max_mean_squared_error); + return CompareBGRxBitmapToPng(bgrx_buffer, bgrx_stride, decoded_png, options); +} + +int CompareBGRABitmapToPng(pdfium::span bitmap_span, + size_t bitmap_stride, + const DecodedPng& decoded_png, + const DiffOptions& options) { + const int width = decoded_png.width; + const size_t dest_row_width = static_cast(width); + const int height = decoded_png.height; + const size_t bgra_stride = width * sizeof(FX_BGRA_STRUCT); + + // Premultiply both decoded PNG and actual bitmap so unweighted color + // channels in transparent or near-transparent pixels do not trigger + // false-positive differences. + std::vector actual_premult(bgra_stride * height); + auto actual_span = fxcrt::reinterpret_span>( + pdfium::span(actual_premult)); + for (int h = 0; h < height; ++h) { + auto src_row = fxcrt::reinterpret_span>( + bitmap_span.subspan(h * bitmap_stride, bitmap_stride)); + auto dest_row = actual_span.take_first(dest_row_width); + for (size_t w = 0; w < dest_row_width; ++w) { + dest_row[w] = PreMultiplyColor(src_row[w]); + } + } + + std::vector expected_premult(decoded_png.pixel_data); + auto expected_span = fxcrt::reinterpret_span>( + pdfium::span(expected_premult)); + for (auto& pixel : expected_span) { + pixel = PreMultiplyColor(pixel); + } + + DecodedPng premult_png; + premult_png.width = width; + premult_png.height = height; + premult_png.pixel_data = std::move(expected_premult); + + return CompareBGRxBitmapToPng(actual_premult, bgra_stride, premult_png, + options); } std::string EncodeBase64(pdfium::span png) { @@ -503,8 +512,7 @@ void ReportMissingExpectation(FPDF_BITMAP bitmap) { void CompareBitmapToPngFile(FPDF_BITMAP bitmap, const std::string& png_path, - int max_pixel_per_channel_delta, - double max_mean_squared_error) { + const DiffOptions& options) { std::vector png_data = GetFileContents(png_path.c_str()); if (png_data.empty()) { ReportMissingExpectation(bitmap); @@ -531,27 +539,27 @@ void CompareBitmapToPngFile(FPDF_BITMAP bitmap, 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); + pixels_different = + CompareGrayBitmapToPng(bitmap_span, stride, decoded_png, options); break; case FPDFBitmap_BGR: - pixels_different = CompareBGRBitmapToPng(bitmap_span, stride, decoded_png, - max_pixel_per_channel_delta, - max_mean_squared_error); + pixels_different = + CompareBGRBitmapToPng(bitmap_span, stride, decoded_png, options); break; - case FPDFBitmap_BGRx: + case FPDFBitmap_BGRx: { + pixels_different = + CompareBGRxBitmapToPng(bitmap_span, stride, decoded_png, options); + break; + } case FPDFBitmap_BGRA: { - pixels_different = CompareBGRxBitmapToPng( - bitmap_span, stride, decoded_png, max_pixel_per_channel_delta, - max_mean_squared_error); + pixels_different = + CompareBGRABitmapToPng(bitmap_span, stride, decoded_png, options); 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); + pixels_different = CompareBGRxPremultBitmapToPng(bitmap_span, stride, + decoded_png, options); break; #endif // PDF_USE_SKIA default: @@ -775,7 +783,7 @@ FPDF_FORMHANDLE EmbedderTest::SetupFormFillEnvironment( formfillinfo->FFI_GetPage = GetPageTrampoline; formfillinfo->FFI_GetCurrentPage = GetCurrentPageStub; formfillinfo->FFI_GetRotation = GetRotationStub; - formfillinfo->FFI_ExecuteNamedAction = ExecuteNamedActionStub; + formfillinfo->FFI_ExecuteNamedAction = ExecuteNamedActionTrampoline; formfillinfo->FFI_SetTextFieldFocus = SetTextFieldFocusStub; formfillinfo->FFI_DoURIAction = DoURIActionTrampoline; formfillinfo->FFI_DoGoToAction = DoGoToActionTrampoline; @@ -1104,16 +1112,11 @@ void EmbedderTest::VerifySavedRendering(FPDF_PAGE page, 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) { + std::string_view expectation_png_name, + const DiffOptions& options) { ScopedFPDFBitmap bitmap = VerifySavedRenderingCommon(page); - CompareBitmapWithFuzzyExpectationSuffix(bitmap.get(), expectation_png_name); + CompareBitmapWithExpectationSuffix(bitmap.get(), expectation_png_name, + options); } ScopedFPDFBitmap EmbedderTest::VerifySavedRenderingCommon(FPDF_PAGE page) { @@ -1128,15 +1131,11 @@ void EmbedderTest::VerifySavedDocument(std::string_view 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) { + std::string_view expectation_png_name, + const DiffOptions& options) { ScopedFPDFBitmap bitmap = VerifySavedDocumentCommon(); - CompareBitmapWithFuzzyExpectationSuffix(bitmap.get(), expectation_png_name); + CompareBitmapWithExpectationSuffix(bitmap.get(), expectation_png_name, + options); } ScopedFPDFBitmap EmbedderTest::VerifySavedDocumentCommon() { @@ -1203,8 +1202,11 @@ 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); + DiffOptions options = kExactDiffOptions; + if (EmbedderTestEnvironment::GetInstance()->fontations()) { + options = kFontationsDiffOptions; + } + CompareBitmapToPngFile(bitmap, png_path, options); if (EmbedderTestEnvironment::GetInstance()->write_pngs()) { WriteBitmapToPng(bitmap, png_path); } @@ -1214,8 +1216,23 @@ void EmbedderTest::CompareBitmap(FPDF_BITMAP bitmap, void EmbedderTest::CompareBitmapWithExpectationSuffix( FPDF_BITMAP bitmap, std::string_view expectation_png_name, - int max_pixel_per_channel_delta, - double max_mean_squared_error) { + const DiffOptions& options) { + DiffOptions effective_options = options; + if (EmbedderTestEnvironment::GetInstance()->fontations()) { + effective_options.max_pixel_per_channel_delta = + std::max(options.max_pixel_per_channel_delta, + kFontationsDiffOptions.max_pixel_per_channel_delta); + effective_options.max_mean_squared_error = + std::max(options.max_mean_squared_error, + kFontationsDiffOptions.max_mean_squared_error); + if (options.window_size > 0) { + effective_options.window_size = + std::max(options.window_size, kFontationsDiffOptions.window_size); + effective_options.max_window_mean_squared_error = + std::max(options.max_window_mean_squared_error, + kFontationsDiffOptions.max_window_mean_squared_error); + } + } std::vector candidate_png_path = GetEmbedderTestExpectationsWithSuffixPath(expectation_png_name); for (const std::string& png_path : candidate_png_path) { @@ -1225,8 +1242,7 @@ void EmbedderTest::CompareBitmapWithExpectationSuffix( SCOPED_TRACE(testing::Message() << "CompareBitmapWithExpectationSuffix() with " << png_path); - CompareBitmapToPngFile(bitmap, png_path, max_pixel_per_channel_delta, - max_mean_squared_error); + CompareBitmapToPngFile(bitmap, png_path, effective_options); if (EmbedderTestEnvironment::GetInstance()->write_pngs()) { WriteBitmapToPng(bitmap, png_path); } @@ -1236,15 +1252,6 @@ void EmbedderTest::CompareBitmapWithExpectationSuffix( 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, diff --git a/testing/embedder_test.h b/testing/embedder_test.h index 8571af5bd2..368997c27e 100644 --- a/testing/embedder_test.h +++ b/testing/embedder_test.h @@ -29,6 +29,7 @@ #include "public/fpdfview.h" #include "testing/fake_file_access.h" #include "testing/gtest/include/gtest/gtest.h" +#include "testing/utils/pixel_diff_util.h" class TestLoader; @@ -95,6 +96,9 @@ class EmbedderTest : public ::testing::Test, virtual void DoURIActionWithKeyboardModifier(FPDF_FORMFILLINFO* info, FPDF_BYTESTRING uri, int modifiers) {} + + // Equivalent to FPDF_FORMFILLINFO::FFI_ExecuteNamedAction(). + virtual void ExecuteNamedAction(FPDF_BYTESTRING name) {} }; class ScopedSavedDoc { @@ -346,20 +350,12 @@ class EmbedderTest : public ::testing::Test, // // This is similar to the behavior in testing/tools/pngdiffer.py. // - // `max_pixel_per_channel_delta` and `max_mean_squared_error` can optionally - // be set to tolerate minor pixel discrepancies. The default is exact - // matching. + // `options` can optionally be set to tolerate minor pixel discrepancies. + // The default is exact matching. static void CompareBitmapWithExpectationSuffix( FPDF_BITMAP bitmap, std::string_view expectation_png_name, - int max_pixel_per_channel_delta = 0, - double max_mean_squared_error = 0.0); - - // Same as `CompareBitmapWithExpectationSuffix()`, but automatically - // applies standard fuzzy tolerance. - static void CompareBitmapWithFuzzyExpectationSuffix( - FPDF_BITMAP bitmap, - std::string_view expectation_png_name); + const DiffOptions& options = kExactDiffOptions); void ClearString() { data_string_.clear(); } const std::string& GetString() const { return data_string_; } @@ -381,15 +377,12 @@ class EmbedderTest : public ::testing::Test, std::string_view expectation_png_name); void VerifySavedRenderingWithExpectationSuffix( FPDF_PAGE page, - std::string_view expectation_png_name); - void VerifySavedRenderingWithFuzzyExpectationSuffix( - FPDF_PAGE page, - std::string_view expectation_png_name); + std::string_view expectation_png_name, + const DiffOptions& options = kExactDiffOptions); void VerifySavedDocument(std::string_view expectation_png_name); void VerifySavedDocumentWithExpectationSuffix( - std::string_view expectation_png_name); - void VerifySavedDocumentWithFuzzyExpectationSuffix( - std::string_view expectation_png_name); + std::string_view expectation_png_name, + const DiffOptions& options = kExactDiffOptions); void SetWholeFileAvailable(); diff --git a/testing/embedder_test_environment.cpp b/testing/embedder_test_environment.cpp index cc610bd424..dee2cbb88a 100644 --- a/testing/embedder_test_environment.cpp +++ b/testing/embedder_test_environment.cpp @@ -77,7 +77,6 @@ void EmbedderTestEnvironment::AddFlags(int argc, char** argv) { for (int i = 1; i < argc; ++i) { AddFlag(UNSAFE_TODO(argv[i])); } - CHECK(CheckFlags()); } void EmbedderTestEnvironment::AddFlag(const std::string& flag) { @@ -99,19 +98,13 @@ void EmbedderTestEnvironment::AddFlag(const std::string& flag) { } return; } +#endif // defined(PDF_USE_SKIA) +#if defined(PDF_ENABLE_FONTATIONS) if (flag == "--fontations") { fontations_ = true; return; } -#endif // defined(PDF_USE_SKIA) +#endif // defined(PDF_ENABLE_FONTATIONS) std::cerr << "Unknown flag: " << flag << "\n"; } - -bool EmbedderTestEnvironment::CheckFlags() { - if (fontations_ && renderer_type_ != FPDF_RENDERERTYPE_SKIA) { - std::cerr << "--fontations requires --use-renderer=skia as well.\n"; - return false; - } - return true; -} diff --git a/testing/embedder_test_environment.h b/testing/embedder_test_environment.h index 4efd3920d3..3ac640422e 100644 --- a/testing/embedder_test_environment.h +++ b/testing/embedder_test_environment.h @@ -32,10 +32,10 @@ class EmbedderTestEnvironment : public testing::Environment { void AddFlags(int argc, char** argv); bool write_pngs() const { return write_pngs_; } + bool fontations() const { return fontations_; } private: void AddFlag(const std::string& flag); - bool CheckFlags(); int version_ = 6; FPDF_RENDERER_TYPE renderer_type_; diff --git a/testing/embedder_test_mock_delegate.h b/testing/embedder_test_mock_delegate.h index 88f40b3d3a..1c2f32707b 100644 --- a/testing/embedder_test_mock_delegate.h +++ b/testing/embedder_test_mock_delegate.h @@ -33,6 +33,7 @@ class EmbedderTestMockDelegate : public EmbedderTest::Delegate { MOCK_METHOD(void, DoURIActionWithKeyboardModifier, (FPDF_FORMFILLINFO * info, FPDF_BYTESTRING uri, int modifiers)); + MOCK_METHOD(void, ExecuteNamedAction, (FPDF_BYTESTRING name)); }; #endif // TESTING_EMBEDDER_TEST_MOCK_DELEGATE_H_ diff --git a/testing/external_engine_embedder_test.cpp b/testing/external_engine_embedder_test.cpp index 34d1eb88e0..f587647d87 100644 --- a/testing/external_engine_embedder_test.cpp +++ b/testing/external_engine_embedder_test.cpp @@ -21,7 +21,7 @@ void ExternalEngineEmbedderTest::SetUp() { v8::Isolate::Scope isolate_scope(isolate()); v8::HandleScope handle_scope(isolate()); - CFXJS_PerIsolateData::SetUp(isolate()); + CFXJS_PerIsolateData::GetOrCreate(isolate()); engine_ = std::make_unique(isolate()); engine_->InitializeEngine(); } diff --git a/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc b/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc index 91e5795efd..59690fbe9e 100644 --- a/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc +++ b/testing/fuzzers/pdf_cfgas_stringformatter_fuzzer.cc @@ -42,7 +42,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { uint8_t type_selector = pre[2] % std::size(kTypes); auto* state = static_cast(FPDF_GetFuzzerPerProcessState()); - cppgc::Heap* heap = state->GetHeap(); + FXGC_Heap* heap = state->GetHeap(); size_t pattern_len = span.size() / 2; WideString pattern = diff --git a/testing/fuzzers/xfa_process_state.cc b/testing/fuzzers/xfa_process_state.cc index 91d552cb26..90109400f1 100644 --- a/testing/fuzzers/xfa_process_state.cc +++ b/testing/fuzzers/xfa_process_state.cc @@ -7,17 +7,19 @@ #include "fxjs/gc/heap.h" #include "v8/include/libplatform/libplatform.h" +// Pass nullptr to always get a standalone cppgc heap, even though an isolate +// is available. Since V8 attaches a default CppHeap to every isolate, passing +// `isolate` would instead hand back that attached heap, and collecting it +// performs a full V8 GC. That requires both --expose-gc and the isolate to +// have been entered, neither of which holds when ForceGCAndPump() runs from a +// fuzzer case, nor when the destructor runs from a static destructor at exit. XFAProcessState::XFAProcessState(v8::Platform* platform, v8::Isolate* isolate) - : platform_(platform), isolate_(isolate), heap_(FXGC_CreateHeap()) {} + : platform_(platform), isolate_(isolate), heap_(FXGC_CreateHeap(nullptr)) {} XFAProcessState::~XFAProcessState() { FXGC_ForceGarbageCollection(heap_.get()); } -cppgc::Heap* XFAProcessState::GetHeap() const { - return heap_.get(); -} - void XFAProcessState::ForceGCAndPump() { FXGC_ForceGarbageCollection(heap_.get()); while (v8::platform::PumpMessageLoop(platform_, isolate_)) { diff --git a/testing/fuzzers/xfa_process_state.h b/testing/fuzzers/xfa_process_state.h index 12d8a7d389..c584c36504 100644 --- a/testing/fuzzers/xfa_process_state.h +++ b/testing/fuzzers/xfa_process_state.h @@ -21,7 +21,7 @@ class XFAProcessState { XFAProcessState(v8::Platform* platform, v8::Isolate* isolate); ~XFAProcessState(); - cppgc::Heap* GetHeap() const; + FXGC_Heap* GetHeap() const { return heap_.get(); } void ForceGCAndPump(); private: diff --git a/testing/fxgc_unittest.cpp b/testing/fxgc_unittest.cpp index cc01ab48b2..31c6b3125e 100644 --- a/testing/fxgc_unittest.cpp +++ b/testing/fxgc_unittest.cpp @@ -17,7 +17,7 @@ void FXGCUnitTest::SetUp() { ::testing::Test::SetUp(); auto* env = V8TestEnvironment::GetInstance(); FXGC_Initialize(env->platform(), env->isolate()); - heap_ = FXGC_CreateHeap(); + heap_ = FXGC_CreateHeap(nullptr); ASSERT_TRUE(heap_); } diff --git a/testing/fxgc_unittest.h b/testing/fxgc_unittest.h index 0625190922..aca331b112 100644 --- a/testing/fxgc_unittest.h +++ b/testing/fxgc_unittest.h @@ -17,7 +17,7 @@ class FXGCUnitTest : public ::testing::Test { void SetUp() override; void TearDown() override; - cppgc::Heap* heap() const { return heap_.get(); } + FXGC_Heap* heap() const { return heap_.get(); } void ForceGCAndPump(); void Pump(); diff --git a/testing/image_diff.cpp b/testing/image_diff.cpp index 0ecff329b8..09092f145c 100644 --- a/testing/image_diff.cpp +++ b/testing/image_diff.cpp @@ -40,6 +40,8 @@ constexpr uint32_t RGBA_ALPHA = 0xff000000; class Image { public: Image() = default; + Image(int w, int h) + : w_(w), h_(h), data_(static_cast(w) * h * 4, 0) {} Image(const Image& image) = default; Image& operator=(const Image& other) = default; @@ -150,14 +152,23 @@ void CountImageSizeMismatchAsPixelDifference(const Image& baseline, *pixels_different += (max_h - h) * max_w; } +// Optional per-comparison measurements, reported by `--metrics`. +struct DiffMetrics { + int raw_diff_pixels = 0; + uint8_t max_delta = 0; + double mse = 0.0; + double win_mse = 0.0; +}; + +// Computes the number of differing pixels. When `out_metrics` is non-null, +// the underlying measurements are gathered as well, at additional cost. int PixelsDifferent(const Image& baseline, const Image& actual, - uint8_t max_pixel_per_channel_delta, - double max_mean_squared_error) { + DiffMetrics* out_metrics) { int w = std::min(baseline.w(), actual.w()); int h = std::min(baseline.h(), actual.h()); - // Compute pixels different in the overlap. + uint8_t max_delta = 0; int pixels_different = 0; uint64_t total_squared_error = 0; for (int y = 0; y < h; ++y) { @@ -168,22 +179,36 @@ int PixelsDifferent(const Image& baseline, continue; } - if (MaxPixelPerChannelDelta(baseline_pixel, actual_pixel) > - max_pixel_per_channel_delta) { - ++pixels_different; - } + ++pixels_different; + max_delta = std::max( + max_delta, MaxPixelPerChannelDelta(baseline_pixel, actual_pixel)); total_squared_error += PixelSquaredError(baseline_pixel, actual_pixel); } } - if (w > 0 && h > 0 && max_mean_squared_error > 0.0) { - double mse = static_cast(total_squared_error) / (3.0 * w * h); - if (mse > max_mean_squared_error) { - ++pixels_different; + if (out_metrics) { + out_metrics->raw_diff_pixels = pixels_different; + out_metrics->max_delta = max_delta; + if (w > 0 && h > 0) { + out_metrics->mse = + static_cast(total_squared_error) / (3.0 * w * h); + + std::vector baseline_overlap(w * h); + std::vector actual_overlap(w * h); + for (int y = 0; y < h; ++y) { + for (int x = 0; x < w; ++x) { + baseline_overlap[y * w + x] = baseline.pixel_at(x, y); + actual_overlap[y * w + x] = actual.pixel_at(x, y); + } + } + out_metrics->win_mse = CalculateMaxWindowMSE( + baseline_overlap, static_cast(w), actual_overlap, + static_cast(w), w, h, kMaxFuzzyWindowSize); } } CountImageSizeMismatchAsPixelDifference(baseline, actual, &pixels_different); + return pixels_different; } @@ -232,8 +257,11 @@ void PrintHelp(const std::string& binary_name) { " RGBA value histograms (which is resistant to shifts in layout).\n" " Passing \"--reverse-byte-order\" additionally assumes the\n" " compare file has BGRA byte ordering.\n" - " Passing \"--fuzzy\" additionally allows individual pixels to\n" - " differ by at most 1 on each channel.\n\n" + " Passing \"--metrics\" additionally prints a machine-readable line\n" + " of measurements.\n" + " Under \"--histogram\" or \"--metrics\", the comparison result is\n" + " reported in the output rather than through the exit code, which\n" + " then indicates only whether the comparison could be performed.\n\n" " %s --diff \n" " Compares two files on disk, and if they differ, outputs an image\n" " to that visualizes the differing pixels as red\n" @@ -250,8 +278,7 @@ int CompareImages(const std::string& binary_name, const std::string& file2, bool compare_histograms, bool reverse_byte_order, - uint8_t max_pixel_per_channel_delta, - double max_mean_squared_error) { + bool report_metrics) { Image actual_image; Image baseline_image; @@ -281,14 +308,38 @@ int CompareImages(const std::string& binary_name, } const char* const diff_name = compare_histograms ? "exact diff" : "diff"; - int pixels_different = - PixelsDifferent(actual_image, baseline_image, max_pixel_per_channel_delta, - max_mean_squared_error); + DiffMetrics metrics; + int pixels_different = PixelsDifferent(actual_image, baseline_image, + report_metrics ? &metrics : nullptr); + float percent = CalculateDifferencePercentage(actual_image, pixels_different); const char* const passed = percent > 0.0 ? "failed" : "passed"; + + if (report_metrics) { + int overlap_w = std::min(actual_image.w(), baseline_image.w()); + int overlap_h = std::min(actual_image.h(), baseline_image.h()); + const size_t last_separator = file1.rfind(PATH_SEPARATOR); + std::string base_name = last_separator != std::string::npos + ? file1.substr(last_separator + 1) + : file1; + + printf( + "%s: actual_w=%d actual_h=%d expected_w=%d expected_h=%d pixels=%d " + "total=%d max_delta=%u mse=%.6f win_mse=%.6f\n", + base_name.c_str(), actual_image.w(), actual_image.h(), + baseline_image.w(), baseline_image.h(), metrics.raw_diff_pixels, + overlap_w * overlap_h, metrics.max_delta, metrics.mse, metrics.win_mse); + } + UNSAFE_TODO(printf("%s: %01.2f%% %s (%d pixels differ)\n", diff_name, percent, passed, pixels_different)); + if (compare_histograms || report_metrics) { + // Measurement modes: the result is reported in the output above, leaving + // the exit code to indicate only whether the comparison could be + // performed at all. + return kStatusSame; + } if (percent > 0.0) { // failure: The WebKit version also writes the difference image to // stdout, which seems excessive for our needs. @@ -299,25 +350,31 @@ int CompareImages(const std::string& binary_name, } bool CreateImageDiff(const Image& image1, const Image& image2, Image* out) { - int w = std::min(image1.w(), image2.w()); - int h = std::min(image1.h(), image2.h()); - *out = Image(image1); + int union_w = std::max(image1.w(), image2.w()); + int union_h = std::max(image1.h(), image2.h()); + int inter_w = std::min(image1.w(), image2.w()); + int inter_h = std::min(image1.h(), image2.h()); + *out = Image(union_w, union_h); bool same = (image1.w() == image2.w()) && (image1.h() == image2.h()); - // TODO(estade): do something with the extra pixels if the image sizes - // are different. - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - uint32_t base_pixel = image1.pixel_at(x, y); - if (base_pixel != image2.pixel_at(x, y)) { - // Set differing pixels red. + for (int y = 0; y < union_h; ++y) { + for (int x = 0; x < union_w; ++x) { + if (x < inter_w && y < inter_h) { + uint32_t base_pixel = image1.pixel_at(x, y); + if (base_pixel != image2.pixel_at(x, y)) { + // Set differing pixels red. + out->set_pixel_at(x, y, RGBA_RED | RGBA_ALPHA); + same = false; + } else { + // Set same pixels as faded. + uint32_t alpha = base_pixel & RGBA_ALPHA; + uint32_t new_pixel = base_pixel - ((alpha / 2) & RGBA_ALPHA); + out->set_pixel_at(x, y, new_pixel); + } + } else { + // Set out-of-bounds pixels red. out->set_pixel_at(x, y, RGBA_RED | RGBA_ALPHA); same = false; - } else { - // Set same pixels as faded. - uint32_t alpha = base_pixel & RGBA_ALPHA; - uint32_t new_pixel = base_pixel - ((alpha / 2) & RGBA_ALPHA); - out->set_pixel_at(x, y, new_pixel); } } } @@ -326,37 +383,44 @@ bool CreateImageDiff(const Image& image1, const Image& image2, Image* out) { } bool SubtractImages(const Image& image1, const Image& image2, Image* out) { - int w = std::min(image1.w(), image2.w()); - int h = std::min(image1.h(), image2.h()); - *out = Image(image1); + int union_w = std::max(image1.w(), image2.w()); + int union_h = std::max(image1.h(), image2.h()); + int inter_w = std::min(image1.w(), image2.w()); + int inter_h = std::min(image1.h(), image2.h()); + *out = Image(union_w, union_h); bool same = (image1.w() == image2.w()) && (image1.h() == image2.h()); - for (int y = 0; y < h; ++y) { - for (int x = 0; x < w; ++x) { - uint32_t pixel1 = image1.pixel_at(x, y); - int32_t r1 = pixel1 & 0xff; - int32_t g1 = (pixel1 >> 8) & 0xff; - int32_t b1 = (pixel1 >> 16) & 0xff; - - uint32_t pixel2 = image2.pixel_at(x, y); - int32_t r2 = pixel2 & 0xff; - int32_t g2 = (pixel2 >> 8) & 0xff; - int32_t b2 = (pixel2 >> 16) & 0xff; - - int32_t delta_r = r1 - r2; - int32_t delta_g = g1 - g2; - int32_t delta_b = b1 - b2; - same &= (delta_r == 0 && delta_g == 0 && delta_b == 0); - - delta_r = std::clamp(128 + delta_r * 8, 0, 255); - delta_g = std::clamp(128 + delta_g * 8, 0, 255); - delta_b = std::clamp(128 + delta_b * 8, 0, 255); - - uint32_t new_pixel = RGBA_ALPHA; - new_pixel |= delta_r; - new_pixel |= (delta_g << 8); - new_pixel |= (delta_b << 16); - out->set_pixel_at(x, y, new_pixel); + for (int y = 0; y < union_h; ++y) { + for (int x = 0; x < union_w; ++x) { + if (x < inter_w && y < inter_h) { + uint32_t pixel1 = image1.pixel_at(x, y); + int32_t r1 = pixel1 & 0xff; + int32_t g1 = (pixel1 >> 8) & 0xff; + int32_t b1 = (pixel1 >> 16) & 0xff; + + uint32_t pixel2 = image2.pixel_at(x, y); + int32_t r2 = pixel2 & 0xff; + int32_t g2 = (pixel2 >> 8) & 0xff; + int32_t b2 = (pixel2 >> 16) & 0xff; + + int32_t delta_r = r1 - r2; + int32_t delta_g = g1 - g2; + int32_t delta_b = b1 - b2; + same &= (delta_r == 0 && delta_g == 0 && delta_b == 0); + + delta_r = std::clamp(128 + delta_r * 8, 0, 255); + delta_g = std::clamp(128 + delta_g * 8, 0, 255); + delta_b = std::clamp(128 + delta_b * 8, 0, 255); + + uint32_t new_pixel = RGBA_ALPHA; + new_pixel |= delta_r; + new_pixel |= (delta_g << 8); + new_pixel |= (delta_b << 16); + out->set_pixel_at(x, y, new_pixel); + } else { + out->set_pixel_at(x, y, RGBA_RED | RGBA_ALPHA); + same = false; + } } } return same; @@ -418,8 +482,7 @@ int main(int argc, const char* argv[]) { bool produce_diff_image = false; bool produce_image_subtraction = false; bool reverse_byte_order = false; - uint8_t max_pixel_per_channel_delta = 0; - double max_mean_squared_error = 0.0; + bool report_metrics = false; std::string filename1; std::string filename2; std::string diff_filename; @@ -443,9 +506,8 @@ int main(int argc, const char* argv[]) { produce_image_subtraction = true; } else if (UNSAFE_TODO(strcmp(arg, "--reverse-byte-order")) == 0) { reverse_byte_order = true; - } else if (UNSAFE_TODO(strcmp(arg, "--fuzzy")) == 0) { - max_pixel_per_channel_delta = kMaxFuzzyPixelDelta; - max_mean_squared_error = kMaxFuzzyMeanSquaredError; + } else if (UNSAFE_TODO(strcmp(arg, "--metrics")) == 0) { + report_metrics = true; } } if (i < argc) { @@ -465,8 +527,7 @@ int main(int argc, const char* argv[]) { } } else if (!filename2.empty()) { return CompareImages(binary_name, filename1, filename2, histograms, - reverse_byte_order, max_pixel_per_channel_delta, - max_mean_squared_error); + reverse_byte_order, report_metrics); } PrintHelp(binary_name); diff --git a/testing/js_embedder_test.h b/testing/js_embedder_test.h index dda285a33a..980c357a3a 100644 --- a/testing/js_embedder_test.h +++ b/testing/js_embedder_test.h @@ -16,7 +16,7 @@ class JSEmbedderTest : public EmbedderTest { JSEmbedderTest(); ~JSEmbedderTest() override; - v8::Isolate* isolate() const; + virtual v8::Isolate* isolate() const; }; #endif // TESTING_JS_EMBEDDER_TEST_H_ diff --git a/testing/pdf_test_environment.cpp b/testing/pdf_test_environment.cpp index 0125ff7c0b..5b4f70a1f1 100644 --- a/testing/pdf_test_environment.cpp +++ b/testing/pdf_test_environment.cpp @@ -4,6 +4,10 @@ #include "testing/pdf_test_environment.h" +#include +#include + +#include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/span.h" #include "core/fxge/cfx_gemodule.h" @@ -15,9 +19,30 @@ PDFTestEnvironment::~PDFTestEnvironment() = default; void PDFTestEnvironment::SetUp() { CFX_GEModule::Create(test_fonts_.FontPathsSpan(), CFX_GEModule::RendererType::kDefault, +#if defined(PDF_ENABLE_FONTATIONS) + fontations_ ? CFX_FontMgr::FontBackend::kFontations + : CFX_FontMgr::FontBackend::kFreeType); +#else CFX_FontMgr::FontBackend::kFreeType); +#endif } void PDFTestEnvironment::TearDown() { CFX_GEModule::Destroy(); } + +void PDFTestEnvironment::AddFlags(int argc, char** argv) { + for (int i = 1; i < argc; ++i) { + AddFlag(UNSAFE_TODO(argv[i])); + } +} + +void PDFTestEnvironment::AddFlag(const std::string& flag) { +#if defined(PDF_ENABLE_FONTATIONS) + if (flag == "--fontations") { + fontations_ = true; + return; + } +#endif // defined(PDF_ENABLE_FONTATIONS) + std::cerr << "Unknown flag: " << flag << "\n"; +} diff --git a/testing/pdf_test_environment.h b/testing/pdf_test_environment.h index 818d8e4e01..ba369d3022 100644 --- a/testing/pdf_test_environment.h +++ b/testing/pdf_test_environment.h @@ -5,6 +5,8 @@ #ifndef TESTING_PDF_TEST_ENVIRONMENT_H_ #define TESTING_PDF_TEST_ENVIRONMENT_H_ +#include + #include "testing/gtest/include/gtest/gtest.h" #include "testing/test_fonts.h" @@ -17,7 +19,14 @@ class PDFTestEnvironment : public testing::Environment { void SetUp() override; void TearDown() override; + void AddFlags(int argc, char** argv); + private: + void AddFlag(const std::string& flag); + +#if defined(PDF_ENABLE_FONTATIONS) + bool fontations_ = false; +#endif // defined(PDF_ENABLE_FONTATIONS) TestFonts test_fonts_; }; diff --git a/testing/pdfium_test/pdfium_test.cc b/testing/pdfium_test/pdfium_test.cc index 6ace38dacb..c584cd7e9e 100644 --- a/testing/pdfium_test/pdfium_test.cc +++ b/testing/pdfium_test/pdfium_test.cc @@ -103,6 +103,11 @@ #include "v8/include/v8-snapshot.h" #endif // PDF_ENABLE_V8 +#ifdef PDF_ENABLE_XFA +#include "fpdfsdk/cpdfsdk_formfillenvironment.h" +#include "fpdfsdk/cpdfsdk_helpers.h" +#endif // PDF_ENABLE_XFA + #ifdef _WIN32 #define access _access #define snprintf _snprintf @@ -183,9 +188,9 @@ struct Options { bool save_thumbnails_decoded = false; bool save_thumbnails_raw = false; RendererType use_renderer_type = RendererType::kDefault; -#if defined(PDF_ENABLE_SKIA) +#if defined(PDF_ENABLE_FONTATIONS) bool use_fontations_backend = false; -#endif // defined(PDF_ENABLE_SKIA) +#endif // defined(PDF_ENABLE_FONTATIONS) #ifdef PDF_ENABLE_V8 bool disable_javascript = false; std::string js_flags; // Extra flags to pass to v8 init. @@ -196,7 +201,7 @@ struct Options { bool pages = false; bool md5 = false; #ifdef PDF_ENABLE_BROTLI - bool enable_brotli = false; + bool enable_brotli = true; #endif // PDF_ENABLE_BROTLI #ifdef ENABLE_CALLGRIND bool callgrind_delimiters = false; @@ -585,9 +590,11 @@ bool ParseCommandLine(const std::vector& args, #if defined(PDF_ENABLE_SKIA) } else if (cur_arg == "--render-premultiplied") { options->render_premultiplied_alpha = true; +#endif // defined(PDF_ENABLE_SKIA) +#if defined(PDF_ENABLE_FONTATIONS) } else if (cur_arg == "--fontations") { options->use_fontations_backend = true; -#endif // defined(PDF_ENABLE_SKIA) +#endif // defined(PDF_ENABLE_FONTATIONS) #ifdef PDF_ENABLE_V8 } else if (cur_arg == "--disable-javascript") { options->disable_javascript = true; @@ -603,8 +610,8 @@ bool ParseCommandLine(const std::vector& args, #endif // PDF_ENABLE_XFA #endif // PDF_ENABLE_V8 #ifdef PDF_ENABLE_BROTLI - } else if (cur_arg == "--enable-brotli") { - options->enable_brotli = true; + } else if (cur_arg == "--disable-brotli") { + options->enable_brotli = false; #endif // PDF_ENABLE_BROTLI #ifdef ENABLE_CALLGRIND } else if (cur_arg == "--callgrind-delim") { @@ -801,11 +808,6 @@ bool ParseCommandLine(const std::vector& args, "Cannot use --render_premultiplied with selected renderer\n"); return false; } - if (options->use_fontations_backend && - options->use_renderer_type != RendererType::kSkia) { - fprintf(stderr, "Cannot use --fontations with selected renderer\n"); - return false; - } #endif // defined(PDF_ENABLE_SKIA) return true; } @@ -882,21 +884,22 @@ FPDF_BOOL NeedToPauseNow(IFSDK_PAUSE* p) { class Processor final { public: - Processor(const Options* options, const std::function* idler) + Processor(const Options* options, + const std::function* idler) : options_(options), idler_(idler) { DCHECK(options_); DCHECK(idler_); } const Options& options() const { return *options_; } - const std::function& idler() const { return *idler_; } + const std::function& idler() const { return *idler_; } #ifdef _WIN32 ComFactory& com_factory() { return com_factory_; } #endif // _WIN32 // Invokes `idler()`. - void Idle() const { idler()(); } + void Idle(FPDF_FORMHANDLE form) const { idler()(form); } void ProcessPdf(const std::string& name, pdfium::span data, @@ -904,7 +907,7 @@ class Processor final { private: const Options* options_; - const std::function* idler_; + const std::function* idler_; #ifdef _WIN32 ComFactory com_factory_; @@ -938,7 +941,6 @@ class PdfProcessor final { private: // Per processor state. const Options& options() const { return processor_->options(); } - const std::function& idler() const { return processor_->idler(); } #ifdef _WIN32 ComFactory& com_factory() { return processor_->com_factory(); } @@ -950,8 +952,7 @@ class PdfProcessor final { FPDF_DOCUMENT doc() const { return doc_; } FPDF_FORMHANDLE form() const { return form_; } - // Invokes `idler()`. - void Idle() const { idler()(); } + void Idle() const { processor_->Idle(form_); } FPDF_PAGE GetPage(int page_index) const { return GetPageForIndex(form_fill_info_, doc_, page_index); @@ -1477,8 +1478,10 @@ bool PdfProcessor::ProcessPage(const int page_index) { return false; } + const std::function page_idler = [this]() { Idle(); }; + if (options().send_events) { - SendPageEvents(form(), page, events(), idler()); + SendPageEvents(form(), page, events(), page_idler); } if (options().save_images) { WriteImages(page, name().c_str(), page_index); @@ -1504,7 +1507,6 @@ bool PdfProcessor::ProcessPage(const int page_index) { return true; } - ScopedFPDFTextPage text_page(FPDFText_LoadPage(page)); double scale = 1.0; if (!options().scale_factor_as_string.empty()) { std::stringstream(options().scale_factor_as_string) >> scale; @@ -1605,7 +1607,7 @@ bool PdfProcessor::ProcessPage(const int page_index) { #ifdef _WIN32 if (!renderer && options().use_renderer_type == RendererType::kGdi) { renderer = std::make_unique( - page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler(), + page, /*width=*/width, /*height=*/height, /*flags=*/flags, page_idler, std::move(writer)); } #endif // _WIN32 @@ -1623,11 +1625,11 @@ bool PdfProcessor::ProcessPage(const int page_index) { // Use a rasterizing page renderer by default. if (options().render_oneshot) { renderer = std::make_unique( - page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler(), + page, /*width=*/width, /*height=*/height, /*flags=*/flags, page_idler, options().render_premultiplied_alpha, std::move(writer)); } else { renderer = std::make_unique( - page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler(), + page, /*width=*/width, /*height=*/height, /*flags=*/flags, page_idler, options().render_premultiplied_alpha, std::move(writer), options().forced_color ? &kColorScheme : nullptr); } @@ -1820,12 +1822,12 @@ void Processor::ProcessPdf(const std::string& name, } else { ++bad_pages; } - Idle(); + Idle(form.get()); } } FORM_DoDocumentAAction(form.get(), FPDFDOC_AACTION_WC); - Idle(); + Idle(form.get()); fprintf(stderr, "Processed %d pages.\n", processed_pages); if (bad_pages) { @@ -1918,7 +1920,6 @@ constexpr char kUsageString[] = #endif // _WIN32 " --render-premultiplied - render image using premultiplied alpha when " "the renderer is Skia\n" - " -- fontations - Use fontations back-end library\n" #else #ifdef _WIN32 " --use-renderer - renderer to use, one of [agg | gdi]\n" @@ -1926,7 +1927,9 @@ constexpr char kUsageString[] = " --use-renderer - renderer to use, one of [agg]\n" #endif // _WIN32 #endif // defined(PDF_ENABLE_SKIA) - +#if defined(PDF_ENABLE_FONTATIONS) + " --fontations - Use fontations back-end library\n" +#endif #ifdef PDF_ENABLE_V8 " --disable-javascript - do not execute JS in PDF files\n" " --js-flags= - additional flags to pass to V8\n" @@ -1935,8 +1938,8 @@ constexpr char kUsageString[] = #endif // PDF_ENABLE_XFA #endif // PDF_ENABLE_V8 #ifdef PDF_ENABLE_BROTLI - " --enable-brotli - Enable support for the experimental PDF 2.0 " - "/BrotliDecode filter.\n" + " --disable-brotli - Disables support for the PDF 2.0 /BrotliDecode " + "filter.\n" #endif // PDF_ENABLE_BROTLI #ifdef ENABLE_CALLGRIND " --callgrind-delim - delimit interesting section when using " @@ -2017,7 +2020,7 @@ int main(int argc, const char* argv[]) { } FPDF_LIBRARY_CONFIG config; - config.version = 6; + config.version = 7; config.m_pUserFontPaths = nullptr; config.m_pIsolate = nullptr; config.m_v8EmbedderSlot = 0; @@ -2028,6 +2031,12 @@ int main(int argc, const char* argv[]) { #else config.m_BrotliEnabled = false; #endif // PDF_ENABLE_BROTLI +#if defined(PDF_ENABLE_V8) && defined(PDF_ENABLE_XFA) + config.m_IsolatePerDocument = + !options.disable_javascript && !options.disable_xfa; +#else + config.m_IsolatePerDocument = false; +#endif switch (options.use_renderer_type) { case RendererType::kDefault: @@ -2052,10 +2061,12 @@ int main(int argc, const char* argv[]) { #endif // defined(PDF_ENABLE_SKIA) } -#if defined(PDF_ENABLE_SKIA) +#if defined(PDF_ENABLE_FONTATIONS) if (options.use_fontations_backend) { config.m_FontLibraryType = FPDF_FONTBACKENDTYPE_FONTATIONS; } +#endif // defined(PDF_ENABLE_FONTATIONS) +#if defined(PDF_ENABLE_SKIA) #if defined(BUILD_WITH_CHROMIUM) // Needed to support Chromium's copy of Skia, which uses a // `DiscardableMemoryAllocator`. @@ -2065,7 +2076,7 @@ int main(int argc, const char* argv[]) { #endif // defined(BUILD_WITH_CHROMIUM) #endif // defined(PDF_ENABLE_SKIA) - std::function idler = []() {}; + std::function idler = [](FPDF_FORMHANDLE) {}; #ifdef PDF_ENABLE_V8 #ifdef V8_USE_EXTERNAL_STARTUP_DATA v8::StartupData snapshot; @@ -2085,16 +2096,31 @@ int main(int argc, const char* argv[]) { } config.m_pPlatform = platform.get(); - v8::Isolate::CreateParams params; - params.array_buffer_allocator = static_cast( - FPDF_GetArrayBufferAllocatorSharedInstance()); - isolate.reset(v8::Isolate::New(params)); - config.m_pIsolate = isolate.get(); + if (!config.m_IsolatePerDocument) { + v8::Isolate::CreateParams params; + params.array_buffer_allocator = static_cast( + FPDF_GetArrayBufferAllocatorSharedInstance()); + isolate.reset(v8::Isolate::New(params)); + config.m_pIsolate = isolate.get(); + } - idler = [&platform, &isolate]() { - v8::Isolate::Scope isolate_scope(isolate.get()); + idler = [&platform, &isolate](FPDF_FORMHANDLE form) { + v8::Isolate* target_isolate = isolate.get(); +#ifdef PDF_ENABLE_XFA + if (!target_isolate && form) { + auto* form_fill_env = + CPDFSDKFormFillEnvironmentFromFPDFFormHandle(form); + if (form_fill_env) { + target_isolate = form_fill_env->GetIsolate(); + } + } +#endif // PDF_ENABLE_XFA + if (!target_isolate) { + return; + } + v8::Isolate::Scope isolate_scope(target_isolate); int task_count = 0; - while (v8::platform::PumpMessageLoop(platform.get(), isolate.get())) { + while (v8::platform::PumpMessageLoop(platform.get(), target_isolate)) { ++task_count; } if (task_count) { diff --git a/testing/resources/arabic.in b/testing/resources/arabic.in new file mode 100644 index 0000000000..62bfa1ca5f --- /dev/null +++ b/testing/resources/arabic.in @@ -0,0 +1,96 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /MediaBox [0 0 200 200] + /Resources << + /Font << + /F1 5 0 R + >> + >> +>> +endobj +{{object 4 0}} << + {{streamlen}} +>> +stream +BT +/F1 12 Tf +20 100 Td +<090608020A03010705040A03> Tj +ET +endstream +endobj +{{object 5 0}} << + /Type /Font + /Subtype /TrueType + /BaseFont /AAAAAA+Arabic + /FirstChar 1 + /FontDescriptor 6 0 R + /LastChar 10 + /ToUnicode 7 0 R + /Widths [318 305 278 302 646 483 552 483 776 305] +>> +endobj +{{object 6 0}} << + /Type /FontDescriptor + /Ascent 760 + /CapHeight 342 + /Descent -240 + /Flags 32 + /FontBBox [-42 -244 755 1028] + /FontName /AAAAAA+Arabic + /ItalicAngle 0 + /StemV 80 +>> +endobj +{{object 7 0}} << + {{streamlen}} +>> +stream +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo << +/Registry (Adobe) +/Ordering (UCS) +/Supplement 0 +>> def +/CMapName /AAAA-Arabic def +/CMapType 2 def +1 begincodespacerange +<00> endcodespacerange +10 beginbfchar +<01> <0020> +<02> +<03> +<04> +<05> +<06> +<07> +<08> +<09> +<0A> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/arabic.pdf b/testing/resources/arabic.pdf new file mode 100644 index 0000000000..ad3ae4e853 Binary files /dev/null and b/testing/resources/arabic.pdf differ diff --git a/testing/resources/brotli_decodeparms.in b/testing/resources/brotli_decodeparms.in new file mode 100644 index 0000000000..292c2ab7c0 --- /dev/null +++ b/testing/resources/brotli_decodeparms.in @@ -0,0 +1,114 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj + +{{object 2 0}} << + /Type /Pages + /Count 1 + /Kids [3 0 R] +>> +endobj + +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /MediaBox [0 0 600 200] + /Resources << + /ProcSet [/PDF /ImageC] + /ColorSpace << + /C1 [/Pattern] + >> + /Pattern << + /P1 5 0 R + >> + >> +>> +endobj + +{{object 4 0}} << + {{streamlen}} +>> +stream +q +0.24 0 0 0.24 0 0 cm +/C1 cs +/P1 scn +888 400 108 60 re +f* +0 0 0 rg +888 460 m +888 400 l +S +/C1 cs +/P1 scn +1536 400 108 60 re +f* +0 0 0 rg +1536 460 m +1536 400 l +S +/C1 cs +/P1 scn +2184 400 108 60 re +f* +0 0 0 rg +2184 460 m +2184 400 l +S +Q +endstream +endobj + +{{object 5 0}} << + /Type /Pattern + /PaintType 1 + /PatternType 1 + /TilingType 1 + /BBox [0 0 128 32] + /Matrix [.202488 0 0 -.449947 0 791.999] + /Resources << + /ProcSet [/PDF /ImageC] + /XObject << + /R17 6 0 R + >> + >> + /XStep 128 + /YStep 32 + {{streamlen}} +>> +stream +0.24 0 0 0.24 0 0 cm +q +0 134 534 -134 re +W n +q 533.333 0 0 -133.333 0 133.333 cm +/R17 Do +Q +Q +endstream +endobj + +{{object 6 0}} << + /Type /XObject + /Subtype /Image + /Width 4 + /Height 4 + /BitsPerComponent 8 + /ColorSpace /DeviceRGB + /DecodeParms [ null << /Columns 4 /Colors 3 /Predictor 15 >> ] + /Filter [/ASCII85Decode /BrotliDecode] + {{streamlen}} +>> +stream +)`%KhVZC<6O9\e5^cO.m$NL~> +endstream +endobj + +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/brotli_decodeparms.pdf b/testing/resources/brotli_decodeparms.pdf new file mode 100644 index 0000000000..23c805d99e Binary files /dev/null and b/testing/resources/brotli_decodeparms.pdf differ diff --git a/testing/resources/embedded_attachments_with_af.in b/testing/resources/embedded_attachments_with_af.in new file mode 100644 index 0000000000..bc3e593b6c --- /dev/null +++ b/testing/resources/embedded_attachments_with_af.in @@ -0,0 +1,71 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Names 4 0 R + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /MediaBox [0 0 612 792] + /Resources << + /ProcSet [/PDF] + >> +>> +endobj +{{object 4 0}} << + /EmbeddedFiles 5 0 R +>> +endobj +{{object 5 0}} << + /Names [(1.txt) 6 0 R (2.txt) 7 0 R (3.txt) 9 0 R] +>> +endobj +{{object 6 0}} << + /Type /Filespec + /AFRelationship /Alternative % Present -> "Alternative". + /EF << + /F 8 0 R + >> + /F (1.txt) + /UF (1.txt) +>> +endobj +{{object 7 0}} << + /Type /Filespec + % No /AFRelationship -> empty string. + /EF << + /F 8 0 R + >> + /F (2.txt) + /UF (2.txt) +>> +endobj +{{object 8 0}} << + {{streamlen}} +>> +stream +hello +endstream +endobj +{{object 9 0}} << + /Type /Filespec + /AFRelationship 5 % Number, not a name -> empty string. + /EF << + /F 8 0 R + >> + /F (3.txt) + /UF (3.txt) +>> +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/embedded_attachments_with_af.pdf b/testing/resources/embedded_attachments_with_af.pdf new file mode 100644 index 0000000000..c14e577b59 Binary files /dev/null and b/testing/resources/embedded_attachments_with_af.pdf differ diff --git a/testing/resources/embedder_tests/annotation_highlight_long_content_added_underline_skia_linux.png b/testing/resources/embedder_tests/annotation_highlight_long_content_added_underline_skia_linux.png index b0357652bd..cb52996360 100644 Binary files a/testing/resources/embedder_tests/annotation_highlight_long_content_added_underline_skia_linux.png and b/testing/resources/embedder_tests/annotation_highlight_long_content_added_underline_skia_linux.png differ diff --git a/testing/resources/embedder_tests/annotation_highlight_long_content_added_underline_skia_win.png b/testing/resources/embedder_tests/annotation_highlight_long_content_added_underline_skia_win.png index be0895f807..d609002494 100644 Binary files a/testing/resources/embedder_tests/annotation_highlight_long_content_added_underline_skia_win.png and b/testing/resources/embedder_tests/annotation_highlight_long_content_added_underline_skia_win.png differ diff --git a/testing/resources/embedder_tests/annotation_highlight_square_with_ap_modified_highlight_skia_mac.png b/testing/resources/embedder_tests/annotation_highlight_square_with_ap_modified_highlight_skia_mac.png index 3932f755d5..3afe771c9c 100644 Binary files a/testing/resources/embedder_tests/annotation_highlight_square_with_ap_modified_highlight_skia_mac.png and b/testing/resources/embedder_tests/annotation_highlight_square_with_ap_modified_highlight_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_highlight_square_with_ap_modified_square_skia_mac.png b/testing/resources/embedder_tests/annotation_highlight_square_with_ap_modified_square_skia_mac.png index a7eaf72d94..416e7c7533 100644 Binary files a/testing/resources/embedder_tests/annotation_highlight_square_with_ap_modified_square_skia_mac.png and b/testing/resources/embedder_tests/annotation_highlight_square_with_ap_modified_square_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_highlight_square_with_ap_skia_mac.png b/testing/resources/embedder_tests/annotation_highlight_square_with_ap_skia_mac.png index 1f701a12df..5296cb1118 100644 Binary files a/testing/resources/embedder_tests/annotation_highlight_square_with_ap_skia_mac.png and b/testing/resources/embedder_tests/annotation_highlight_square_with_ap_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_ink_multiple_skia_linux.png b/testing/resources/embedder_tests/annotation_ink_multiple_skia_linux.png index 635aa4c3f1..159e83f400 100644 Binary files a/testing/resources/embedder_tests/annotation_ink_multiple_skia_linux.png and b/testing/resources/embedder_tests/annotation_ink_multiple_skia_linux.png differ diff --git a/testing/resources/embedder_tests/annotation_ink_multiple_skia_win.png b/testing/resources/embedder_tests/annotation_ink_multiple_skia_win.png index d645f96958..910df86977 100644 Binary files a/testing/resources/embedder_tests/annotation_ink_multiple_skia_win.png and b/testing/resources/embedder_tests/annotation_ink_multiple_skia_win.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_linux.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_linux.png index f2543d781e..22d3f3ca68 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_linux.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_linux.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_mac.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_mac.png index 585f74febd..bfe07da8a2 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_mac.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_win.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_win.png index 2ae2033dbb..05bec08d98 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_win.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_base_content_skia_win.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia.png index deacf586aa..d86556b54b 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia_mac.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia_mac.png index cd30589233..ae8ac6c2ad 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia_mac.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia_win.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia_win.png index e1fb33f3d3..915327d0d8 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia_win.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_image_skia_win.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia.png index 86ab90c4b9..877616c539 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia_mac.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia_mac.png index 34c6e33465..0a1641264b 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia_mac.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia_win.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia_win.png index f9c9cf50d2..adddd12074 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia_win.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_path_skia_win.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia.png index 61ef9c2bec..c138198bca 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia_mac.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia_mac.png index e04b993734..17e8e1f2af 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia_mac.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia_win.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia_win.png index ce3fe85981..aa56074041 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia_win.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_modified_text_skia_win.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia.png index a87d6c6fe2..cba0a70540 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia_mac.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia_mac.png index 0dfc2ac7c6..f11735de16 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia_mac.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia_win.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia_win.png index 95f9e43123..773b6aa3f7 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia_win.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_annot_skia_win.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia.png index 082ae57f9c..a2976e659a 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia_mac.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia_mac.png index 32b0b853df..397e301459 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia_mac.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia_win.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia_win.png index 81a206d3b6..3139da1486 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia_win.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_image_skia_win.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia.png index 2be47c0d80..83f0049b68 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia_mac.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia_mac.png index 2cad1c5913..df9469903f 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia_mac.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia_win.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia_win.png index 33534aff94..e3f054fff5 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia_win.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_new_text_skia_win.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_skia.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_skia.png index 0bab03b522..f3dc7d3245 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_skia.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_skia.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_skia_mac.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_skia_mac.png index 466fc4cc1b..fe4985e575 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_skia_mac.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_skia_win.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_skia_win.png index a52ad9938f..0356a48ab0 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_skia_win.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_skia_win.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia.png index 5a98710966..7872b4427c 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia_mac.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia_mac.png index 41f62765de..a914d41aef 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia_mac.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia_win.png b/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia_win.png index bd1cb0c58f..5b62ec8ee4 100644 Binary files a/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia_win.png and b/testing/resources/embedder_tests/annotation_stamp_with_ap_two_paths_skia_win.png differ diff --git a/testing/resources/embedder_tests/annotation_with_grayscale_skia_mac.png b/testing/resources/embedder_tests/annotation_with_grayscale_skia_mac.png index a332a0211d..de06c4ffdb 100644 Binary files a/testing/resources/embedder_tests/annotation_with_grayscale_skia_mac.png and b/testing/resources/embedder_tests/annotation_with_grayscale_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_linux.png b/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_linux.png index cd4aaf832d..e595c2bcd2 100644 Binary files a/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_linux.png and b/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_linux.png differ diff --git a/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_mac.png b/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_mac.png index f7d203c0c4..10abc115bf 100644 Binary files a/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_mac.png and b/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_win.png b/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_win.png index 1d58495e71..d8fd1a803f 100644 Binary files a/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_win.png and b/testing/resources/embedder_tests/annots_page1_focus_highlight_skia_win.png differ diff --git a/testing/resources/embedder_tests/annots_page1_focus_link_skia_linux.png b/testing/resources/embedder_tests/annots_page1_focus_link_skia_linux.png index c815092ecb..2706954d24 100644 Binary files a/testing/resources/embedder_tests/annots_page1_focus_link_skia_linux.png and b/testing/resources/embedder_tests/annots_page1_focus_link_skia_linux.png differ diff --git a/testing/resources/embedder_tests/annots_page1_focus_link_skia_mac.png b/testing/resources/embedder_tests/annots_page1_focus_link_skia_mac.png index a2bc829459..d8de4a350c 100644 Binary files a/testing/resources/embedder_tests/annots_page1_focus_link_skia_mac.png and b/testing/resources/embedder_tests/annots_page1_focus_link_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annots_page1_focus_link_skia_win.png b/testing/resources/embedder_tests/annots_page1_focus_link_skia_win.png index e74c811817..c93d2f0bdd 100644 Binary files a/testing/resources/embedder_tests/annots_page1_focus_link_skia_win.png and b/testing/resources/embedder_tests/annots_page1_focus_link_skia_win.png differ diff --git a/testing/resources/embedder_tests/annots_page1_skia_linux.png b/testing/resources/embedder_tests/annots_page1_skia_linux.png index 32662ffc23..81a93127d7 100644 Binary files a/testing/resources/embedder_tests/annots_page1_skia_linux.png and b/testing/resources/embedder_tests/annots_page1_skia_linux.png differ diff --git a/testing/resources/embedder_tests/annots_page1_skia_mac.png b/testing/resources/embedder_tests/annots_page1_skia_mac.png index 4a21a3cce2..785e39bcc5 100644 Binary files a/testing/resources/embedder_tests/annots_page1_skia_mac.png and b/testing/resources/embedder_tests/annots_page1_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annots_page1_skia_win.png b/testing/resources/embedder_tests/annots_page1_skia_win.png index 325326767c..a07e2a7f0e 100644 Binary files a/testing/resources/embedder_tests/annots_page1_skia_win.png and b/testing/resources/embedder_tests/annots_page1_skia_win.png differ diff --git a/testing/resources/embedder_tests/annots_page2_modified_skia_linux.png b/testing/resources/embedder_tests/annots_page2_modified_skia_linux.png index 56d6302795..2c13a5b5af 100644 Binary files a/testing/resources/embedder_tests/annots_page2_modified_skia_linux.png and b/testing/resources/embedder_tests/annots_page2_modified_skia_linux.png differ diff --git a/testing/resources/embedder_tests/annots_page2_modified_skia_mac.png b/testing/resources/embedder_tests/annots_page2_modified_skia_mac.png index 406ad0f933..79c7c6e274 100644 Binary files a/testing/resources/embedder_tests/annots_page2_modified_skia_mac.png and b/testing/resources/embedder_tests/annots_page2_modified_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annots_page2_modified_skia_win.png b/testing/resources/embedder_tests/annots_page2_modified_skia_win.png index 54c9fefe9a..df6c48f328 100644 Binary files a/testing/resources/embedder_tests/annots_page2_modified_skia_win.png and b/testing/resources/embedder_tests/annots_page2_modified_skia_win.png differ diff --git a/testing/resources/embedder_tests/annots_page2_skia_linux.png b/testing/resources/embedder_tests/annots_page2_skia_linux.png index 633eb60444..e25b9e0eef 100644 Binary files a/testing/resources/embedder_tests/annots_page2_skia_linux.png and b/testing/resources/embedder_tests/annots_page2_skia_linux.png differ diff --git a/testing/resources/embedder_tests/annots_page2_skia_mac.png b/testing/resources/embedder_tests/annots_page2_skia_mac.png index 87120beebc..581e5511b2 100644 Binary files a/testing/resources/embedder_tests/annots_page2_skia_mac.png and b/testing/resources/embedder_tests/annots_page2_skia_mac.png differ diff --git a/testing/resources/embedder_tests/annots_page2_skia_win.png b/testing/resources/embedder_tests/annots_page2_skia_win.png index c858743d73..193683055f 100644 Binary files a/testing/resources/embedder_tests/annots_page2_skia_win.png and b/testing/resources/embedder_tests/annots_page2_skia_win.png differ diff --git a/testing/resources/embedder_tests/blank_200x200.png b/testing/resources/embedder_tests/blank_200x200.png index f97e340a54..041c94fec7 100644 Binary files a/testing/resources/embedder_tests/blank_200x200.png and b/testing/resources/embedder_tests/blank_200x200.png differ diff --git a/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_linux.png b/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_linux.png index f468f57e56..1dc83c74c7 100644 Binary files a/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_linux.png and b/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_mac.png b/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_mac.png index e552a8b23a..dc607077e5 100644 Binary files a/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_mac.png and b/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_mac_x86.png b/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_mac_x86.png new file mode 100644 index 0000000000..a7ce02d077 Binary files /dev/null and b/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_mac_x86.png differ diff --git a/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_win.png b/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_win.png index 570d4debd0..08476a6b56 100644 Binary files a/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_win.png and b/testing/resources/embedder_tests/bottom_text_added_bold_and_stretched_text_skia_win.png differ diff --git a/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_linux.png b/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_linux.png index 1cfc92b29f..dad708444d 100644 Binary files a/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_linux.png and b/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_mac.png b/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_mac.png index 11e4b5840e..7fd1057c33 100644 Binary files a/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_mac.png and b/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_win.png b/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_win.png index eba4add12b..1352d27967 100644 Binary files a/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_win.png and b/testing/resources/embedder_tests/bottom_text_added_bold_text_skia_win.png differ diff --git a/testing/resources/embedder_tests/bottom_text_skia_linux.png b/testing/resources/embedder_tests/bottom_text_skia_linux.png index bf29af4f74..bc0fd22122 100644 Binary files a/testing/resources/embedder_tests/bottom_text_skia_linux.png and b/testing/resources/embedder_tests/bottom_text_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bottom_text_skia_mac.png b/testing/resources/embedder_tests/bottom_text_skia_mac.png index c60c46d035..78b710e939 100644 Binary files a/testing/resources/embedder_tests/bottom_text_skia_mac.png and b/testing/resources/embedder_tests/bottom_text_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bottom_text_skia_win.png b/testing/resources/embedder_tests/bottom_text_skia_win.png index 551de61752..b84d61ae1a 100644 Binary files a/testing/resources/embedder_tests/bottom_text_skia_win.png and b/testing/resources/embedder_tests/bottom_text_skia_win.png differ diff --git a/testing/resources/embedder_tests/bottom_text_text_object_skia_linux.png b/testing/resources/embedder_tests/bottom_text_text_object_skia_linux.png index 0b111c97bc..3efd060ec5 100644 Binary files a/testing/resources/embedder_tests/bottom_text_text_object_skia_linux.png and b/testing/resources/embedder_tests/bottom_text_text_object_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bottom_text_text_object_skia_mac.png b/testing/resources/embedder_tests/bottom_text_text_object_skia_mac.png index 886b6c4df4..fadc93af1f 100644 Binary files a/testing/resources/embedder_tests/bottom_text_text_object_skia_mac.png and b/testing/resources/embedder_tests/bottom_text_text_object_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bottom_text_text_object_skia_win.png b/testing/resources/embedder_tests/bottom_text_text_object_skia_win.png index 406d0a79c1..a6a308977f 100644 Binary files a/testing/resources/embedder_tests/bottom_text_text_object_skia_win.png and b/testing/resources/embedder_tests/bottom_text_text_object_skia_win.png differ diff --git a/testing/resources/embedder_tests/brotli_decode_parms_agg.png b/testing/resources/embedder_tests/brotli_decode_parms_agg.png new file mode 100644 index 0000000000..c87b9aac62 Binary files /dev/null and b/testing/resources/embedder_tests/brotli_decode_parms_agg.png differ diff --git a/testing/resources/embedder_tests/brotli_decode_parms_skia.png b/testing/resources/embedder_tests/brotli_decode_parms_skia.png new file mode 100644 index 0000000000..0a4705ba31 Binary files /dev/null and b/testing/resources/embedder_tests/brotli_decode_parms_skia.png differ diff --git a/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_linux.png b/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_linux.png index 664d25007d..48ee4e3e72 100644 Binary files a/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_linux.png and b/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_mac.png b/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_mac.png index 44af22d4c8..6e3d11ca82 100644 Binary files a/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_mac.png and b/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_win.png b/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_win.png index 0e90acade5..40e663d6a9 100644 Binary files a/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_win.png and b/testing/resources/embedder_tests/bug_1302455_edit_both_forms_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_linux.png b/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_linux.png index 8c426c4a32..eb49f0f23d 100644 Binary files a/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_linux.png and b/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_mac.png b/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_mac.png index 84fdd1f551..18035f3a7a 100644 Binary files a/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_mac.png and b/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_win.png b/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_win.png index 395259f135..3c2468571d 100644 Binary files a/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_win.png and b/testing/resources/embedder_tests/bug_1302455_edit_first_form_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_linux.png b/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_linux.png index 0e0819577c..5d7db1b70b 100644 Binary files a/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_linux.png and b/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_mac.png b/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_mac.png index 0210d22df9..ea1cbdc1ca 100644 Binary files a/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_mac.png and b/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_win.png b/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_win.png index d86a18e61b..63428e6d1f 100644 Binary files a/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_win.png and b/testing/resources/embedder_tests/bug_1302455_edit_second_form_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_1558_skia_linux.png b/testing/resources/embedder_tests/bug_1558_skia_linux.png index 1c3544bb61..2c2a8c76f5 100644 Binary files a/testing/resources/embedder_tests/bug_1558_skia_linux.png and b/testing/resources/embedder_tests/bug_1558_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bug_1558_skia_mac.png b/testing/resources/embedder_tests/bug_1558_skia_mac.png index 38e29db223..9ac91b03da 100644 Binary files a/testing/resources/embedder_tests/bug_1558_skia_mac.png and b/testing/resources/embedder_tests/bug_1558_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_1558_skia_win.png b/testing/resources/embedder_tests/bug_1558_skia_win.png index f6a0742e3d..6e1b5c15d3 100644 Binary files a/testing/resources/embedder_tests/bug_1558_skia_win.png and b/testing/resources/embedder_tests/bug_1558_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_1574_skia_linux.png b/testing/resources/embedder_tests/bug_1574_skia_linux.png index 4f7e896fdc..13a610774c 100644 Binary files a/testing/resources/embedder_tests/bug_1574_skia_linux.png and b/testing/resources/embedder_tests/bug_1574_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bug_1574_skia_mac.png b/testing/resources/embedder_tests/bug_1574_skia_mac.png index 41c94b7ab7..d813b7fb1c 100644 Binary files a/testing/resources/embedder_tests/bug_1574_skia_mac.png and b/testing/resources/embedder_tests/bug_1574_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_1574_skia_win.png b/testing/resources/embedder_tests/bug_1574_skia_win.png index 8df11298ea..9ad9b7e1f5 100644 Binary files a/testing/resources/embedder_tests/bug_1574_skia_win.png and b/testing/resources/embedder_tests/bug_1574_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_1893_removed_underline_skia_linux.png b/testing/resources/embedder_tests/bug_1893_removed_underline_skia_linux.png index 8569aff68a..1d6d7663a3 100644 Binary files a/testing/resources/embedder_tests/bug_1893_removed_underline_skia_linux.png and b/testing/resources/embedder_tests/bug_1893_removed_underline_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bug_1893_removed_underline_skia_mac.png b/testing/resources/embedder_tests/bug_1893_removed_underline_skia_mac.png index 20b32e5128..ab40c4219f 100644 Binary files a/testing/resources/embedder_tests/bug_1893_removed_underline_skia_mac.png and b/testing/resources/embedder_tests/bug_1893_removed_underline_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_1893_removed_underline_skia_win.png b/testing/resources/embedder_tests/bug_1893_removed_underline_skia_win.png index 05f32e5a3b..a56a6ba372 100644 Binary files a/testing/resources/embedder_tests/bug_1893_removed_underline_skia_win.png and b/testing/resources/embedder_tests/bug_1893_removed_underline_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_1893_skia_linux.png b/testing/resources/embedder_tests/bug_1893_skia_linux.png index 8c4869fe8c..52497d2ab6 100644 Binary files a/testing/resources/embedder_tests/bug_1893_skia_linux.png and b/testing/resources/embedder_tests/bug_1893_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bug_1893_skia_mac.png b/testing/resources/embedder_tests/bug_1893_skia_mac.png index 6ef3f710fa..a25e6706e2 100644 Binary files a/testing/resources/embedder_tests/bug_1893_skia_mac.png and b/testing/resources/embedder_tests/bug_1893_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_1893_skia_win.png b/testing/resources/embedder_tests/bug_1893_skia_win.png index 0089304df0..396f681092 100644 Binary files a/testing/resources/embedder_tests/bug_1893_skia_win.png and b/testing/resources/embedder_tests/bug_1893_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_1919_skia_linux.png b/testing/resources/embedder_tests/bug_1919_skia_linux.png index 31cc30e6e2..124110b121 100644 Binary files a/testing/resources/embedder_tests/bug_1919_skia_linux.png and b/testing/resources/embedder_tests/bug_1919_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bug_1919_skia_win.png b/testing/resources/embedder_tests/bug_1919_skia_win.png index 9c592b9a59..ed83761d9e 100644 Binary files a/testing/resources/embedder_tests/bug_1919_skia_win.png and b/testing/resources/embedder_tests/bug_1919_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_410996566_skia.png b/testing/resources/embedder_tests/bug_410996566_skia.png index 67baec98eb..379e056b78 100644 Binary files a/testing/resources/embedder_tests/bug_410996566_skia.png and b/testing/resources/embedder_tests/bug_410996566_skia.png differ diff --git a/testing/resources/embedder_tests/bug_410996566_skia_mac.png b/testing/resources/embedder_tests/bug_410996566_skia_mac.png index 3e888cc529..a2b1fa711b 100644 Binary files a/testing/resources/embedder_tests/bug_410996566_skia_mac.png and b/testing/resources/embedder_tests/bug_410996566_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_410996566_skia_win.png b/testing/resources/embedder_tests/bug_410996566_skia_win.png index f8994ba89e..ff47df6f7f 100644 Binary files a/testing/resources/embedder_tests/bug_410996566_skia_win.png and b/testing/resources/embedder_tests/bug_410996566_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_410996566_skia_x86.png b/testing/resources/embedder_tests/bug_410996566_skia_x86.png deleted file mode 100644 index 2647707e69..0000000000 Binary files a/testing/resources/embedder_tests/bug_410996566_skia_x86.png and /dev/null differ diff --git a/testing/resources/embedder_tests/bug_461845674_skia.png b/testing/resources/embedder_tests/bug_461845674_skia.png index 1dccd0067e..5f5e8a33fd 100644 Binary files a/testing/resources/embedder_tests/bug_461845674_skia.png and b/testing/resources/embedder_tests/bug_461845674_skia.png differ diff --git a/testing/resources/embedder_tests/bug_461845674_skia_mac.png b/testing/resources/embedder_tests/bug_461845674_skia_mac.png index 3b5cf186df..a38638887e 100644 Binary files a/testing/resources/embedder_tests/bug_461845674_skia_mac.png and b/testing/resources/embedder_tests/bug_461845674_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_461845674_skia_win.png b/testing/resources/embedder_tests/bug_461845674_skia_win.png index 977b7e1c30..3b7907daeb 100644 Binary files a/testing/resources/embedder_tests/bug_461845674_skia_win.png and b/testing/resources/embedder_tests/bug_461845674_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_535730401_agg_mac.png b/testing/resources/embedder_tests/bug_535730401_agg_mac.png deleted file mode 100644 index 058d64cfa8..0000000000 Binary files a/testing/resources/embedder_tests/bug_535730401_agg_mac.png and /dev/null differ diff --git a/testing/resources/embedder_tests/bug_535730401_combobox_agg_mac.png b/testing/resources/embedder_tests/bug_535730401_combobox_agg_mac.png deleted file mode 100644 index 773133b937..0000000000 Binary files a/testing/resources/embedder_tests/bug_535730401_combobox_agg_mac.png and /dev/null differ diff --git a/testing/resources/embedder_tests/bug_535730401_combobox_skia_linux.png b/testing/resources/embedder_tests/bug_535730401_combobox_skia_linux.png index 6a16f31c9e..27952e9243 100644 Binary files a/testing/resources/embedder_tests/bug_535730401_combobox_skia_linux.png and b/testing/resources/embedder_tests/bug_535730401_combobox_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bug_535730401_combobox_skia_mac.png b/testing/resources/embedder_tests/bug_535730401_combobox_skia_mac.png index d0f9f49b12..4f7e93bb14 100644 Binary files a/testing/resources/embedder_tests/bug_535730401_combobox_skia_mac.png and b/testing/resources/embedder_tests/bug_535730401_combobox_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_535730401_combobox_skia_win.png b/testing/resources/embedder_tests/bug_535730401_combobox_skia_win.png index c6699a3c7f..346fce9411 100644 Binary files a/testing/resources/embedder_tests/bug_535730401_combobox_skia_win.png and b/testing/resources/embedder_tests/bug_535730401_combobox_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_535730401_combobox_win.png b/testing/resources/embedder_tests/bug_535730401_combobox_win.png deleted file mode 100644 index 72d69ab4f5..0000000000 Binary files a/testing/resources/embedder_tests/bug_535730401_combobox_win.png and /dev/null differ diff --git a/testing/resources/embedder_tests/bug_535730401_skia_linux.png b/testing/resources/embedder_tests/bug_535730401_skia_linux.png index 58b1d208ce..7c0f98ef8a 100644 Binary files a/testing/resources/embedder_tests/bug_535730401_skia_linux.png and b/testing/resources/embedder_tests/bug_535730401_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bug_535730401_skia_mac.png b/testing/resources/embedder_tests/bug_535730401_skia_mac.png index 20f57042fb..9c22e2783f 100644 Binary files a/testing/resources/embedder_tests/bug_535730401_skia_mac.png and b/testing/resources/embedder_tests/bug_535730401_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_535730401_skia_win.png b/testing/resources/embedder_tests/bug_535730401_skia_win.png index fa6a6fc07d..ef7d6b3fc1 100644 Binary files a/testing/resources/embedder_tests/bug_535730401_skia_win.png and b/testing/resources/embedder_tests/bug_535730401_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_535730401_win.png b/testing/resources/embedder_tests/bug_535730401_win.png deleted file mode 100644 index 9cd89de150..0000000000 Binary files a/testing/resources/embedder_tests/bug_535730401_win.png and /dev/null differ diff --git a/testing/resources/embedder_tests/bug_555784.png b/testing/resources/embedder_tests/bug_555784.png new file mode 100644 index 0000000000..038433d90f Binary files /dev/null and b/testing/resources/embedder_tests/bug_555784.png differ diff --git a/testing/resources/embedder_tests/bug_664284_no_native_text.png b/testing/resources/embedder_tests/bug_664284_no_native_text.png index 2f2e5a99a5..3db3b6b063 100644 Binary files a/testing/resources/embedder_tests/bug_664284_no_native_text.png and b/testing/resources/embedder_tests/bug_664284_no_native_text.png differ diff --git a/testing/resources/embedder_tests/bug_664284_skia_linux.png b/testing/resources/embedder_tests/bug_664284_skia_linux.png index 2430c95836..650d48b24d 100644 Binary files a/testing/resources/embedder_tests/bug_664284_skia_linux.png and b/testing/resources/embedder_tests/bug_664284_skia_linux.png differ diff --git a/testing/resources/embedder_tests/bug_664284_skia_mac.png b/testing/resources/embedder_tests/bug_664284_skia_mac.png index c097f6ea5a..c6f7f89e3d 100644 Binary files a/testing/resources/embedder_tests/bug_664284_skia_mac.png and b/testing/resources/embedder_tests/bug_664284_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_664284_skia_win.png b/testing/resources/embedder_tests/bug_664284_skia_win.png index 761b5fee55..a0a5feeb9e 100644 Binary files a/testing/resources/embedder_tests/bug_664284_skia_win.png and b/testing/resources/embedder_tests/bug_664284_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_889099_flattened_skia.png b/testing/resources/embedder_tests/bug_889099_flattened_skia.png index 4331c7f56c..039d46cf20 100644 Binary files a/testing/resources/embedder_tests/bug_889099_flattened_skia.png and b/testing/resources/embedder_tests/bug_889099_flattened_skia.png differ diff --git a/testing/resources/embedder_tests/bug_889099_flattened_skia_mac.png b/testing/resources/embedder_tests/bug_889099_flattened_skia_mac.png index 8a3cd5aa1b..90fd86f98b 100644 Binary files a/testing/resources/embedder_tests/bug_889099_flattened_skia_mac.png and b/testing/resources/embedder_tests/bug_889099_flattened_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_889099_flattened_skia_win.png b/testing/resources/embedder_tests/bug_889099_flattened_skia_win.png index b30baf8f5f..1b1a3ee389 100644 Binary files a/testing/resources/embedder_tests/bug_889099_flattened_skia_win.png and b/testing/resources/embedder_tests/bug_889099_flattened_skia_win.png differ diff --git a/testing/resources/embedder_tests/bug_889099_skia.png b/testing/resources/embedder_tests/bug_889099_skia.png index 6561d13841..5e6df9e3bb 100644 Binary files a/testing/resources/embedder_tests/bug_889099_skia.png and b/testing/resources/embedder_tests/bug_889099_skia.png differ diff --git a/testing/resources/embedder_tests/bug_889099_skia_mac.png b/testing/resources/embedder_tests/bug_889099_skia_mac.png index 29951ee138..46fab79cd8 100644 Binary files a/testing/resources/embedder_tests/bug_889099_skia_mac.png and b/testing/resources/embedder_tests/bug_889099_skia_mac.png differ diff --git a/testing/resources/embedder_tests/bug_889099_skia_win.png b/testing/resources/embedder_tests/bug_889099_skia_win.png index 3fe141a242..74db7febf2 100644 Binary files a/testing/resources/embedder_tests/bug_889099_skia_win.png and b/testing/resources/embedder_tests/bug_889099_skia_win.png differ diff --git a/testing/resources/embedder_tests/encrypted_skia_linux.png b/testing/resources/embedder_tests/encrypted_skia_linux.png index 2679e137fe..a22cc8639a 100644 Binary files a/testing/resources/embedder_tests/encrypted_skia_linux.png and b/testing/resources/embedder_tests/encrypted_skia_linux.png differ diff --git a/testing/resources/embedder_tests/encrypted_skia_mac.png b/testing/resources/embedder_tests/encrypted_skia_mac.png index e71bf741a2..496bb4bf99 100644 Binary files a/testing/resources/embedder_tests/encrypted_skia_mac.png and b/testing/resources/embedder_tests/encrypted_skia_mac.png differ diff --git a/testing/resources/embedder_tests/encrypted_skia_win.png b/testing/resources/embedder_tests/encrypted_skia_win.png index 21080e01c9..0c12000d1a 100644 Binary files a/testing/resources/embedder_tests/encrypted_skia_win.png and b/testing/resources/embedder_tests/encrypted_skia_win.png differ diff --git a/testing/resources/embedder_tests/focused_text_form_with_abc_skia_linux.png b/testing/resources/embedder_tests/focused_text_form_with_abc_skia_linux.png index 1b964c982c..ea055d8584 100644 Binary files a/testing/resources/embedder_tests/focused_text_form_with_abc_skia_linux.png and b/testing/resources/embedder_tests/focused_text_form_with_abc_skia_linux.png differ diff --git a/testing/resources/embedder_tests/focused_text_form_with_abc_skia_mac.png b/testing/resources/embedder_tests/focused_text_form_with_abc_skia_mac.png index 79436d9518..accad382a4 100644 Binary files a/testing/resources/embedder_tests/focused_text_form_with_abc_skia_mac.png and b/testing/resources/embedder_tests/focused_text_form_with_abc_skia_mac.png differ diff --git a/testing/resources/embedder_tests/focused_text_form_with_abc_skia_win.png b/testing/resources/embedder_tests/focused_text_form_with_abc_skia_win.png index d6aabb6de7..a244278c16 100644 Binary files a/testing/resources/embedder_tests/focused_text_form_with_abc_skia_win.png and b/testing/resources/embedder_tests/focused_text_form_with_abc_skia_win.png differ diff --git a/testing/resources/embedder_tests/form_object_scaled_skia_linux.png b/testing/resources/embedder_tests/form_object_scaled_skia_linux.png index 7f5fc203b7..d491ee6455 100644 Binary files a/testing/resources/embedder_tests/form_object_scaled_skia_linux.png and b/testing/resources/embedder_tests/form_object_scaled_skia_linux.png differ diff --git a/testing/resources/embedder_tests/form_object_scaled_skia_mac.png b/testing/resources/embedder_tests/form_object_scaled_skia_mac.png index 17ae663a47..8ad445c3fd 100644 Binary files a/testing/resources/embedder_tests/form_object_scaled_skia_mac.png and b/testing/resources/embedder_tests/form_object_scaled_skia_mac.png differ diff --git a/testing/resources/embedder_tests/form_object_scaled_skia_win.png b/testing/resources/embedder_tests/form_object_scaled_skia_win.png index ab837f1a88..9ab0de0c00 100644 Binary files a/testing/resources/embedder_tests/form_object_scaled_skia_win.png and b/testing/resources/embedder_tests/form_object_scaled_skia_win.png differ diff --git a/testing/resources/embedder_tests/form_object_skia_linux.png b/testing/resources/embedder_tests/form_object_skia_linux.png index f0bb217e58..7cfc2d043c 100644 Binary files a/testing/resources/embedder_tests/form_object_skia_linux.png and b/testing/resources/embedder_tests/form_object_skia_linux.png differ diff --git a/testing/resources/embedder_tests/form_object_skia_mac.png b/testing/resources/embedder_tests/form_object_skia_mac.png index 6cba57850c..3ded0fe73f 100644 Binary files a/testing/resources/embedder_tests/form_object_skia_mac.png and b/testing/resources/embedder_tests/form_object_skia_mac.png differ diff --git a/testing/resources/embedder_tests/form_object_skia_win.png b/testing/resources/embedder_tests/form_object_skia_win.png index 37838fd033..85ea1fe9f5 100644 Binary files a/testing/resources/embedder_tests/form_object_skia_win.png and b/testing/resources/embedder_tests/form_object_skia_win.png differ diff --git a/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_linux.png b/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_linux.png index 24bcc55d3f..9bb3766c64 100644 Binary files a/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_linux.png and b/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_linux.png differ diff --git a/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_mac.png b/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_mac.png index c2c688a236..e85de509bd 100644 Binary files a/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_mac.png and b/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_mac.png differ diff --git a/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_win.png b/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_win.png index 80a6e8188d..ff520a4f9f 100644 Binary files a/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_win.png and b/testing/resources/embedder_tests/freetext_annotation_without_da_modified_skia_win.png differ diff --git a/testing/resources/embedder_tests/freetext_annotation_without_da_skia_linux.png b/testing/resources/embedder_tests/freetext_annotation_without_da_skia_linux.png index 69a3f23a85..e58455b6fe 100644 Binary files a/testing/resources/embedder_tests/freetext_annotation_without_da_skia_linux.png and b/testing/resources/embedder_tests/freetext_annotation_without_da_skia_linux.png differ diff --git a/testing/resources/embedder_tests/freetext_annotation_without_da_skia_mac.png b/testing/resources/embedder_tests/freetext_annotation_without_da_skia_mac.png index 6e0a016e8e..e1549e1275 100644 Binary files a/testing/resources/embedder_tests/freetext_annotation_without_da_skia_mac.png and b/testing/resources/embedder_tests/freetext_annotation_without_da_skia_mac.png differ diff --git a/testing/resources/embedder_tests/freetext_annotation_without_da_skia_win.png b/testing/resources/embedder_tests/freetext_annotation_without_da_skia_win.png index 9a4116e48e..83ba6771ac 100644 Binary files a/testing/resources/embedder_tests/freetext_annotation_without_da_skia_win.png and b/testing/resources/embedder_tests/freetext_annotation_without_da_skia_win.png differ diff --git a/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_linux.png b/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_linux.png index 5664e0915f..e97e4eb783 100644 Binary files a/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_linux.png and b/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_linux.png differ diff --git a/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_mac.png b/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_mac.png index 3f043510cc..491293ad60 100644 Binary files a/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_mac.png and b/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_mac.png differ diff --git a/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_win.png b/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_win.png index dd0358bba4..2ee719646d 100644 Binary files a/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_win.png and b/testing/resources/embedder_tests/goodbye_world_scaled_10_skia_win.png differ diff --git a/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_linux.png b/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_linux.png index 9e5d533930..33a64163e5 100644 Binary files a/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_linux.png and b/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_linux.png differ diff --git a/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_mac.png b/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_mac.png index 2855282b34..77ae7400a6 100644 Binary files a/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_mac.png and b/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_mac.png differ diff --git a/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_win.png b/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_win.png index d390dde1a1..a13a20fc0e 100644 Binary files a/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_win.png and b/testing/resources/embedder_tests/goodbye_world_scaled_1_skia_win.png differ diff --git a/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_linux.png b/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_linux.png index dabfe701c8..02a0166b38 100644 Binary files a/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_linux.png and b/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_linux.png differ diff --git a/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_mac.png b/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_mac.png index 4bc5939087..5dcf787db3 100644 Binary files a/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_mac.png and b/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_mac.png differ diff --git a/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_win.png b/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_win.png index 815ffd08ff..42d57a08f2 100644 Binary files a/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_win.png and b/testing/resources/embedder_tests/goodbye_world_scaled_2_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_added_paths_skia_linux.png b/testing/resources/embedder_tests/hello_world_added_paths_skia_linux.png index c8e72cae26..4deffb1ef7 100644 Binary files a/testing/resources/embedder_tests/hello_world_added_paths_skia_linux.png and b/testing/resources/embedder_tests/hello_world_added_paths_skia_linux.png differ diff --git a/testing/resources/embedder_tests/hello_world_added_paths_skia_mac.png b/testing/resources/embedder_tests/hello_world_added_paths_skia_mac.png index 12aec37c18..ac184c6776 100644 Binary files a/testing/resources/embedder_tests/hello_world_added_paths_skia_mac.png and b/testing/resources/embedder_tests/hello_world_added_paths_skia_mac.png differ diff --git a/testing/resources/embedder_tests/hello_world_added_paths_skia_win.png b/testing/resources/embedder_tests/hello_world_added_paths_skia_win.png index 19bd842601..b9135dc816 100644 Binary files a/testing/resources/embedder_tests/hello_world_added_paths_skia_win.png and b/testing/resources/embedder_tests/hello_world_added_paths_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_forced_dark_skia_linux.png b/testing/resources/embedder_tests/hello_world_forced_dark_skia_linux.png index 570c19ef0e..345272560d 100644 Binary files a/testing/resources/embedder_tests/hello_world_forced_dark_skia_linux.png and b/testing/resources/embedder_tests/hello_world_forced_dark_skia_linux.png differ diff --git a/testing/resources/embedder_tests/hello_world_forced_dark_skia_mac.png b/testing/resources/embedder_tests/hello_world_forced_dark_skia_mac.png index ead3f2acd2..9cce5a07fc 100644 Binary files a/testing/resources/embedder_tests/hello_world_forced_dark_skia_mac.png and b/testing/resources/embedder_tests/hello_world_forced_dark_skia_mac.png differ diff --git a/testing/resources/embedder_tests/hello_world_forced_dark_skia_win.png b/testing/resources/embedder_tests/hello_world_forced_dark_skia_win.png index 4cd3c76a30..15bcd3b38d 100644 Binary files a/testing/resources/embedder_tests/hello_world_forced_dark_skia_win.png and b/testing/resources/embedder_tests/hello_world_forced_dark_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_lcd_skia_linux.png b/testing/resources/embedder_tests/hello_world_lcd_skia_linux.png index 167bc71b7d..f3d082b552 100644 Binary files a/testing/resources/embedder_tests/hello_world_lcd_skia_linux.png and b/testing/resources/embedder_tests/hello_world_lcd_skia_linux.png differ diff --git a/testing/resources/embedder_tests/hello_world_lcd_skia_mac.png b/testing/resources/embedder_tests/hello_world_lcd_skia_mac.png index a9f934805e..fbceb90a2d 100644 Binary files a/testing/resources/embedder_tests/hello_world_lcd_skia_mac.png and b/testing/resources/embedder_tests/hello_world_lcd_skia_mac.png differ diff --git a/testing/resources/embedder_tests/hello_world_lcd_skia_win.png b/testing/resources/embedder_tests/hello_world_lcd_skia_win.png index fea8136ab2..dcace6262d 100644 Binary files a/testing/resources/embedder_tests/hello_world_lcd_skia_win.png and b/testing/resources/embedder_tests/hello_world_lcd_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_no_smoothtext_skia_linux.png b/testing/resources/embedder_tests/hello_world_no_smoothtext_skia_linux.png index 26957566f2..eb9a900bdd 100644 Binary files a/testing/resources/embedder_tests/hello_world_no_smoothtext_skia_linux.png and b/testing/resources/embedder_tests/hello_world_no_smoothtext_skia_linux.png differ diff --git a/testing/resources/embedder_tests/hello_world_no_smoothtext_skia_win.png b/testing/resources/embedder_tests/hello_world_no_smoothtext_skia_win.png index 30a41719f8..fd25580610 100644 Binary files a/testing/resources/embedder_tests/hello_world_no_smoothtext_skia_win.png and b/testing/resources/embedder_tests/hello_world_no_smoothtext_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_linux.png b/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_linux.png index b89ef1a920..1f9395913f 100644 Binary files a/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_linux.png and b/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_linux.png differ diff --git a/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_mac.png b/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_mac.png index a50834197c..01ea206e60 100644 Binary files a/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_mac.png and b/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_mac.png differ diff --git a/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_win.png b/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_win.png index 0881a95b9a..179b5775a6 100644 Binary files a/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_win.png and b/testing/resources/embedder_tests/hello_world_removed_hello_world_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_removed_skia_linux.png b/testing/resources/embedder_tests/hello_world_removed_skia_linux.png index 59bdee9747..a58639db86 100644 Binary files a/testing/resources/embedder_tests/hello_world_removed_skia_linux.png and b/testing/resources/embedder_tests/hello_world_removed_skia_linux.png differ diff --git a/testing/resources/embedder_tests/hello_world_removed_skia_mac.png b/testing/resources/embedder_tests/hello_world_removed_skia_mac.png index f7b4c7029b..9bd01fdde4 100644 Binary files a/testing/resources/embedder_tests/hello_world_removed_skia_mac.png and b/testing/resources/embedder_tests/hello_world_removed_skia_mac.png differ diff --git a/testing/resources/embedder_tests/hello_world_removed_skia_win.png b/testing/resources/embedder_tests/hello_world_removed_skia_win.png index 5e024c8476..1af9e44411 100644 Binary files a/testing/resources/embedder_tests/hello_world_removed_skia_win.png and b/testing/resources/embedder_tests/hello_world_removed_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_scaled_10_skia_linux.png b/testing/resources/embedder_tests/hello_world_scaled_10_skia_linux.png index c441062177..9ffdd40e48 100644 Binary files a/testing/resources/embedder_tests/hello_world_scaled_10_skia_linux.png and b/testing/resources/embedder_tests/hello_world_scaled_10_skia_linux.png differ diff --git a/testing/resources/embedder_tests/hello_world_scaled_10_skia_mac.png b/testing/resources/embedder_tests/hello_world_scaled_10_skia_mac.png index 1dfebca027..8d06bc97bf 100644 Binary files a/testing/resources/embedder_tests/hello_world_scaled_10_skia_mac.png and b/testing/resources/embedder_tests/hello_world_scaled_10_skia_mac.png differ diff --git a/testing/resources/embedder_tests/hello_world_scaled_10_skia_win.png b/testing/resources/embedder_tests/hello_world_scaled_10_skia_win.png index 17209d5eec..05fa51ce63 100644 Binary files a/testing/resources/embedder_tests/hello_world_scaled_10_skia_win.png and b/testing/resources/embedder_tests/hello_world_scaled_10_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_scaled_1_skia_linux.png b/testing/resources/embedder_tests/hello_world_scaled_1_skia_linux.png index 87b227d92e..28cb606885 100644 Binary files a/testing/resources/embedder_tests/hello_world_scaled_1_skia_linux.png and b/testing/resources/embedder_tests/hello_world_scaled_1_skia_linux.png differ diff --git a/testing/resources/embedder_tests/hello_world_scaled_1_skia_mac.png b/testing/resources/embedder_tests/hello_world_scaled_1_skia_mac.png index 6943d411ae..e3a651481d 100644 Binary files a/testing/resources/embedder_tests/hello_world_scaled_1_skia_mac.png and b/testing/resources/embedder_tests/hello_world_scaled_1_skia_mac.png differ diff --git a/testing/resources/embedder_tests/hello_world_scaled_1_skia_win.png b/testing/resources/embedder_tests/hello_world_scaled_1_skia_win.png index 32bddb5c4b..3c6b0b7f81 100644 Binary files a/testing/resources/embedder_tests/hello_world_scaled_1_skia_win.png and b/testing/resources/embedder_tests/hello_world_scaled_1_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_scaled_2_skia_linux.png b/testing/resources/embedder_tests/hello_world_scaled_2_skia_linux.png index 1031fcefc3..fae96ef0ee 100644 Binary files a/testing/resources/embedder_tests/hello_world_scaled_2_skia_linux.png and b/testing/resources/embedder_tests/hello_world_scaled_2_skia_linux.png differ diff --git a/testing/resources/embedder_tests/hello_world_scaled_2_skia_mac.png b/testing/resources/embedder_tests/hello_world_scaled_2_skia_mac.png index 3f5b792eb2..b3da9aee1a 100644 Binary files a/testing/resources/embedder_tests/hello_world_scaled_2_skia_mac.png and b/testing/resources/embedder_tests/hello_world_scaled_2_skia_mac.png differ diff --git a/testing/resources/embedder_tests/hello_world_scaled_2_skia_win.png b/testing/resources/embedder_tests/hello_world_scaled_2_skia_win.png index a13ae46c96..75ba91fcb9 100644 Binary files a/testing/resources/embedder_tests/hello_world_scaled_2_skia_win.png and b/testing/resources/embedder_tests/hello_world_scaled_2_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_skia.png b/testing/resources/embedder_tests/hello_world_skia.png index 167bc71b7d..f3d082b552 100644 Binary files a/testing/resources/embedder_tests/hello_world_skia.png and b/testing/resources/embedder_tests/hello_world_skia.png differ diff --git a/testing/resources/embedder_tests/hello_world_skia_mac.png b/testing/resources/embedder_tests/hello_world_skia_mac.png index a9f934805e..fbceb90a2d 100644 Binary files a/testing/resources/embedder_tests/hello_world_skia_mac.png and b/testing/resources/embedder_tests/hello_world_skia_mac.png differ diff --git a/testing/resources/embedder_tests/hello_world_skia_win.png b/testing/resources/embedder_tests/hello_world_skia_win.png index 6c16ea8be6..698dee978d 100644 Binary files a/testing/resources/embedder_tests/hello_world_skia_win.png and b/testing/resources/embedder_tests/hello_world_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_linux.png b/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_linux.png index 0ad0455751..08a4c85964 100644 Binary files a/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_linux.png and b/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_linux.png differ diff --git a/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_mac.png b/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_mac.png index 6e5f47dadf..83a4d29298 100644 Binary files a/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_mac.png and b/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_mac.png differ diff --git a/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_win.png b/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_win.png index cc238417ce..286070a9c9 100644 Binary files a/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_win.png and b/testing/resources/embedder_tests/hello_world_split_streams_removed_hello_world_skia_win.png differ diff --git a/testing/resources/embedder_tests/hello_world_with_changed_text_skia_linux.png b/testing/resources/embedder_tests/hello_world_with_changed_text_skia_linux.png index 6726f9ba7e..f98460bcb4 100644 Binary files a/testing/resources/embedder_tests/hello_world_with_changed_text_skia_linux.png and b/testing/resources/embedder_tests/hello_world_with_changed_text_skia_linux.png differ diff --git a/testing/resources/embedder_tests/hello_world_with_changed_text_skia_mac.png b/testing/resources/embedder_tests/hello_world_with_changed_text_skia_mac.png index dfc5e4da43..2477eb6aaa 100644 Binary files a/testing/resources/embedder_tests/hello_world_with_changed_text_skia_mac.png and b/testing/resources/embedder_tests/hello_world_with_changed_text_skia_mac.png differ diff --git a/testing/resources/embedder_tests/hello_world_with_changed_text_skia_win.png b/testing/resources/embedder_tests/hello_world_with_changed_text_skia_win.png index 9cd6a5b901..1090f68723 100644 Binary files a/testing/resources/embedder_tests/hello_world_with_changed_text_skia_win.png and b/testing/resources/embedder_tests/hello_world_with_changed_text_skia_win.png differ diff --git a/testing/resources/embedder_tests/highlight_with_color_scheme_skia_mac.png b/testing/resources/embedder_tests/highlight_with_color_scheme_skia_mac.png index c06228f731..483c76c5da 100644 Binary files a/testing/resources/embedder_tests/highlight_with_color_scheme_skia_mac.png and b/testing/resources/embedder_tests/highlight_with_color_scheme_skia_mac.png differ diff --git a/testing/resources/embedder_tests/highlight_with_stroke_skia_mac.png b/testing/resources/embedder_tests/highlight_with_stroke_skia_mac.png index 4b384ba594..015a78384a 100644 Binary files a/testing/resources/embedder_tests/highlight_with_stroke_skia_mac.png and b/testing/resources/embedder_tests/highlight_with_stroke_skia_mac.png differ diff --git a/testing/resources/embedder_tests/ink_with_color_scheme_skia_linux.png b/testing/resources/embedder_tests/ink_with_color_scheme_skia_linux.png index 1b53483775..d7dc3aac6e 100644 Binary files a/testing/resources/embedder_tests/ink_with_color_scheme_skia_linux.png and b/testing/resources/embedder_tests/ink_with_color_scheme_skia_linux.png differ diff --git a/testing/resources/embedder_tests/ink_with_color_scheme_skia_win.png b/testing/resources/embedder_tests/ink_with_color_scheme_skia_win.png index 0e66648bed..514164022b 100644 Binary files a/testing/resources/embedder_tests/ink_with_color_scheme_skia_win.png and b/testing/resources/embedder_tests/ink_with_color_scheme_skia_win.png differ diff --git a/testing/resources/embedder_tests/japanese_cid_font_text_skia.png b/testing/resources/embedder_tests/japanese_cid_font_text_skia.png index 4e03d3775f..c61676073b 100644 Binary files a/testing/resources/embedder_tests/japanese_cid_font_text_skia.png and b/testing/resources/embedder_tests/japanese_cid_font_text_skia.png differ diff --git a/testing/resources/embedder_tests/loaded_text_skia_linux.png b/testing/resources/embedder_tests/loaded_text_skia_linux.png index 2e22c9f5d7..c957e32e65 100644 Binary files a/testing/resources/embedder_tests/loaded_text_skia_linux.png and b/testing/resources/embedder_tests/loaded_text_skia_linux.png differ diff --git a/testing/resources/embedder_tests/loaded_text_skia_mac.png b/testing/resources/embedder_tests/loaded_text_skia_mac.png index 758ae0b3c2..b25c5eccb9 100644 Binary files a/testing/resources/embedder_tests/loaded_text_skia_mac.png and b/testing/resources/embedder_tests/loaded_text_skia_mac.png differ diff --git a/testing/resources/embedder_tests/loaded_text_skia_win.png b/testing/resources/embedder_tests/loaded_text_skia_win.png index 912b85977d..d9adc8946f 100644 Binary files a/testing/resources/embedder_tests/loaded_text_skia_win.png and b/testing/resources/embedder_tests/loaded_text_skia_win.png differ diff --git a/testing/resources/embedder_tests/noto_sans_sc_skia_linux.png b/testing/resources/embedder_tests/noto_sans_sc_skia_linux.png index 8beb9881ba..a3e6e5dd92 100644 Binary files a/testing/resources/embedder_tests/noto_sans_sc_skia_linux.png and b/testing/resources/embedder_tests/noto_sans_sc_skia_linux.png differ diff --git a/testing/resources/embedder_tests/noto_sans_sc_skia_win.png b/testing/resources/embedder_tests/noto_sans_sc_skia_win.png index a0078e536e..8df1b7a4f6 100644 Binary files a/testing/resources/embedder_tests/noto_sans_sc_skia_win.png and b/testing/resources/embedder_tests/noto_sans_sc_skia_win.png differ diff --git a/testing/resources/embedder_tests/pattern_fill.png b/testing/resources/embedder_tests/pattern_fill.png new file mode 100644 index 0000000000..3d4c7bde26 Binary files /dev/null and b/testing/resources/embedder_tests/pattern_fill.png differ diff --git a/testing/resources/embedder_tests/pattern_fill_uncolored.png b/testing/resources/embedder_tests/pattern_fill_uncolored.png new file mode 100644 index 0000000000..764ddc5544 Binary files /dev/null and b/testing/resources/embedder_tests/pattern_fill_uncolored.png differ diff --git a/testing/resources/embedder_tests/pattern_stroke.png b/testing/resources/embedder_tests/pattern_stroke.png new file mode 100644 index 0000000000..3a84afeac4 Binary files /dev/null and b/testing/resources/embedder_tests/pattern_stroke.png differ diff --git a/testing/resources/embedder_tests/pattern_stroke_uncolored.png b/testing/resources/embedder_tests/pattern_stroke_uncolored.png new file mode 100644 index 0000000000..fb17d2c423 Binary files /dev/null and b/testing/resources/embedder_tests/pattern_stroke_uncolored.png differ diff --git a/testing/resources/embedder_tests/rotated_text_10_skia_mac.png b/testing/resources/embedder_tests/rotated_text_10_skia_mac.png index c5f55fcf6e..d45ac085f6 100644 Binary files a/testing/resources/embedder_tests/rotated_text_10_skia_mac.png and b/testing/resources/embedder_tests/rotated_text_10_skia_mac.png differ diff --git a/testing/resources/embedder_tests/rotated_text_1_skia_mac.png b/testing/resources/embedder_tests/rotated_text_1_skia_mac.png index 431d1653b9..55f5b14e4d 100644 Binary files a/testing/resources/embedder_tests/rotated_text_1_skia_mac.png and b/testing/resources/embedder_tests/rotated_text_1_skia_mac.png differ diff --git a/testing/resources/embedder_tests/rotated_text_2_skia_mac.png b/testing/resources/embedder_tests/rotated_text_2_skia_mac.png index dc623708ca..727631d327 100644 Binary files a/testing/resources/embedder_tests/rotated_text_2_skia_mac.png and b/testing/resources/embedder_tests/rotated_text_2_skia_mac.png differ diff --git a/testing/resources/embedder_tests/save_multiple_fonts_skia_linux.png b/testing/resources/embedder_tests/save_multiple_fonts_skia_linux.png index c4a2f25c91..3a311c53db 100644 Binary files a/testing/resources/embedder_tests/save_multiple_fonts_skia_linux.png and b/testing/resources/embedder_tests/save_multiple_fonts_skia_linux.png differ diff --git a/testing/resources/embedder_tests/save_multiple_fonts_skia_mac.png b/testing/resources/embedder_tests/save_multiple_fonts_skia_mac.png index 5e1a6ada60..4e37f2bb14 100644 Binary files a/testing/resources/embedder_tests/save_multiple_fonts_skia_mac.png and b/testing/resources/embedder_tests/save_multiple_fonts_skia_mac.png differ diff --git a/testing/resources/embedder_tests/save_multiple_fonts_skia_win.png b/testing/resources/embedder_tests/save_multiple_fonts_skia_win.png index 45626519de..8544b0d61d 100644 Binary files a/testing/resources/embedder_tests/save_multiple_fonts_skia_win.png and b/testing/resources/embedder_tests/save_multiple_fonts_skia_win.png differ diff --git a/testing/resources/embedder_tests/save_new_text_skia_linux.png b/testing/resources/embedder_tests/save_new_text_skia_linux.png index 45033509de..fec8aa3a33 100644 Binary files a/testing/resources/embedder_tests/save_new_text_skia_linux.png and b/testing/resources/embedder_tests/save_new_text_skia_linux.png differ diff --git a/testing/resources/embedder_tests/save_new_text_skia_mac.png b/testing/resources/embedder_tests/save_new_text_skia_mac.png index 079f294f20..7d411f5124 100644 Binary files a/testing/resources/embedder_tests/save_new_text_skia_mac.png and b/testing/resources/embedder_tests/save_new_text_skia_mac.png differ diff --git a/testing/resources/embedder_tests/save_new_text_skia_win.png b/testing/resources/embedder_tests/save_new_text_skia_win.png index 9598a8371f..bc6a3ed3c3 100644 Binary files a/testing/resources/embedder_tests/save_new_text_skia_win.png and b/testing/resources/embedder_tests/save_new_text_skia_win.png differ diff --git a/testing/resources/embedder_tests/set_positions_bengali2_skia.png b/testing/resources/embedder_tests/set_positions_bengali2_skia.png index 0adb5bed64..22ebafe7a8 100644 Binary files a/testing/resources/embedder_tests/set_positions_bengali2_skia.png and b/testing/resources/embedder_tests/set_positions_bengali2_skia.png differ diff --git a/testing/resources/embedder_tests/set_positions_bengali2_skia_win.png b/testing/resources/embedder_tests/set_positions_bengali2_skia_win.png index e00a15fcec..a406306399 100644 Binary files a/testing/resources/embedder_tests/set_positions_bengali2_skia_win.png and b/testing/resources/embedder_tests/set_positions_bengali2_skia_win.png differ diff --git a/testing/resources/embedder_tests/set_positions_bengali3_skia.png b/testing/resources/embedder_tests/set_positions_bengali3_skia.png index 55beb2b4d0..1dd860da63 100644 Binary files a/testing/resources/embedder_tests/set_positions_bengali3_skia.png and b/testing/resources/embedder_tests/set_positions_bengali3_skia.png differ diff --git a/testing/resources/embedder_tests/set_positions_bengali3_skia_win.png b/testing/resources/embedder_tests/set_positions_bengali3_skia_win.png index c517861ab4..8fde85e1be 100644 Binary files a/testing/resources/embedder_tests/set_positions_bengali3_skia_win.png and b/testing/resources/embedder_tests/set_positions_bengali3_skia_win.png differ diff --git a/testing/resources/embedder_tests/split_streams_removed_stream1_skia_linux.png b/testing/resources/embedder_tests/split_streams_removed_stream1_skia_linux.png index 29afea0a40..26468ff156 100644 Binary files a/testing/resources/embedder_tests/split_streams_removed_stream1_skia_linux.png and b/testing/resources/embedder_tests/split_streams_removed_stream1_skia_linux.png differ diff --git a/testing/resources/embedder_tests/split_streams_removed_stream1_skia_mac.png b/testing/resources/embedder_tests/split_streams_removed_stream1_skia_mac.png index 7c082a536f..bc91c5c9f4 100644 Binary files a/testing/resources/embedder_tests/split_streams_removed_stream1_skia_mac.png and b/testing/resources/embedder_tests/split_streams_removed_stream1_skia_mac.png differ diff --git a/testing/resources/embedder_tests/split_streams_removed_stream1_skia_mac_x86.png b/testing/resources/embedder_tests/split_streams_removed_stream1_skia_mac_x86.png new file mode 100644 index 0000000000..d1780d5199 Binary files /dev/null and b/testing/resources/embedder_tests/split_streams_removed_stream1_skia_mac_x86.png differ diff --git a/testing/resources/embedder_tests/split_streams_removed_stream1_skia_win.png b/testing/resources/embedder_tests/split_streams_removed_stream1_skia_win.png index 84a6cd2aa9..830206a74c 100644 Binary files a/testing/resources/embedder_tests/split_streams_removed_stream1_skia_win.png and b/testing/resources/embedder_tests/split_streams_removed_stream1_skia_win.png differ diff --git a/testing/resources/embedder_tests/stamp_with_color_scheme_skia_linux.png b/testing/resources/embedder_tests/stamp_with_color_scheme_skia_linux.png index 9c9d21717f..e6ab1f6e13 100644 Binary files a/testing/resources/embedder_tests/stamp_with_color_scheme_skia_linux.png and b/testing/resources/embedder_tests/stamp_with_color_scheme_skia_linux.png differ diff --git a/testing/resources/embedder_tests/stamp_with_color_scheme_skia_mac.png b/testing/resources/embedder_tests/stamp_with_color_scheme_skia_mac.png index 11e262c52b..8c3ffcaa1b 100644 Binary files a/testing/resources/embedder_tests/stamp_with_color_scheme_skia_mac.png and b/testing/resources/embedder_tests/stamp_with_color_scheme_skia_mac.png differ diff --git a/testing/resources/embedder_tests/stamp_with_color_scheme_skia_win.png b/testing/resources/embedder_tests/stamp_with_color_scheme_skia_win.png index 3291d1ba88..3d13d7fd8d 100644 Binary files a/testing/resources/embedder_tests/stamp_with_color_scheme_skia_win.png and b/testing/resources/embedder_tests/stamp_with_color_scheme_skia_win.png differ diff --git a/testing/resources/embedder_tests/text_form_no_highlight_skia_linux.png b/testing/resources/embedder_tests/text_form_no_highlight_skia_linux.png index 8e96af8ee5..5e9c43a6c4 100644 Binary files a/testing/resources/embedder_tests/text_form_no_highlight_skia_linux.png and b/testing/resources/embedder_tests/text_form_no_highlight_skia_linux.png differ diff --git a/testing/resources/embedder_tests/text_form_no_highlight_skia_mac.png b/testing/resources/embedder_tests/text_form_no_highlight_skia_mac.png index 04e368d4a4..5b02ce7cd0 100644 Binary files a/testing/resources/embedder_tests/text_form_no_highlight_skia_mac.png and b/testing/resources/embedder_tests/text_form_no_highlight_skia_mac.png differ diff --git a/testing/resources/embedder_tests/text_form_no_highlight_skia_win.png b/testing/resources/embedder_tests/text_form_no_highlight_skia_win.png index dfa1b5b297..1cd2dfcecd 100644 Binary files a/testing/resources/embedder_tests/text_form_no_highlight_skia_win.png and b/testing/resources/embedder_tests/text_form_no_highlight_skia_win.png differ diff --git a/testing/resources/embedder_tests/text_form_skia_linux.png b/testing/resources/embedder_tests/text_form_skia_linux.png index e4644f62ea..ca89ce3b24 100644 Binary files a/testing/resources/embedder_tests/text_form_skia_linux.png and b/testing/resources/embedder_tests/text_form_skia_linux.png differ diff --git a/testing/resources/embedder_tests/text_form_skia_mac.png b/testing/resources/embedder_tests/text_form_skia_mac.png index ef0cdfaee0..135d170939 100644 Binary files a/testing/resources/embedder_tests/text_form_skia_mac.png and b/testing/resources/embedder_tests/text_form_skia_mac.png differ diff --git a/testing/resources/embedder_tests/text_form_skia_win.png b/testing/resources/embedder_tests/text_form_skia_win.png index d6c537fe7a..3a6b444f03 100644 Binary files a/testing/resources/embedder_tests/text_form_skia_win.png and b/testing/resources/embedder_tests/text_form_skia_win.png differ diff --git a/testing/resources/embedder_tests/text_in_page_marked_agg_mac_x86.png b/testing/resources/embedder_tests/text_in_page_marked_agg_mac_x86.png index 584c4e10e7..11c2d1dcb0 100644 Binary files a/testing/resources/embedder_tests/text_in_page_marked_agg_mac_x86.png and b/testing/resources/embedder_tests/text_in_page_marked_agg_mac_x86.png differ diff --git a/testing/resources/embedder_tests/text_in_page_marked_non_primes_agg_mac_x86.png b/testing/resources/embedder_tests/text_in_page_marked_non_primes_agg_mac_x86.png index 7ef7b012de..855c8db698 100644 Binary files a/testing/resources/embedder_tests/text_in_page_marked_non_primes_agg_mac_x86.png and b/testing/resources/embedder_tests/text_in_page_marked_non_primes_agg_mac_x86.png differ diff --git a/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_linux.png b/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_linux.png index 387d7b8e53..bea4e2bc2d 100644 Binary files a/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_linux.png and b/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_linux.png differ diff --git a/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_mac.png b/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_mac.png index a91bfeb7f5..637ea516d2 100644 Binary files a/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_mac.png and b/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_mac.png differ diff --git a/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_mac_x86.png b/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_mac_x86.png new file mode 100644 index 0000000000..a2afb32f45 Binary files /dev/null and b/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_mac_x86.png differ diff --git a/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_win.png b/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_win.png index e496aee70c..95df7b02ef 100644 Binary files a/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_win.png and b/testing/resources/embedder_tests/text_in_page_marked_non_primes_skia_win.png differ diff --git a/testing/resources/embedder_tests/text_in_page_marked_skia_linux.png b/testing/resources/embedder_tests/text_in_page_marked_skia_linux.png index deb1e2fc3a..d3050d2460 100644 Binary files a/testing/resources/embedder_tests/text_in_page_marked_skia_linux.png and b/testing/resources/embedder_tests/text_in_page_marked_skia_linux.png differ diff --git a/testing/resources/embedder_tests/text_in_page_marked_skia_mac.png b/testing/resources/embedder_tests/text_in_page_marked_skia_mac.png index 68c1023a45..fef8a22212 100644 Binary files a/testing/resources/embedder_tests/text_in_page_marked_skia_mac.png and b/testing/resources/embedder_tests/text_in_page_marked_skia_mac.png differ diff --git a/testing/resources/embedder_tests/text_in_page_marked_skia_mac_x86.png b/testing/resources/embedder_tests/text_in_page_marked_skia_mac_x86.png new file mode 100644 index 0000000000..938600f748 Binary files /dev/null and b/testing/resources/embedder_tests/text_in_page_marked_skia_mac_x86.png differ diff --git a/testing/resources/embedder_tests/text_in_page_marked_skia_win.png b/testing/resources/embedder_tests/text_in_page_marked_skia_win.png index 030e7643ae..b475768739 100644 Binary files a/testing/resources/embedder_tests/text_in_page_marked_skia_win.png and b/testing/resources/embedder_tests/text_in_page_marked_skia_win.png differ diff --git a/testing/resources/embedder_tests/text_render_mode_skia_linux.png b/testing/resources/embedder_tests/text_render_mode_skia_linux.png index 34bde6efd4..afc00715af 100644 Binary files a/testing/resources/embedder_tests/text_render_mode_skia_linux.png and b/testing/resources/embedder_tests/text_render_mode_skia_linux.png differ diff --git a/testing/resources/embedder_tests/text_render_mode_skia_mac.png b/testing/resources/embedder_tests/text_render_mode_skia_mac.png index 18e0b9b1b8..58a619940c 100644 Binary files a/testing/resources/embedder_tests/text_render_mode_skia_mac.png and b/testing/resources/embedder_tests/text_render_mode_skia_mac.png differ diff --git a/testing/resources/embedder_tests/text_render_mode_skia_win.png b/testing/resources/embedder_tests/text_render_mode_skia_win.png index a2c417612e..e5c26faf50 100644 Binary files a/testing/resources/embedder_tests/text_render_mode_skia_win.png and b/testing/resources/embedder_tests/text_render_mode_skia_win.png differ diff --git a/testing/resources/embedder_tests/truetype_font_text_skia_linux.png b/testing/resources/embedder_tests/truetype_font_text_skia_linux.png index 457d55e1d6..7fbef11daa 100644 Binary files a/testing/resources/embedder_tests/truetype_font_text_skia_linux.png and b/testing/resources/embedder_tests/truetype_font_text_skia_linux.png differ diff --git a/testing/resources/embedder_tests/truetype_font_text_skia_mac.png b/testing/resources/embedder_tests/truetype_font_text_skia_mac.png index beec574182..c2f857f918 100644 Binary files a/testing/resources/embedder_tests/truetype_font_text_skia_mac.png and b/testing/resources/embedder_tests/truetype_font_text_skia_mac.png differ diff --git a/testing/resources/embedder_tests/truetype_font_text_skia_win.png b/testing/resources/embedder_tests/truetype_font_text_skia_win.png index 20d6fdaf47..6cb0761f2e 100644 Binary files a/testing/resources/embedder_tests/truetype_font_text_skia_win.png and b/testing/resources/embedder_tests/truetype_font_text_skia_win.png differ diff --git a/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_linux.png b/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_linux.png index 43479db825..72565b7566 100644 Binary files a/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_linux.png and b/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_linux.png differ diff --git a/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_mac.png b/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_mac.png index abc1dba46d..b55c8b18d6 100644 Binary files a/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_mac.png and b/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_mac.png differ diff --git a/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_win.png b/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_win.png index 50cd61abae..a99e0a47c8 100644 Binary files a/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_win.png and b/testing/resources/embedder_tests/unfocused_text_form_with_abc_skia_win.png differ diff --git a/testing/resources/embedder_tests/vertical_text_positioned_skia_win.png b/testing/resources/embedder_tests/vertical_text_positioned_skia_win.png index f9aa63f891..44f2a935c0 100644 Binary files a/testing/resources/embedder_tests/vertical_text_positioned_skia_win.png and b/testing/resources/embedder_tests/vertical_text_positioned_skia_win.png differ diff --git a/testing/resources/embedder_tests/xfa_multiline_textfield_backspace_skia_win.png b/testing/resources/embedder_tests/xfa_multiline_textfield_backspace_skia_win.png index bc8997dc6e..46c598bfea 100644 Binary files a/testing/resources/embedder_tests/xfa_multiline_textfield_backspace_skia_win.png and b/testing/resources/embedder_tests/xfa_multiline_textfield_backspace_skia_win.png differ diff --git a/testing/resources/embedder_tests/xfa_multiline_textfield_filled_skia_win.png b/testing/resources/embedder_tests/xfa_multiline_textfield_filled_skia_win.png index 874979b236..e5ea91a6eb 100644 Binary files a/testing/resources/embedder_tests/xfa_multiline_textfield_filled_skia_win.png and b/testing/resources/embedder_tests/xfa_multiline_textfield_filled_skia_win.png differ diff --git a/testing/resources/javascript/named_action_expected.txt b/testing/resources/javascript/named_action_expected.txt deleted file mode 100644 index 1099a7fcd4..0000000000 --- a/testing/resources/javascript/named_action_expected.txt +++ /dev/null @@ -1 +0,0 @@ -Execute named action: Print diff --git a/testing/resources/javascript/textbox_keyboard_event.evt b/testing/resources/javascript/textbox_keyboard_event.evt new file mode 100644 index 0000000000..f164fdd11c --- /dev/null +++ b/testing/resources/javascript/textbox_keyboard_event.evt @@ -0,0 +1,3 @@ +mousedown,left,100,100 +mouseup,left,100,100 +charcode,65 diff --git a/testing/resources/javascript/textbox_keyboard_event.in b/testing/resources/javascript/textbox_keyboard_event.in new file mode 100644 index 0000000000..080a117d55 --- /dev/null +++ b/testing/resources/javascript/textbox_keyboard_event.in @@ -0,0 +1,55 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R + /AcroForm << + /Fields [4 0 R] + /DR << + /Font << + /F1 5 0 R + >> + >> + >> +>> +endobj +{{object 2 0}} << + /Type /Pages + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /MediaBox [0 0 200 200] + /Annots [4 0 R] +>> +endobj +{{object 4 0}} << + /Type /Annot + /Subtype /Widget + /FT /Tx + /T (MyField) + /DA (0 0 0 rg /F1 12 Tf) + /Rect [50 50 150 150] + /AA << + /K 6 0 R + >> +>> +endobj +{{object 5 0}} << + /Type /Font + /Subtype /Type1 + /BaseFont /Helvetica +>> +endobj +{{object 6 0}} << + /Type /Action + /S /JavaScript + /JS (app.alert('done')) +>> +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/javascript/textbox_keyboard_event_expected.txt b/testing/resources/javascript/textbox_keyboard_event_expected.txt new file mode 100644 index 0000000000..daa1ecadc7 --- /dev/null +++ b/testing/resources/javascript/textbox_keyboard_event_expected.txt @@ -0,0 +1 @@ +Alert: done diff --git a/testing/resources/javascript/textbox_keyboard_no_event.in b/testing/resources/javascript/textbox_keyboard_no_event.in new file mode 100644 index 0000000000..080a117d55 --- /dev/null +++ b/testing/resources/javascript/textbox_keyboard_no_event.in @@ -0,0 +1,55 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R + /AcroForm << + /Fields [4 0 R] + /DR << + /Font << + /F1 5 0 R + >> + >> + >> +>> +endobj +{{object 2 0}} << + /Type /Pages + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /MediaBox [0 0 200 200] + /Annots [4 0 R] +>> +endobj +{{object 4 0}} << + /Type /Annot + /Subtype /Widget + /FT /Tx + /T (MyField) + /DA (0 0 0 rg /F1 12 Tf) + /Rect [50 50 150 150] + /AA << + /K 6 0 R + >> +>> +endobj +{{object 5 0}} << + /Type /Font + /Subtype /Type1 + /BaseFont /Helvetica +>> +endobj +{{object 6 0}} << + /Type /Action + /S /JavaScript + /JS (app.alert('done')) +>> +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/javascript/textbox_keyboard_no_event_expected.txt b/testing/resources/javascript/textbox_keyboard_no_event_expected.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/resources/javascript/textbox_on_load.in b/testing/resources/javascript/textbox_on_load.in new file mode 100644 index 0000000000..fbe5b268c7 --- /dev/null +++ b/testing/resources/javascript/textbox_on_load.in @@ -0,0 +1,74 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R + /AcroForm << + /Fields [4 0 R 6 0 R] + /DR << + /Font << + /F1 7 0 R + >> + >> + >> + /OpenAction 8 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Annots [4 0 R 6 0 R] + /MediaBox [0 0 400 400] +>> +endobj +{{object 4 0}} << + /Type /Annot + /Subtype /Widget + /T (SubmitFormButton) + /FT /Btn + /Ff 65536 + /Rect [150 150 250 250] + /AA << + /D 5 0 R + >> +>> +endobj +{{object 5 0}} << + /Type /Action + /S /JavaScript + /JS (app.alert('done')) +>> +endobj +{{object 6 0}} << + /Type /Annot + /Subtype /Widget + /T (Field1) + /FT /Tx + /DA (0 0 0 rg /F1 12 Tf) + /Rect [50 300 350 340] + /AA << + /K 5 0 R + >> +>> +endobj +{{object 7 0}} << + /Type /Font + /Subtype /Type1 + /BaseFont /Helvetica +>> +endobj +{{object 8 0}} << + /Type /Action + /S /JavaScript + /JS (this.getField\('Field1'\).value = 'A';) +>> +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/javascript/textbox_on_load_expected.txt b/testing/resources/javascript/textbox_on_load_expected.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/resources/javascript/util_printd.in b/testing/resources/javascript/util_printd.in index d0d17b4833..96795e2b8a 100644 --- a/testing/resources/javascript/util_printd.in +++ b/testing/resources/javascript/util_printd.in @@ -116,7 +116,6 @@ TestOneXFAFormat("mm", d1, false); TestOneXFAFormat("mm", d1, true); // Date with year 0. -// TODO(thestig): Why is the output different from Acrobat? TestOneFormat("mm/dd/yyyy HH:MM:ss", new Date(0, 06, 04, 15, 59, 58)); // Date with month 20. TestOneFormat("mm/dd/yyyy HH:MM:ss", new Date(2014, 20, 04, 15, 59, 58)); diff --git a/testing/resources/javascript/util_printd_expected.txt b/testing/resources/javascript/util_printd_expected.txt index a642a4a6c2..9d0de181da 100644 --- a/testing/resources/javascript/util_printd_expected.txt +++ b/testing/resources/javascript/util_printd_expected.txt @@ -10,8 +10,8 @@ Alert: mmmm: July Alert: mmm: Jul Alert: mm: 07 Alert: m: 7 -Alert: dddd: Sunday -Alert: ddd: Sun +Alert: dddd: Friday +Alert: ddd: Fri Alert: dd: 04 Alert: d: 4 Alert: yyyy: 2014 @@ -41,7 +41,7 @@ Alert: [object Object]: Caught error: util.printd: Incorrect parameter type. Alert: clams,3: Caught error: util.printd: Incorrect parameter type. Alert: mm: 07 Alert: mm: Caught error: util.printd: Operation not supported. -Alert: mm/dd/yyyy HH:MM:ss: 07/03/1900 15:59:58 +Alert: mm/dd/yyyy HH:MM:ss: 07/04/1900 15:59:58 Alert: mm/dd/yyyy HH:MM:ss: 09/04/2015 14:59:58 Alert: mm/dd/yyyy HH:MM:ss: 12/09/2015 15:59:58 Alert: mm/dd/yyyy HH:MM:ss: 07/06/2014 01:59:58 diff --git a/testing/resources/javascript/util_scand.in b/testing/resources/javascript/util_scand.in index 344aacdc50..e169bfd28c 100644 --- a/testing/resources/javascript/util_scand.in +++ b/testing/resources/javascript/util_scand.in @@ -44,8 +44,17 @@ try { expect("util.scand('yyyy/mm/dd', '2016/12/20').toUTCString()", tuesday); expect("util.scand('dd/mmm/yyyy', '20/Dec/2016').toUTCString()", tuesday); expect("util.scand('..dd:-:mmm/yyyy', '**20/*/Dec.2016').toUTCString()", tuesday); + expect("util.scand('dd/mm/yy', '20/12/16').toUTCString()", tuesday); + expect("util.scand('dd/mm/yy', '31/12/85').toUTCString()", "Tue, 31 Dec 1985 21:48:50 GMT"); expect("util.scand('hh:MM:ss', '11:22:03').toUTCString()", friday); expect("util.scand('s:MM:hh', '3:22:11').toUTCString()", friday); + expect("util.scand('hh:MM:ss tt', '11:22:03 am').toUTCString()", friday); + expect("util.scand('hh:MM:ss tt', '11:22:03 pm').toUTCString()", "Fri, 09 May 2014 23:22:03 GMT"); + expect("util.scand('hh:MM:ss tt', '11:22:03 PM').toUTCString()", "Fri, 09 May 2014 23:22:03 GMT"); + expect("util.scand('hh:MM:ss tt', '12:22:03 pm').toUTCString()", "Fri, 09 May 2014 12:22:03 GMT"); + expect("util.scand('hh:MM:ss tt', '12:22:03 am').toUTCString()", "Fri, 09 May 2014 00:22:03 GMT"); + expect("util.scand('hh:MM:ss t', '12:22:03 p').toUTCString()", "Fri, 09 May 2014 12:22:03 GMT"); + expect("util.scand('hh:MM:ss t', '12:22:03 a').toUTCString()", "Fri, 09 May 2014 00:22:03 GMT"); } catch (e) { app.alert("Truly unexpected error: " + e); } diff --git a/testing/resources/javascript/util_scand_expected.txt b/testing/resources/javascript/util_scand_expected.txt index cafe7934d7..9d6777cfc3 100644 --- a/testing/resources/javascript/util_scand_expected.txt +++ b/testing/resources/javascript/util_scand_expected.txt @@ -5,5 +5,14 @@ Alert: PASS: util.scand('dd/mm/yyyy', '20/12/2016').toUTCString() = Tue, 20 Dec Alert: PASS: util.scand('yyyy/mm/dd', '2016/12/20').toUTCString() = Tue, 20 Dec 2016 21:48:50 GMT Alert: PASS: util.scand('dd/mmm/yyyy', '20/Dec/2016').toUTCString() = Tue, 20 Dec 2016 21:48:50 GMT Alert: PASS: util.scand('..dd:-:mmm/yyyy', '**20/*/Dec.2016').toUTCString() = Tue, 20 Dec 2016 21:48:50 GMT +Alert: PASS: util.scand('dd/mm/yy', '20/12/16').toUTCString() = Tue, 20 Dec 2016 21:48:50 GMT +Alert: PASS: util.scand('dd/mm/yy', '31/12/85').toUTCString() = Tue, 31 Dec 1985 21:48:50 GMT Alert: PASS: util.scand('hh:MM:ss', '11:22:03').toUTCString() = Fri, 09 May 2014 11:22:03 GMT Alert: PASS: util.scand('s:MM:hh', '3:22:11').toUTCString() = Fri, 09 May 2014 11:22:03 GMT +Alert: PASS: util.scand('hh:MM:ss tt', '11:22:03 am').toUTCString() = Fri, 09 May 2014 11:22:03 GMT +Alert: PASS: util.scand('hh:MM:ss tt', '11:22:03 pm').toUTCString() = Fri, 09 May 2014 23:22:03 GMT +Alert: PASS: util.scand('hh:MM:ss tt', '11:22:03 PM').toUTCString() = Fri, 09 May 2014 23:22:03 GMT +Alert: PASS: util.scand('hh:MM:ss tt', '12:22:03 pm').toUTCString() = Fri, 09 May 2014 12:22:03 GMT +Alert: PASS: util.scand('hh:MM:ss tt', '12:22:03 am').toUTCString() = Fri, 09 May 2014 00:22:03 GMT +Alert: PASS: util.scand('hh:MM:ss t', '12:22:03 p').toUTCString() = Fri, 09 May 2014 12:22:03 GMT +Alert: PASS: util.scand('hh:MM:ss t', '12:22:03 a').toUTCString() = Fri, 09 May 2014 00:22:03 GMT diff --git a/testing/resources/javascript/xfa_specific/resolve_nodes_1_expected.txt b/testing/resources/javascript/xfa_specific/resolve_nodes_1_expected.txt index 335a4e2876..d4cc4424d3 100644 --- a/testing/resources/javascript/xfa_specific/resolve_nodes_1_expected.txt +++ b/testing/resources/javascript/xfa_specific/resolve_nodes_1_expected.txt @@ -1 +1 @@ -: Find 12 items +: \Find 1 items diff --git a/testing/resources/javascript/named_action.in b/testing/resources/named_action.in similarity index 100% rename from testing/resources/javascript/named_action.in rename to testing/resources/named_action.in diff --git a/testing/resources/named_action.pdf b/testing/resources/named_action.pdf new file mode 100644 index 0000000000..fb9c87c071 Binary files /dev/null and b/testing/resources/named_action.pdf differ diff --git a/testing/resources/pattern_fill.in b/testing/resources/pattern_fill.in new file mode 100644 index 0000000000..89c102ee34 --- /dev/null +++ b/testing/resources/pattern_fill.in @@ -0,0 +1,56 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /MediaBox [0 0 200 300] + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /Resources << + /Pattern << + /P0 5 0 R + >> + >> +>> +endobj +{{object 4 0}} << + {{streamlen}} +>> +stream +q +/Pattern cs +/P0 scn +72 108 72 72 re +f +Q +endstream +endobj +{{object 5 0}} << + /Type /Pattern + /PatternType 1 + /PaintType 1 + /TilingType 1 + /BBox [0 0 36 36] + /XStep 36 + /YStep 36 + {{streamlen}} +>> +stream +0 1 0.91 0 k +0 0 18 18 re +f +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/pattern_fill.pdf b/testing/resources/pattern_fill.pdf new file mode 100644 index 0000000000..796d4c0c2e Binary files /dev/null and b/testing/resources/pattern_fill.pdf differ diff --git a/testing/resources/pattern_fill_uncolored.in b/testing/resources/pattern_fill_uncolored.in new file mode 100644 index 0000000000..19f21a448f --- /dev/null +++ b/testing/resources/pattern_fill_uncolored.in @@ -0,0 +1,58 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /MediaBox [0 0 200 300] + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /Resources << + /ColorSpace << + /CS0 [/Pattern /DeviceRGB] + >> + /Pattern << + /P0 5 0 R + >> + >> +>> +endobj +{{object 4 0}} << + {{streamlen}} +>> +stream +q +/CS0 cs +0 0 1 /P0 scn +72 108 72 72 re +f +Q +endstream +endobj +{{object 5 0}} << + /Type /Pattern + /PatternType 1 + /PaintType 2 + /TilingType 1 + /BBox [0 0 36 36] + /XStep 36 + /YStep 36 + {{streamlen}} +>> +stream +0 0 18 18 re +f +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/pattern_fill_uncolored.pdf b/testing/resources/pattern_fill_uncolored.pdf new file mode 100644 index 0000000000..af83595f71 Binary files /dev/null and b/testing/resources/pattern_fill_uncolored.pdf differ diff --git a/testing/resources/pattern_stroke.in b/testing/resources/pattern_stroke.in new file mode 100644 index 0000000000..3829c8be2f --- /dev/null +++ b/testing/resources/pattern_stroke.in @@ -0,0 +1,59 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /MediaBox [0 0 200 300] + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /Resources << + /Pattern << + /P0 5 0 R + >> + >> +>> +endobj +{{object 4 0}} << + {{streamlen}} +>> +stream +q +/Pattern CS +/P0 SCN +40 w +20 150 m +180 150 l +S +Q +endstream +endobj +{{object 5 0}} << + /Type /Pattern + /PatternType 1 + /PaintType 1 + /TilingType 1 + /BBox [0 0 72 72] + /XStep 72 + /YStep 72 + /Resources <<>> + {{streamlen}} +>> +stream +0 1 0.91 0 k +0 0 72 72 re +f +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/pattern_stroke.pdf b/testing/resources/pattern_stroke.pdf new file mode 100644 index 0000000000..de3e4298d8 Binary files /dev/null and b/testing/resources/pattern_stroke.pdf differ diff --git a/testing/resources/pattern_stroke_uncolored.in b/testing/resources/pattern_stroke_uncolored.in new file mode 100644 index 0000000000..5cd5c16664 --- /dev/null +++ b/testing/resources/pattern_stroke_uncolored.in @@ -0,0 +1,61 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /MediaBox [0 0 200 300] + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /Resources << + /ColorSpace << + /CS0 [/Pattern /DeviceRGB] + >> + /Pattern << + /P0 5 0 R + >> + >> +>> +endobj +{{object 4 0}} << + {{streamlen}} +>> +stream +q +/CS0 CS +0 0 1 /P0 SCN +40 w +20 150 m +180 150 l +S +Q +endstream +endobj +{{object 5 0}} << + /Type /Pattern + /PatternType 1 + /PaintType 2 + /TilingType 1 + /BBox [0 0 72 72] + /XStep 72 + /YStep 72 + /Resources <<>> + {{streamlen}} +>> +stream +0 0 72 72 re +f +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/pattern_stroke_uncolored.pdf b/testing/resources/pattern_stroke_uncolored.pdf new file mode 100644 index 0000000000..b167d727de Binary files /dev/null and b/testing/resources/pattern_stroke_uncolored.pdf differ diff --git a/testing/resources/pixel/bug_1021762_expected_skia_linux.pdf.0.png b/testing/resources/pixel/bug_1021762_expected_skia_linux.pdf.0.png index 1419d2ece6..a252c9c412 100644 Binary files a/testing/resources/pixel/bug_1021762_expected_skia_linux.pdf.0.png and b/testing/resources/pixel/bug_1021762_expected_skia_linux.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1021762_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_1021762_expected_skia_win.pdf.0.png index a4e4f6b9b6..44b836e184 100644 Binary files a/testing/resources/pixel/bug_1021762_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_1021762_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1072440_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1072440_expected_skia.pdf.0.png index 600419fe06..0077178d0c 100644 Binary files a/testing/resources/pixel/bug_1072440_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_1072440_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1072440_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_1072440_expected_skia_mac.pdf.0.png index 3ca8cc269f..e28d330ef2 100644 Binary files a/testing/resources/pixel/bug_1072440_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_1072440_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1072440_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_1072440_expected_skia_win.pdf.0.png index c5e621b112..e196185d0f 100644 Binary files a/testing/resources/pixel/bug_1072440_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_1072440_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_113910_expected_skia.pdf.0.png b/testing/resources/pixel/bug_113910_expected_skia.pdf.0.png index ec6cb0f8d4..6e382c970e 100644 Binary files a/testing/resources/pixel/bug_113910_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_113910_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_113910_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_113910_expected_skia_mac.pdf.0.png index 2eefbf1263..78c81408de 100644 Binary files a/testing/resources/pixel/bug_113910_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_113910_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_113910_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_113910_expected_skia_win.pdf.0.png index da82b90ac5..f069fd60d6 100644 Binary files a/testing/resources/pixel/bug_113910_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_113910_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1271578_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_1271578_expected_skia_mac.pdf.0.png index 18d6fbdabb..9404dabde0 100644 Binary files a/testing/resources/pixel/bug_1271578_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_1271578_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1308_1_expected_skia_linux.pdf.0.png b/testing/resources/pixel/bug_1308_1_expected_skia_linux.pdf.0.png index 784332c7da..560929247e 100644 Binary files a/testing/resources/pixel/bug_1308_1_expected_skia_linux.pdf.0.png and b/testing/resources/pixel/bug_1308_1_expected_skia_linux.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1308_1_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_1308_1_expected_skia_win.pdf.0.png index e76a14bd13..19e6ef6e5f 100644 Binary files a/testing/resources/pixel/bug_1308_1_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_1308_1_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1308_expected_skia_linux.pdf.0.png b/testing/resources/pixel/bug_1308_expected_skia_linux.pdf.0.png index fd6bd9d324..b73e1d1cbf 100644 Binary files a/testing/resources/pixel/bug_1308_expected_skia_linux.pdf.0.png and b/testing/resources/pixel/bug_1308_expected_skia_linux.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1308_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_1308_expected_skia_win.pdf.0.png index 010ca198fa..2adc67de93 100644 Binary files a/testing/resources/pixel/bug_1308_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_1308_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1355_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1355_expected_skia.pdf.0.png index f097788688..ca2247eed9 100644 Binary files a/testing/resources/pixel/bug_1355_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_1355_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1355_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_1355_expected_skia_win.pdf.0.png index e38a5414f6..b1a1d0cff2 100644 Binary files a/testing/resources/pixel/bug_1355_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_1355_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1372651_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1372651_expected_skia.pdf.0.png index 80aaf35a3a..1732c2d84d 100644 Binary files a/testing/resources/pixel/bug_1372651_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_1372651_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1372651_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_1372651_expected_skia_mac.pdf.0.png index e21e25fad1..09d0a7dd5d 100644 Binary files a/testing/resources/pixel/bug_1372651_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_1372651_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1372651_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_1372651_expected_skia_win.pdf.0.png index f756b17e25..787d2480d9 100644 Binary files a/testing/resources/pixel/bug_1372651_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_1372651_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1388_2_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1388_2_expected_skia.pdf.0.png index c5270f8100..29b9e3c388 100644 Binary files a/testing/resources/pixel/bug_1388_2_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_1388_2_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1388_2_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_1388_2_expected_skia_mac.pdf.0.png index 604e6217f6..99fd189eea 100644 Binary files a/testing/resources/pixel/bug_1388_2_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_1388_2_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1388_2_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_1388_2_expected_skia_win.pdf.0.png index 7a60c7df72..1eb2a0c9a0 100644 Binary files a/testing/resources/pixel/bug_1388_2_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_1388_2_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1388_3_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1388_3_expected_skia.pdf.0.png index f3e85d3e52..b2ae5ebcbb 100644 Binary files a/testing/resources/pixel/bug_1388_3_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_1388_3_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1388_3_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_1388_3_expected_skia_mac.pdf.0.png index 01b896d886..7d4d8496ca 100644 Binary files a/testing/resources/pixel/bug_1388_3_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_1388_3_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1388_3_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_1388_3_expected_skia_win.pdf.0.png index 1ef9e63798..bb029a8549 100644 Binary files a/testing/resources/pixel/bug_1388_3_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_1388_3_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1402_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1402_expected_skia.pdf.0.png index 9d2c92e2f7..887b4bd2ea 100644 Binary files a/testing/resources/pixel/bug_1402_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_1402_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1402_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_1402_expected_skia_win.pdf.0.png index 5a708ef951..b5f2b43cdc 100644 Binary files a/testing/resources/pixel/bug_1402_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_1402_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1752_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1752_expected_skia.pdf.0.png index 6c129efd90..fa549a5385 100644 Binary files a/testing/resources/pixel/bug_1752_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_1752_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1752_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_1752_expected_skia_mac.pdf.0.png index c50dac44c6..ca4748deb0 100644 Binary files a/testing/resources/pixel/bug_1752_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_1752_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1752_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_1752_expected_skia_win.pdf.0.png index 10ea42df16..af97c1b9d8 100644 Binary files a/testing/resources/pixel/bug_1752_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_1752_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1922_expected_skia.pdf.0.png b/testing/resources/pixel/bug_1922_expected_skia.pdf.0.png index 8b11ac6809..d72736a295 100644 Binary files a/testing/resources/pixel/bug_1922_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_1922_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1922_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_1922_expected_skia_mac.pdf.0.png index 2eace1360b..39bc3e7891 100644 Binary files a/testing/resources/pixel/bug_1922_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_1922_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_1922_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_1922_expected_skia_win.pdf.0.png index 0df6b392e1..0982d4a055 100644 Binary files a/testing/resources/pixel/bug_1922_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_1922_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_358039783_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_358039783_expected_skia_mac.pdf.0.png index 01b8a73427..a8038d254e 100644 Binary files a/testing/resources/pixel/bug_358039783_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_358039783_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_42270459_expected_skia_linux.pdf.0.png b/testing/resources/pixel/bug_42270459_expected_skia_linux.pdf.0.png index 8e26b613d0..7a5840a212 100644 Binary files a/testing/resources/pixel/bug_42270459_expected_skia_linux.pdf.0.png and b/testing/resources/pixel/bug_42270459_expected_skia_linux.pdf.0.png differ diff --git a/testing/resources/pixel/bug_42270459_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_42270459_expected_skia_win.pdf.0.png index 981ec63216..931e4ffe07 100644 Binary files a/testing/resources/pixel/bug_42270459_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_42270459_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_432208037_expected_skia.pdf.0.png b/testing/resources/pixel/bug_432208037_expected_skia.pdf.0.png index 5f82413938..a044b9d514 100644 Binary files a/testing/resources/pixel/bug_432208037_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_432208037_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_432208037_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_432208037_expected_skia_mac.pdf.0.png index 5d64fe5627..529dc6f541 100644 Binary files a/testing/resources/pixel/bug_432208037_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_432208037_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_432208037_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_432208037_expected_skia_win.pdf.0.png index efb5be29ea..3181fee287 100644 Binary files a/testing/resources/pixel/bug_432208037_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_432208037_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_477200528_expected_skia.pdf.0.png b/testing/resources/pixel/bug_477200528_expected_skia.pdf.0.png index 0b2027e215..4fbe7d093c 100644 Binary files a/testing/resources/pixel/bug_477200528_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_477200528_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_477200528_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_477200528_expected_skia_mac.pdf.0.png index 687bc9eed3..45927cb39e 100644 Binary files a/testing/resources/pixel/bug_477200528_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_477200528_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_477200528_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_477200528_expected_skia_win.pdf.0.png index 41823c8f76..09ad7d294e 100644 Binary files a/testing/resources/pixel/bug_477200528_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_477200528_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_1_expected_skia.pdf.0.png b/testing/resources/pixel/bug_524043_1_expected_skia.pdf.0.png index 5bec703c13..7e8ec39695 100644 Binary files a/testing/resources/pixel/bug_524043_1_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_524043_1_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_1_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_524043_1_expected_skia_mac.pdf.0.png index 76bbd7aff5..016e45981d 100644 Binary files a/testing/resources/pixel/bug_524043_1_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_524043_1_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_1_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_524043_1_expected_skia_win.pdf.0.png index 04b94b0d57..e10eb91c8c 100644 Binary files a/testing/resources/pixel/bug_524043_1_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_524043_1_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_2_expected_skia.pdf.0.png b/testing/resources/pixel/bug_524043_2_expected_skia.pdf.0.png index 376f3915d3..3b453177f8 100644 Binary files a/testing/resources/pixel/bug_524043_2_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_524043_2_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_2_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_524043_2_expected_skia_mac.pdf.0.png index a3f2f93f70..95619f8c87 100644 Binary files a/testing/resources/pixel/bug_524043_2_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_524043_2_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_2_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_524043_2_expected_skia_win.pdf.0.png index 431699bd18..3fd89c0bb1 100644 Binary files a/testing/resources/pixel/bug_524043_2_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_524043_2_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_3_expected_skia.pdf.0.png b/testing/resources/pixel/bug_524043_3_expected_skia.pdf.0.png index 5bec703c13..7e8ec39695 100644 Binary files a/testing/resources/pixel/bug_524043_3_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_524043_3_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_3_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_524043_3_expected_skia_mac.pdf.0.png index 76bbd7aff5..016e45981d 100644 Binary files a/testing/resources/pixel/bug_524043_3_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_524043_3_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_3_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_524043_3_expected_skia_win.pdf.0.png index 04b94b0d57..e10eb91c8c 100644 Binary files a/testing/resources/pixel/bug_524043_3_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_524043_3_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_4_expected_skia.pdf.0.png b/testing/resources/pixel/bug_524043_4_expected_skia.pdf.0.png index 5bec703c13..7e8ec39695 100644 Binary files a/testing/resources/pixel/bug_524043_4_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_524043_4_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_4_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_524043_4_expected_skia_mac.pdf.0.png index 76bbd7aff5..016e45981d 100644 Binary files a/testing/resources/pixel/bug_524043_4_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_524043_4_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_4_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_524043_4_expected_skia_win.pdf.0.png index 04b94b0d57..e10eb91c8c 100644 Binary files a/testing/resources/pixel/bug_524043_4_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_524043_4_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_5_expected_skia.pdf.0.png b/testing/resources/pixel/bug_524043_5_expected_skia.pdf.0.png index 5bec703c13..7e8ec39695 100644 Binary files a/testing/resources/pixel/bug_524043_5_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_524043_5_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_5_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_524043_5_expected_skia_mac.pdf.0.png index 76bbd7aff5..016e45981d 100644 Binary files a/testing/resources/pixel/bug_524043_5_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_524043_5_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_5_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_524043_5_expected_skia_win.pdf.0.png index 04b94b0d57..e10eb91c8c 100644 Binary files a/testing/resources/pixel/bug_524043_5_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_524043_5_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_7_expected_skia.pdf.0.png b/testing/resources/pixel/bug_524043_7_expected_skia.pdf.0.png index 376f3915d3..3b453177f8 100644 Binary files a/testing/resources/pixel/bug_524043_7_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_524043_7_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_7_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_524043_7_expected_skia_mac.pdf.0.png index a3f2f93f70..95619f8c87 100644 Binary files a/testing/resources/pixel/bug_524043_7_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_524043_7_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_524043_7_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_524043_7_expected_skia_win.pdf.0.png index 431699bd18..3fd89c0bb1 100644 Binary files a/testing/resources/pixel/bug_524043_7_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_524043_7_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_528103_expected_skia.pdf.0.png b/testing/resources/pixel/bug_528103_expected_skia.pdf.0.png index 6c4c4acfce..464ca92519 100644 Binary files a/testing/resources/pixel/bug_528103_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_528103_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_528103_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_528103_expected_skia_mac.pdf.0.png index 18ae44e9ae..9423e0e809 100644 Binary files a/testing/resources/pixel/bug_528103_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_528103_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_528103_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_528103_expected_skia_win.pdf.0.png index 8176382f09..63c2b79293 100644 Binary files a/testing/resources/pixel/bug_528103_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_528103_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_543018_1_expected_skia.pdf.0.png b/testing/resources/pixel/bug_543018_1_expected_skia.pdf.0.png index 5bec703c13..7e8ec39695 100644 Binary files a/testing/resources/pixel/bug_543018_1_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_543018_1_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_543018_1_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_543018_1_expected_skia_mac.pdf.0.png index 76bbd7aff5..016e45981d 100644 Binary files a/testing/resources/pixel/bug_543018_1_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_543018_1_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_543018_1_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_543018_1_expected_skia_win.pdf.0.png index 04b94b0d57..e10eb91c8c 100644 Binary files a/testing/resources/pixel/bug_543018_1_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_543018_1_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_543018_2_expected_skia.pdf.0.png b/testing/resources/pixel/bug_543018_2_expected_skia.pdf.0.png index 5bec703c13..7e8ec39695 100644 Binary files a/testing/resources/pixel/bug_543018_2_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_543018_2_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_543018_2_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_543018_2_expected_skia_mac.pdf.0.png index 76bbd7aff5..016e45981d 100644 Binary files a/testing/resources/pixel/bug_543018_2_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_543018_2_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_543018_2_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_543018_2_expected_skia_win.pdf.0.png index 04b94b0d57..e10eb91c8c 100644 Binary files a/testing/resources/pixel/bug_543018_2_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_543018_2_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_551258_1_expected_skia.pdf.0.png b/testing/resources/pixel/bug_551258_1_expected_skia.pdf.0.png index 5bec703c13..7e8ec39695 100644 Binary files a/testing/resources/pixel/bug_551258_1_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_551258_1_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_551258_1_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_551258_1_expected_skia_mac.pdf.0.png index 76bbd7aff5..016e45981d 100644 Binary files a/testing/resources/pixel/bug_551258_1_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_551258_1_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_551258_1_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_551258_1_expected_skia_win.pdf.0.png index 04b94b0d57..e10eb91c8c 100644 Binary files a/testing/resources/pixel/bug_551258_1_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_551258_1_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_665467_expected_skia.pdf.0.png b/testing/resources/pixel/bug_665467_expected_skia.pdf.0.png index 2e9c915410..05d7698b19 100644 Binary files a/testing/resources/pixel/bug_665467_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_665467_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_665467_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_665467_expected_skia_mac.pdf.0.png index ae23d485a7..76d111c13e 100644 Binary files a/testing/resources/pixel/bug_665467_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_665467_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_665467_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_665467_expected_skia_win.pdf.0.png index f8484187af..9cb209335f 100644 Binary files a/testing/resources/pixel/bug_665467_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_665467_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_725389_expected_skia.pdf.0.png b/testing/resources/pixel/bug_725389_expected_skia.pdf.0.png index db7472e94e..51f80f6c36 100644 Binary files a/testing/resources/pixel/bug_725389_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_725389_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_725389_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_725389_expected_skia_mac.pdf.0.png index cfb8b93f69..11f06afee0 100644 Binary files a/testing/resources/pixel/bug_725389_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_725389_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_725389_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_725389_expected_skia_win.pdf.0.png index 4a0783149c..7afd161ef9 100644 Binary files a/testing/resources/pixel/bug_725389_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_725389_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_733528_expected_skia.pdf.0.png b/testing/resources/pixel/bug_733528_expected_skia.pdf.0.png index 7583fd943f..faefe10463 100644 Binary files a/testing/resources/pixel/bug_733528_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_733528_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_733528_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_733528_expected_skia_mac.pdf.0.png index a479faaa98..a2ff3772cd 100644 Binary files a/testing/resources/pixel/bug_733528_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_733528_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_733528_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_733528_expected_skia_win.pdf.0.png index e4db08a740..7b314e5c5f 100644 Binary files a/testing/resources/pixel/bug_733528_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_733528_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_736695_2_expected_skia.pdf.0.png b/testing/resources/pixel/bug_736695_2_expected_skia.pdf.0.png index c959f202d1..9cd45ef3ff 100644 Binary files a/testing/resources/pixel/bug_736695_2_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_736695_2_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_736695_2_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_736695_2_expected_skia_mac.pdf.0.png index e5b9426498..c4928be404 100644 Binary files a/testing/resources/pixel/bug_736695_2_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_736695_2_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_736695_2_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_736695_2_expected_skia_win.pdf.0.png index 401d0dd058..5397a5917f 100644 Binary files a/testing/resources/pixel/bug_736695_2_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_736695_2_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_736695_3_expected_skia.pdf.0.png b/testing/resources/pixel/bug_736695_3_expected_skia.pdf.0.png index 9c6a60b7ab..4ebd784bb5 100644 Binary files a/testing/resources/pixel/bug_736695_3_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_736695_3_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_736695_3_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_736695_3_expected_skia_mac.pdf.0.png index f1258e51f4..8a347de9c7 100644 Binary files a/testing/resources/pixel/bug_736695_3_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_736695_3_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_736695_3_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_736695_3_expected_skia_win.pdf.0.png index 78dc0fb35b..da8b4b98e4 100644 Binary files a/testing/resources/pixel/bug_736695_3_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_736695_3_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_820345_expected_skia.pdf.0.png b/testing/resources/pixel/bug_820345_expected_skia.pdf.0.png index 9813a96243..f3d082b552 100644 Binary files a/testing/resources/pixel/bug_820345_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_820345_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_820345_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_820345_expected_skia_mac.pdf.0.png index 811464708a..fbceb90a2d 100644 Binary files a/testing/resources/pixel/bug_820345_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_820345_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_820345_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_820345_expected_skia_win.pdf.0.png index 6bb5321e6e..698dee978d 100644 Binary files a/testing/resources/pixel/bug_820345_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_820345_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_845697_expected_skia.pdf.0.png b/testing/resources/pixel/bug_845697_expected_skia.pdf.0.png index cf04e70b9a..2c9613b3e8 100644 Binary files a/testing/resources/pixel/bug_845697_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_845697_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_845697_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_845697_expected_skia_mac.pdf.0.png index 4a290e6d35..6a36a66170 100644 Binary files a/testing/resources/pixel/bug_845697_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_845697_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_845697_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_845697_expected_skia_win.pdf.0.png index 46fbc46e02..03440a37ab 100644 Binary files a/testing/resources/pixel/bug_845697_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_845697_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_846_expected_skia.pdf.0.png b/testing/resources/pixel/bug_846_expected_skia.pdf.0.png index 10cde92c99..f498bfaf41 100644 Binary files a/testing/resources/pixel/bug_846_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_846_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_846_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_846_expected_skia_mac.pdf.0.png index 584112e9b2..ff2edaca43 100644 Binary files a/testing/resources/pixel/bug_846_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_846_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_846_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_846_expected_skia_win.pdf.0.png index be0078ef92..44f0952720 100644 Binary files a/testing/resources/pixel/bug_846_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_846_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_909762_expected_skia_linux.pdf.0.png b/testing/resources/pixel/bug_909762_expected_skia_linux.pdf.0.png index 2a06ee056b..abac6e4fac 100644 Binary files a/testing/resources/pixel/bug_909762_expected_skia_linux.pdf.0.png and b/testing/resources/pixel/bug_909762_expected_skia_linux.pdf.0.png differ diff --git a/testing/resources/pixel/bug_909762_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_909762_expected_skia_win.pdf.0.png index 47db32db66..abb471a2cc 100644 Binary files a/testing/resources/pixel/bug_909762_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_909762_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_925736_expected_skia.pdf.0.png b/testing/resources/pixel/bug_925736_expected_skia.pdf.0.png index 154cfeee27..58a43bb0fe 100644 Binary files a/testing/resources/pixel/bug_925736_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_925736_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_925736_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_925736_expected_skia_mac.pdf.0.png index 241e0aee5e..097b908db0 100644 Binary files a/testing/resources/pixel/bug_925736_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_925736_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_925736_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_925736_expected_skia_win.pdf.0.png index 53fe8d7b4f..625cb08a80 100644 Binary files a/testing/resources/pixel/bug_925736_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_925736_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/bug_984811_expected_skia.pdf.0.png b/testing/resources/pixel/bug_984811_expected_skia.pdf.0.png index 9c713b55e5..c5ce654a7f 100644 Binary files a/testing/resources/pixel/bug_984811_expected_skia.pdf.0.png and b/testing/resources/pixel/bug_984811_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/bug_984811_expected_skia_mac.pdf.0.png b/testing/resources/pixel/bug_984811_expected_skia_mac.pdf.0.png index 47ff00c281..7efe4bd9e9 100644 Binary files a/testing/resources/pixel/bug_984811_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/bug_984811_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/bug_984811_expected_skia_win.pdf.0.png b/testing/resources/pixel/bug_984811_expected_skia_win.pdf.0.png index c24d4c4461..0155e899ea 100644 Binary files a/testing/resources/pixel/bug_984811_expected_skia_win.pdf.0.png and b/testing/resources/pixel/bug_984811_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/font_size_expected_skia.pdf.0.png b/testing/resources/pixel/font_size_expected_skia.pdf.0.png index 2b638ab318..8f33bc6d27 100644 Binary files a/testing/resources/pixel/font_size_expected_skia.pdf.0.png and b/testing/resources/pixel/font_size_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/font_size_expected_skia_mac.pdf.0.png b/testing/resources/pixel/font_size_expected_skia_mac.pdf.0.png index c60463905c..bc0aac2b7d 100644 Binary files a/testing/resources/pixel/font_size_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/font_size_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/font_size_expected_skia_win.pdf.0.png b/testing/resources/pixel/font_size_expected_skia_win.pdf.0.png index 770bb50d27..3852717d2a 100644 Binary files a/testing/resources/pixel/font_size_expected_skia_win.pdf.0.png and b/testing/resources/pixel/font_size_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_focused_ltr_expected_skia.pdf.0.png b/testing/resources/pixel/form_textfield_focused_ltr_expected_skia.pdf.0.png index 15a17b22c6..9f4dd11682 100644 Binary files a/testing/resources/pixel/form_textfield_focused_ltr_expected_skia.pdf.0.png and b/testing/resources/pixel/form_textfield_focused_ltr_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_focused_ltr_expected_skia_mac.pdf.0.png b/testing/resources/pixel/form_textfield_focused_ltr_expected_skia_mac.pdf.0.png index 20098d6198..dadde1e66d 100644 Binary files a/testing/resources/pixel/form_textfield_focused_ltr_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/form_textfield_focused_ltr_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_focused_ltr_expected_skia_win.pdf.0.png b/testing/resources/pixel/form_textfield_focused_ltr_expected_skia_win.pdf.0.png index 862663d2a6..d881c06590 100644 Binary files a/testing/resources/pixel/form_textfield_focused_ltr_expected_skia_win.pdf.0.png and b/testing/resources/pixel/form_textfield_focused_ltr_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_focused_rtl_expected_skia.pdf.0.png b/testing/resources/pixel/form_textfield_focused_rtl_expected_skia.pdf.0.png index 906e529a94..64d4cec8ed 100644 Binary files a/testing/resources/pixel/form_textfield_focused_rtl_expected_skia.pdf.0.png and b/testing/resources/pixel/form_textfield_focused_rtl_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_focused_rtl_expected_skia_mac.pdf.0.png b/testing/resources/pixel/form_textfield_focused_rtl_expected_skia_mac.pdf.0.png index 868bf41671..4712370c4c 100644 Binary files a/testing/resources/pixel/form_textfield_focused_rtl_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/form_textfield_focused_rtl_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_focused_rtl_expected_skia_win.pdf.0.png b/testing/resources/pixel/form_textfield_focused_rtl_expected_skia_win.pdf.0.png index 854d05d1d0..aeea0208c7 100644 Binary files a/testing/resources/pixel/form_textfield_focused_rtl_expected_skia_win.pdf.0.png and b/testing/resources/pixel/form_textfield_focused_rtl_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_selected_ltr_expected_skia.pdf.0.png b/testing/resources/pixel/form_textfield_selected_ltr_expected_skia.pdf.0.png index 06580a48d6..a89936b88c 100644 Binary files a/testing/resources/pixel/form_textfield_selected_ltr_expected_skia.pdf.0.png and b/testing/resources/pixel/form_textfield_selected_ltr_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_selected_ltr_expected_skia_mac.pdf.0.png b/testing/resources/pixel/form_textfield_selected_ltr_expected_skia_mac.pdf.0.png index 0985a6a833..573f5cee99 100644 Binary files a/testing/resources/pixel/form_textfield_selected_ltr_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/form_textfield_selected_ltr_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_selected_ltr_expected_skia_win.pdf.0.png b/testing/resources/pixel/form_textfield_selected_ltr_expected_skia_win.pdf.0.png index 63c474dc86..192c11643b 100644 Binary files a/testing/resources/pixel/form_textfield_selected_ltr_expected_skia_win.pdf.0.png and b/testing/resources/pixel/form_textfield_selected_ltr_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_selected_rtl_expected_skia.pdf.0.png b/testing/resources/pixel/form_textfield_selected_rtl_expected_skia.pdf.0.png index 93b18d2a28..126cb8506b 100644 Binary files a/testing/resources/pixel/form_textfield_selected_rtl_expected_skia.pdf.0.png and b/testing/resources/pixel/form_textfield_selected_rtl_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_selected_rtl_expected_skia_mac.pdf.0.png b/testing/resources/pixel/form_textfield_selected_rtl_expected_skia_mac.pdf.0.png index 351f50eee4..74646f28ca 100644 Binary files a/testing/resources/pixel/form_textfield_selected_rtl_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/form_textfield_selected_rtl_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_selected_rtl_expected_skia_win.pdf.0.png b/testing/resources/pixel/form_textfield_selected_rtl_expected_skia_win.pdf.0.png index 67c3f7ec92..07bd91a601 100644 Binary files a/testing/resources/pixel/form_textfield_selected_rtl_expected_skia_win.pdf.0.png and b/testing/resources/pixel/form_textfield_selected_rtl_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected_skia.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected_skia.pdf.0.png index a77cd697c3..faadfaf71d 100644 Binary files a/testing/resources/pixel/freetext_annotation_with_da_expected_skia.pdf.0.png and b/testing/resources/pixel/freetext_annotation_with_da_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected_skia_mac.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_mac.pdf.0.png index 04e4110e30..382cc0ea1c 100644 Binary files a/testing/resources/pixel/freetext_annotation_with_da_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/freetext_annotation_with_da_expected_skia_win.pdf.0.png b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_win.pdf.0.png index 66be929173..ac6b8d2701 100644 Binary files a/testing/resources/pixel/freetext_annotation_with_da_expected_skia_win.pdf.0.png and b/testing/resources/pixel/freetext_annotation_with_da_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/freetext_annotation_without_da_expected_skia.pdf.0.png b/testing/resources/pixel/freetext_annotation_without_da_expected_skia.pdf.0.png index 71f5ee7af9..e58455b6fe 100644 Binary files a/testing/resources/pixel/freetext_annotation_without_da_expected_skia.pdf.0.png and b/testing/resources/pixel/freetext_annotation_without_da_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/freetext_annotation_without_da_expected_skia_mac.pdf.0.png b/testing/resources/pixel/freetext_annotation_without_da_expected_skia_mac.pdf.0.png index 7f2ab0f147..e1549e1275 100644 Binary files a/testing/resources/pixel/freetext_annotation_without_da_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/freetext_annotation_without_da_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/freetext_annotation_without_da_expected_skia_win.pdf.0.png b/testing/resources/pixel/freetext_annotation_without_da_expected_skia_win.pdf.0.png index 1ad08ca134..83ba6771ac 100644 Binary files a/testing/resources/pixel/freetext_annotation_without_da_expected_skia_win.pdf.0.png and b/testing/resources/pixel/freetext_annotation_without_da_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/generation_numbers1_expected_skia.pdf.0.png b/testing/resources/pixel/generation_numbers1_expected_skia.pdf.0.png index 5e3dc05df7..c782b65840 100644 Binary files a/testing/resources/pixel/generation_numbers1_expected_skia.pdf.0.png and b/testing/resources/pixel/generation_numbers1_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/generation_numbers1_expected_skia_mac.pdf.0.png b/testing/resources/pixel/generation_numbers1_expected_skia_mac.pdf.0.png index 5e99bc4035..cc573787dc 100644 Binary files a/testing/resources/pixel/generation_numbers1_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/generation_numbers1_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/generation_numbers1_expected_skia_win.pdf.0.png b/testing/resources/pixel/generation_numbers1_expected_skia_win.pdf.0.png index 3382f596d3..d0e9fe48cf 100644 Binary files a/testing/resources/pixel/generation_numbers1_expected_skia_win.pdf.0.png and b/testing/resources/pixel/generation_numbers1_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/generation_numbers2_expected_skia.pdf.0.png b/testing/resources/pixel/generation_numbers2_expected_skia.pdf.0.png index 5e3dc05df7..c782b65840 100644 Binary files a/testing/resources/pixel/generation_numbers2_expected_skia.pdf.0.png and b/testing/resources/pixel/generation_numbers2_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/generation_numbers2_expected_skia_mac.pdf.0.png b/testing/resources/pixel/generation_numbers2_expected_skia_mac.pdf.0.png index 5e99bc4035..cc573787dc 100644 Binary files a/testing/resources/pixel/generation_numbers2_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/generation_numbers2_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/generation_numbers2_expected_skia_win.pdf.0.png b/testing/resources/pixel/generation_numbers2_expected_skia_win.pdf.0.png index 3382f596d3..d0e9fe48cf 100644 Binary files a/testing/resources/pixel/generation_numbers2_expected_skia_win.pdf.0.png and b/testing/resources/pixel/generation_numbers2_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/password_expected_skia.pdf.0.png b/testing/resources/pixel/password_expected_skia.pdf.0.png index 49badda574..e01f5d0c77 100644 Binary files a/testing/resources/pixel/password_expected_skia.pdf.0.png and b/testing/resources/pixel/password_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/password_expected_skia_mac.pdf.0.png b/testing/resources/pixel/password_expected_skia_mac.pdf.0.png index 8c8b699a24..18e549d59b 100644 Binary files a/testing/resources/pixel/password_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/password_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/password_expected_skia_win.pdf.0.png b/testing/resources/pixel/password_expected_skia_win.pdf.0.png index 8c53278fe4..e292d7b492 100644 Binary files a/testing/resources/pixel/password_expected_skia_win.pdf.0.png and b/testing/resources/pixel/password_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/scrollable_widgets1_expected_skia.pdf.0.png b/testing/resources/pixel/scrollable_widgets1_expected_skia.pdf.0.png index ca05122b8d..fa5232c779 100644 Binary files a/testing/resources/pixel/scrollable_widgets1_expected_skia.pdf.0.png and b/testing/resources/pixel/scrollable_widgets1_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/scrollable_widgets1_expected_skia_mac.pdf.0.png b/testing/resources/pixel/scrollable_widgets1_expected_skia_mac.pdf.0.png index d723ddcde0..7d405f5ced 100644 Binary files a/testing/resources/pixel/scrollable_widgets1_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/scrollable_widgets1_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/scrollable_widgets1_expected_skia_win.pdf.0.png b/testing/resources/pixel/scrollable_widgets1_expected_skia_win.pdf.0.png index a221a706d0..78edc4d0e4 100644 Binary files a/testing/resources/pixel/scrollable_widgets1_expected_skia_win.pdf.0.png and b/testing/resources/pixel/scrollable_widgets1_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/scrollable_widgets2_expected_skia.pdf.0.png b/testing/resources/pixel/scrollable_widgets2_expected_skia.pdf.0.png index da29e3251d..16486a272b 100644 Binary files a/testing/resources/pixel/scrollable_widgets2_expected_skia.pdf.0.png and b/testing/resources/pixel/scrollable_widgets2_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/scrollable_widgets2_expected_skia_mac.pdf.0.png b/testing/resources/pixel/scrollable_widgets2_expected_skia_mac.pdf.0.png index b67b38b089..b605fcd331 100644 Binary files a/testing/resources/pixel/scrollable_widgets2_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/scrollable_widgets2_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/scrollable_widgets2_expected_skia_win.pdf.0.png b/testing/resources/pixel/scrollable_widgets2_expected_skia_win.pdf.0.png index 06cf7615f3..599b2b24d0 100644 Binary files a/testing/resources/pixel/scrollable_widgets2_expected_skia_win.pdf.0.png and b/testing/resources/pixel/scrollable_widgets2_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/text_form_custom_font_expected_skia.pdf.0.png b/testing/resources/pixel/text_form_custom_font_expected_skia.pdf.0.png index 417ea99c29..478ebce911 100644 Binary files a/testing/resources/pixel/text_form_custom_font_expected_skia.pdf.0.png and b/testing/resources/pixel/text_form_custom_font_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/text_form_custom_font_expected_skia_mac.pdf.0.png b/testing/resources/pixel/text_form_custom_font_expected_skia_mac.pdf.0.png index d116b9b9dd..90cd7bde16 100644 Binary files a/testing/resources/pixel/text_form_custom_font_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/text_form_custom_font_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/text_form_custom_font_expected_skia_win.pdf.0.png b/testing/resources/pixel/text_form_custom_font_expected_skia_win.pdf.0.png index ac51a5888f..979ffb9d99 100644 Binary files a/testing/resources/pixel/text_form_custom_font_expected_skia_win.pdf.0.png and b/testing/resources/pixel/text_form_custom_font_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/text_form_multiline_expected_skia.pdf.0.png b/testing/resources/pixel/text_form_multiline_expected_skia.pdf.0.png index 2ce0ec1271..4ea13243d4 100644 Binary files a/testing/resources/pixel/text_form_multiline_expected_skia.pdf.0.png and b/testing/resources/pixel/text_form_multiline_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/text_form_multiline_expected_skia_mac.pdf.0.png b/testing/resources/pixel/text_form_multiline_expected_skia_mac.pdf.0.png index 1e64846739..2ea024f347 100644 Binary files a/testing/resources/pixel/text_form_multiline_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/text_form_multiline_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/text_form_multiline_expected_skia_win.pdf.0.png b/testing/resources/pixel/text_form_multiline_expected_skia_win.pdf.0.png index 272d007deb..9f8bd6762f 100644 Binary files a/testing/resources/pixel/text_form_multiline_expected_skia_win.pdf.0.png and b/testing/resources/pixel/text_form_multiline_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/barcode_test_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/barcode_test_expected_skia.pdf.0.png index 3b1a20bcce..1f128a6dc1 100644 Binary files a/testing/resources/pixel/xfa_specific/barcode_test_expected_skia.pdf.0.png and b/testing/resources/pixel/xfa_specific/barcode_test_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/barcode_test_expected_skia_mac.pdf.0.png b/testing/resources/pixel/xfa_specific/barcode_test_expected_skia_mac.pdf.0.png index 21580bdc01..19087a8140 100644 Binary files a/testing/resources/pixel/xfa_specific/barcode_test_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/xfa_specific/barcode_test_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/barcode_test_expected_skia_win.pdf.0.png b/testing/resources/pixel/xfa_specific/barcode_test_expected_skia_win.pdf.0.png index bb699980dd..55bb52304f 100644 Binary files a/testing/resources/pixel/xfa_specific/barcode_test_expected_skia_win.pdf.0.png and b/testing/resources/pixel/xfa_specific/barcode_test_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia.pdf.0.png index 6ee4531b26..f2ffd72639 100644 Binary files a/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia.pdf.0.png and b/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia_mac.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia_mac.pdf.0.png index 625da53c12..a8dacd5218 100644 Binary files a/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia_win.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia_win.pdf.0.png index c6ab17835f..83a2b62b6b 100644 Binary files a/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia_win.pdf.0.png and b/testing/resources/pixel/xfa_specific/dynamic_list_box_allow_multiple_selection_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia.pdf.0.png index d9e72c3d46..dea8c6003d 100644 Binary files a/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia.pdf.0.png and b/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia_mac.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia_mac.pdf.0.png index 80229bbd6b..85aebec06d 100644 Binary files a/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia_win.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia_win.pdf.0.png index 8e3aeebdac..ba1c499c00 100644 Binary files a/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia_win.pdf.0.png and b/testing/resources/pixel/xfa_specific/dynamic_password_field_background_fill_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia.pdf.0.png index 3f1a4152e4..eb285b9a46 100644 Binary files a/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia.pdf.0.png and b/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia_mac.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia_mac.pdf.0.png index 3500492e88..3558cc2c90 100644 Binary files a/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia_win.pdf.0.png b/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia_win.pdf.0.png index d0c2e97e35..9cd7442032 100644 Binary files a/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia_win.pdf.0.png and b/testing/resources/pixel/xfa_specific/dynamic_table_color_and_width_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia.pdf.0.png index 14cb481a17..4a70a9deca 100644 Binary files a/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia.pdf.0.png and b/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia_mac.pdf.0.png b/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia_mac.pdf.0.png index 579db4959e..eca0582452 100644 Binary files a/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia_win.pdf.0.png b/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia_win.pdf.0.png index d08cb47e3e..917cd1bf51 100644 Binary files a/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia_win.pdf.0.png and b/testing/resources/pixel/xfa_specific/resolve_nodes_0_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.0.png index cee65723bd..18d654fa13 100644 Binary files a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.0.png and b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.1.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.1.png index 589a4a0a9c..d46e02a3c7 100644 Binary files a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.1.png and b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia.pdf.1.png differ diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.0.png index 1d9de6ffa7..9b272bba2d 100644 Binary files a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.0.png and b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.1.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.1.png index 25d42657d5..7d409d2979 100644 Binary files a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.1.png and b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_mac.pdf.1.png differ diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.0.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.0.png index 3b2987e7ec..115df80630 100644 Binary files a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.0.png and b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.1.png b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.1.png index 43ba0e99ae..efaefd0f09 100644 Binary files a/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.1.png and b/testing/resources/pixel/xfa_specific/xfa_node_caption_expected_skia_win.pdf.1.png differ diff --git a/testing/resources/raw_type_text.in b/testing/resources/raw_type_text.in new file mode 100644 index 0000000000..ddc098ffe7 --- /dev/null +++ b/testing/resources/raw_type_text.in @@ -0,0 +1,138 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R + /StructTreeRoot 6 0 R + /Lang (en-US) + /MarkInfo << + /Marked true + >> +>> +endobj +{{object 2 0}} << + /Type /Pages + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Contents 4 0 R + /MediaBox [0 0 612 792] + /Group << + /CS /DeviceRGB + /I true + /S /Transparency + >> + /Resources << + /ProcSet [/PDF /ImageC /ImageI /ImageB] + /XObject << + /Im7 6 0 R + >> + >> + /StructParents 0 +>> +endobj +{{object 4 0}} << + {{streamlen}} +>> +stream +0.1 w +/Artifact +BMC +q +0 0 612 792 re +W* n +Q +EMC +/Figure<> +BDC +q +281 685.3 50 50 re +W* n +q +49.9 0 0 50 281.1 685.4 cm +/Im7 Do +Q +Q +EMC +endstream +endobj +{{object 5 0}} << + /Type /XObject + /Subtype /Image + /Width 50 + /Height 50 + /BitsPerComponent 8 + /ColorSpace /DeviceRGB + /Filter [/ASCIIHexDecode /FlateDecode] + {{streamlen}} +>> +stream +789cedc13101000000c2a0f54fed6f06a00000000000000078031d4c0001 +endstream +endobj +{{object 6 0}} << + /Type /StructTreeRoot + /ParentTree 7 0 R + /K [8 0 R 9 0 R 10 0 R 11 0 R] + /RoleMap << + /Document /Document + /Standard /P + /Figure /Figure + >> +>> +endobj +{{object 7 0}} << + /Nums [0 [8 0 R 9 0 R 10 0 R 11 0 R]] +>> +endobj +{{object 8 0}} << + /Type /StructElem + /S /Workbook + /P 6 0 R + /Alt + /Pg 3 0 R +>> +endobj +{{object 9 0}} << + /Type /StructElem + /S 11 + /P 6 0 R + /T (TitleText) + /Pg 3 0 R +>> +endobj +{{object 10 0}} << + /Type /StructElem + /P 6 0 R + /T + /Pg 3 0 R +>> +endobj +{{object 11 0}} << + /Type /StructElem + /S /Part + /P 6 0 R + /T + /Pg 3 0 R +>> +endobj +{{object 12 0}} << + /O /Layout + /Placement /Block + /BBox [281.1 685.3 331.1 735.3] + /Width 99.9 + /Height 99.9 +>> +endobj +{{object 13 0}} << + /O /Layout + /Placement /Block +>> +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/raw_type_text.pdf b/testing/resources/raw_type_text.pdf new file mode 100644 index 0000000000..f86cf626c1 Binary files /dev/null and b/testing/resources/raw_type_text.pdf differ diff --git a/testing/tools/BUILD.gn b/testing/tools/BUILD.gn index 49f99e06d2..6b27cc56ea 100644 --- a/testing/tools/BUILD.gn +++ b/testing/tools/BUILD.gn @@ -69,15 +69,14 @@ if (pdf_is_standalone) { data = [ ".", "../SUPPRESSIONS", - "../SUPPRESSIONS_EXACT_MATCHING", - "../SUPPRESSIONS_IMAGE_DIFF", "../corpus/", - "../../.vpython3", "../../build/skia_gold_common/", "../../build/util/lib/", "../../third_party/test_fonts/", "../../tools/resultdb/", "../../tools/skia_goldctl/", + "../../vpython.toml", + "../../vpython.toml.uv.lock", ] # Built runtime dependencies. diff --git a/testing/tools/common.py b/testing/tools/common.py index 5d937336e4..9583cf091d 100755 --- a/testing/tools/common.py +++ b/testing/tools/common.py @@ -47,7 +47,8 @@ def RunCommandPropagateErr(cmd, exit_status_on_error: If specified, upon an error in the subprocess the caller script exits immediately with the given status. """ - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) output, err = p.communicate() if p.returncode: diff --git a/testing/tools/find_performance_steps.py b/testing/tools/find_performance_steps.py new file mode 100755 index 0000000000..c98aff2b6e --- /dev/null +++ b/testing/tools/find_performance_steps.py @@ -0,0 +1,857 @@ +#!/usr/bin/env python3 +# Copyright 2026 The PDFium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Finds performance step functions (regressions or improvements) in CI.""" + +import argparse +import collections +import concurrent.futures +import datetime +import json +import os +import statistics +import subprocess +import sys +import tempfile + + +def ParseISO8601(date_str): + """Parses an ISO-8601 date or date-time string into a UTC datetime.""" + norm_str = date_str[:-1] + '+00:00' if date_str.endswith('Z') else date_str + dt = datetime.datetime.fromisoformat(norm_str) + if dt.tzinfo is None: + dt = dt.replace(tzinfo=datetime.timezone.utc) + else: + dt = dt.astimezone(datetime.timezone.utc) + return dt + + +def GetCIBuilders(): + """Returns a list of all CI builders for the pdfium project.""" + cmd = ['bb', 'builders', 'pdfium/ci'] + proc = subprocess.run(cmd, capture_output=True, text=True, check=False) + if proc.returncode != 0: + err = proc.stderr.strip() + if err: + print(f'Warning: "bb builders" failed: {err}', file=sys.stderr) + return [] + builders = [] + for line in proc.stdout.splitlines(): + line = line.strip() + if line.startswith('pdfium/ci/'): + builders.append(line.replace('pdfium/ci/', '')) + return sorted(builders) + + +def GetBuildsForBuilder(builder, since_dt, until_dt): + """Fetches successful builds for `builder` within the date range.""" + predicate = json.dumps({ + 'builder': { + 'project': 'pdfium', + 'bucket': 'ci', + 'builder': builder, + }, + 'status': 'SUCCESS', + 'createTime': { + 'startTime': since_dt.strftime('%Y-%m-%dT%H:%M:%S.%fZ'), + 'endTime': until_dt.strftime('%Y-%m-%dT%H:%M:%S.%fZ'), + }, + }) + cmd = ['bb', 'ls', '-predicate', predicate, '-json'] + builds = [] + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + with proc: + for line in proc.stdout: + line = line.strip() + if not line: + continue + try: + d = json.loads(line) + create_time = d.get('createTime') or d.get('startTime') + if not create_time: + continue + ct = ParseISO8601(create_time) + if ct > until_dt or ct < since_dt: + continue + + build_id = d.get('id') + build_num = d.get('number') + commit = (d.get('input') or {}).get('gitilesCommit', {}).get('id', '') + if not commit: + for t in d.get('tags') or []: + val = t.get('value') or '' + if t.get('key') == 'buildset' and 'commit/gitiles' in val: + commit = val.split('/')[-1] + break + if build_id: + builds.append({ + 'id': build_id, + 'number': build_num, + 'commit': commit, + 'create_time': create_time, + }) + except (json.JSONDecodeError, ValueError): + pass + proc.wait() + if proc.returncode != 0: + err = proc.stderr.read().strip() + if err: + print(f'Warning: "bb ls" failed for {builder}: {err}', file=sys.stderr) + # Buildbucket returns newest first; reverse so order is chronological. + builds.reverse() + return builds + + +def QueryResultDBForBuilds(build_ids, batch_size=25): + """Queries ResultDB in batches and yields (build_id, test_result) tuples.""" + for i in range(0, len(build_ids), batch_size): + batch = build_ids[i:i + batch_size] + inv_args = [f'build-{bid}' for bid in batch] + cmd = ['rdb', 'query', '-json', '-tr-fields', 'testId,duration,variant' + ] + inv_args + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + with proc: + for line in proc.stdout: + line = line.strip() + if not line: + continue + try: + data = json.loads(line) + tr = data.get('testResult') + inv_id = (data.get('invocationId') or '').replace('build-', '') + if tr and inv_id: + yield inv_id, tr + except json.JSONDecodeError: + pass + proc.wait() + if proc.returncode != 0: + err = proc.stderr.read().strip() + if err: + print(f'Warning: "rdb query" failed: {err}', file=sys.stderr) + + +SUITE_ALIASES = { + 'unittest': 'pdfium_unittests', + 'unittests': 'pdfium_unittests', + 'embedder': 'pdfium_embeddertests', + 'embeddertest': 'pdfium_embeddertests', + 'embeddertests': 'pdfium_embeddertests', +} + + +def MatchesSuiteFilter(suite, suite_filters, exact=False): + """Returns True if `suite` matches any filter in `suite_filters`.""" + if not suite_filters: + return True + suite_lower = suite.lower() + if exact: + return suite_lower in ( + SUITE_ALIASES.get(sf.lower(), sf.lower()) for sf in suite_filters) + # Strip '_javascript_disabled' when matching general filters like 'javascript' + # so that 'corpus_javascript_disabled' and 'pixel_javascript_disabled' are not + # mistakenly matched by '--javascript'. + base_suite = suite_lower.replace('_javascript_disabled', '') + for sf in suite_filters: + if 'javascript_disabled' in sf: + if sf in suite_lower: + return True + elif sf in base_suite: + return True + return False + + +def ExtractTestData(build_ids, + suite_filters=None, + exact_suite=False, + use_cache=True, + verbose=False): + """Collects suite sums, test counts, and test durations across `build_ids`.""" + test_timings = collections.defaultdict(lambda: collections.defaultdict(dict)) + suite_totals = collections.defaultdict(lambda: collections.defaultdict(float)) + suite_counts = collections.defaultdict(lambda: collections.defaultdict(int)) + + cache_dir = os.path.join(tempfile.gettempdir(), 'pdfium_rdb_cache') + if use_cache: + os.makedirs(cache_dir, exist_ok=True) + + needed_build_ids = [] + for bid in build_ids: + cache_path = os.path.join(cache_dir, f'{bid}.json') if use_cache else None + if cache_path and os.path.isfile(cache_path): + try: + with open(cache_path, 'r', encoding='utf-8') as f: + build_data = json.load(f) + for suite, tests in build_data.items(): + if suite_filters and not MatchesSuiteFilter( + suite, suite_filters, exact=exact_suite): + continue + for test_id, dur in tests.items(): + test_timings[bid][suite][test_id] = dur + suite_totals[bid][suite] += dur + suite_counts[bid][suite] += 1 + continue + except (json.JSONDecodeError, OSError): + pass + needed_build_ids.append(bid) + + if needed_build_ids: + if verbose: + print( + f'Querying ResultDB for {len(needed_build_ids)} builds...', + file=sys.stderr, + flush=True) + + new_raw = collections.defaultdict( + lambda: collections.defaultdict(lambda: collections.defaultdict(list))) + for bid, tr in QueryResultDBForBuilds(needed_build_ids): + variant = (tr.get('variant') or {}).get('def') or {} + suite = variant.get('test_suite') or 'unknown' + test_id = tr.get('testId') or 'unknown' + dur_str = tr.get('duration') or '0s' + try: + dur = float(dur_str.rstrip('s')) if dur_str.endswith('s') else 0.0 + except ValueError: + dur = 0.0 + + new_raw[bid][suite][test_id].append(dur) + + for bid in needed_build_ids: + suites = new_raw[bid] + build_cache_data = {} + for suite, tests in suites.items(): + build_cache_data[suite] = {} + include_suite = not suite_filters or MatchesSuiteFilter( + suite, suite_filters, exact=exact_suite) + for test_id, durs in tests.items(): + mean_dur = durs[0] if len(durs) == 1 else (sum(durs) / len(durs)) + build_cache_data[suite][test_id] = mean_dur + if include_suite: + test_timings[bid][suite][test_id] = mean_dur + suite_totals[bid][suite] += mean_dur + suite_counts[bid][suite] += 1 + + if use_cache and build_cache_data: + cache_path = os.path.join(cache_dir, f'{bid}.json') + try: + with open(cache_path, 'w', encoding='utf-8') as f: + json.dump(build_cache_data, f) + except OSError: + pass + + return suite_totals, suite_counts, test_timings + + +def DetectSuiteSteps(runs, + suite_totals, + suite_counts=None, + threshold_pct=15.0, + min_delta=2.0, + persistence_runs=2, + suite_filters=None, + exact_suite=False): + """Detects persistent step functions in suite durations. + + A step occurs at index `i` if the median of `runs[i : i + 1 + + persistence_runs]` differs from the pre-change baseline (median of + `runs[i - 3 : i]`) by at least `threshold_pct` and `min_delta` seconds, + and every run in the post window maintains that shift. + """ + all_suites = sorted( + set().union(*(suite_totals[r['id']].keys() for r in runs))) + if suite_filters: + all_suites = [ + s for s in all_suites + if MatchesSuiteFilter(s, suite_filters, exact=exact_suite) + ] + post_window_size = persistence_runs + 1 + step_events = [] + + for suite in all_suites: + # Filter to runs where this suite had test results. + suite_runs = [r for r in runs if suite in suite_totals[r['id']]] + if len(suite_runs) < 3 + post_window_size: + continue + + durations = [suite_totals[r['id']][suite] for r in suite_runs] + counts = [ + suite_counts[r['id']][suite] if suite_counts else 0 for r in suite_runs + ] + idx = 3 + limit = len(suite_runs) - post_window_size + + while idx <= limit: + pre_window = durations[idx - 3:idx] + post_window = durations[idx:idx + post_window_size] + + m_pre = statistics.median(pre_window) + m_post = statistics.median(post_window) + + delta = m_post - m_pre + pct = (delta / m_pre * 100.0) if m_pre > 0.0 else 0.0 + + if abs(pct) >= threshold_pct and abs(delta) >= min_delta: + c_pre = statistics.median(counts[idx - 3:idx]) + c_post = statistics.median(counts[idx:idx + post_window_size]) + if delta > 0: + # Regression: all post-runs must remain elevated. + min_allowed = m_pre * (1.0 + (threshold_pct / 200.0)) + if all(d >= min_allowed for d in post_window): + step_events.append({ + 'type': 'REGRESSION', + 'suite': suite, + 'run_index': idx, + 'pre_runs': suite_runs[idx - 3:idx], + 'trigger_run': suite_runs[idx], + 'prev_run': suite_runs[idx - 1], + 'future_runs': suite_runs[idx + 1:idx + post_window_size], + 'm_pre': m_pre, + 'm_post': m_post, + 'delta': delta, + 'pct': pct, + 'count_pre': int(c_pre), + 'count_post': int(c_post), + }) + idx += post_window_size + continue + else: + # Improvement: all post-runs must remain lower. + max_allowed = m_pre * (1.0 - (threshold_pct / 200.0)) + if all(d <= max_allowed for d in post_window): + step_events.append({ + 'type': 'IMPROVEMENT', + 'suite': suite, + 'run_index': idx, + 'pre_runs': suite_runs[idx - 3:idx], + 'trigger_run': suite_runs[idx], + 'prev_run': suite_runs[idx - 1], + 'future_runs': suite_runs[idx + 1:idx + post_window_size], + 'm_pre': m_pre, + 'm_post': m_post, + 'delta': delta, + 'pct': pct, + 'count_pre': int(c_pre), + 'count_post': int(c_post), + }) + idx += post_window_size + continue + idx += 1 + + return step_events + + +def FindCulpritTests(step_event, + test_timings, + top_n=5, + test_threshold_pct=30.0, + test_min_delta=0.2): + """Finds individual tests that persistently shifted across the step.""" + suite = step_event['suite'] + pre_runs = step_event.get('pre_runs', [step_event['prev_run']]) + pre_ids = [r['id'] for r in pre_runs] + trigger_id = step_event['trigger_run']['id'] + future_ids = [r['id'] for r in step_event['future_runs']] + post_ids = [trigger_id] + future_ids + + all_test_ids = set() + for bid in pre_ids + post_ids: + all_test_ids.update(test_timings[bid].get(suite, {}).keys()) + + candidates = [] + is_regression = step_event['type'] == 'REGRESSION' + + for test_id in all_test_ids: + pre_durs = [ + test_timings[bid][suite][test_id] + for bid in pre_ids + if test_id in test_timings[bid].get(suite, {}) + ] + post_durs = [ + test_timings[bid][suite][test_id] + for bid in post_ids + if test_id in test_timings[bid].get(suite, {}) + ] + + # Test must run at least once in each epoch to compare durations. + if not pre_durs or not post_durs: + continue + + t_pre = statistics.median(pre_durs) + m_test_post = statistics.median(post_durs) + t_delta = m_test_post - t_pre + t_pct = (t_delta / t_pre * 100.0) if t_pre > 0.0 else 0.0 + + if abs(t_pct) >= test_threshold_pct and abs(t_delta) >= test_min_delta: + if is_regression and t_delta > 0 and all(d >= t_pre for d in post_durs): + candidates.append({ + 'test_id': test_id, + 'pre_dur': t_pre, + 'post_durs': post_durs, + 'm_post': m_test_post, + 'delta': t_delta, + 'pct': t_pct, + }) + elif (not is_regression and t_delta < 0 and + all(d <= t_pre for d in post_durs)): + candidates.append({ + 'test_id': test_id, + 'pre_dur': t_pre, + 'post_durs': post_durs, + 'm_post': m_test_post, + 'delta': t_delta, + 'pct': t_pct, + }) + + # Rank by absolute delta descending. + candidates.sort(key=lambda x: abs(x['delta']), reverse=True) + total_candidate_delta = sum(c['delta'] for c in candidates) + return { + 'candidates': candidates[:top_n], + 'total_count': len(candidates), + 'total_delta': total_candidate_delta, + } + + +def AnalyzeBuilder(builder, + since_dt, + until_dt, + threshold_pct, + min_delta, + persistence_runs, + top_n_culprits, + test_threshold_pct=30.0, + test_min_delta=0.2, + suite_filters=None, + exact_suite=False, + use_cache=True, + verbose=False): + """Fetches and evaluates performance step functions for `builder`.""" + if verbose: + print( + f'Fetching builds for {builder} between {since_dt.isoformat()} ' + f'and {until_dt.isoformat()}...', + file=sys.stderr, + flush=True) + + builds = GetBuildsForBuilder(builder, since_dt, until_dt) + if len(builds) < 3 + 1 + persistence_runs: + if verbose: + print( + f'Insufficient builds found for {builder} ({len(builds)} builds).', + file=sys.stderr) + return [] + + build_ids = [b['id'] for b in builds] + suite_totals, suite_counts, test_timings = ExtractTestData( + build_ids, + suite_filters=suite_filters, + exact_suite=exact_suite, + use_cache=use_cache, + verbose=verbose) + + step_events = DetectSuiteSteps( + runs=builds, + suite_totals=suite_totals, + suite_counts=suite_counts, + threshold_pct=threshold_pct, + min_delta=min_delta, + persistence_runs=persistence_runs, + suite_filters=suite_filters, + exact_suite=exact_suite) + + results = [] + for step in step_events: + culprit_info = FindCulpritTests( + step_event=step, + test_timings=test_timings, + top_n=top_n_culprits, + test_threshold_pct=test_threshold_pct, + test_min_delta=test_min_delta) + results.append({ + 'builder': builder, + 'step': step, + 'culprits': culprit_info['candidates'], + 'culprit_total_count': culprit_info['total_count'], + 'culprit_total_delta': culprit_info['total_delta'], + }) + + return results + + +def GetCommitRangeUrl(prev_commit, trigger_commit): + """Returns a Gitiles log URL between `prev_commit` and `trigger_commit`.""" + if prev_commit and trigger_commit and prev_commit != trigger_commit: + return (f'https://pdfium.googlesource.com/pdfium/+log/' + f'{prev_commit[:10]}..{trigger_commit[:10]}') + return '' + + +def FormatEventText(event): + """Formats a single step function event for text display.""" + builder = event['builder'] + step = event['step'] + culprits = event['culprits'] + culprit_total_count = event.get('culprit_total_count', len(culprits)) + culprit_total_delta = event.get('culprit_total_delta', + sum(c['delta'] for c in culprits)) + trigger = step['trigger_run'] + prev = step['prev_run'] + future = step['future_runs'] + + stype = step['type'] + sign = '+' if step['delta'] > 0 else '' + trigger_commit = trigger.get('commit') or '' + prev_commit = prev.get('commit') or '' + range_url = GetCommitRangeUrl(prev_commit, trigger_commit) + range_line = f' Commit Range: {range_url}\n' if range_url else '' + + test_count_line = '' + c_pre = step.get('count_pre', 0) + c_post = step.get('count_post', 0) + if c_pre or c_post: + c_delta = c_post - c_pre + csign = '+' if c_delta > 0 else '' + test_count_line = f' Tests Run: {c_pre} -> {c_post} ({csign}{c_delta} tests)\n' + + header = (f'=== [{stype}] builder: {builder} | suite: {step["suite"]} ===\n' + f' Trigger Build: #{trigger.get("number") or "unknown"} ' + f'(commit: {(trigger_commit or "unknown")[:10]})\n' + f' Pre Build: #{prev.get("number") or "unknown"} ' + f'(commit: {(prev_commit or "unknown")[:10]})\n' + f'{range_line}' + f' Date: {trigger.get("create_time") or "unknown"}\n' + f'{test_count_line}' + f' Suite Shift: {step["m_pre"]:.2f}s -> {step["m_post"]:.2f}s ' + f'({sign}{step["pct"]:.1f}%, {sign}{step["delta"]:.2f}s)\n' + f' Persisted in: ' + + ', '.join(f'#{r.get("number") or "?"}' for r in future)) + + lines = [header] + if culprits: + csign = '+' if culprit_total_delta > 0 else '' + lines.append( + f' Culprit Tests (showing top {len(culprits)} of {culprit_total_count}, ' + f'accounting for {csign}{culprit_total_delta:.2f}s of {sign}{step["delta"]:.2f}s shift):' + ) + for c in culprits: + c_item_sign = '+' if c['delta'] > 0 else '' + post_str = ', '.join(f'{d:.2f}s' for d in c['post_durs']) + lines.append( + f' - {c["test_id"]}\n' + f' duration: {c["pre_dur"]:.2f}s -> ({post_str}) ' + f'[{c_item_sign}{c["pct"]:.1f}%, {c_item_sign}{c["delta"]:.2f}s]') + else: + lines.append( + ' Culprit Tests: None met threshold (diffuse across entire suite)') + + return '\n'.join(lines) + + +def main(): + parser = argparse.ArgumentParser( + description='Find performance step functions across CI builders.') + parser.add_argument( + '--since', + help='Start date/time in ISO-8601 format (e.g. "2026-09-14" or ' + '"2026-09-14T00:00:00Z"). Defaults to 7 days ago.') + parser.add_argument( + '--until', + help='End date/time in ISO-8601 format (e.g. "2026-09-21" or ' + '"2026-09-21T23:59:59Z"). Defaults to current time.') + parser.add_argument( + '--builder', + '-b', + help='Specific CI builder to inspect (e.g. "linux", or comma-separated ' + 'list). Defaults to "linux" if omitted and --all-builders is not set.') + parser.add_argument( + '--all-builders', + action='store_true', + help='Inspect all active CI builders in pdfium/ci.') + parser.add_argument( + '--threshold', + type=float, + default=15.0, + help='Relative threshold percentage to flag a step function ' + '(default: 15.0)') + parser.add_argument( + '--min-delta', + type=float, + default=2.0, + help='Minimum absolute change in seconds to flag a step function ' + '(default: 2.0)') + parser.add_argument( + '--persistence', + type=int, + default=2, + help='Number of future runs required to confirm persistence (default: 2)') + parser.add_argument( + '--top-culprits', + type=int, + default=5, + help='Number of individual culprit tests to report per step (default: 5)') + parser.add_argument( + '--test-threshold', + type=float, + default=30.0, + help='Relative percentage threshold for individual culprit tests ' + '(default: 30.0)') + parser.add_argument( + '--test-min-delta', + type=float, + default=0.2, + help='Minimum absolute change in seconds for individual culprit tests ' + '(default: 0.2)') + parser.add_argument( + '--suite', + '-s', + help='Filter to specific test suite(s), comma-separated or substring ' + '(e.g. "unittests", "embeddertests", "javascript", "corpus", "pixel")') + parser.add_argument( + '--exact-suite', + action='store_true', + help='Require exact suite name match instead of substring matching') + parser.add_argument( + '--unittest', + '--unittests', + action='store_true', + help='Shortcut to filter for unit tests') + parser.add_argument( + '--corpus', + action='store_true', + help='Shortcut to filter for corpus tests') + parser.add_argument( + '--pixel', action='store_true', help='Shortcut to filter for pixel tests') + parser.add_argument( + '--embeddertest', + '--embeddertests', + action='store_true', + help='Shortcut to filter for embedder tests') + parser.add_argument( + '--javascript', + action='store_true', + help='Shortcut to filter for javascript tests') + parser.add_argument( + '--concurrency', + '-j', + type=int, + default=4, + help='Number of concurrent builders to process in parallel (default: 4)') + parser.add_argument( + '--no-cache', + action='store_true', + help='Disable local disk caching of ResultDB build queries') + parser.add_argument( + '--json', action='store_true', help='Output results as JSON') + parser.add_argument( + '--verbose', + '-v', + action='store_true', + help='Print verbose progress messages') + + args = parser.parse_args() + + if args.persistence < 1: + print('Error: --persistence must be at least 1.', file=sys.stderr) + return 1 + if args.threshold <= 0.0: + print('Error: --threshold must be positive.', file=sys.stderr) + return 1 + if args.min_delta < 0.0: + print('Error: --min-delta must be non-negative.', file=sys.stderr) + return 1 + if args.test_threshold <= 0.0: + print('Error: --test-threshold must be positive.', file=sys.stderr) + return 1 + if args.test_min_delta < 0.0: + print('Error: --test-min-delta must be non-negative.', file=sys.stderr) + return 1 + + now = datetime.datetime.now(datetime.timezone.utc) + if args.until: + try: + until_dt = ParseISO8601(args.until) + if len(args.until) <= 10: + until_dt = until_dt.replace( + hour=23, minute=59, second=59, microsecond=999999) + except ValueError as e: + print(f'Error: Invalid ISO-8601 format for --until: {e}', file=sys.stderr) + return 1 + else: + until_dt = now + + if args.since: + try: + since_dt = ParseISO8601(args.since) + except ValueError as e: + print(f'Error: Invalid ISO-8601 format for --since: {e}', file=sys.stderr) + return 1 + else: + since_dt = now - datetime.timedelta(days=7) + + if since_dt >= until_dt: + print( + 'Error: --since date must be earlier than --until date.', + file=sys.stderr) + return 1 + + suite_filters = [] + if args.suite: + suite_filters.extend( + [s.strip().lower() for s in args.suite.split(',') if s.strip()]) + if args.unittest: + suite_filters.append('unittests') + if args.corpus: + suite_filters.append('corpus') + if args.pixel: + suite_filters.append('pixel') + if args.embeddertest: + suite_filters.append('embeddertests') + if args.javascript: + suite_filters.append('javascript') + + if args.all_builders: + builders = GetCIBuilders() + elif args.builder: + builders = [b.strip() for b in args.builder.split(',') if b.strip()] + else: + builders = ['linux'] + + if not builders: + print('Error: No builders specified or found.', file=sys.stderr) + return 1 + + if not args.json: + print(f'Analyzing builds from {since_dt.isoformat()} to ' + f'{until_dt.isoformat()} for builders: {", ".join(builders)}') + if suite_filters: + print(f'Suite filters: {", ".join(suite_filters)}') + print(f'Threshold: {args.threshold}%, Min Delta: {args.min_delta}s, ' + f'Persistence: {args.persistence} future runs\n') + + all_events = [] + if len(builders) > 1 and args.concurrency > 1: + with concurrent.futures.ThreadPoolExecutor( + max_workers=min(args.concurrency, len(builders))) as executor: + futures = { + executor.submit( + AnalyzeBuilder, + builder=b, + since_dt=since_dt, + until_dt=until_dt, + threshold_pct=args.threshold, + min_delta=args.min_delta, + persistence_runs=args.persistence, + top_n_culprits=args.top_culprits, + test_threshold_pct=args.test_threshold, + test_min_delta=args.test_min_delta, + suite_filters=suite_filters, + exact_suite=args.exact_suite, + use_cache=not args.no_cache, + verbose=args.verbose): + b for b in builders + } + for future in concurrent.futures.as_completed(futures): + b = futures[future] + try: + events = future.result() + all_events.extend(events) + except Exception as e: + print(f'Error analyzing builder {b}: {e}', file=sys.stderr) + else: + for b in builders: + try: + events = AnalyzeBuilder( + builder=b, + since_dt=since_dt, + until_dt=until_dt, + threshold_pct=args.threshold, + min_delta=args.min_delta, + persistence_runs=args.persistence, + top_n_culprits=args.top_culprits, + test_threshold_pct=args.test_threshold, + test_min_delta=args.test_min_delta, + suite_filters=suite_filters, + exact_suite=args.exact_suite, + use_cache=not args.no_cache, + verbose=args.verbose) + all_events.extend(events) + except Exception as e: + print(f'Error analyzing builder {b}: {e}', file=sys.stderr) + + # Sort events deterministically by builder and trigger time. + all_events.sort(key=lambda e: (e['builder'], e['step']['trigger_run'].get( + 'create_time') or '')) + + if args.json: + json_output = [] + for event in all_events: + step = event['step'] + c_pre = step.get('count_pre') + c_post = step.get('count_post') + c_delta = (c_post - + c_pre) if (c_pre is not None and c_post is not None) else None + json_output.append({ + 'builder': + event['builder'], + 'type': + step['type'], + 'suite': + step['suite'], + 'trigger_build_number': + step['trigger_run'].get('number'), + 'trigger_build_id': + step['trigger_run'].get('id'), + 'trigger_commit': + step['trigger_run'].get('commit'), + 'trigger_time': + step['trigger_run'].get('create_time'), + 'pre_build_number': + step['prev_run'].get('number'), + 'pre_commit': + step['prev_run'].get('commit'), + 'commit_range_url': + GetCommitRangeUrl(step['prev_run'].get('commit') or '', + step['trigger_run'].get('commit') or ''), + 'test_count_pre': + c_pre, + 'test_count_post': + c_post, + 'test_count_delta': + c_delta, + 'median_pre_seconds': + step['m_pre'], + 'median_post_seconds': + step['m_post'], + 'delta_seconds': + step['delta'], + 'percent_change': + step['pct'], + 'future_build_numbers': [ + r.get('number') for r in step['future_runs'] + ], + 'culprits': + event['culprits'], + 'culprit_total_count': + event.get('culprit_total_count'), + 'culprit_total_delta': + event.get('culprit_total_delta'), + }) + print(json.dumps(json_output, indent=2)) + return 0 + + if not all_events: + print('No persistent performance step functions detected.') + return 0 + + for event in all_events: + print(FormatEventText(event)) + print() + + print(f'Total step events found: {len(all_events)}') + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/testing/tools/find_surprise_passes.py b/testing/tools/find_surprise_passes.py new file mode 100755 index 0000000000..d9e3728e1e --- /dev/null +++ b/testing/tools/find_surprise_passes.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +# Copyright 2026 The PDFium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Finds surprise passes (unexpected successes) in ResultDB for a Gerrit CL.""" + +import argparse +import collections +import json +import subprocess +import sys +import urllib.error +import urllib.request + +from githelper import GitHelper + + +def GetLatestPatchset(issue): + """Returns the highest patchset number for a Gerrit issue.""" + url = f'https://pdfium-review.googlesource.com/changes/{issue}' + try: + with urllib.request.urlopen(url, timeout=10) as response: + raw = response.read().decode('utf-8') + data = json.loads(raw.removeprefix(")]}'\n")) + return data.get('current_revision_number') + except (urllib.error.URLError, json.JSONDecodeError, ValueError, OSError): + return None + + +def GetTryResults(issue, patchset=None): + """Fetches tryjob results as JSON via git cl try-results.""" + if patchset: + patchsets = [patchset] + else: + latest = GetLatestPatchset(issue) + patchsets = range(latest, 0, -1) if latest else [None] + + for ps in patchsets: + cmd = ['git', 'cl', 'try-results', '--json=-', '--issue', str(issue)] + if ps is not None: + cmd.extend(['--patchset', str(ps)]) + result = subprocess.run(cmd, capture_output=True, text=True, check=False) + try: + builds = json.loads(result.stdout) + if builds: + return builds + except (json.JSONDecodeError, ValueError): + pass + return [] + + +def QueryResultDB(invocation_ids, batch_size=50): + """Queries ResultDB for test results across invocations.""" + for i in range(0, len(invocation_ids), batch_size): + batch = invocation_ids[i:i + batch_size] + cmd = ['rdb', 'query', '-json', '-tr-fields', 'testId,status,tags,variant' + ] + batch + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True) + for line in proc.stdout: + line = line.strip() + if not line: + continue + try: + data = json.loads(line) + tr = data.get('testResult') + if tr: + yield tr + except json.JSONDecodeError: + pass + proc.wait() + + +def ExtractTags(test_result): + """Extracts tags list into a dictionary of key -> value.""" + tags = {} + for item in test_result.get('tags', []): + key = item.get('key') + value = item.get('value') + if key: + tags[key] = value + return tags + + +def main(): + parser = argparse.ArgumentParser( + description='Query ResultDB for surprise passes in a Gerrit issue.') + parser.add_argument( + '--issue', + '-i', + help='Gerrit issue number (defaults to current branch issue)') + parser.add_argument( + '--patchset', + '-p', + help='Patchset number (defaults to latest patchset with tryjobs)') + parser.add_argument( + '--all-skips', + action='store_true', + help='Also include ordinary skipped tests in output') + parser.add_argument( + '--by-builder', + action='store_true', + help='Group results by builder instead of by test') + parser.add_argument( + '--json', action='store_true', help='Output raw results as JSON') + args = parser.parse_args() + + git = GitHelper() + branch = git.GetCurrentBranchName() + issue = args.issue or git.GetGerritIssue(branch) + if not issue: + print( + 'Error: Could not determine Gerrit issue for current branch. ' + 'Specify --issue manually.', + file=sys.stderr) + return 1 + + if not args.json: + print(f'Branch: {branch or "(detached HEAD)"}') + print(f'Gerrit Issue: {issue}') + + tryjobs = GetTryResults(issue, patchset=args.patchset) + if not tryjobs: + if args.json: + print('{}') + else: + print('No tryjob results found.') + return 0 + + # Filter out non-test builders that lack test invocations (e.g. presubmit). + test_builds = [ + b for b in tryjobs + if 'id' in b and b.get('builder', {}).get('builder') != 'pdfium_presubmit' + ] + invocation_ids = [f'build-{b["id"]}' for b in test_builds] + + if not args.json: + print(f'Querying ResultDB across {len(invocation_ids)} tryjob builds...') + + surprises = [] + ordinary_skips = [] + + for tr in QueryResultDB(invocation_ids): + status = tr.get('status') + if status != 'SKIP': + continue + + tags = ExtractTags(tr) + builder = tr.get('variant', {}).get('def', {}).get('builder', 'unknown') + test_id = tr.get('testId', 'unknown') + suppression_tag = tags.get('pdfium_suppression') + + entry = { + 'test_id': test_id, + 'builder': builder, + 'status': status, + 'pdfium_suppression': suppression_tag or '(none)', + 'is_surprise': suppression_tag == 'unexpected_success', + 'raw_status': tags.get('raw_status', '(none)'), + 'step_name': tags.get('step_name', '(none)') + } + + if entry['is_surprise']: + surprises.append(entry) + else: + ordinary_skips.append(entry) + + if args.json: + out = {'surprises': surprises} + if args.all_skips: + out['ordinary_skips'] = ordinary_skips + print(json.dumps(out, indent=2)) + return 0 + + print('\n=== Surprise Passes (Unexpected Successes) ===') + if not surprises: + print('No surprise passes found.') + elif args.by_builder: + builder_map = collections.defaultdict(list) + for s in surprises: + builder_map[s['builder']].append(s) + for builder in sorted(builder_map.keys()): + entries = builder_map[builder] + print(f'\nBuilder: {builder} ({len(entries)} entries)') + for e in sorted(entries, key=lambda x: (x['test_id'], x['step_name'])): + print(f' {e["test_id"]}') + print(f' status: {e["status"]}') + print(f' tag: pdfium_suppression={e["pdfium_suppression"]}') + if e['step_name'] != '(none)': + print(f' step: {e["step_name"]}') + else: + test_map = collections.defaultdict(list) + for s in surprises: + test_map[s['test_id']].append(s) + for test_id in sorted(test_map.keys()): + entries = test_map[test_id] + tag_val = entries[0]['pdfium_suppression'] + status = entries[0]['status'] + print(f'\nTest: {test_id}') + print(f' status: {status}') + print(f' tag: pdfium_suppression={tag_val}') + steps = sorted( + set(f'[{e["builder"]}] {e["step_name"]}' if e['step_name'] != + '(none)' else f'[{e["builder"]}]' for e in entries)) + print(f' configurations ({len(entries)}):') + for step in steps: + print(f' - {step}') + + print('\n----------------------------------------') + distinct_tests = len(set(s['test_id'] for s in surprises)) + distinct_builders = len(set(s['builder'] for s in surprises)) + print( + f'Total surprise passes: {len(surprises)} occurrences ' + f'across {distinct_tests} distinct tests on {distinct_builders} builders.' + ) + + if args.all_skips: + print('\n=== Ordinary Skips (No Surprise Tag) ===') + distinct_ord = len(set(s['test_id'] for s in ordinary_skips)) + print(f'Total ordinary skips: {len(ordinary_skips)} occurrences ' + f'across {distinct_ord} distinct tests.') + + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/testing/tools/githelper.py b/testing/tools/githelper.py index 4333e6afda..103dda4a1a 100644 --- a/testing/tools/githelper.py +++ b/testing/tools/githelper.py @@ -46,6 +46,21 @@ def GetCurrentBranchHash(self): return RunCommandPropagateErr(['git', 'rev-parse', 'HEAD'], exit_status_on_error=1).strip() + def GetGerritIssue(self, branch=None): + """Returns the Gerrit issue number for the branch or current issue.""" + if branch: + issue = RunCommandPropagateErr( + ['git', 'config', '--default', '', f'branch.{branch}.gerritissue']) + if issue and issue.strip(): + return issue.strip() + + output = RunCommandPropagateErr(['git', 'cl', 'issue']) + if output and 'Issue number:' in output: + issue = output.split('Issue number:')[1].split('(')[0].strip() + if issue and issue != 'None': + return issue + return None + def IsCurrentBranchClean(self): output = RunCommandPropagateErr(['git', 'status', '--porcelain'], exit_status_on_error=1) diff --git a/testing/tools/pngdiffer.py b/testing/tools/pngdiffer.py index 2b904fe610..c180ebebc4 100755 --- a/testing/tools/pngdiffer.py +++ b/testing/tools/pngdiffer.py @@ -23,6 +23,23 @@ _SKIA_SUFFIX_ORDER = ('_skia_{os}', '_skia') + _COMMON_SUFFIX_ORDER +@dataclass +class DiffMetrics: + actual_w: int + actual_h: int + expected_w: int + expected_h: int + pixels: int + total: int + max_delta: int + mse: float + win_mse: float + + @property + def dims_match(self): + return self.actual_w == self.expected_w and self.actual_h == self.expected_h + + @dataclass class ImageDiff: """Details about an image diff. @@ -32,12 +49,17 @@ class ImageDiff: expected_path: Path to the expected image file, or `None` if no matches. diff_path: Path to the diff image file, or `None` if no diff. reason: Optional reason for the diff. + metrics: Optional DiffMetrics from comparison. + comparison_failure: Whether the diff came from comparing images rather + than a tool execution failure. """ actual_path: str expected_path: str = None diff_path: str = None reason: str = None + metrics: DiffMetrics = None + comparison_failure: bool = True class PNGDiffer: @@ -87,24 +109,121 @@ def _RunCommand(self, cmd): except subprocess.CalledProcessError as e: return e + def _ParseTolerances(self, algorithm, extra_flags): + if algorithm != FUZZY_MATCHING: + return None + # These duplicate kMaxFuzzy* in testing/utils/pixel_diff_util.h. Change one + # without the other and fuzzy tests silently judge by the wrong limits. + delta = 3 + mse = 0.05 + win_mse = 15.0 + for flag in extra_flags: + if flag.startswith('--fuzzy='): + params = flag[len('--fuzzy='):].split(',') + if len(params) > 0 and params[0]: + delta = int(params[0]) + if len(params) > 1 and params[1]: + mse = float(params[1]) + if len(params) > 2 and params[2]: + win_mse = float(params[2]) + return delta, mse, win_mse + + def EvaluateMatch(self, metrics, image_matching_algorithm): + if not metrics.dims_match: + return False, ( + f'dimension mismatch: actual {metrics.actual_w}x{metrics.actual_h} ' + f'!= expected {metrics.expected_w}x{metrics.expected_h}') + + algorithm = image_matching_algorithm + extra_flags = [] + if isinstance(image_matching_algorithm, (tuple, list)): + algorithm, extra_flags = image_matching_algorithm + + if algorithm == EXACT_MATCHING: + if metrics.pixels == 0: + return True, None + return False, ( + f'diff: {metrics.pixels} pixels, max_delta={metrics.max_delta}, ' + f'mse={metrics.mse:.6f}') + + if algorithm == FUZZY_MATCHING: + delta, mse, win_mse = self._ParseTolerances(algorithm, extra_flags) + if (metrics.max_delta <= delta and (mse <= 0.0 or metrics.mse <= mse) and + (win_mse <= 0.0 or metrics.win_mse <= win_mse)): + return True, None + return False, ( + f'exceeds fuzzy: max_delta={metrics.max_delta} (max {delta}), ' + f'mse={metrics.mse:.6f} (max {mse}), ' + f'win_mse={metrics.win_mse:.6f} (max {win_mse})') + + return False, f'Unknown algorithm {algorithm}' + def _RunImageCompareCommand(self, image_diff, image_matching_algorithm): - cmd = [self.pdfium_diff_path] + cmd = [self.pdfium_diff_path, '--metrics'] if self.reverse_byte_order: cmd.append('--reverse-byte-order') - if image_matching_algorithm == FUZZY_MATCHING: - cmd.append('--fuzzy') cmd.extend([image_diff.actual_path, image_diff.expected_path]) - return self._RunCommand(cmd) + + # Failure to run the binary at all is fatal, and is left to propagate. + result = subprocess.run(cmd, capture_output=True, text=True, check=False) + + # `--metrics` reports the comparison result in-band, so a non-zero exit + # means the comparison could not be performed at all. + if result.returncode != 0: + return subprocess.CalledProcessError( + result.returncode, cmd, output=result.stdout, stderr=result.stderr) + + # Parse metrics line: : actual_w=... + base_name = os.path.basename(image_diff.actual_path) + metrics_line = None + for line in result.stdout.splitlines(): + if line.startswith(f'{base_name}:'): + metrics_line = line + break + + if not metrics_line: + return RuntimeError( + f'Failed to find metric line for {base_name} in output: ' + f'{result.stdout}') + + tokens = metrics_line.split() + kv = {} + for token in tokens[1:]: + if '=' in token: + k, v = token.split('=', 1) + kv[k] = v + + metrics = DiffMetrics( + actual_w=int(kv['actual_w']), + actual_h=int(kv['actual_h']), + expected_w=int(kv['expected_w']), + expected_h=int(kv['expected_h']), + pixels=int(kv['pixels']), + total=int(kv['total']), + max_delta=int(kv['max_delta']), + mse=float(kv['mse']), + win_mse=float(kv['win_mse'])) + image_diff.metrics = metrics + + matched, failure_reason = self.EvaluateMatch(metrics, + image_matching_algorithm) + return None if matched else failure_reason def _RunImageDiffCommand(self, image_diff): # TODO(crbug.com/42270934): Diff mode ignores --reverse-byte-order. - return self._RunCommand([ + cmd = [ self.pdfium_diff_path, '--subtract', image_diff.actual_path, image_diff.expected_path, image_diff.diff_path, - ]) + ] + # `--subtract` writes nothing when it considers the images the same, so + # remove any diff left over from a previous run before checking below. + if os.path.exists(image_diff.diff_path): + os.unlink(image_diff.diff_path) + subprocess.run(cmd, capture_output=True, check=False) + return os.path.exists(image_diff.diff_path) def ComputeDifferences(self, input_filename, source_dir, working_dir, image_matching_algorithm): @@ -120,6 +239,13 @@ def ComputeDifferences(self, input_filename, source_dir, working_dir, for page in itertools.count(): page_diff = ImageDiff(actual_path=path_templates.GetActualPath(page)) if not os.path.exists(page_diff.actual_path): + expected_path = path_templates.GetExpectedPath( + page, default_to_base=False) + if expected_path: + page_diff.expected_path = expected_path + page_diff.reason = f'{page_diff.actual_path} does not exist' + image_diffs.append(page_diff) + continue # No more actual pages. break @@ -131,12 +257,14 @@ def ComputeDifferences(self, input_filename, source_dir, working_dir, image_matching_algorithm) if compare_error: page_diff.reason = str(compare_error) - - # TODO(crbug.com/42270934): Compare and diff simultaneously. - page_diff.diff_path = path_templates.GetDiffPath(page) - if not self._RunImageDiffCommand(page_diff): - print(f'WARNING: No diff for {page_diff.actual_path}') - page_diff.diff_path = None + if isinstance(compare_error, Exception): + page_diff.comparison_failure = False + else: + # TODO(crbug.com/42270934): Compare and diff simultaneously. + page_diff.diff_path = path_templates.GetDiffPath(page) + if not self._RunImageDiffCommand(page_diff): + print(f'WARNING: No diff for {page_diff.actual_path}') + page_diff.diff_path = None else: # Validate that no other paths match exactly. for unexpected_path in path_templates.GetExpectedPaths(page)[1:]: diff --git a/testing/tools/suppressor.py b/testing/tools/suppressor.py index 180536e322..d7c4c9925b 100755 --- a/testing/tools/suppressor.py +++ b/testing/tools/suppressor.py @@ -9,43 +9,119 @@ import pngdiffer +def _ParseFuzzyAction(token): + """Converts a fuzzy action token into a single pdfium_diff flag.""" + if token == 'fuzzy': + return '--fuzzy' + params = token[len('fuzzy='):].split(',') + if not (1 <= len(params) <= 3): + raise ValueError(f'Invalid fuzzy option format: {token}') + try: + delta = int(params[0]) + except ValueError as e: + raise ValueError(f'Invalid delta value in fuzzy option: {token}') from e + if not (0 <= delta <= 255): + raise ValueError( + f'Delta value {delta} out of range [0, 255] in fuzzy option: {token}') + for p in params[1:]: + try: + val = float(p) + except ValueError as e: + raise ValueError( + f'Invalid float value "{p}" in fuzzy option: {token}') from e + if val < 0.0: + raise ValueError( + f'Negative value {val} not allowed in fuzzy option: {token}') + return f'--{token}' + + +# Legal values for each predicate column, keyed by column index. Column 0 is +# the test file name, which is unconstrained. +_VALID_COLUMN_VALUES = { + 1: {'*', 'win', 'mac', 'mac_arm', 'mac_x86', 'linux'}, + 2: {'*', 'nov8', 'v8'}, + 3: {'*', 'noxfa', 'xfa'}, + 4: {'*', 'agg', 'gdi', 'gdi_agg', 'gdi_skia', 'skia'}, + 5: {'*', 'freetype', 'fontations'}, +} + +# Legal keywords for the action in column 6. +_VALID_ACTIONS = {'diff', 'blank', 'fuzzy', 'skip'} + + +def _ValidatePredicates(item): + """Rejects unknown tokens, which would otherwise silently never match.""" + for column, valid_values in _VALID_COLUMN_VALUES.items(): + for value in item[column].split(','): + if value not in valid_values: + raise ValueError(f'Unexpected value "{value}" in column {column} of ' + f'suppressions: {" ".join(item)}') + + +def _ParseAction(token): + """Validates an action token, returning its keyword and any diff flags.""" + keyword = token.split('=', 1)[0] + if keyword not in _VALID_ACTIONS: + raise ValueError(f'Unexpected action in suppressions: {token}') + return keyword, ([_ParseFuzzyAction(token)] if keyword == 'fuzzy' else []) + + +def _SuppressionSpecificity(item): + """Scores columns 1-5 as a little-endian specificity bitmask.""" + return sum((item[col] != '*') << (col - 1) for col in range(1, 6)) + + class Suppressor: def __init__(self, finder, features, js_disabled, xfa_disabled, - rendering_option): + rendering_option, font_engine): self.has_v8 = not js_disabled and 'V8' in features self.has_xfa = not js_disabled and not xfa_disabled and 'XFA' in features - self.has_rust_decoders = ('RUST_BMP' in features or - 'RUST_JPEG' in features or 'RUST_PNG' in features) - self.rendering_option = rendering_option - self.suppression_set = self._LoadSuppressedSet('SUPPRESSIONS', finder) - self.image_suppression_set = self._LoadSuppressedSet( - 'SUPPRESSIONS_IMAGE_DIFF', finder) - self.exact_matching_suppression_set = self._LoadSuppressedSet( - 'SUPPRESSIONS_EXACT_MATCHING', finder) - - def _LoadSuppressedSet(self, suppressions_filename, finder): + if rendering_option == 'gdi': + self.rendering_option = 'gdi_skia' if 'SKIA' in features else 'gdi_agg' + else: + self.rendering_option = rendering_option + self.font_engine = font_engine + self.suppression_set = set() + self.execution_suppression_set = set() + self.image_suppression_set = set() + self.exact_matching_suppression_dict = {} + self._LoadSuppressions(finder) + + def _LoadSuppressions(self, finder): v8_option = "v8" if self.has_v8 else "nov8" xfa_option = "xfa" if self.has_xfa else "noxfa" - with open(os.path.join(finder.TestingDir(), suppressions_filename)) as f: + with open(os.path.join(finder.TestingDir(), 'SUPPRESSIONS')) as f: os_name = common.os_name() mac_platform = common.mac_platform() if os_name == 'mac' else None - return set( - self._FilterSuppressions(os_name, mac_platform, v8_option, xfa_option, - self.rendering_option, - self._ExtractSuppressions(f))) + fuzzy_specificity = {} + for item in self._ExtractSuppressions(f): + if len(item) != 7: + raise ValueError(f'Unexpected column count in suppressions: {item}') + _ValidatePredicates(item) + keyword, flags = _ParseAction(item[6]) + if not self._MatchSuppression(item, os_name, mac_platform, v8_option, + xfa_option, self.rendering_option, + self.font_engine): + continue + filename = item[0] + if keyword == 'diff': + self.suppression_set.add(filename) + elif keyword == 'skip': + self.execution_suppression_set.add(filename) + elif keyword == 'blank': + self.image_suppression_set.add(filename) + elif keyword == 'fuzzy': + specificity = _SuppressionSpecificity(item) + if specificity >= fuzzy_specificity.get(filename, -1): + fuzzy_specificity[filename] = specificity + self.exact_matching_suppression_dict[filename] = flags + else: + raise AssertionError(f'Unhandled action: {keyword}') def _ExtractSuppressions(self, f): return [ - y.split(' ') for y in [x.split('#')[0].strip() - for x in f.readlines()] if y - ] - - def _FilterSuppressions(self, os_name, mac_platform, js, xfa, - rendering_option, unfiltered_list): - return [ - x[0] for x in unfiltered_list if self._MatchSuppression( - x, os_name, mac_platform, js, xfa, rendering_option) + y.split() for y in [x.split('#')[0].strip() for x in f.readlines()] if y ] @staticmethod @@ -57,17 +133,28 @@ def _MatchOs(os_name, mac_platform, os_column): return f'{os_name}_{mac_platform}' in os_column return False + @staticmethod + def _MatchRenderer(rendering_option, rendering_option_column): + if ('*' in rendering_option_column or + rendering_option in rendering_option_column): + return True + if rendering_option.startswith('gdi_'): + return 'gdi' in rendering_option_column + return False + def _MatchSuppression(self, item, os_name, mac_platform, js, xfa, - rendering_option): + rendering_option, font_engine): os_column = item[1].split(",") js_column = item[2].split(",") xfa_column = item[3].split(",") rendering_option_column = item[4].split(",") + font_engine_column = item[5].split(",") return (Suppressor._MatchOs(os_name, mac_platform, os_column) and ('*' in js_column or js in js_column) and ('*' in xfa_column or xfa in xfa_column) and - ('*' in rendering_option_column or - rendering_option in rendering_option_column)) + Suppressor._MatchRenderer(rendering_option, + rendering_option_column) and + ('*' in font_engine_column or font_engine in font_engine_column)) def IsResultSuppressed(self, input_filename): if input_filename in self.suppression_set: @@ -76,7 +163,8 @@ def IsResultSuppressed(self, input_filename): return False def IsExecutionSuppressed(self, input_filepath): - if "xfa_specific" in input_filepath and not self.has_xfa: + if (('xfa_specific' in input_filepath and not self.has_xfa) or + os.path.basename(input_filepath) in self.execution_suppression_set): print("%s execution is suppressed" % input_filepath) return True return False @@ -88,8 +176,8 @@ def IsImageDiffSuppressed(self, input_filename): return False def GetImageMatchingAlgorithm(self, input_filename): - if (self.has_rust_decoders or - input_filename in self.exact_matching_suppression_set): + if input_filename in self.exact_matching_suppression_dict: print(f"{input_filename} image diff comparison is fuzzy") - return pngdiffer.FUZZY_MATCHING - return pngdiffer.EXACT_MATCHING + return (pngdiffer.FUZZY_MATCHING, + self.exact_matching_suppression_dict[input_filename]) + return (pngdiffer.EXACT_MATCHING, []) diff --git a/testing/tools/test_runner.py b/testing/tools/test_runner.py index 9a8605663d..9edd9b5994 100644 --- a/testing/tools/test_runner.py +++ b/testing/tools/test_runner.py @@ -70,13 +70,18 @@ def IsResultSuppressed(self, input_filename): def HandleResult(self, test_case, test_result): input_filename = os.path.basename(test_case.input_path) - test_result.status = self._SuppressStatus(input_filename, - test_result.status) - if test_result.status == result_types.UNKNOWN: - self.result_suppressed_cases.append(input_filename) - self.surprises.append(test_case.input_path) - elif test_result.status == result_types.SKIP: + unexpected_success = False + if self.IsResultSuppressed(input_filename) and test_result.IsSuppressible(): self.result_suppressed_cases.append(input_filename) + unexpected_success = test_result.IsPass() + if unexpected_success: + self.surprises.append(test_case.input_path) + + # There is no status for ignored, so report both succeeded-but-ignored + # and failed-but-ignored as skipped. Anything else is recorded as an + # unexpected result, which turns the build red mid-run over a + # non-failure and pollutes the "abort" status used by real timeouts. + test_result.status = result_types.SKIP elif not test_result.IsPass(): self.failures.append(test_case.input_path) @@ -111,6 +116,9 @@ def HandleResult(self, test_case, test_result): only_artifacts = only.GetDiffArtifacts() if only.GetDiffReason(): only_failure_reason += f': {only.GetDiffReason()}' + tags = None + if unexpected_success: + tags = [('pdfium_suppression', 'unexpected_success')] self.resultdb.Post( test_id=test_result.test_id, status=test_result.status, @@ -118,7 +126,8 @@ def HandleResult(self, test_case, test_result): test_log=test_result.log, test_file=None, artifacts=only_artifacts, - failure_reason=only_failure_reason) + failure_reason=only_failure_reason, + tags=tags) # Milo only supports a single diff per test, so if we have multiple pages, # report each page as its own "test." @@ -134,21 +143,6 @@ def HandleResult(self, test_case, test_result): artifacts=artifact.GetDiffArtifacts(), failure_reason=artifact.GetDiffReason()) - def _SuppressStatus(self, input_filename, status): - if not self.IsResultSuppressed(input_filename): - return status - - if status == result_types.PASS: - # There isn't an actual status for succeeded-but-ignored, so use the - # "abort" status to differentiate this from failed-but-ignored. - # - # Note that this appears as a preliminary failure in Gerrit. - return result_types.UNKNOWN - - # There isn't an actual status for failed-but-ignored, so use the "skip" - # status to differentiate this from succeeded-but-ignored. - return result_types.SKIP - def _SuppressArtifactStatus(self, test_result, status): if status != result_types.FAIL: return status @@ -454,6 +448,11 @@ class _PerProcessConfig: default_renderer: str = None rendering_option: str = None + @property + def font_engine(self): + """The font engine to use (freetype or fontations).""" + return 'fontations' if self.options.fontations else 'freetype' + def NewFinder(self): return common.DirectoryFinder(self.options.build_dir) @@ -508,7 +507,7 @@ def __init__(self, config): self.test_suppressor = suppressor.Suppressor( finder, self.features, self.options.disable_javascript, - self.options.disable_xfa, config.rendering_option) + self.options.disable_xfa, config.rendering_option, config.font_engine) self.image_differ = pngdiffer.PNGDiffer(finder, self.options.reverse_byte_order, config.rendering_option, @@ -630,10 +629,12 @@ def RunCommand(self, command, stdout=None): if not test_result.IsPass(): # On failure, report captured output to the test log. if stderr == subprocess.STDOUT: - test_result.log = run_result.stdout + log = run_result.stdout else: - test_result.log = run_result.stderr - test_result.log = test_result.log.decode(errors='backslashreplace') + log = run_result.stderr + # There is nothing captured on timeout, because `subprocess.run()` only + # kills and reaps the process before re-raising. + test_result.log = (log or b'').decode(errors='backslashreplace') return test_result def GenerateAndTest(self, test_function): @@ -698,15 +699,16 @@ def TestText(self): return self._VerifyEmptyText(txt_path) # If JavaScript is disabled, the output should be empty. - # However, if the test is suppressed and JavaScript is disabled, do not - # verify that the text is empty so the suppressed test does not surprise. - if self.options.disable_javascript and not self.IsResultSuppressed(): + if self.options.disable_javascript: return self._VerifyEmptyText(txt_path) - return self.RunCommand([ + test_result = self.RunCommand([ sys.executable, _per_process_state.text_diff_path, expected_txt_path, txt_path ]) + # The differ does nothing but compare, so a plain failure is a mismatch. + test_result.comparison_failure = test_result.status == result_types.FAIL + return test_result def _VerifyEmptyText(self, txt_path): with open(txt_path, 'rb') as txt_file: @@ -716,7 +718,8 @@ def _VerifyEmptyText(self, txt_path): return self.test_case.NewResult( result_types.FAIL, log=txt_data.decode(errors='backslashreplace'), - reason=f'{txt_path} should be empty') + reason=f'{txt_path} should be empty', + comparison_failure=True) return self.test_case.NewResult(result_types.PASS) @@ -791,13 +794,17 @@ def TestPixel(self): if image_diffs: test_result.status = result_types.FAIL test_result.reason = 'Images differ' + test_result.comparison_failure = True # Merge image diffs into test result. diff_map = {} diff_log = [] for diff in image_diffs: diff_map[diff.actual_path] = diff - diff_log.append(f'{os.path.basename(diff.actual_path)} vs. ') + if os.path.exists(diff.actual_path): + diff_log.append(f'{os.path.basename(diff.actual_path)} vs. ') + else: + diff_log.append('missing actual file vs. ') if diff.expected_path: diff_log.append(f'{os.path.basename(diff.expected_path)}\n') else: @@ -811,6 +818,7 @@ def TestPixel(self): if not self.IsImageDiffSuppressed(): test_result.status = result_types.FAIL test_result.reason = 'Missing expected images' + test_result.comparison_failure = True if not test_result.IsPass(): self._RegenerateIfNeeded() @@ -865,6 +873,9 @@ class TestResult: log: Optional log of the test's output. image_artfacts: Optional list of image artifacts. reason: Optional reason why the test failed. + comparison_failure: Whether the failure came from comparing output + against expectations, rather than from the test failing to run to + completion. """ test_id: str status: str @@ -872,11 +883,21 @@ class TestResult: log: str = None image_artifacts: list = field(default_factory=list) reason: str = None + comparison_failure: bool = False def IsPass(self): """Whether the test passed.""" return self.status == result_types.PASS + def IsSuppressible(self): + """Whether a suppression is allowed to ignore this result. + + Suppressions excuse output that does not match expectations. A crash, + a timeout or a missing result is a defect in its own right, and is + reported however far off the expectations are allowed to be. + """ + return self.IsPass() or self.comparison_failure + @dataclass class ImageArtifact: diff --git a/testing/unit_test_main.cpp b/testing/unit_test_main.cpp index 8253a7b844..2e23828fbc 100644 --- a/testing/unit_test_main.cpp +++ b/testing/unit_test_main.cpp @@ -56,7 +56,8 @@ int main(int argc, char** argv) { #endif // BUILDFLAG(IS_WIN) // PDF test environment will be deleted by gtest. - AddGlobalTestEnvironment(new PDFTestEnvironment()); + auto* pdf_test_environment = new PDFTestEnvironment(); + AddGlobalTestEnvironment(pdf_test_environment); #ifdef PDF_ENABLE_V8 // V8 test environment will be deleted by gtest. @@ -70,5 +71,8 @@ int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); testing::InitGoogleMock(&argc, argv); + // Anything remaining in argc/argv is a unit_tests flag. + pdf_test_environment->AddFlags(argc, argv); + return RUN_ALL_TESTS(); } diff --git a/testing/utils/pixel_diff_util.cpp b/testing/utils/pixel_diff_util.cpp index 433a53b164..a9531fe536 100644 --- a/testing/utils/pixel_diff_util.cpp +++ b/testing/utils/pixel_diff_util.cpp @@ -4,9 +4,11 @@ #include "testing/utils/pixel_diff_util.h" +#include +#include + #include -#include -#include +#include namespace { @@ -50,3 +52,138 @@ uint32_t PixelSquaredError(uint32_t baseline_pixel, uint32_t actual_pixel) { int db = baseline_unpacked.blue - actual_unpacked.blue; return static_cast(dr * dr + dg * dg + db * db); } + +double CalculateMaxWindowMSE(pdfium::span baseline, + size_t baseline_stride_pixels, + pdfium::span actual, + size_t actual_stride_pixels, + int w, + int h, + int window_size) { + if (w <= 0 || h <= 0 || window_size <= 0) { + return 0.0; + } + + constexpr double kChannelCount = 3.0; + if (baseline_stride_pixels < static_cast(w) || + actual_stride_pixels < static_cast(w)) { + return 0.0; + } + const size_t min_baseline_size = + (static_cast(h) - 1) * baseline_stride_pixels + w; + const size_t min_actual_size = + (static_cast(h) - 1) * actual_stride_pixels + w; + if (baseline.size() < min_baseline_size || actual.size() < min_actual_size) { + return 0.0; + } + + // If image is smaller than window_size in either dimension, evaluate global + // MSE. + if (w < window_size || h < window_size) { + uint64_t total_sq_err = 0; + for (int y = 0; y < h; ++y) { + const size_t baseline_row_offset = y * baseline_stride_pixels; + const size_t actual_row_offset = y * actual_stride_pixels; + for (int x = 0; x < w; ++x) { + total_sq_err += PixelSquaredError(baseline[baseline_row_offset + x], + actual[actual_row_offset + x]); + } + } + const size_t total_pixels = static_cast(w) * h; + return static_cast(total_sq_err) / (kChannelCount * total_pixels); + } + + // Compute 2D Summed-Area Table (Integral Image) of squared errors. + const size_t sat_stride = static_cast(w) + 1; + std::vector sat(sat_stride * (h + 1), 0); + + for (int y = 0; y < h; ++y) { + const size_t baseline_row_offset = y * baseline_stride_pixels; + const size_t actual_row_offset = y * actual_stride_pixels; + for (int x = 0; x < w; ++x) { + const uint64_t sq = PixelSquaredError(baseline[baseline_row_offset + x], + actual[actual_row_offset + x]); + sat[(y + 1) * sat_stride + (x + 1)] = sq + sat[y * sat_stride + (x + 1)] + + sat[(y + 1) * sat_stride + x] - + sat[y * sat_stride + x]; + } + } + + // Find maximum local MSE across all overlapping (window_size x window_size) + // windows. + double max_win_mse = 0.0; + const double denom = kChannelCount * window_size * window_size; + + for (int y = 0; y + window_size <= h; ++y) { + for (int x = 0; x + window_size <= w; ++x) { + const int x1 = x; + const int y1 = y; + const int x2 = x + window_size; + const int y2 = y + window_size; + + const uint64_t win_sum = + sat[y2 * sat_stride + x2] - sat[y1 * sat_stride + x2] - + sat[y2 * sat_stride + x1] + sat[y1 * sat_stride + x1]; + + const double win_mse = static_cast(win_sum) / denom; + max_win_mse = std::max(max_win_mse, win_mse); + } + } + + return max_win_mse; +} + +int CalculatePixelsDifferent(pdfium::span baseline, + size_t baseline_stride_pixels, + pdfium::span actual, + size_t actual_stride_pixels, + int w, + int h, + const DiffOptions& options) { + if (w <= 0 || h <= 0) { + return 0; + } + + int pixels_different = 0; + uint64_t total_squared_error = 0; + for (int y = 0; y < h; ++y) { + const size_t baseline_row_offset = y * baseline_stride_pixels; + const size_t actual_row_offset = y * actual_stride_pixels; + for (int x = 0; x < w; ++x) { + const uint32_t baseline_pixel = baseline[baseline_row_offset + x]; + const uint32_t actual_pixel = actual[actual_row_offset + x]; + if (baseline_pixel == actual_pixel) { + continue; + } + + if (options.max_pixel_per_channel_delta == 0 || + MaxPixelPerChannelDelta(baseline_pixel, actual_pixel) > + options.max_pixel_per_channel_delta) { + ++pixels_different; + } + if (options.max_mean_squared_error > 0.0) { + total_squared_error += PixelSquaredError(baseline_pixel, actual_pixel); + } + } + } + + if (options.max_mean_squared_error > 0.0) { + constexpr double kChannelCount = 3.0; + const double mse = + static_cast(total_squared_error) / (kChannelCount * w * h); + if (mse > options.max_mean_squared_error) { + ++pixels_different; + } + } + + if (options.max_window_mean_squared_error > 0.0 && options.window_size > 0) { + const double max_win_mse = + CalculateMaxWindowMSE(baseline, baseline_stride_pixels, actual, + actual_stride_pixels, w, h, options.window_size); + if (max_win_mse > options.max_window_mean_squared_error) { + ++pixels_different; + } + } + + return pixels_different; +} diff --git a/testing/utils/pixel_diff_util.h b/testing/utils/pixel_diff_util.h index 9a67500e00..c15e6366ce 100644 --- a/testing/utils/pixel_diff_util.h +++ b/testing/utils/pixel_diff_util.h @@ -5,11 +5,48 @@ #ifndef TESTING_UTILS_PIXEL_DIFF_UTIL_H_ #define TESTING_UTILS_PIXEL_DIFF_UTIL_H_ -#include +#include -// Standard fuzzy matching limits. +#include "core/fxcrt/span.h" + +// Standard fuzzy matching limits, duplicated in testing/tools/pngdiffer.py. +// Change one without the other and fuzzy tests silently judge by the wrong +// limits. inline constexpr uint8_t kMaxFuzzyPixelDelta = 3; inline constexpr double kMaxFuzzyMeanSquaredError = 0.05; +inline constexpr int kMaxFuzzyWindowSize = 8; +inline constexpr double kMaxFuzzyWindowMeanSquaredError = 15.0; + +// Fontations fuzzy matching limits: allows a larger per-channel delta to +// accommodate subpixel curve antialiasing variances while enforcing a tight +// mean squared error bound. +inline constexpr uint8_t kMaxFontationsPixelDelta = 10; +inline constexpr double kMaxFontationsMeanSquaredError = 0.10; +inline constexpr int kMaxFontationsWindowSize = kMaxFuzzyWindowSize; +inline constexpr double kMaxFontationsWindowMeanSquaredError = + kMaxFuzzyWindowMeanSquaredError; + +// Options controlling pixel difference comparisons. +struct DiffOptions { + int max_pixel_per_channel_delta = 0; + double max_mean_squared_error = 0.0; + int window_size = 0; + double max_window_mean_squared_error = 0.0; +}; + +inline constexpr DiffOptions kExactDiffOptions = {}; +inline constexpr DiffOptions kFuzzyDiffOptions = { + .max_pixel_per_channel_delta = kMaxFuzzyPixelDelta, + .max_mean_squared_error = kMaxFuzzyMeanSquaredError, + .window_size = kMaxFuzzyWindowSize, + .max_window_mean_squared_error = kMaxFuzzyWindowMeanSquaredError, +}; +inline constexpr DiffOptions kFontationsDiffOptions = { + .max_pixel_per_channel_delta = kMaxFontationsPixelDelta, + .max_mean_squared_error = kMaxFontationsMeanSquaredError, + .window_size = kMaxFontationsWindowSize, + .max_window_mean_squared_error = kMaxFontationsWindowMeanSquaredError, +}; // Returns the largest difference in pixel channels between `baseline_pixel` and // `actual_pixel`. Pixels are expected to be in 32-bit ARGB or BGRA format. @@ -19,4 +56,28 @@ uint8_t MaxPixelPerChannelDelta(uint32_t baseline_pixel, uint32_t actual_pixel); // between `baseline_pixel` and `actual_pixel`. uint32_t PixelSquaredError(uint32_t baseline_pixel, uint32_t actual_pixel); +// Computes the maximum local Mean Squared Error across all overlapping +// (`window_size` x `window_size`) windows between two images of dimensions +// (`w`, `h`) with respective row strides in pixels (`baseline_stride_pixels`, +// `actual_stride_pixels`). Pixels are in 32-bit ARGB or BGRA format. +double CalculateMaxWindowMSE(pdfium::span baseline, + size_t baseline_stride_pixels, + pdfium::span actual, + size_t actual_stride_pixels, + int w, + int h, + int window_size); + +// Returns the number of differing pixels between `baseline` and `actual` within +// the overlap region of dimensions (`w`, `h`), taking into account maximum +// per-channel delta, global mean squared error, and optional windowed mean +// squared error. Pixels are in 32-bit ARGB or BGRA format. +int CalculatePixelsDifferent(pdfium::span baseline, + size_t baseline_stride_pixels, + pdfium::span actual, + size_t actual_stride_pixels, + int w, + int h, + const DiffOptions& options = kExactDiffOptions); + #endif // TESTING_UTILS_PIXEL_DIFF_UTIL_H_ diff --git a/testing/utils/pixel_diff_util_unittest.cpp b/testing/utils/pixel_diff_util_unittest.cpp index 5890a08904..cc60693556 100644 --- a/testing/utils/pixel_diff_util_unittest.cpp +++ b/testing/utils/pixel_diff_util_unittest.cpp @@ -6,6 +6,8 @@ #include +#include + #include "testing/gtest/include/gtest/gtest.h" TEST(PixelDiffUtilTest, MaxPixelPerChannelDeltaExactMatch) { @@ -46,4 +48,165 @@ TEST(PixelDiffUtilTest, PixelSquaredError) { TEST(PixelDiffUtilTest, FuzzyThresholds) { EXPECT_EQ(3, kMaxFuzzyPixelDelta); EXPECT_DOUBLE_EQ(0.05, kMaxFuzzyMeanSquaredError); + EXPECT_EQ(8, kMaxFuzzyWindowSize); + EXPECT_DOUBLE_EQ(15.0, kMaxFuzzyWindowMeanSquaredError); +} + +TEST(PixelDiffUtilTest, CalculateMaxWindowMSEExactMatch) { + std::vector img(64, 0xffffffff); + EXPECT_DOUBLE_EQ(0.0, CalculateMaxWindowMSE(img, 8, img, 8, 8, 8, 8)); +} + +TEST(PixelDiffUtilTest, CalculateMaxWindowMSEUniformDelta) { + // Uniform delta of 1 on red channel across entire image. + std::vector img1(64, 0x00000000); + std::vector img2(64, 0x00000001); + // Squared error = 1 on 1 channel per pixel -> local MSE = 1 / 3 = 0.3333... + EXPECT_NEAR(1.0 / 3.0, CalculateMaxWindowMSE(img1, 8, img2, 8, 8, 8, 4), + 1e-6); +} + +TEST(PixelDiffUtilTest, CalculateMaxWindowMSELocalizedDefect) { + // 16x16 white image with a 4x4 black box defect at (4, 4). + std::vector baseline(16 * 16, 0x00ffffff); + std::vector actual = baseline; + for (int y = 4; y < 8; ++y) { + for (int x = 4; x < 8; ++x) { + actual[y * 16 + x] = 0x00000000; + } + } + + // Window size = 8. + // The window from (4, 4) to (12, 12) captures all 16 defect pixels. + // Defect error = 16 pixels * (3 * 255^2) = 3121200. + // Window MSE = 3121200 / (3 * 64) = 16256.25. + EXPECT_DOUBLE_EQ(16256.25, + CalculateMaxWindowMSE(baseline, 16, actual, 16, 16, 16, 8)); +} + +TEST(PixelDiffUtilTest, CalculateMaxWindowMSEChromaEdge) { + // 8x8 image with an 8-pixel line of delta = 10 on all 3 channels. + std::vector baseline(8 * 8, 0x00000000); + std::vector actual = baseline; + for (int x = 0; x < 8; ++x) { + actual[x] = 0x000a0a0a; // r=10, g=10, b=10 + } + + // 8 pixels * 3 channels * 10^2 = 2400. + // Window MSE = 2400 / (3 * 64) = 12.5. + EXPECT_DOUBLE_EQ(12.5, + CalculateMaxWindowMSE(baseline, 8, actual, 8, 8, 8, 8)); +} + +TEST(PixelDiffUtilTest, CalculateMaxWindowMSEWithStrides) { + // 8x8 image within a buffer with stride = 12. + constexpr int kWidth = 8; + constexpr int kHeight = 8; + constexpr size_t kStride = 12; + std::vector baseline(kStride * kHeight, 0x00000000); + std::vector actual = baseline; + // Uniform delta of 1 on red channel inside the 8x8 region. + for (int y = 0; y < kHeight; ++y) { + for (int x = 0; x < kWidth; ++x) { + actual[y * kStride + x] = 0x00000001; + } + } + // Padding pixels outside 8x8 should not affect calculation. + for (int y = 0; y < kHeight; ++y) { + for (size_t x = kWidth; x < kStride; ++x) { + actual[y * kStride + x] = 0x00ffffff; + } + } + EXPECT_NEAR(1.0 / 3.0, + CalculateMaxWindowMSE(baseline, kStride, actual, kStride, kWidth, + kHeight, 4), + 1e-6); +} + +TEST(PixelDiffUtilTest, CalculateMaxWindowMSEInvalidInputs) { + std::vector img(16, 0); + EXPECT_DOUBLE_EQ(0.0, CalculateMaxWindowMSE(img, 0, img, 0, 0, 0, 8)); + EXPECT_DOUBLE_EQ(0.0, CalculateMaxWindowMSE(img, 4, img, 4, 4, 4, 0)); +} + +TEST(PixelDiffUtilTest, CalculatePixelsDifferentExactMatch) { + std::vector img(64, 0xffffffff); + EXPECT_EQ(0, + CalculatePixelsDifferent(img, 8, img, 8, 8, 8, kExactDiffOptions)); +} + +TEST(PixelDiffUtilTest, CalculatePixelsDifferentTolerance) { + std::vector baseline(64, 0x00000000); + std::vector actual = baseline; + actual[0] = 0x00020202; // delta 2 on all channels + // Exact match fails. + EXPECT_EQ(1, CalculatePixelsDifferent(baseline, 8, actual, 8, 8, 8, + kExactDiffOptions)); + // Tolerating delta 2 passes. + EXPECT_EQ(0, CalculatePixelsDifferent( + baseline, 8, actual, 8, 8, 8, + DiffOptions{.max_pixel_per_channel_delta = 2})); + // Tolerating delta 2 but strict MSE (0.0001) fails. + EXPECT_EQ(1, CalculatePixelsDifferent( + baseline, 8, actual, 8, 8, 8, + DiffOptions{.max_pixel_per_channel_delta = 2, + .max_mean_squared_error = 0.0001})); + // Window MSE failure triggers. + EXPECT_EQ(1, CalculatePixelsDifferent( + baseline, 8, actual, 8, 8, 8, + DiffOptions{.max_pixel_per_channel_delta = 2, + .max_mean_squared_error = 1.0, + .window_size = 8, + .max_window_mean_squared_error = 0.01})); + + // Under exact diff, all 64 pixels differ. + std::vector img1(64, 0x00000000); + std::vector img2(64, 0x00000001); // delta 1 on red + EXPECT_EQ( + 64, CalculatePixelsDifferent(img1, 8, img2, 8, 8, 8, kExactDiffOptions)); + + // Under fuzzy diff (tolerates delta <= 3 and MSE <= 0.05). + // Here MSE = (64 * 1) / (3 * 64) = 0.333 > 0.05, so MSE fails and returns > + // 0. + EXPECT_GT(CalculatePixelsDifferent(img1, 8, img2, 8, 8, 8, kFuzzyDiffOptions), + 0); + + // With a relaxed MSE threshold of 0.5, it should report 0 differences. + DiffOptions relaxed_options = { + .max_pixel_per_channel_delta = 1, + .max_mean_squared_error = 0.5, + }; + EXPECT_EQ(0, + CalculatePixelsDifferent(img1, 8, img2, 8, 8, 8, relaxed_options)); +} + +TEST(PixelDiffUtilTest, DiffOptionsAggregateAndDefaults) { + static_assert(std::is_aggregate_v); + + constexpr DiffOptions default_options; + EXPECT_EQ(0, default_options.max_pixel_per_channel_delta); + EXPECT_DOUBLE_EQ(0.0, default_options.max_mean_squared_error); + EXPECT_EQ(0, default_options.window_size); + EXPECT_DOUBLE_EQ(0.0, default_options.max_window_mean_squared_error); + + EXPECT_EQ(0, kExactDiffOptions.max_pixel_per_channel_delta); + EXPECT_DOUBLE_EQ(0.0, kExactDiffOptions.max_mean_squared_error); + EXPECT_EQ(0, kExactDiffOptions.window_size); + EXPECT_DOUBLE_EQ(0.0, kExactDiffOptions.max_window_mean_squared_error); + + EXPECT_EQ(kMaxFuzzyPixelDelta, kFuzzyDiffOptions.max_pixel_per_channel_delta); + EXPECT_DOUBLE_EQ(kMaxFuzzyMeanSquaredError, + kFuzzyDiffOptions.max_mean_squared_error); + EXPECT_EQ(kMaxFuzzyWindowSize, kFuzzyDiffOptions.window_size); + EXPECT_DOUBLE_EQ(kMaxFuzzyWindowMeanSquaredError, + kFuzzyDiffOptions.max_window_mean_squared_error); + + // Verification with options struct. + std::vector baseline(64, 0x00000000); + std::vector actual = baseline; + actual[0] = 0x00000002; + EXPECT_EQ(1, CalculatePixelsDifferent(baseline, 8, actual, 8, 8, 8, + kExactDiffOptions)); + EXPECT_EQ(0, CalculatePixelsDifferent(baseline, 8, actual, 8, 8, 8, + kFuzzyDiffOptions)); } diff --git a/testing/xfa_js_embedder_test.cpp b/testing/xfa_js_embedder_test.cpp index aede13d3f3..9bbdfabe62 100644 --- a/testing/xfa_js_embedder_test.cpp +++ b/testing/xfa_js_embedder_test.cpp @@ -23,6 +23,17 @@ XFAJSEmbedderTest::XFAJSEmbedderTest() = default; XFAJSEmbedderTest::~XFAJSEmbedderTest() = default; +v8::Isolate* XFAJSEmbedderTest::isolate() const { + auto* doc = CPDFDocumentFromFPDFDocument(document()); + if (doc) { + auto* context = static_cast(doc->GetExtension()); + if (context && context->GetIsolate()) { + return context->GetIsolate(); + } + } + return JSEmbedderTest::isolate(); +} + void XFAJSEmbedderTest::SetUp() { JSEmbedderTest::SetUp(); } diff --git a/testing/xfa_js_embedder_test.h b/testing/xfa_js_embedder_test.h index 6badcc4b77..2152a7a59d 100644 --- a/testing/xfa_js_embedder_test.h +++ b/testing/xfa_js_embedder_test.h @@ -21,6 +21,9 @@ class XFAJSEmbedderTest : public JSEmbedderTest { XFAJSEmbedderTest(); ~XFAJSEmbedderTest() override; + // JSEmbedderTest: + v8::Isolate* isolate() const override; + // EmbedderTest: void SetUp() override; void TearDown() override; diff --git a/third_party/BUILD.gn b/third_party/BUILD.gn index d8da88e425..2872699033 100644 --- a/third_party/BUILD.gn +++ b/third_party/BUILD.gn @@ -205,6 +205,7 @@ if (is_linux || is_chromeos) { config("system_fontconfig") { libs = [ "fontconfig" ] } + group("fontconfig") { public_configs = [ ":system_fontconfig" ] } @@ -218,6 +219,7 @@ if (pdf_use_agg) { cflags = [ "-Wno-unused-function" ] } } + source_set("fx_agg") { configs -= [ "//build/config/compiler:chromium_code" ] configs += [ @@ -374,6 +376,7 @@ if (use_system_lcms2) { packages = [ "lcms2" ] } } + group("lcms2") { if (use_system_lcms2) { public_configs = [ ":lcms2_from_pkgconfig" ] @@ -388,6 +391,7 @@ if (use_system_libopenjpeg2) { packages = [ "libopenjp2" ] } } + group("libopenjpeg2") { if (use_system_libopenjpeg2) { public_configs = [ ":libopenjpeg2_from_pkgconfig" ] @@ -542,9 +546,10 @@ if (pdf_enable_xfa) { if (pdf_use_partition_alloc) { group("pdfium_pa") { public_deps = [ + "//base/allocator/partition_allocator/src/partition_alloc", "//base/allocator/partition_allocator/src/partition_alloc:buildflags", "//base/allocator/partition_allocator/src/partition_alloc:raw_ptr", ] - deps = [ "//base/allocator/partition_allocator/src/partition_alloc" ] + visibility = [ "../*" ] } } diff --git a/third_party/cpu_features/README.pdfium b/third_party/cpu_features/README.pdfium index 47fad3c187..5252d40f47 100644 --- a/third_party/cpu_features/README.pdfium +++ b/third_party/cpu_features/README.pdfium @@ -5,7 +5,7 @@ Version: v0.8.0 Date: 2023-05-17 Update Mechanism: Manual License: Apache-2.0 -License File: src/LICENSE +License File: //third_party/cpu_features/src/LICENSE Security Critical: yes Shipped: no diff --git a/third_party/dragonbox/README.pdfium b/third_party/dragonbox/README.pdfium index 8801c74445..607cd8686f 100644 --- a/third_party/dragonbox/README.pdfium +++ b/third_party/dragonbox/README.pdfium @@ -4,7 +4,7 @@ Version: 1.1.3 Revision: 6c7c925b571d54486b9ffae8d9d18a822801cbda Update Mechanism: Manual License: Apache-with-LLVM-Exception, BSL-1.0 -License File: src/LICENSE-Apache2-LLVM, src/LICENSE-Boost +License File: //third_party/dragonbox/src/LICENSE-Apache2-LLVM, //third_party/dragonbox/src/LICENSE-Boost Shipped: yes Security Critical: yes CPEPrefix: unknown diff --git a/third_party/fast_float/README.pdfium b/third_party/fast_float/README.pdfium index 322d28fc7c..fc5b71154a 100644 --- a/third_party/fast_float/README.pdfium +++ b/third_party/fast_float/README.pdfium @@ -4,7 +4,7 @@ Version: 7.0.0 Revision: cb1d42aaa1e14b09e1452cfdef373d051b8c02a4 Update Mechanism: Manual License: MIT -License File: src/LICENSE-MIT +License File: //third_party/fast_float/src/LICENSE-MIT Shipped: yes Security Critical: yes CPEPrefix: unknown diff --git a/third_party/freetype/OWNERS b/third_party/freetype/OWNERS index d3d5e9583f..1967bf567e 100644 --- a/third_party/freetype/OWNERS +++ b/third_party/freetype/OWNERS @@ -1,2 +1 @@ -drott@chromium.org thestig@chromium.org diff --git a/third_party/googletest/README.pdfium b/third_party/googletest/README.pdfium index 52086d7969..d0477348ee 100644 --- a/third_party/googletest/README.pdfium +++ b/third_party/googletest/README.pdfium @@ -5,7 +5,7 @@ Version: N/A Revision: DEPS Update Mechanism: Manual License: BSD-3-Clause -License File: src/LICENSE +License File: //third_party/googletest/src/LICENSE Shipped: no Security critical: no diff --git a/third_party/harfbuzz/README.pdfium b/third_party/harfbuzz/README.pdfium index a44e02240b..c3024ae5c0 100644 --- a/third_party/harfbuzz/README.pdfium +++ b/third_party/harfbuzz/README.pdfium @@ -9,7 +9,7 @@ Update Mechanism: Manual Security Critical: yes Shipped: yes License: MIT-Modern-Variant -License File: src/COPYING +License File: //third_party/harfbuzz/src/COPYING Description: HarfBuzz is a text shaping engine. It primarily supports OpenType, but also diff --git a/third_party/libopenjpeg/0052-opj_j2k_copy_default_mcc_records.patch b/third_party/libopenjpeg/0052-opj_j2k_copy_default_mcc_records.patch new file mode 100644 index 0000000000..ce6b829d88 --- /dev/null +++ b/third_party/libopenjpeg/0052-opj_j2k_copy_default_mcc_records.patch @@ -0,0 +1,12 @@ +diff --git a/third_party/libopenjpeg/j2k.c b/third_party/libopenjpeg/j2k.c +index 1fb83f8b5f..2835adbbb4 100644 +--- a/third_party/libopenjpeg/j2k.c ++++ b/third_party/libopenjpeg/j2k.c +@@ -9306,6 +9306,7 @@ static OPJ_BOOL opj_j2k_copy_default_tcp_and_create_tcd(opj_j2k_t * p_j2k, + } + memcpy(l_tcp->m_mcc_records, l_default_tcp->m_mcc_records, l_mcc_records_size); + l_tcp->m_nb_max_mcc_records = l_default_tcp->m_nb_max_mcc_records; ++ l_tcp->m_nb_mcc_records = l_default_tcp->m_nb_mcc_records; + + /* Copy the mcc record data from dflt_tile_cp to the current tile*/ + l_src_mcc_rec = l_default_tcp->m_mcc_records; diff --git a/third_party/libopenjpeg/README.pdfium b/third_party/libopenjpeg/README.pdfium index 2823e66e0c..c0650c69af 100644 --- a/third_party/libopenjpeg/README.pdfium +++ b/third_party/libopenjpeg/README.pdfium @@ -37,6 +37,7 @@ Local Modifications: 0049-dwt_neon_5-3_idwt.patch: Cherry-pick https://github.com/uclouvain/openjpeg/pull/1630 0050-opj_pi_initialise_encode_overflow.patch: Cherry-pick https://github.com/uclouvain/openjpeg/pull/1619 0051-opj_j2k_copy_default_double_free.patch: Fix a double free. +0052-opj_j2k_copy_default_mcc_records.patch: Fix MCC records count. Note: diff --git a/third_party/libopenjpeg/j2k.c b/third_party/libopenjpeg/j2k.c index 1fb83f8b5f..2835adbbb4 100644 --- a/third_party/libopenjpeg/j2k.c +++ b/third_party/libopenjpeg/j2k.c @@ -9306,6 +9306,7 @@ static OPJ_BOOL opj_j2k_copy_default_tcp_and_create_tcd(opj_j2k_t * p_j2k, } memcpy(l_tcp->m_mcc_records, l_default_tcp->m_mcc_records, l_mcc_records_size); l_tcp->m_nb_max_mcc_records = l_default_tcp->m_nb_max_mcc_records; + l_tcp->m_nb_mcc_records = l_default_tcp->m_nb_mcc_records; /* Copy the mcc record data from dflt_tile_cp to the current tile*/ l_src_mcc_rec = l_default_tcp->m_mcc_records; diff --git a/third_party/llvm-libc/README.md b/third_party/llvm-libc/README.pdfium similarity index 67% rename from third_party/llvm-libc/README.md rename to third_party/llvm-libc/README.pdfium index 89f1bc754f..2c739f19fa 100644 --- a/third_party/llvm-libc/README.md +++ b/third_party/llvm-libc/README.pdfium @@ -2,8 +2,8 @@ Name: llvm-libc URL: https://libc.llvm.org/ Version: N/A Revision: DEPS -License: MIT, University of Illinois/NCSA Open Source License -License File: src/LICENSE.TXT +License: Apache-with-LLVM-Exception, NCSA +License File: //third_party/llvm-libc/src/LICENSE.TXT Security Critical: yes Shipped: yes diff --git a/tools/roll_pdfium_deps_test.py b/tools/roll_pdfium_deps_test.py index b771f52cd6..c9bd7f4f84 100644 --- a/tools/roll_pdfium_deps_test.py +++ b/tools/roll_pdfium_deps_test.py @@ -148,10 +148,6 @@ 'checkout_instrumented_libraries': 'checkout_linux and checkout_configuration != "small"', - # By default bot checkouts the WPR archive files only when this - # flag is set True. - 'checkout_wpr_archives': False, - # By default, do not check out WebKit for iOS, as it is not needed unless # running against ToT WebKit rather than system WebKit. This can be overridden # e.g. with custom_vars. diff --git a/vpython.toml b/vpython.toml new file mode 100644 index 0000000000..1349814a83 --- /dev/null +++ b/vpython.toml @@ -0,0 +1,13 @@ +# Copyright 2026 The PDFium Authors +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +requires-python = '>=3.11,<3.12' +dependencies = [ + 'certifi==2023.7.22', + 'charset-normalizer==3.1.0', + 'idna==3.4', + 'pyfakefs==3.7.2', + 'requests==2.31.0', + 'urllib3==2.0.3', +] diff --git a/vpython.toml.uv.lock b/vpython.toml.uv.lock new file mode 100644 index 0000000000..442cba9896 --- /dev/null +++ b/vpython.toml.uv.lock @@ -0,0 +1,33 @@ +certifi==2023.7.22 \ + --hash=sha256:85478a6407953dbbe31f782e3c9b72952c1841d05ac7445371fa1ff9d7ea51cc \ + --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9 + # via requests +charset-normalizer==3.1.0 \ + --hash=sha256:2e2eb7f44bc541efc6d333083f1eb1bf9b2ede59eb9c3224f55a6e23b49f06f1 \ + --hash=sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d + # via requests +idna==3.4 \ + --hash=sha256:52926a5baa595cfe5376963d8c5bbed33c4c83628dbd1958fc5734d664701b0c \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 \ + --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 + # via requests +pyfakefs==3.7.2 \ + --hash=sha256:8259042941a0690b89d92d55ed8fb1720ca4a83db87713d2fb9717cfd5e5f18e \ + --hash=sha256:9b3718c246ec98ca8269c6d19e9c33c1cc3af32e22bf189bddb921bc7fc16e39 \ + --hash=sha256:9e98f7179507c650ce17824e6493a4effef03e726ffe9f84917f5800266edcc9 \ + --hash=sha256:f415f38fe488b46974700af95f22b37e951a51159403eb9011ca37db0fde8b97 \ + --hash=sha256:f415f38fe488b46974700af95f22b37e951a51159403eb9011ca37db0fde8b97 \ + --hash=sha256:f415f38fe488b46974700af95f22b37e951a51159403eb9011ca37db0fde8b97 +requests==2.31.0 \ + --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ + --hash=sha256:7c024df3d83d462cc8127c6c9a2dae2e5d2bddb54c1039624ab3baf485d752ca +urllib3==2.0.3 \ + --hash=sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1 \ + --hash=sha256:e8f53a057713d1015942230188267a70017526bcda7d4427b067e4fe9fb53626 + # via requests diff --git a/xfa/fde/cfde_textout_unittest.cpp b/xfa/fde/cfde_textout_unittest.cpp index 4add08e456..ce5a9b9ce6 100644 --- a/xfa/fde/cfde_textout_unittest.cpp +++ b/xfa/fde/cfde_textout_unittest.cpp @@ -94,8 +94,13 @@ TEST_F(CFDETextOutTest, DrawLogicTextBasic) { text_out().DrawLogicText(device(), L"foo", CFX_RectF(0, 0, 2100, 100)); const char* checksum = []() { #if defined(PDF_USE_SKIA) && BUILDFLAG(IS_WIN) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { - return "bc1f736237b08d13db06c09f6becc9f7"; + if (CFX_GEModule::IsSkiaRenderer()) { + return "02c55ce12d3c31ae32fa08fe59c38996"; + } +#endif +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + return "59f4a2cfb7938032f144954642babe58"; } #endif return "c143f8450f661a489cc9423de7cc1acc"; @@ -132,9 +137,14 @@ class CFDETextOutLargeBitmapTest : public CFDETextOutTest { const char* GetLargeTextBlobChecksum() { #if defined(PDF_USE_SKIA) - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { return "e9aaffff1ea680bd5dc40a7b8904788d"; } +#endif +#if defined(PDF_ENABLE_FONTATIONS) + if (CFX_GEModule::IsFontations()) { + return "89adff3e02833425d42a2f89ef8fd7e3"; + } #endif return "add7cf2819b3e1397d8a60a9ec436a86"; } diff --git a/xfa/fwl/cfwl_app.h b/xfa/fwl/cfwl_app.h index 8ce895f96b..e29a2bc2c5 100644 --- a/xfa/fwl/cfwl_app.h +++ b/xfa/fwl/cfwl_app.h @@ -27,7 +27,7 @@ class CFWL_App final : public cppgc::GarbageCollected { virtual CFWL_WidgetMgr::AdapterIface* GetWidgetMgrAdapter() = 0; virtual CFX_Timer::HandlerIface* GetTimerHandler() = 0; virtual IFWL_ThemeProvider* GetThemeProvider() = 0; - virtual cppgc::Heap* GetHeap() = 0; + virtual FXGC_Heap* GetHeap() = 0; }; CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED; @@ -44,7 +44,7 @@ class CFWL_App final : public cppgc::GarbageCollected { IFWL_ThemeProvider* GetThemeProvider() const { return adapter_->GetThemeProvider(); } - cppgc::Heap* GetHeap() const { return adapter_->GetHeap(); } + FXGC_Heap* GetHeap() const { return adapter_->GetHeap(); } CFWL_WidgetMgr* GetWidgetMgr() const { return widget_mgr_; } CFWL_NoteDriver* GetNoteDriver() const { return note_driver_; } diff --git a/xfa/fwl/cfwl_edit.cpp b/xfa/fwl/cfwl_edit.cpp index 4950f81f95..8543c5acf5 100644 --- a/xfa/fwl/cfwl_edit.cpp +++ b/xfa/fwl/cfwl_edit.cpp @@ -1059,8 +1059,6 @@ void CFWL_Edit::OnChar(CFWL_MessageKey* pMsg) { UpdateCaret(); } break; - case L'\n': - case 27: // Esc case 127: // Delete break; case L'\t': @@ -1077,6 +1075,9 @@ void CFWL_Edit::OnChar(CFWL_MessageKey* pMsg) { if (pdfium::IsPlatformShortcutKey(pMsg->flags_)) { break; } + if (c < 32) { + break; + } edit_engine_->Insert(cursor_position_, WideString(c)); SetCursorPosition(cursor_position_ + 1); diff --git a/xfa/fwl/cfwl_edit_embeddertest.cpp b/xfa/fwl/cfwl_edit_embeddertest.cpp index d7efaab530..1c43f01edf 100644 --- a/xfa/fwl/cfwl_edit_embeddertest.cpp +++ b/xfa/fwl/cfwl_edit_embeddertest.cpp @@ -77,7 +77,7 @@ TEST_F(CFWLEditEmbedderTest, LeftClickMouseSelection) { TEST_F(CFWLEditEmbedderTest, DragMouseSelection) { #if defined(PDF_USE_SKIA) // TODO(crbug.com/40096188): Fix this test and enable for Skia variants. - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { return; } #endif @@ -113,7 +113,7 @@ TEST_F(CFWLEditEmbedderTest, DragMouseSelection) { TEST_F(CFWLEditEmbedderTest, SimpleFill) { #if defined(PDF_USE_SKIA) // TODO(crbug.com/40096188): Fix this test and enable for Skia variants. - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { return; } #endif @@ -142,7 +142,7 @@ TEST_F(CFWLEditEmbedderTest, SimpleFill) { TEST_F(CFWLEditEmbedderTest, FillWithNewLineWithoutMultiline) { #if defined(PDF_USE_SKIA) // TODO(crbug.com/40096188): Fix this test and enable for Skia variants. - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { return; } #endif @@ -219,7 +219,7 @@ TEST_F(CFWLEditEmbedderTest, FillWithNewLineWithMultiline) { TEST_F(CFWLEditEmbedderTest, DateTimePickerTest) { #if defined(PDF_USE_SKIA) // TODO(crbug.com/40096188): Fix this test and enable for Skia variants. - if (CFX_GEModule::Get()->UseSkiaRenderer()) { + if (CFX_GEModule::IsSkiaRenderer()) { return; } #endif @@ -269,7 +269,8 @@ TEST_F(CFWLEditEmbedderTest, ImageEditTest) { constexpr char kFilledBasename[] = "xfa_image_edit"; ScopedFPDFBitmap page_bitmap = RenderLoadedPageWithFlags(page.get(), FPDF_ANNOT); - CompareBitmapWithFuzzyExpectationSuffix(page_bitmap.get(), kFilledBasename); + CompareBitmapWithExpectationSuffix(page_bitmap.get(), kFilledBasename, + kFuzzyDiffOptions); } TEST_F(CFWLEditEmbedderTest, ComboBoxTest) { diff --git a/xfa/fwl/ifwl_themeprovider.cpp b/xfa/fwl/ifwl_themeprovider.cpp index 0bae463d39..5d1a88425b 100644 --- a/xfa/fwl/ifwl_themeprovider.cpp +++ b/xfa/fwl/ifwl_themeprovider.cpp @@ -21,7 +21,7 @@ namespace pdfium { -IFWL_ThemeProvider::IFWL_ThemeProvider(cppgc::Heap* pHeap) +IFWL_ThemeProvider::IFWL_ThemeProvider(FXGC_Heap* pHeap) : check_box_tp_(cppgc::MakeGarbageCollected( pHeap->GetAllocationHandle())), list_box_tp_(cppgc::MakeGarbageCollected( diff --git a/xfa/fwl/ifwl_themeprovider.h b/xfa/fwl/ifwl_themeprovider.h index ccb3675f53..a8bbf0220f 100644 --- a/xfa/fwl/ifwl_themeprovider.h +++ b/xfa/fwl/ifwl_themeprovider.h @@ -48,7 +48,7 @@ class IFWL_ThemeProvider : public cppgc::GarbageCollectedMixin { const CFWL_ThemePart& pThemePart) const = 0; protected: - explicit IFWL_ThemeProvider(cppgc::Heap* pHeap); + explicit IFWL_ThemeProvider(FXGC_Heap* pHeap); CFWL_WidgetTP* GetTheme(const CFWL_Widget* pWidget) const; diff --git a/xfa/fxfa/cxfa_ffapp.cpp b/xfa/fxfa/cxfa_ffapp.cpp index 82314f0820..105a93d678 100644 --- a/xfa/fxfa/cxfa_ffapp.cpp +++ b/xfa/fxfa/cxfa_ffapp.cpp @@ -58,6 +58,6 @@ IFWL_ThemeProvider* CXFA_FFApp::GetThemeProvider() { return fwltheme_; } -cppgc::Heap* CXFA_FFApp::GetHeap() { +FXGC_Heap* CXFA_FFApp::GetHeap() { return provider_->GetGCHeap(); } diff --git a/xfa/fxfa/cxfa_ffapp.h b/xfa/fxfa/cxfa_ffapp.h index 9afd7326f4..bc0245af01 100644 --- a/xfa/fxfa/cxfa_ffapp.h +++ b/xfa/fxfa/cxfa_ffapp.h @@ -128,7 +128,7 @@ class CXFA_FFApp final : public cppgc::GarbageCollected, const WideString& wsEncode) = 0; virtual CFX_Timer::HandlerIface* GetTimerHandler() const = 0; - virtual cppgc::Heap* GetGCHeap() const = 0; + virtual FXGC_Heap* GetGCHeap() const = 0; }; CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED; @@ -139,7 +139,7 @@ class CXFA_FFApp final : public cppgc::GarbageCollected, CFWL_WidgetMgr::AdapterIface* GetWidgetMgrAdapter() override; CFX_Timer::HandlerIface* GetTimerHandler() override; pdfium::IFWL_ThemeProvider* GetThemeProvider() override; - cppgc::Heap* GetHeap() override; + FXGC_Heap* GetHeap() override; bool LoadFWLTheme(CXFA_FFDoc* doc); CFWL_WidgetMgr* GetFWLWidgetMgr() const { return fwlapp_->GetWidgetMgr(); } diff --git a/xfa/fxfa/cxfa_ffdoc.cpp b/xfa/fxfa/cxfa_ffdoc.cpp index 1d8bfa1b58..170bc6330b 100644 --- a/xfa/fxfa/cxfa_ffdoc.cpp +++ b/xfa/fxfa/cxfa_ffdoc.cpp @@ -57,7 +57,7 @@ FX_IMAGEDIB_AND_DPI::~FX_IMAGEDIB_AND_DPI() = default; CXFA_FFDoc::CXFA_FFDoc(CXFA_FFApp* pApp, CallbackIface* pDocEnvironment, CPDF_Document* pPDFDoc, - cppgc::Heap* pHeap) + FXGC_Heap* pHeap) : doc_environment_(pDocEnvironment), pdfdoc_(pPDFDoc), heap_(pHeap), diff --git a/xfa/fxfa/cxfa_ffdoc.h b/xfa/fxfa/cxfa_ffdoc.h index d56bee5da5..e500083ed5 100644 --- a/xfa/fxfa/cxfa_ffdoc.h +++ b/xfa/fxfa/cxfa_ffdoc.h @@ -159,7 +159,7 @@ class CXFA_FFDoc : public cppgc::GarbageCollected { CXFA_FFDocView* CreateDocView(); FormType GetFormType() const { return form_type_; } - cppgc::Heap* GetHeap() const { return heap_; } + FXGC_Heap* GetHeap() const { return heap_; } CXFA_Document* GetXFADoc() const { return document_; } CXFA_FFApp* GetApp() const { return app_; } CPDF_Document* GetPDFDoc() const { return pdfdoc_; } @@ -179,12 +179,12 @@ class CXFA_FFDoc : public cppgc::GarbageCollected { CXFA_FFDoc(CXFA_FFApp* pApp, CallbackIface* pDocEnvironment, CPDF_Document* pPDFDoc, - cppgc::Heap* pHeap); + FXGC_Heap* pHeap); bool BuildDoc(CFX_XMLDocument* pXML); UnownedPtr const doc_environment_; UnownedPtr const pdfdoc_; - UnownedPtr const heap_; + UnownedPtr const heap_; cppgc::Member const app_; cppgc::Member notify_; cppgc::Member document_; diff --git a/xfa/fxfa/cxfa_fwltheme.cpp b/xfa/fxfa/cxfa_fwltheme.cpp index 3b5ff1aa05..d0686c1bef 100644 --- a/xfa/fxfa/cxfa_fwltheme.cpp +++ b/xfa/fxfa/cxfa_fwltheme.cpp @@ -51,7 +51,7 @@ CXFA_FFWidget* GetOutmostFFWidget(CFWL_Widget* pWidget) { } // namespace -CXFA_FWLTheme::CXFA_FWLTheme(cppgc::Heap* pHeap, CXFA_FFApp* pApp) +CXFA_FWLTheme::CXFA_FWLTheme(FXGC_Heap* pHeap, CXFA_FFApp* pApp) : IFWL_ThemeProvider(pHeap), text_out_(std::make_unique()), app_(pApp) {} diff --git a/xfa/fxfa/cxfa_fwltheme.h b/xfa/fxfa/cxfa_fwltheme.h index 3a4752a2f0..59bb31ca8d 100644 --- a/xfa/fxfa/cxfa_fwltheme.h +++ b/xfa/fxfa/cxfa_fwltheme.h @@ -53,7 +53,7 @@ class CXFA_FWLTheme final : public cppgc::GarbageCollected, bool LoadCalendarFont(CXFA_FFDoc* doc); private: - CXFA_FWLTheme(cppgc::Heap* pHeap, CXFA_FFApp* pApp); + CXFA_FWLTheme(FXGC_Heap* pHeap, CXFA_FFApp* pApp); std::unique_ptr text_out_; RetainPtr fwlfont_; diff --git a/xfa/fxfa/formcalc/cxfa_fmparser.cpp b/xfa/fxfa/formcalc/cxfa_fmparser.cpp index 392b41d965..1212ccf66d 100644 --- a/xfa/fxfa/formcalc/cxfa_fmparser.cpp +++ b/xfa/fxfa/formcalc/cxfa_fmparser.cpp @@ -20,7 +20,7 @@ constexpr unsigned int kMaxExpressionListSize = 10000; } // namespace -CXFA_FMParser::CXFA_FMParser(cppgc::Heap* pHeap, CXFA_FMLexer* pLexer) +CXFA_FMParser::CXFA_FMParser(FXGC_Heap* pHeap, CXFA_FMLexer* pLexer) : heap_(pHeap), lexer_(pLexer), max_parse_depth_(kMaxParseDepth) {} CXFA_FMParser::~CXFA_FMParser() = default; diff --git a/xfa/fxfa/formcalc/cxfa_fmparser.h b/xfa/fxfa/formcalc/cxfa_fmparser.h index 22002c3fb6..a4d3bfebe9 100644 --- a/xfa/fxfa/formcalc/cxfa_fmparser.h +++ b/xfa/fxfa/formcalc/cxfa_fmparser.h @@ -22,7 +22,7 @@ class CXFA_FMParser { CPPGC_STACK_ALLOCATED(); // Allow Raw/Unowned pointers. public: - CXFA_FMParser(cppgc::Heap* heap, CXFA_FMLexer* pLexer); + CXFA_FMParser(FXGC_Heap* heap, CXFA_FMLexer* pLexer); ~CXFA_FMParser(); // Returned object is owned by cppgc heap. @@ -64,7 +64,7 @@ class CXFA_FMParser { std::optional>> ParseArgumentList(); - UnownedPtr const heap_; + UnownedPtr const heap_; UNOWNED_PTR_EXCLUSION CXFA_FMLexer* const lexer_; // Stack allocated. CXFA_FMLexer::Token token_; bool error_ = false; diff --git a/xfa/fxfa/layout/cxfa_contentlayoutprocessor.cpp b/xfa/fxfa/layout/cxfa_contentlayoutprocessor.cpp index c5a018392f..dde8848666 100644 --- a/xfa/fxfa/layout/cxfa_contentlayoutprocessor.cpp +++ b/xfa/fxfa/layout/cxfa_contentlayoutprocessor.cpp @@ -656,7 +656,7 @@ CFX_PointF CalculatePositionedContainerPos(CXFA_Node* pNode, } // namespace CXFA_ContentLayoutProcessor::CXFA_ContentLayoutProcessor( - cppgc::Heap* pHeap, + FXGC_Heap* pHeap, CXFA_Node* pNode, CXFA_ViewLayoutProcessor* pViewLayoutProcessor) : heap_(pHeap), diff --git a/xfa/fxfa/layout/cxfa_contentlayoutprocessor.h b/xfa/fxfa/layout/cxfa_contentlayoutprocessor.h index d5cf9b4800..c06b6d40c8 100644 --- a/xfa/fxfa/layout/cxfa_contentlayoutprocessor.h +++ b/xfa/fxfa/layout/cxfa_contentlayoutprocessor.h @@ -57,7 +57,7 @@ class CXFA_ContentLayoutProcessor ~CXFA_ContentLayoutProcessor(); void Trace(cppgc::Visitor* visitor) const; - cppgc::Heap* GetHeap() const { return heap_; } + FXGC_Heap* GetHeap() const { return heap_; } Result DoLayout(bool bUseBreakControl, float fHeightLimit, float fRealHeight); void DoLayoutPageArea(CXFA_ViewLayoutItem* pPageAreaLayoutItem); @@ -82,7 +82,7 @@ class CXFA_ContentLayoutProcessor using ContentLayoutItemVector = std::vector>; - CXFA_ContentLayoutProcessor(cppgc::Heap* pHeap, + CXFA_ContentLayoutProcessor(FXGC_Heap* pHeap, CXFA_Node* pNode, CXFA_ViewLayoutProcessor* pViewLayoutProcessor); @@ -226,7 +226,7 @@ class CXFA_ContentLayoutProcessor float last_row_width_ = 0; float last_row_y_ = 0; float width_limit_ = 0; - UnownedPtr heap_; + UnownedPtr heap_; cppgc::Member const form_node_; cppgc::Member cur_child_node_; cppgc::Member keep_head_node_; diff --git a/xfa/fxfa/layout/cxfa_layoutprocessor.cpp b/xfa/fxfa/layout/cxfa_layoutprocessor.cpp index 16c3807fb8..a0e5d6d122 100644 --- a/xfa/fxfa/layout/cxfa_layoutprocessor.cpp +++ b/xfa/fxfa/layout/cxfa_layoutprocessor.cpp @@ -26,7 +26,7 @@ CXFA_LayoutProcessor* CXFA_LayoutProcessor::FromDocument( return static_cast(pXFADoc->GetLayoutProcessor()); } -CXFA_LayoutProcessor::CXFA_LayoutProcessor(cppgc::Heap* pHeap) : heap_(pHeap) {} +CXFA_LayoutProcessor::CXFA_LayoutProcessor(FXGC_Heap* pHeap) : heap_(pHeap) {} CXFA_LayoutProcessor::~CXFA_LayoutProcessor() = default; diff --git a/xfa/fxfa/layout/cxfa_layoutprocessor.h b/xfa/fxfa/layout/cxfa_layoutprocessor.h index 2b0b399cf1..b2ffd80101 100644 --- a/xfa/fxfa/layout/cxfa_layoutprocessor.h +++ b/xfa/fxfa/layout/cxfa_layoutprocessor.h @@ -22,10 +22,6 @@ class CXFA_Node; class CXFA_ViewLayoutItem; class CXFA_ViewLayoutProcessor; -namespace cppgc { -class Heap; -} // namespace cppgc - class CXFA_LayoutProcessor final : public CXFA_Document::LayoutProcessorIface { public: static CXFA_LayoutProcessor* FromDocument(const CXFA_Document* pXFADoc); @@ -53,13 +49,13 @@ class CXFA_LayoutProcessor final : public CXFA_Document::LayoutProcessorIface { } private: - explicit CXFA_LayoutProcessor(cppgc::Heap* pHeap); + explicit CXFA_LayoutProcessor(FXGC_Heap* pHeap); - cppgc::Heap* GetHeap() { return heap_; } + FXGC_Heap* GetHeap() { return heap_; } bool NeedLayout() const; int32_t RestartLayout(); - UnownedPtr const heap_; + UnownedPtr const heap_; cppgc::Member view_layout_processor_; cppgc::Member content_layout_processor_; uint32_t progress_counter_ = 0; diff --git a/xfa/fxfa/layout/cxfa_viewlayoutprocessor.cpp b/xfa/fxfa/layout/cxfa_viewlayoutprocessor.cpp index 5ba0f71f6f..9303c389f4 100644 --- a/xfa/fxfa/layout/cxfa_viewlayoutprocessor.cpp +++ b/xfa/fxfa/layout/cxfa_viewlayoutprocessor.cpp @@ -353,7 +353,7 @@ void CXFA_ViewLayoutProcessor::CXFA_ViewRecord::Trace( } CXFA_ViewLayoutProcessor::CXFA_ViewLayoutProcessor( - cppgc::Heap* pHeap, + FXGC_Heap* pHeap, CXFA_LayoutProcessor* pLayoutProcessor) : heap_(pHeap), layout_processor_(pLayoutProcessor), diff --git a/xfa/fxfa/layout/cxfa_viewlayoutprocessor.h b/xfa/fxfa/layout/cxfa_viewlayoutprocessor.h index f434acf32e..dbc4b77fd3 100644 --- a/xfa/fxfa/layout/cxfa_viewlayoutprocessor.h +++ b/xfa/fxfa/layout/cxfa_viewlayoutprocessor.h @@ -51,7 +51,7 @@ class CXFA_ViewLayoutProcessor void PreFinalize(); void Trace(cppgc::Visitor* visitor) const; - cppgc::Heap* GetHeap() const { return heap_; } + FXGC_Heap* GetHeap() const { return heap_; } bool InitLayoutPage(CXFA_Node* pFormNode); bool PrepareFirstPage(CXFA_Node* pRootSubform); @@ -92,7 +92,7 @@ class CXFA_ViewLayoutProcessor CXFA_ViewRecord(); }; - CXFA_ViewLayoutProcessor(cppgc::Heap* pHeap, + CXFA_ViewLayoutProcessor(FXGC_Heap* pHeap, CXFA_LayoutProcessor* pLayoutProcessor); bool AppendNewPage(bool bFirstTemPage); @@ -183,7 +183,7 @@ class CXFA_ViewLayoutProcessor void ProcessSimplexOrDuplexPageSets(CXFA_ViewLayoutItem* pPageSetLayoutItem, bool bIsSimplex); - UnownedPtr heap_; + UnownedPtr heap_; cppgc::Member layout_processor_; cppgc::Member page_set_node_; cppgc::Member cur_page_area_; diff --git a/xfa/fxfa/parser/cxfa_arraynodelist.cpp b/xfa/fxfa/parser/cxfa_arraynodelist.cpp index 4b21deb906..3963f3c1ab 100644 --- a/xfa/fxfa/parser/cxfa_arraynodelist.cpp +++ b/xfa/fxfa/parser/cxfa_arraynodelist.cpp @@ -9,6 +9,7 @@ #include #include +#include "core/fxcrt/containers/to_vector.h" #include "fxjs/gc/container_trace.h" #include "xfa/fxfa/parser/cxfa_node.h" @@ -25,8 +26,7 @@ void CXFA_ArrayNodeList::Trace(cppgc::Visitor* visitor) const { void CXFA_ArrayNodeList::SetArrayNodeList( const std::vector& srcArray) { if (!srcArray.empty()) { - array_ = - std::vector>(srcArray.begin(), srcArray.end()); + array_ = pdfium::ToVector>(srcArray); } } diff --git a/xfa/fxfa/parser/cxfa_document.cpp b/xfa/fxfa/parser/cxfa_document.cpp index 79a90ba21f..aad2aef05d 100644 --- a/xfa/fxfa/parser/cxfa_document.cpp +++ b/xfa/fxfa/parser/cxfa_document.cpp @@ -16,6 +16,7 @@ #include "core/fxcrt/stl_util.h" #include "core/fxcrt/xml/cfx_xmldocument.h" #include "core/fxcrt/xml/cfx_xmlelement.h" +#include "fxjs/cjs_runtime.h" #include "fxjs/gc/container_trace.h" #include "fxjs/xfa/cfxjse_engine.h" #include "fxjs/xfa/cfxjse_resolveprocessor.h" @@ -1344,7 +1345,7 @@ void UpdateDataRelation(CXFA_Node* pDataNode, CXFA_Node* pDataDescriptionNode) { } // namespace CXFA_Document::CXFA_Document(CXFA_FFNotify* notify, - cppgc::Heap* heap, + FXGC_Heap* heap, LayoutProcessorIface* pLayout) : heap_(heap), notify_(notify), @@ -1528,13 +1529,14 @@ CXFA_LocaleMgr* CXFA_Document::GetLocaleMgr() { return locale_mgr_; } -cppgc::Heap* CXFA_Document::GetHeap() const { +FXGC_Heap* CXFA_Document::GetHeap() const { return heap_; } CFXJSE_Engine* CXFA_Document::InitScriptContext(CJS_Runtime* fxjs_runtime) { DCHECK(!script_context_); - script_context_ = std::make_unique(this, fxjs_runtime); + script_context_ = + std::make_unique(this, fxjs_runtime->GetIsolate()); return script_context_.get(); } diff --git a/xfa/fxfa/parser/cxfa_document.h b/xfa/fxfa/parser/cxfa_document.h index 78e8b65318..10255f2fd7 100644 --- a/xfa/fxfa/parser/cxfa_document.h +++ b/xfa/fxfa/parser/cxfa_document.h @@ -40,10 +40,6 @@ class CXFA_FFNotify; class CXFA_Node; class CXFA_Object; -namespace cppgc { -class Heap; -} // namespace cppgc - enum XFA_VERSION { XFA_VERSION_UNKNOWN = 0, XFA_VERSION_200 = 200, @@ -97,7 +93,7 @@ class CXFA_Document final : public cppgc::GarbageCollected { CXFA_FFNotify* GetNotify() const { return notify_; } CXFA_NodeOwner* GetNodeOwner() { return node_owner_; } - cppgc::Heap* GetHeap() const; + FXGC_Heap* GetHeap() const; CXFA_LocaleMgr* GetLocaleMgr(); CXFA_Object* GetXFAObject(XFA_HashCode wsNodeNameHash); CXFA_Node* GetNodeByID(CXFA_Node* pRoot, WideStringView wsID) const; @@ -158,10 +154,10 @@ class CXFA_Document final : public cppgc::GarbageCollected { WideStringView& wsSOM); CXFA_Document(CXFA_FFNotify* notify, - cppgc::Heap* heap, + FXGC_Heap* heap, LayoutProcessorIface* pLayout); - UnownedPtr heap_; + UnownedPtr heap_; std::unique_ptr script_context_; cppgc::Member const notify_; cppgc::Member const node_owner_; diff --git a/xfa/fxfa/parser/cxfa_localemgr.cpp b/xfa/fxfa/parser/cxfa_localemgr.cpp index c258a72e38..71ce0060ef 100644 --- a/xfa/fxfa/parser/cxfa_localemgr.cpp +++ b/xfa/fxfa/parser/cxfa_localemgr.cpp @@ -1064,7 +1064,7 @@ const uint8_t k_ruRU_Locale[] = { 0xB3, 0x85, 0xFA, 0x59, 0x2A, 0x7A, 0xFF, 0x3D, 0xC4, 0x3F, 0xDE, 0xCB, 0x8B, 0xC4}; -CXFA_XMLLocale* GetLocaleFromBuffer(cppgc::Heap* heap, +CXFA_XMLLocale* GetLocaleFromBuffer(FXGC_Heap* heap, pdfium::span src_span) { if (src_span.empty()) { return nullptr; @@ -1130,7 +1130,7 @@ CXFA_LocaleMgr::LangID GetLanguageID(WideString wsLanguage) { } // namespace -CXFA_LocaleMgr::CXFA_LocaleMgr(cppgc::Heap* pHeap, +CXFA_LocaleMgr::CXFA_LocaleMgr(FXGC_Heap* pHeap, CXFA_Node* pLocaleSet, WideString wsDeflcid) : heap_(pHeap), diff --git a/xfa/fxfa/parser/cxfa_localemgr.h b/xfa/fxfa/parser/cxfa_localemgr.h index 1159db984b..611505b228 100644 --- a/xfa/fxfa/parser/cxfa_localemgr.h +++ b/xfa/fxfa/parser/cxfa_localemgr.h @@ -55,14 +55,12 @@ class CXFA_LocaleMgr final : public cppgc::GarbageCollected, std::optional GetConfigLocaleName(CXFA_Node* pConfig) const; private: - CXFA_LocaleMgr(cppgc::Heap* pHeap, - CXFA_Node* pLocaleSet, - WideString wsDeflcid); + CXFA_LocaleMgr(FXGC_Heap* pHeap, CXFA_Node* pLocaleSet, WideString wsDeflcid); // May allocate a new object on the cppgc heap. CXFA_XMLLocale* GetLocale(LangID lcid); - UnownedPtr heap_; + UnownedPtr heap_; std::vector> locale_array_; std::vector> xmllocale_array_; cppgc::Member def_locale_; diff --git a/xfa/fxfa/parser/cxfa_node.cpp b/xfa/fxfa/parser/cxfa_node.cpp index 8e746d5500..cff2ebbc97 100644 --- a/xfa/fxfa/parser/cxfa_node.cpp +++ b/xfa/fxfa/parser/cxfa_node.cpp @@ -22,6 +22,7 @@ #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/containers/contains.h" +#include "core/fxcrt/containers/to_vector.h" #include "core/fxcrt/data_vector.h" #include "core/fxcrt/fx_codepage.h" #include "core/fxcrt/fx_extension.h" @@ -1359,7 +1360,7 @@ CXFA_Node* CXFA_Node::GetBindData() { } std::vector CXFA_Node::GetBindItemsCopy() const { - return std::vector(binding_nodes_.begin(), binding_nodes_.end()); + return pdfium::ToVector(binding_nodes_); } void CXFA_Node::AddBindItem(CXFA_Node* pFormNode) { diff --git a/xfa/fxfa/parser/cxfa_xmllocale.cpp b/xfa/fxfa/parser/cxfa_xmllocale.cpp index 629d1adfc1..10c158cb19 100644 --- a/xfa/fxfa/parser/cxfa_xmllocale.cpp +++ b/xfa/fxfa/parser/cxfa_xmllocale.cpp @@ -30,7 +30,7 @@ constexpr wchar_t kCurrencySymbol[] = L"currencySymbol"; } // namespace // static -CXFA_XMLLocale* CXFA_XMLLocale::Create(cppgc::Heap* heap, +CXFA_XMLLocale* CXFA_XMLLocale::Create(FXGC_Heap* heap, pdfium::span data) { auto stream = pdfium::MakeRetain(data); CFX_XMLParser parser(stream); diff --git a/xfa/fxfa/parser/cxfa_xmllocale.h b/xfa/fxfa/parser/cxfa_xmllocale.h index c81c133f0e..f1ce0a842c 100644 --- a/xfa/fxfa/parser/cxfa_xmllocale.h +++ b/xfa/fxfa/parser/cxfa_xmllocale.h @@ -21,7 +21,7 @@ class CFX_XMLElement; class CXFA_XMLLocale final : public GCedLocaleIface { public: // Object is created on cppgc heap. - static CXFA_XMLLocale* Create(cppgc::Heap* heap, + static CXFA_XMLLocale* Create(FXGC_Heap* heap, pdfium::span data); CONSTRUCT_VIA_MAKE_GARBAGE_COLLECTED; diff --git a/xfa/fxfa/parser/cxfa_xmllocale_unittest.cpp b/xfa/fxfa/parser/cxfa_xmllocale_unittest.cpp index c6f763130e..12dc526eae 100644 --- a/xfa/fxfa/parser/cxfa_xmllocale_unittest.cpp +++ b/xfa/fxfa/parser/cxfa_xmllocale_unittest.cpp @@ -87,7 +87,7 @@ const char kXMLData[] = "" ""; -CXFA_XMLLocale* CreateLocaleHelper(cppgc::Heap* heap) { +CXFA_XMLLocale* CreateLocaleHelper(FXGC_Heap* heap) { return CXFA_XMLLocale::Create(heap, ByteStringView(kXMLData).unsigned_span()); }