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/.gitignore b/.gitignore index c9849c8665..4e57fbf1f2 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ /third_party/brotli /third_party/catapult /third_party/clang-format +/third_party/cpython3/host +/third_party/cpython3/linux-amd64 /third_party/cpu_features/src /third_party/depot_tools /third_party/dragonbox/src diff --git a/.gn b/.gn index e34e6e71a6..13ba5d1c1e 100644 --- a/.gn +++ b/.gn @@ -8,7 +8,7 @@ buildconfig = "//build/config/BUILDCONFIG.gn" # The python interpreter to use by default. On Windows, this will look # for python3.exe and python3.bat. -script_executable = "python3" +script_executable = "//third_party/cpython3/host/bin/python3" default_args = { # Use a production-performant allocator. @@ -17,6 +17,9 @@ default_args = { partition_alloc_is_debug = false enable_pkeys = false + # This security tooling is not needed. + v8_enable_generated_code_validator = false + # PDFs only need to run JavaScript. v8_enable_webassembly = false diff --git a/AUTHORS b/AUTHORS index f808501f14..9fa8a89ac0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,6 +29,7 @@ Haoran Tang Helmut Januschka Huy Ngo Ivan Odulo +Jeroen Bobbeldijk Jiang Jiang Justin Pierce Ke Liu diff --git a/BUILD.gn b/BUILD.gn index a5a8dc0e4b..0cce62ee43 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -47,11 +47,15 @@ config("pdfium_common_config") { defines += [ "PDF_ENABLE_SKIA_TYPEFACE_CHECKS" ] } - if (pdf_use_skia && pdf_enable_rust_bmp) { + if (pdf_enable_rust_bmp) { defines += [ "PDF_ENABLE_RUST_BMP" ] } - if (pdf_use_skia && pdf_enable_rust_png) { + if (pdf_use_skia && pdf_enable_rust_jpeg) { + defines += [ "PDF_ENABLE_RUST_JPEG" ] + } + + if (pdf_enable_rust_png) { defines += [ "PDF_ENABLE_RUST_PNG" ] } @@ -156,18 +160,6 @@ config("pdfium_public_config") { if (pdf_enable_xfa) { defines += [ "PDF_ENABLE_XFA" ] - if (pdf_enable_xfa_bmp) { - defines += [ "PDF_ENABLE_XFA_BMP" ] - } - if (pdf_enable_xfa_gif) { - defines += [ "PDF_ENABLE_XFA_GIF" ] - } - if (pdf_enable_xfa_png) { - defines += [ "PDF_ENABLE_XFA_PNG" ] - } - if (pdf_enable_xfa_tiff) { - defines += [ "PDF_ENABLE_XFA_TIFF" ] - } } } @@ -360,7 +352,7 @@ group("pdfium_unittest_deps") { "fpdfsdk/*", "fxbarcode/*", "fxjs/*", - "testing/image_diff/*", + "testing/*", "xfa/*", ] } @@ -383,7 +375,8 @@ test("pdfium_unittests") { "core/fxge:unittests", "fpdfsdk:unittests", "testing:unit_test_support", - "testing/image_diff:unittests", + "testing:unittests", + "testing/png_codec:unittests", "//testing/gmock", "//testing/gtest", ] @@ -444,7 +437,7 @@ test("pdfium_embeddertests") { "fpdfsdk:embeddertests", "fpdfsdk/formfiller:embeddertests", "fpdfsdk/pwl:embeddertests", - "testing/image_diff", + "testing/png_codec", "//testing/gmock", "//testing/gtest", ] @@ -479,28 +472,14 @@ test("pdfium_embeddertests") { } } -executable("pdfium_diff") { - visibility += [ "testing/tools:test_runner_py" ] - testonly = true - sources = [ "testing/image_diff/image_diff.cpp" ] - deps = [ - "core/fxcrt", - "testing:path_service", - "testing:pixel_diff_utils", - "testing/image_diff", - "//build/win:default_exe_manifest", - ] - configs += [ ":pdfium_strict_config" ] -} - group("pdfium_all") { testonly = true deps = [ - ":pdfium_diff", ":pdfium_embeddertests", ":pdfium_unittests", - "testing:pdfium_test", + "testing:pdfium_diff", "testing/fuzzers", + "testing/pdfium_test", "testing/tools:epdf_layer_memory_benchmark", "testing/tools:epdf_layer_replay_soak", "testing/tools:epdf_thread_soak", diff --git a/DEPS b/DEPS index b7000efb83..811613d299 100644 --- a/DEPS +++ b/DEPS @@ -5,6 +5,7 @@ gclient_gn_args = [ 'android_ndk_version', 'build_with_chromium', 'checkout_android', + 'checkout_libpng', 'checkout_skia', ] @@ -47,6 +48,8 @@ vars = { 'checkout_instrumented_libraries': 'checkout_linux and checkout_configuration != "small" and checkout_configuration != "minimal"', + 'checkout_libpng': True, + # Fetch the rust toolchain. # # Use a custom_vars section to enable it: @@ -83,11 +86,11 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling abseil # and whatever else without interference from each other. - 'abseil_revision': 'd8e483edd8b44da1845874ee84b42489589bb90f', + 'abseil_revision': '435e7d977fb36fb47854a4c552c0706dad0bd7cf', # 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.14608247'), + 'android_ndk_version': Str('2@30.0.15729638'), # Three lines of non-changing comments so that # the commit queue can handle CLs rolling brotli # and whatever else without interference from each other. @@ -95,35 +98,39 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling build # and whatever else without interference from each other. - 'build_revision': '73c0fa98c5cf963c60ea685c57826fa7ba6253d8', + 'build_revision': '2cd94c0abeada712aeea6906d99021913c9fc28d', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling buildtools # and whatever else without interference from each other. - 'buildtools_revision': '0d39be5a3f129cf1f35e7812108a2184e2193315', + 'buildtools_revision': '6f6a5dbf04b734214f3b1f386567d101ec9d607e', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling catapult # and whatever else without interference from each other. - 'catapult_revision': '2cdd339993500e67d43bcb139ad6132dfc5f4855', + 'catapult_revision': '50c971fc0958d8c1fb0adcc97f41b90042fa7c87', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling clang format # and whatever else without interference from each other. - 'clang_format_revision': '6eddfb5ec5f92127a531eda66c568d3a11e7ec11', + 'clang_format_revision': '70510081984cfcdb14a15b3e08dfe9776dc7ed37', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling clang # and whatever else without interference from each other. - 'clang_revision': '66934666a7c7151a24f4ed95e7e94ef8664de057', + 'clang_revision': '450d823868eaee61e2b2cb986e19aec287f16d58', # 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': '88b5286372e2cb7295f962fe23efa58bf0ff59f8', + 'code_coverage_revision': 'e1878fbb1ff79de75d54b6b57caa19a92b90079b', # 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. 'cpu_features_revision': '81d13c49649f0714dd41fb56bb246398b6584085', # Three lines of non-changing comments so that + # the commit queue can handle CLs rolling cpython3_version + # and whatever else without interference from each other. + 'cpython3_version': 'version:3@3.11.9.chromium.38', + # 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': '1709e20d5ca2afe97bfa726168719c3b77eb9883', + 'depot_tools_revision': '732902ab86f8bd629bee00af102d5aa2d9336166', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling dragonbox # and whatever else without interference from each other. @@ -139,15 +146,15 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling freetype # and whatever else without interference from each other. - 'freetype_revision': '656cb777798fa420a13faba3758779e9ed6c4798', + 'freetype_revision': '4800589f76dc62fbe523a74a59e0d4850a5dd7df', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling GN CIPD package version # and whatever else without interference from each other. - 'gn_version': 'git_revision:1d86777e7f2562a86ecea77d1809ac4f82bb5bfe', + 'gn_version': 'git_revision:150a9d6ba0aa7f407aa4feeabc5f03ce9aa7e04b', # 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:42a268cbf8f70a676d8b0146af32a0adeb349c94', + 'goldctl_version': 'git_revision:9379dad302e027bb42a09c353bfbac0166c8c845', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling gtest # and whatever else without interference from each other. @@ -155,7 +162,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling HarfBuzz # and whatever else without interference from each other. - 'harfbuzz_revision': '28f4dc622fef010dab12f4275ac195b5d4a4a704', + 'harfbuzz_revision': '886fc1e645388080b72f6d9b06347533a0018045', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling highway # and whatever else without interference from each other. @@ -163,11 +170,11 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling icu # and whatever else without interference from each other. - 'icu_revision': 'd578f2e8b7bd5938e21cfb6bf15c079e0aa5b738', + 'icu_revision': '8cc91d9b6ab9991802fd208ee03a69714fd0251c', # 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': 'e8cb570a9a2ee9128e2214c73417ad2a3c47780b', + 'instrumented_lib_revision': 'd15c278eed5d38d9acf2d8054cf37baba93cef8e', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling jinja2 # and whatever else without interference from each other. @@ -181,11 +188,11 @@ vars = { # and whatever else without interference from each other. # If you change this, also update the libc++ revision in # //buildtools/deps_revisions.gni. - 'libcxx_revision': '5abc7f839700f0f17338434e1c1c6a8c87c00c11', + 'libcxx_revision': '97b436da4c33663581d394f4ee0a5977fc38c2f4', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libc++abi # and whatever else without interference from each other. - 'libcxxabi_revision': '8f11bb1d4438d0239d0dfc1bd9456a9f31629dda', + 'libcxxabi_revision': 'fc1897a2c12aa27e703c3ed48b62eba8abf4ce19', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libpng # and whatever else without interference from each other. @@ -193,11 +200,11 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libunwind # and whatever else without interference from each other. - 'libunwind_revision': 'ec7ac638dae6f39c950d4606809b81fdca815a19', + 'libunwind_revision': '45120ee2331193c3650acc9c427ed267fab31d62', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling llvm-libc # and whatever else without interference from each other. - 'llvm_libc_revision': '9a821f4e58bbb7f6701f9951d087f8750f523647', + 'llvm_libc_revision': '320824188c37e5c28738b9652a0ca8087c934bc9', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling markupsafe # and whatever else without interference from each other. @@ -213,11 +220,11 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling partition_allocator # and whatever else without interference from each other. - 'partition_allocator_revision': '2723da2d1b2cc49feb9848a6e44fdf9681b8c123', + 'partition_allocator_revision': '89615a645f0220fb03875111c2d9ae4dea9887d8', # 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': 'cfac443cd7d0c5961815f36a63b5989b0a3fdf86', + 'pdfium_tests_revision': 'd4ec3e1987a094b1eae612943b57782d3b0038ff', # 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. @@ -225,7 +232,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling rust # and whatever else without interference from each other. - 'rust_revision': '9bcbc576725c2fe6cf9c3b4c9db8aefbf9d99553', + 'rust_revision': '4dec6f65bcb34c4c289736b0e6fd571b35c6c665', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling simdutf # and whatever else without interference from each other. @@ -233,11 +240,11 @@ 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:24b80aff540b202a88417f9f93b0dc86229eca7a', + 'siso_version': 'git_revision:bc45e8f67ae0f37d337190ad64aa8bb440c791eb', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling skia # and whatever else without interference from each other. - 'skia_revision': '3ba832f8688f6215c4591941355f9e6b1070a012', + 'skia_revision': '5549c93c9a1c66137ceed424f08c86ce9988290c', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling test_fonts # and whatever else without interference from each other. @@ -245,27 +252,27 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling testing_rust # and whatever else without interference from each other. - 'testing_rust_revision': '6712dc59f4a6c5626f391057cded3842700a17eb', + 'testing_rust_revision': 'ead1c91bc31ed99788b7d667d0325455c0fa2568', # 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': 'a7e928b8bb8d79aa2feb809c1bd4752eecc68802', + 'tools_memory_revision': '8d3182bb3c86948e9e27fbfe11d2134c7e29c898', # 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. - 'tools_rust_revision': '1607679eb7fa1df8841b90b911b11315cd4e31dd', + 'tools_rust_revision': '54d1148695dbbbfc3e00dfd05aff502acaa602e3', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling tools_win_revision # and whatever else without interference from each other. - 'tools_win_revision': '45843c2c1e993427751e2a07f904db069dc26ad6', + 'tools_win_revision': '13cb6e5d223dc49eadd082d3aef4c2a5b0e4c0a0', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling v8 # and whatever else without interference from each other. - 'v8_revision': '7ed96d92b72bea98ff3a84513dfaf73c40681227', + 'v8_revision': 'df65d5c8f2932045c5af72cd7ecd0c8002fa631e', # 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': '8b3aa8a1cd7585c0c4c67351481227b046a662a0', + 'zlib_revision': 'c5cc9edf8992ff36dfca3c2c4f6c8327a66b6782', } # Only these hosts are allowed for dependencies in this DEPS file. @@ -383,23 +390,47 @@ deps = { 'condition': 'checkout_android', }, + # Host platform package. + 'third_party/cpython3/host': { + 'packages': [ + { + 'package': 'infra/3pp/tools/cpython3/${{platform}}', + 'version': Var('cpython3_version'), + }, + ], + 'condition': 'non_git_source', + 'dep_type': 'cipd', + }, + + # Always download Linux x64 package regardless of host OS for RBE workers. + 'third_party/cpython3/linux-amd64': { + 'packages': [ + { + 'package': 'infra/3pp/tools/cpython3/linux-amd64', + 'version': Var('cpython3_version'), + }, + ], + 'condition': 'non_git_source', + 'dep_type': 'cipd', + }, + 'third_party/depot_tools': Var('chromium_git') + '/chromium/tools/depot_tools.git@' + Var('depot_tools_revision'), - 'third_party/dragonbox/src': { - 'url': Var('chromium_git') + '/external/github.com/jk-jeon/dragonbox.git@' + + 'third_party/dragonbox/src': + Var('chromium_git') + '/external/github.com/jk-jeon/dragonbox.git@' + Var('dragonbox_revision'), - 'condition': 'checkout_v8', - }, 'third_party/fast_float/src': Var('chromium_git') + '/external/github.com/fastfloat/fast_float.git@' + Var('fast_float_revision'), - 'third_party/fp16/src': - Var('chromium_git') + '/external/github.com/Maratyszcza/FP16.git@' + + 'third_party/fp16/src': { + 'url': Var('chromium_git') + '/external/github.com/Maratyszcza/FP16.git@' + Var('fp16_revision'), + 'condition': 'checkout_v8', + }, 'third_party/freetype/src': Var('chromium_git') + '/chromium/src/third_party/freetype2.git@' + @@ -427,9 +458,11 @@ deps = { '/chromium/third_party/instrumented_libraries.git@' + Var('instrumented_lib_revision'), - 'third_party/jinja2': - Var('chromium_git') + '/chromium/src/third_party/jinja2.git@' + + 'third_party/jinja2': { + 'url': Var('chromium_git') + '/chromium/src/third_party/jinja2.git@' + Var('jinja2_revision'), + 'condition': 'checkout_v8', + }, 'third_party/libc++/src': Var('chromium_git') + @@ -452,167 +485,175 @@ deps = { Var('chromium_git') + '/chromium/deps/libjpeg_turbo.git@' + Var('jpeg_turbo_revision'), - 'third_party/libpng': - Var('chromium_git') + '/chromium/src/third_party/libpng.git@' + + 'third_party/libpng': { + 'url': Var('chromium_git') + '/chromium/src/third_party/libpng.git@' + Var('libpng_revision'), + 'condition': 'checkout_libpng', + }, 'third_party/llvm-build/Release+Asserts': { 'dep_type': 'gcs', 'bucket': 'chromium-browser-clang', 'objects': [ { - # The Android libclang_rt.builtins libraries are currently only included in the Linux clang package. - 'object_name': 'Linux_x64/clang-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': 'e22e06c05fe1657f48f988b15804b8226e691addb00abba5b984a5c99ac98c42', - 'size_bytes': 59093892, - 'generation': 1781627382446731, - 'condition': '(host_os == "linux" or checkout_android) and non_git_source', + 'object_name': 'Linux_x64/clang-android-runtime-library-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '780597375163e78615a307a73ec66346b54c58ea3164aa6fbedf24eae85382fc', + 'size_bytes': 6210856, + 'generation': 1787607008868327, + 'condition': 'checkout_android and non_git_source', }, { - 'object_name': 'Linux_x64/clang-tidy-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '96da04c2fff4e580ac81b840405edfa292108d0b593927a10750c0a1d8599c0a', - 'size_bytes': 14808648, - 'generation': 1781627382496605, + 'object_name': 'Linux_x64/clang-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '565f7d980b31411e76d9478731cf3f0ac907a46f274d289ea7f03d9137354053', + 'size_bytes': 56894668, + 'generation': 1787607001504676, + 'condition': 'host_os == "linux" and non_git_source', + }, + { + 'object_name': 'Linux_x64/clang-tidy-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '4290664a4fa8aa11fd1e6b2d3ff4965d26b41cca1a4950f9835ff19115c2384f', + 'size_bytes': 14877500, + 'generation': 1787607001575317, 'condition': 'host_os == "linux" and checkout_clang_tidy and non_git_source', }, { - 'object_name': 'Linux_x64/clangd-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '1e6b6918bb270659ef517a3e8f80af8f371bc79a9d942241978db8faea22f152', - 'size_bytes': 15001372, - 'generation': 1781627382490030, + 'object_name': 'Linux_x64/clangd-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '9db50937915608c466da28ba4166cfa69a5f6bcad346233e47807f532cc3075b', + 'size_bytes': 15049952, + 'generation': 1787607001603946, 'condition': 'host_os == "linux" and checkout_clangd and non_git_source', }, { - 'object_name': 'Linux_x64/llvm-code-coverage-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': 'bb63e33c20329c344a9dfa958f3742b316c0b9dd602647190fb0037d8d53a7e6', - 'size_bytes': 2332356, - 'generation': 1781627382888550, + 'object_name': 'Linux_x64/llvm-code-coverage-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '34747a8e113c399daa36f75a5a0a4c0963871ceafbce7c49a3b5133f81a63bae', + 'size_bytes': 2352288, + 'generation': 1787607001865886, 'condition': 'host_os == "linux" and checkout_clang_coverage_tools and non_git_source', }, { - 'object_name': 'Linux_x64/llvmobjdump-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': 'e43b98bce71d58bbe1b456f7b62997c4d017cf8362a0367592428ac5a7512f41', - 'size_bytes': 5875704, - 'generation': 1781627382574869, + 'object_name': 'Linux_x64/llvmobjdump-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': 'cc8a621ec8cc3c48471c90cf4ed88dcc56bfb403824eda10f8b55796abaec280', + 'size_bytes': 5912568, + 'generation': 1787607001713199, 'condition': '((checkout_linux or checkout_mac or checkout_android) and host_os == "linux") and non_git_source', }, { - 'object_name': 'Mac/clang-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '84b3934db2fb3c2e657d4a783a83ca6d2facaf598991490ae8ab712fcb03224b', - 'size_bytes': 56083264, - 'generation': 1781627385112611, + 'object_name': 'Mac/clang-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': 'ba2b707d83f7a19d81b9403b4ebe8e999d41ae35fead00a142f02b67e197fe2f', + 'size_bytes': 56420628, + 'generation': 1787607010939621, 'condition': 'host_os == "mac" and host_cpu == "x64"', }, { - 'object_name': 'Mac/clang-mac-runtime-library-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '5079e2ac3f0fa76e8b0218bf99a65a5439e8dcf6a750f886a355a66df34c69c1', - 'size_bytes': 1010816, - 'generation': 1781627394919334, + 'object_name': 'Mac/clang-mac-runtime-library-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': 'da06337f54ba4b36c79e0d379557832bf82b9c7e85fe8ef78a5b953567d98de1', + 'size_bytes': 1012348, + 'generation': 1787607018227451, 'condition': 'checkout_mac and not host_os == "mac"', }, { - 'object_name': 'Mac/clang-tidy-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '1ca3dc160992d8c27765adaffcc8115aed34cdf7645a5354820e8c8b16b75dcd', - 'size_bytes': 14790996, - 'generation': 1781627384977799, + 'object_name': 'Mac/clang-tidy-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': 'ae5cbec1362517b6928162fddb9b160bd1c75e8fd90789718393e1cadf1342ae', + 'size_bytes': 14882308, + 'generation': 1787607010909866, 'condition': 'host_os == "mac" and host_cpu == "x64" and checkout_clang_tidy', }, { - 'object_name': 'Mac/clangd-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '4920275e6050dffbb81ba0210a627a61a20faa896aa50f5ecc2d765522790526', - 'size_bytes': 16720488, - 'generation': 1781627385229783, + 'object_name': 'Mac/clangd-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '73f5de0f007f8c588593d824147963ec770b66d487a707d1c377a06e34952d89', + 'size_bytes': 16779464, + 'generation': 1787607010899287, 'condition': 'host_os == "mac" and host_cpu == "x64" and checkout_clangd', }, { - 'object_name': 'Mac/llvm-code-coverage-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '07e8bf3354e0c086e420ab8f1f376e48c8b6207788cf30b56d1b0ad1fd4b0f12', - 'size_bytes': 2372720, - 'generation': 1781627385661382, + 'object_name': 'Mac/llvm-code-coverage-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': 'c47eb37873d3290cc95ba5f9027337895eeeaa199c3f70e63c5d09b710faf250', + 'size_bytes': 2411184, + 'generation': 1787607011184644, 'condition': 'host_os == "mac" and host_cpu == "x64" and checkout_clang_coverage_tools', }, { - 'object_name': 'Mac/llvmobjdump-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '9b286eecf04996dfa82cd1434bceb19612fa0754e4390649ba8dcd846dca9c1e', - 'size_bytes': 5797028, - 'generation': 1781627385345493, + 'object_name': 'Mac/llvmobjdump-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '4ddefa96f6aaa04ac502610fc736528f65dc3e1b997fc4a5380081937f6ffb1a', + 'size_bytes': 5874672, + 'generation': 1787607011035157, 'condition': 'host_os == "mac" and host_cpu == "x64"', }, { - 'object_name': 'Mac_arm64/clang-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': 'e96c5e31ddc2a6e841bcb0f7278ae82b3a581799904276e6f7673213c9748c27', - 'size_bytes': 47058068, - 'generation': 1781627397208650, + 'object_name': 'Mac_arm64/clang-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '264d803970f9b58ddbdc90464ad82e54cef642c58ec371ba5356dfa7342ece4d', + 'size_bytes': 47226204, + 'generation': 1787607020088085, 'condition': 'host_os == "mac" and host_cpu == "arm64"', }, { - 'object_name': 'Mac_arm64/clang-tidy-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '27eff49b49d393c903246d5e17ba620f62f493a8a39cb6dbdd2e3f5f06c592a2', - 'size_bytes': 12835944, - 'generation': 1781627397575694, + 'object_name': 'Mac_arm64/clang-tidy-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '3e02eae391e05018fb4e95d3c90b270688a5e3d5f5b31df6c59512e602478f36', + 'size_bytes': 12960456, + 'generation': 1787607020070956, 'condition': 'host_os == "mac" and host_cpu == "arm64" and checkout_clang_tidy', }, { - 'object_name': 'Mac_arm64/clangd-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '9519e51006f45a797a351b616d92bff319793ab976eeb59df60901d6181f3c18', - 'size_bytes': 13205948, - 'generation': 1781627397237653, + 'object_name': 'Mac_arm64/clangd-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': 'a56514d09cfcb216efa297e5fecd51a2e248faf7db6edd2d444cfe78e0e219d3', + 'size_bytes': 13320572, + 'generation': 1787607020209265, 'condition': 'host_os == "mac" and host_cpu == "arm64" and checkout_clangd', }, { - 'object_name': 'Mac_arm64/llvm-code-coverage-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '62084a5523a1c3b140166c2ed5a21bc110ceb65503f2759272a9f1feb00b503e', - 'size_bytes': 2000216, - 'generation': 1781627397451117, + 'object_name': 'Mac_arm64/llvm-code-coverage-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': 'b593c509e8f4269cf4a16272f6eddcea0f9dd47f6f3fe39b9813041d0f96d4e3', + 'size_bytes': 2031112, + 'generation': 1787607020489366, 'condition': 'host_os == "mac" and host_cpu == "arm64" and checkout_clang_coverage_tools', }, { - 'object_name': 'Mac_arm64/llvmobjdump-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': 'b6c6c0b1b4147ba4d51603713d3ed21f88ee2f506ff07fb432b3444f1d323295', - 'size_bytes': 5548680, - 'generation': 1781627397409118, + 'object_name': 'Mac_arm64/llvmobjdump-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': 'c67f5dba316d6367be8c153ede456fdbef0dfffad4da10aaf597ff967c07378c', + 'size_bytes': 5610096, + 'generation': 1787607020228775, 'condition': 'host_os == "mac" and host_cpu == "arm64"', }, { - 'object_name': 'Win/clang-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '9e3894b94d0d5e3d5904a559f590f12bd53aa5d1d9b6a902de2acb957825de46', - 'size_bytes': 51306328, - 'generation': 1781627409675428, + 'object_name': 'Win/clang-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '166437ede456b3d7cf0d9317287511e47714f91da547dbde02992261aeef7174', + 'size_bytes': 51467544, + 'generation': 1787607030157700, 'condition': 'host_os == "win"', }, { - 'object_name': 'Win/clang-tidy-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '8fa52c3d1707e73d0689aaa3c8d3789f13e85526bef029d942baf44ed980b442', - 'size_bytes': 14867152, - 'generation': 1781627409620988, + 'object_name': 'Win/clang-tidy-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '13cae70dfa207c9ec27e294f38b732b5c820ef637f48d24cc8b97f5f3a96e220', + 'size_bytes': 15134360, + 'generation': 1787607030186388, 'condition': 'host_os == "win" and checkout_clang_tidy', }, { - 'object_name': 'Win/clang-win-runtime-library-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': 'f925c30e1f63291662b2ad91103592208ecc4354f669c98a07e35360d2561a13', - 'size_bytes': 2623388, - 'generation': 1781627419134531, + 'object_name': 'Win/clang-win-runtime-library-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '4ff9a06f5fc4a711103cd890e724ae4448d67873de438542f3ac6772e68d3c63', + 'size_bytes': 2638196, + 'generation': 1787607037249082, 'condition': 'checkout_win and not host_os == "win"', }, { - 'object_name': 'Win/clangd-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '54042464918d992a19efed3a5f2a170e6fc2e4551884b4870650e3a3a6a03b41', - 'size_bytes': 15262064, - 'generation': 1781627409708803, + 'object_name': 'Win/clangd-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '17f1d575127724051099a778d77e609f650702248cf4e7ff3ec33b377053edd9', + 'size_bytes': 15444040, + 'generation': 1787607030301504, 'condition': 'host_os == "win" and checkout_clangd', }, { - 'object_name': 'Win/llvm-code-coverage-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '7c4fbafcf2741a036d1f4058fc31b52cb5b0951e617561fc3d51b7fbe0a3044b', - 'size_bytes': 2496336, - 'generation': 1781627409916932, + 'object_name': 'Win/llvm-code-coverage-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '78aa6b2b9cf057077be6705b76e43ffed83e46e17ee86da1ab699340129b1aca', + 'size_bytes': 2512968, + 'generation': 1787607030493491, 'condition': 'host_os == "win" and checkout_clang_coverage_tools', }, { - 'object_name': 'Win/llvmobjdump-llvmorg-23-init-19482-g53d18800-1.tar.xz', - 'sha256sum': '7ea19d03f21ef59d2e511af8d9cccff20ea72deb54923557173bb46a64244fa7', - 'size_bytes': 5934284, - 'generation': 1781627409787893, + 'object_name': 'Win/llvmobjdump-llvmorg-24-init-3796-g20e97c4b-4.tar.xz', + 'sha256sum': '4b899193da883a447c75f057e3a94f5e7ab93e5df5724ad94cbd6bf82ea792b4', + 'size_bytes': 5994644, + 'generation': 1787607030276791, 'condition': '(checkout_linux or checkout_mac or checkout_android) and host_os == "win"', }, ] @@ -622,9 +663,11 @@ deps = { Var('chromium_git') + '/external/github.com/llvm/llvm-project/libc.git@' + Var('llvm_libc_revision'), - 'third_party/markupsafe': - Var('chromium_git') + '/chromium/src/third_party/markupsafe.git@' + + 'third_party/markupsafe': { + 'url': Var('chromium_git') + '/chromium/src/third_party/markupsafe.git@' + Var('markupsafe_revision'), + 'condition': 'checkout_v8', + }, 'third_party/nasm': Var('chromium_git') + '/chromium/deps/nasm.git@' + @@ -652,31 +695,31 @@ deps = { 'bucket': 'chromium-browser-clang', 'objects': [ { - 'object_name': 'Linux_x64/rust-toolchain-b998449636a48e2c4a362809085b600a0174e1f2-2-llvmorg-23-init-19482-g53d18800.tar.xz', - 'sha256sum': '88db954bdbaea527bebf832758d6ff8193eb214877b4eef362e97701d64c0b54', - 'size_bytes': 274790504, - 'generation': 1782418940816837, + 'object_name': 'Linux_x64/rust-toolchain-0913b18e489ac1011b580e31fa5559654be12bfc-2-llvmorg-24-init-3796-g20e97c4b.tar.xz', + 'sha256sum': '4b131e81d157a97bcf454ad0fbb71bc2063b2a3708c858410c04201ef06134e5', + 'size_bytes': 276314860, + 'generation': 1786821088882867, 'condition': 'host_os == "linux" and non_git_source', }, { - 'object_name': 'Mac/rust-toolchain-b998449636a48e2c4a362809085b600a0174e1f2-2-llvmorg-23-init-19482-g53d18800.tar.xz', - 'sha256sum': '728aaa01b049cd5252915db4ed8a7492955b3d5d3939990f18e6b085cf8ad4ad', - 'size_bytes': 262355184, - 'generation': 1782418942567956, + 'object_name': 'Mac/rust-toolchain-0913b18e489ac1011b580e31fa5559654be12bfc-2-llvmorg-24-init-3796-g20e97c4b.tar.xz', + 'sha256sum': '12ca849195c27495073ba52ae7126d8a4a9a5807cd7a0dc6ba6b6fa612302a6d', + 'size_bytes': 264031896, + 'generation': 1786821092536805, 'condition': 'host_os == "mac" and host_cpu == "x64"', }, { - 'object_name': 'Mac_arm64/rust-toolchain-b998449636a48e2c4a362809085b600a0174e1f2-2-llvmorg-23-init-19482-g53d18800.tar.xz', - 'sha256sum': '00a11ecce7cebc2ce777518e6e51a150d980dd5f41392200b65f543767536b56', - 'size_bytes': 246777680, - 'generation': 1782418944301166, + 'object_name': 'Mac_arm64/rust-toolchain-0913b18e489ac1011b580e31fa5559654be12bfc-2-llvmorg-24-init-3796-g20e97c4b.tar.xz', + 'sha256sum': 'fe128475561d7a51c797d1cb3ccea6f94fd770b167c8e0cc33bf75e7c25d8225', + 'size_bytes': 248465344, + 'generation': 1786821096222521, 'condition': 'host_os == "mac" and host_cpu == "arm64"', }, { - 'object_name': 'Win/rust-toolchain-b998449636a48e2c4a362809085b600a0174e1f2-2-llvmorg-23-init-19482-g53d18800.tar.xz', - 'sha256sum': '482c5a8946869aae595adc7361b619e8af62dc002cd8f3a00b27fe59db4d74f3', - 'size_bytes': 415939796, - 'generation': 1782418946203516, + 'object_name': 'Win/rust-toolchain-0913b18e489ac1011b580e31fa5559654be12bfc-2-llvmorg-24-init-3796-g20e97c4b.tar.xz', + 'sha256sum': '14bc9cea5e00cb191f58204ef44d68a6794f856a76f885c50298a12d052035bc', + 'size_bytes': 414479372, + 'generation': 1786821099612317, 'condition': 'host_os == "win"', }, ], @@ -839,7 +882,7 @@ include_rules = [ specific_include_rules = { # Allow embedder tests to use public APIs. - '(.*embeddertest\.cpp)': [ + r'(.*embeddertest\.cpp)': [ '+public', ] } diff --git a/PRESUBMIT.py b/PRESUBMIT.py index dc07e5ac2e..f430bf7cc8 100644 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py @@ -331,7 +331,7 @@ def _CheckIncludeOrderInFile(input_api, f, changed_linenums): # check. uncheckable_includes_pattern = input_api.re.compile( r'\s*#include ' - '("ipc/.*macros\.h"||".*gl.*autogen.h")\s*') + r'("ipc/.*macros\.h"||".*gl.*autogen.h")\s*') contents = f.NewContents() warnings = [] @@ -464,23 +464,23 @@ def _CheckTestDuplicates(input_api, output_api): def _CheckPngNames(input_api, output_api): - """Checks that .png files have the right file name format. + r"""Checks that .png files have the right file name format. For most directories, including those used for pixel tests, they must be in the form of: - NAME_expected(_gdi)?(_(agg|skia))?(_(linux|mac|win))?.pdf.\d+.png + NAME_expected(_(agg|skia))?(_(linux|mac|win|gdi))?.pdf.\d+.png This must be the same format as the one in testing/corpus's PRESUBMIT.py. For testing/resources/embedder_tests, they must be in the form of: - NAME(_gdi)?(_(agg|skia))?(_(linux|mac|win))?.png + NAME(_(agg|skia))?(_(linux|mac|win|gdi))?.png """ expected_pattern = input_api.re.compile( - r'.+_expected(_gdi)?(_(agg|skia))?(_(linux|mac|win))?\.pdf\.\d+.png') + r'.+_expected(_(agg|skia))?(_(linux|mac|win|gdi))?\.pdf\.\d+.png') expected_embedder_tests_pattern = input_api.re.compile( - r'.+(_gdi)?(_(agg|skia))?(_(linux|mac|win))?.png') + r'.+(_(agg|skia))?(_(linux|mac|win|gdi))?\.png') results = [] for f in input_api.AffectedFiles(include_deletes=False): if not f.LocalPath().endswith('.png'): @@ -568,6 +568,27 @@ def ChecksCommon(input_api, output_api): return results +def CheckNoIOStreamInHeaders(input_api, output_api): + """Checks to make sure no .h files include .""" + files = [] + pattern = input_api.re.compile(r'^#include\s*', + input_api.re.MULTILINE) + for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile): + if not f.LocalPath().endswith('.h'): + continue + contents = input_api.ReadFile(f) + if pattern.search(contents): + files.append(f) + + if files: + return [ + output_api.PresubmitError( + 'Do not #include in header files, since it inserts ' + 'static initialization into every file including the header. ' + 'Instead, #include . See http://crbug.com/94794', files) + ] + return [] + def CheckChangeOnUpload(input_api, output_api): results = [] results.extend(_CheckNoBannedFunctions(input_api, output_api)) diff --git a/PRESUBMIT_test.py b/PRESUBMIT_test.py index ee2c355800..cb970b6737 100755 --- a/PRESUBMIT_test.py +++ b/PRESUBMIT_test.py @@ -59,12 +59,8 @@ def testCheckPngNames(self): 'test_expected_agg_linux.pdf.3.png', 'test_expected_skia.pdf.2.png', 'test_expected_skia_mac.pdf.4.png', - 'test_expected_gdi_agg.pdf.3.png', - 'test_expected_gdi_agg_win.pdf.4.png', - 'test_expected_gdi_skia.pdf.10.png', - 'test_expected_gdi_skia_linux.pdf.5.png', + 'test_expected_skia_gdi.pdf.10.png', 'test_expected_gdi.pdf.99.png', - 'test_expected_gdi_mac.pdf.0.png', 'notpng.cc', # Check will be skipped for non-PNG files ] wrong_paths = [ @@ -72,13 +68,17 @@ def testCheckPngNames(self): 'test1_expected.0.png', # Missing '.pdf' 'test2_expected.pdf.png', # Missing page number 'test3_expected.pdf.x.png', # Wrong character for page number - 'test4_expected_agg_gdi.pdf.0.png', # Wrong order of keywords + 'test4_expected_win_agg.pdf.0.png', # Wrong order of keywords 'test4_expected_linux_agg.pdf.0.png', # Wrong order of keywords 'test4_expected_mac_skia.pdf.0.png', # Wrong order of keywords - 'test4_expected_skia_gdi.pdf.0.png', # Wrong order of keywords + 'test4_expected_gdi_skia.pdf.0.png', # Wrong order of keywords + 'test4_expected_gdi_agg.pdf.3.png', # Wrong order of keywords 'test5_expected_useskia.pdf.0.png', # Wrong keyword 'test6_expected_win_mac.pdf.0.png', # Too many platforms 'test7_expected_agg_skia.pdf.0.png', # Too many renderers + 'test8_expected_gdi_agg_win.pdf.4.png', # Too many suffixes + 'test9_expected_gdi_skia_linux.pdf.5.png', # Too many suffixes + 'test10_expected_gdi_mac.pdf.0.png', # Too many platforms ] mock_input_api = MockInputApi() mock_output_api = MockOutputApi() diff --git a/build_overrides/pdfium.gni b/build_overrides/pdfium.gni index bca39d36e2..cdbbca4b53 100644 --- a/build_overrides/pdfium.gni +++ b/build_overrides/pdfium.gni @@ -48,6 +48,9 @@ pdf_enable_fontations_override = false # requires Skia and Rust support. pdf_enable_rust_bmp_override = false +# Build PDFium with Skia/Rust-based JPEG decoder. +pdf_enable_rust_jpeg_override = false + # Build PDFium with Rust-based PNG decoder and encoder. # If enabled, it ensures that PDFium doesn't depend on `libpng`. # Note that `pdf_enable_rust_png` requires Skia and Rust support. diff --git a/core/fpdfapi/edit/BUILD.gn b/core/fpdfapi/edit/BUILD.gn index 1f4c30d21a..55a6f3b621 100644 --- a/core/fpdfapi/edit/BUILD.gn +++ b/core/fpdfapi/edit/BUILD.gn @@ -76,21 +76,26 @@ source_set("contentstream_write_utils") { "../../../:pdfium_strict_config", "../../../:pdfium_noshorten_config", ] - deps = [ "../../fxcrt" ] + deps = [ + "../../fxcrt", + "//third_party/dragonbox", + ] } pdfium_unittest_source_set("unittests") { sources = [ + "cpdf_contentstream_write_utils_unittest.cpp", "cpdf_creator_unittest.cpp", - "cpdf_save_trailer_unittest.cpp", "cpdf_npagetooneexporter_unittest.cpp", "cpdf_pagecontentgenerator_unittest.cpp", "cpdf_path_redactor_unittest.cpp", "cpdf_redaction_copies_unittest.cpp", + "cpdf_save_trailer_unittest.cpp", "cpdf_text_redactor_unittest.cpp", "cpdf_tounicodecmap_unittest.cpp", ] deps = [ + ":contentstream_write_utils", ":edit", "../../fxge", "../font", diff --git a/core/fpdfapi/edit/DEPS b/core/fpdfapi/edit/DEPS new file mode 100644 index 0000000000..3cd317f3f0 --- /dev/null +++ b/core/fpdfapi/edit/DEPS @@ -0,0 +1,5 @@ +specific_include_rules = { + 'cpdf_contentstream_write_utils.cpp': [ + '+third_party/dragonbox/src/include/dragonbox/dragonbox.h', + ], +} diff --git a/core/fpdfapi/edit/cpdf_contentstream_write_utils.cpp b/core/fpdfapi/edit/cpdf_contentstream_write_utils.cpp index 76268d9352..d62d463a1f 100644 --- a/core/fpdfapi/edit/cpdf_contentstream_write_utils.cpp +++ b/core/fpdfapi/edit/cpdf_contentstream_write_utils.cpp @@ -4,227 +4,113 @@ #include "core/fpdfapi/edit/cpdf_contentstream_write_utils.h" -#include -#include -#include +#include #include +#include #include -#include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/span.h" +#include "third_party/dragonbox/src/include/dragonbox/dragonbox.h" namespace { -constexpr unsigned kMaximumSkFloatToDecimalLength = 49; - -// Return pow(10.0, e), optimized for common cases. -double pow10(int e) { - switch (e) { - case 0: - return 1.0; // common cases - case 1: - return 10.0; - case 2: - return 100.0; - case 3: - return 1e+03; - case 4: - return 1e+04; - case 5: - return 1e+05; - case 6: - return 1e+06; - case 7: - return 1e+07; - case 8: - return 1e+08; - case 9: - return 1e+09; - case 10: - return 1e+10; - case 11: - return 1e+11; - case 12: - return 1e+12; - case 13: - return 1e+13; - case 14: - return 1e+14; - case 15: - return 1e+15; - default: - if (e > 15) { - double value = 1e+15; - while (e-- > 15) { - value *= 10.0; - } - return value; - } else { - assert(e < 0); - double value = 1.0; - while (e++ < 0) { - value /= 10.0; - } - return value; - } - } -} +constexpr unsigned kMaximumFloatToDecimalLength = 49; -// SkFloatToDecimal -// // Convert a float into a decimal string. // -// The resulting string will be in the form `[-]?([0-9]*\.)?[0-9]+` (It does -// not use scientific notation.) and `sscanf(output, "%f", &x)` will return -// the original value if the value is finite. This function accepts all -// possible input values. +// The resulting string will be in the form `[-]?([0-9]*\.)?[0-9]+` (it does +// not use scientific notation). // // INFINITY and -INFINITY are rounded to FLT_MAX and -FLT_MAX. // // NAN values are converted to 0. // // This function will always add a terminating '\0' to the output. -// -// @param value Any floating-point number -// @param output The buffer to write the string into. Must be non-null. -// -// @return strlen(output) -// -// Write a string into output, including a terminating '\0' (for -// unit testing). Return strlen(output) (for SkWStream::write) The -// resulting string will be in the form /[-]?([0-9]*.)?[0-9]+/ and -// sscanf(output, "%f", &x) will return the original value iff the -// value is finite. This function accepts all possible input values. -// -// Motivation: "PDF does not support [numbers] in exponential format -// (such as 6.02e23)." Otherwise, this function would rely on a -// sprintf-type function from the standard library. -unsigned SkFloatToDecimal( +unsigned FloatToDecimal( float value, - pdfium::span output) { + pdfium::span output) { // The longest result is -FLT_MIN. - // We serialize it as "-.0000000000000000000000000000000000000117549435" + // Serialize it as "-.0000000000000000000000000000000000000117549435" // which has 48 characters plus a terminating '\0'. - static_assert(kMaximumSkFloatToDecimalLength == 49, ""); - - // 3 = '-', '.', and '\0' characters. - // 9 = number of significant digits - // abs(FLT_MIN_10_EXP) = number of zeros in FLT_MIN - static_assert(kMaximumSkFloatToDecimalLength == 3 + 9 - FLT_MIN_10_EXP, ""); - - // section C.1 of the PDF 1.4 spec (http://goo.gl/0SCswJ) says that - // most PDF rasterizers will use fixed-point scalars that lack the - // dynamic range of floats. Even if this is the case, I want to - // serialize these (uncommon) very small and very large scalar - // values with enough precision to allow a floating-point - // rasterizer to read them in with perfect accuracy. - // Experimentally, rasterizers such as pdfium do seem to benefit - // from this. Rasterizers that rely on fixed-point scalars should - // gracefully ignore these values that they can not parse. - char* output_ptr = output.data(); - - // last(1) leaves space for '\0'. - const char* const end = output.last(1u).data(); - - // This function is written to accept any possible input value, - // including non-finite values such as INF and NAN. In that case, - // we ignore value-correctness and output a syntacticly-valid - // number. + static_assert(kMaximumFloatToDecimalLength == 49, ""); + static_assert(kMaximumFloatToDecimalLength == + 3 + 9 - std::numeric_limits::min_exponent10, + ""); + + size_t out_idx = 0; + + if (std::isnan(value) || value == 0.0f) { + // NAN is unsupported in PDF. Always output a valid number. + // Also catch zero here, as a special case. + output[out_idx++] = '0'; + output[out_idx] = '\0'; + return static_cast(out_idx); + } + if (value == INFINITY) { - value = FLT_MAX; // nearest finite float. + value = std::numeric_limits::max(); + } else if (value == -INFINITY) { + value = std::numeric_limits::lowest(); } - if (value == -INFINITY) { - value = -FLT_MAX; // nearest finite float. + + if (value < 0.0f) { + output[out_idx++] = '-'; + value = -value; } - UNSAFE_TODO({ - if (!std::isfinite(value) || value == 0.0f) { - // NAN is unsupported in PDF. Always output a valid number. - // Also catch zero here, as a special case. - *output_ptr++ = '0'; - *output_ptr = '\0'; - return static_cast(output_ptr - output.data()); - } - if (value < 0.0) { - *output_ptr++ = '-'; - value = -value; - } - assert(value >= 0.0f); - - int binaryExponent; - (void)std::frexp(value, &binaryExponent); - static const double kLog2 = 0.3010299956639812; // log10(2.0); - int decimalExponent = static_cast(std::floor(kLog2 * binaryExponent)); - int decimalShift = decimalExponent - 8; - double power = pow10(-decimalShift); - assert(value * power <= (double)INT_MAX); - int d = static_cast(value * power + 0.5); - // assert(value == (float)(d * pow(10.0, decimalShift))); - assert(d <= 999999999); - if (d > 167772159) { // floor(pow(10,1+log10(1<<24))) - // need one fewer decimal digits for 24-bit precision. - decimalShift = decimalExponent - 7; - // assert(power * 0.1 = pow10(-decimalShift)); - // recalculate to get rounding right. - d = static_cast(value * (power * 0.1) + 0.5); - assert(d <= 99999999); + + const auto decimal = + jkj::dragonbox::to_decimal(value, jkj::dragonbox::policy::sign::ignore, + jkj::dragonbox::policy::trailing_zero::remove); + + std::array digits; + size_t num_digits = 0; + uint32_t significand = decimal.significand; + do { + digits[num_digits++] = static_cast('0' + (significand % 10)); + significand /= 10; + } while (significand > 0); + + const int exponent = decimal.exponent; + if (exponent >= 0) { + while (num_digits > 0) { + output[out_idx++] = digits[--num_digits]; } - while (d % 10 == 0) { - d /= 10; - ++decimalShift; + for (int i = 0; i < exponent; ++i) { + output[out_idx++] = '0'; } - assert(d > 0); - // assert(value == (float)(d * pow(10.0, decimalShift))); - unsigned char buffer[9]; // decimal value buffer. - int bufferIndex = 0; - do { - buffer[bufferIndex++] = d % 10; - d /= 10; - } while (d != 0); - assert(bufferIndex <= (int)sizeof(buffer) && bufferIndex > 0); - if (decimalShift >= 0) { - do { - --bufferIndex; - *output_ptr++ = '0' + buffer[bufferIndex]; - } while (bufferIndex); - for (int i = 0; i < decimalShift; ++i) { - *output_ptr++ = '0'; + } else { + const int places_before_decimal = static_cast(num_digits) + exponent; + if (places_before_decimal > 0) { + for (int i = 0; i < places_before_decimal; ++i) { + output[out_idx++] = digits[--num_digits]; + } + output[out_idx++] = '.'; + while (num_digits > 0) { + output[out_idx++] = digits[--num_digits]; } } else { - int placesBeforeDecimal = bufferIndex + decimalShift; - if (placesBeforeDecimal > 0) { - while (placesBeforeDecimal-- > 0) { - --bufferIndex; - *output_ptr++ = '0' + buffer[bufferIndex]; - } - *output_ptr++ = '.'; - } else { - *output_ptr++ = '.'; - int placesAfterDecimal = -placesBeforeDecimal; - while (placesAfterDecimal-- > 0) { - *output_ptr++ = '0'; - } + output[out_idx++] = '.'; + for (int i = 0; i < -places_before_decimal; ++i) { + output[out_idx++] = '0'; } - while (bufferIndex > 0) { - --bufferIndex; - *output_ptr++ = '0' + buffer[bufferIndex]; - if (output_ptr == end) { - break; // denormalized: don't need extra precision. - // Note: denormalized numbers will not have the same number - // of significantDigits, but do not need them to round-trip. + while (num_digits > 0) { + output[out_idx++] = digits[--num_digits]; + if (out_idx + 1 >= output.size()) { + break; } } } - assert(output_ptr <= end); - *output_ptr = '\0'; - return static_cast(output_ptr - output.data()); - }); + } + + output[out_idx] = '\0'; + return static_cast(out_idx); } } // namespace std::ostream& WriteFloat(std::ostream& stream, float value) { - char buffer[kMaximumSkFloatToDecimalLength]; - unsigned size = SkFloatToDecimal(value, buffer); + char buffer[kMaximumFloatToDecimalLength]; + unsigned size = FloatToDecimal(value, buffer); stream.write(buffer, size); return stream; } diff --git a/core/fpdfapi/edit/cpdf_contentstream_write_utils_unittest.cpp b/core/fpdfapi/edit/cpdf_contentstream_write_utils_unittest.cpp new file mode 100644 index 0000000000..a28964783f --- /dev/null +++ b/core/fpdfapi/edit/cpdf_contentstream_write_utils_unittest.cpp @@ -0,0 +1,69 @@ +// 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/fpdfapi/edit/cpdf_contentstream_write_utils.h" + +#include + +#include + +#include "core/fxcrt/bytestring.h" +#include "core/fxcrt/fx_coordinates.h" +#include "core/fxcrt/fx_string_wrappers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +ByteString WriteFloatToString(float value) { + fxcrt::ostringstream stream; + WriteFloat(stream, value); + return ByteString(stream); +} + +} // namespace + +TEST(CPDFContentstreamWriteUtils, WriteFloat) { + EXPECT_EQ("0", WriteFloatToString(0.0f)); + EXPECT_EQ("0", WriteFloatToString(-0.0f)); + EXPECT_EQ("1", WriteFloatToString(1.0f)); + EXPECT_EQ("-1", WriteFloatToString(-1.0f)); + EXPECT_EQ(".5", WriteFloatToString(0.5f)); + EXPECT_EQ("-.5", WriteFloatToString(-0.5f)); + EXPECT_EQ(".00125", WriteFloatToString(0.00125f)); + EXPECT_EQ("123.45", WriteFloatToString(123.45f)); + EXPECT_EQ("-7.5", WriteFloatToString(-7.5f)); + EXPECT_EQ("38.895287", WriteFloatToString(38.895285f)); + EXPECT_EQ("-77.03723", WriteFloatToString(-77.037232f)); + EXPECT_EQ("340282350000000000000000000000000000000", + WriteFloatToString(std::numeric_limits::max())); + EXPECT_EQ("-340282350000000000000000000000000000000", + WriteFloatToString(-std::numeric_limits::max())); + EXPECT_EQ(".000000000000000000000000000000000000011754944", + WriteFloatToString(std::numeric_limits::min())); + EXPECT_EQ("-.000000000000000000000000000000000000011754944", + WriteFloatToString(-std::numeric_limits::min())); + EXPECT_EQ("340282350000000000000000000000000000000", + WriteFloatToString(INFINITY)); + EXPECT_EQ("-340282350000000000000000000000000000000", + WriteFloatToString(-INFINITY)); + EXPECT_EQ("0", WriteFloatToString(NAN)); +} + +TEST(CPDFContentstreamWriteUtils, WriteMatrix) { + fxcrt::ostringstream stream; + WriteMatrix(stream, CFX_Matrix(1.0f, 0.0f, 0.0f, 1.0f, 10.5f, 20.25f)); + EXPECT_EQ("1 0 0 1 10.5 20.25", ByteString(stream)); +} + +TEST(CPDFContentstreamWriteUtils, WritePoint) { + fxcrt::ostringstream stream; + WritePoint(stream, CFX_PointF(1.0f, 2.5f)); + EXPECT_EQ("1 2.5", ByteString(stream)); +} + +TEST(CPDFContentstreamWriteUtils, WriteRect) { + fxcrt::ostringstream stream; + WriteRect(stream, CFX_FloatRect(1.0f, 2.0f, 10.0f, 20.0f)); + EXPECT_EQ("1 2 9 18", ByteString(stream)); +} diff --git a/core/fpdfapi/edit/cpdf_creator.cpp b/core/fpdfapi/edit/cpdf_creator.cpp index 865713de76..12d45b7649 100644 --- a/core/fpdfapi/edit/cpdf_creator.cpp +++ b/core/fpdfapi/edit/cpdf_creator.cpp @@ -37,6 +37,7 @@ #include "core/fpdfapi/parser/cpdf_string.h" #include "core/fpdfapi/parser/fpdf_parser_utility.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/containers/contains.h" #include "core/fxcrt/fixed_size_data_vector.h" #include "core/fxcrt/fx_extension.h" @@ -728,7 +729,7 @@ CPDF_Creator::Stage CPDF_Creator::WriteDoc_Stage3() { } CPDF_Creator::Stage CPDF_Creator::WriteDoc_Stage4() { - DCHECK(stage_ >= Stage::kWriteTrailerAndFinish90); + DCHECK_GE(stage_, Stage::kWriteTrailerAndFinish90); // EmbedPDF(layer-save): an append-only delta writes a cross-reference // table, never a stream. diff --git a/core/fpdfapi/edit/cpdf_npagetooneexporter.cpp b/core/fpdfapi/edit/cpdf_npagetooneexporter.cpp index 947c3a834c..b20813726c 100644 --- a/core/fpdfapi/edit/cpdf_npagetooneexporter.cpp +++ b/core/fpdfapi/edit/cpdf_npagetooneexporter.cpp @@ -23,7 +23,7 @@ #include "core/fpdfapi/parser/cpdf_reference.h" #include "core/fpdfapi/parser/cpdf_stream.h" #include "core/fpdfapi/parser/cpdf_stream_acc.h" -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/fx_string_wrappers.h" #include "core/fxcrt/span.h" @@ -76,10 +76,10 @@ NupState::NupState(const CFX_SizeF& pagesize, pages_on_x_axis_(pages_on_x_axis), pages_on_y_axis_(pages_on_y_axis), pages_per_sheet_(pages_on_x_axis * pages_on_y_axis) { - DCHECK(pages_on_x_axis_ > 0); - DCHECK(pages_on_y_axis_ > 0); - DCHECK(dest_page_size_.width > 0); - DCHECK(dest_page_size_.height > 0); + DCHECK_GT(pages_on_x_axis_, 0); + DCHECK_GT(pages_on_y_axis_, 0); + DCHECK_GT(dest_page_size_.width, 0); + DCHECK_GT(dest_page_size_.height, 0); sub_page_size_.width = dest_page_size_.width / pages_on_x_axis_; sub_page_size_.height = dest_page_size_.height / pages_on_y_axis_; diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp index 42681d3d62..d4aecc60fb 100644 --- a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp +++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp @@ -102,7 +102,7 @@ TEST_F(CPDFPageContentGeneratorTest, ProcessRect) { CFX_Path::Point::Type::kLine); buf.str(""); TestProcessPath(&generator, &buf, pPathObj.get()); - EXPECT_EQ("q 0 0 5.1999998 3.78 re n Q\n", ByteString(buf)); + EXPECT_EQ("q 0 0 5.2 3.78 re n Q\n", ByteString(buf)); } TEST_F(CPDFPageContentGeneratorTest, Bug937) { @@ -129,11 +129,11 @@ TEST_F(CPDFPageContentGeneratorTest, Bug937) { fxcrt::ostringstream buf; TestProcessPath(&generator, &buf, pPathObj.get()); EXPECT_EQ( - "q .00000000000000000000099999997 .69999999 .34999999 rg " - ".00000000000000000000099999997 .69999999 .34999999 RG " - "200000000000000000000 w 1 0 0 1 .00000000000000000000099999997 " - "200000000000000 cm .00000000000000000000099999997 " - ".00000000000000000000099999997 100 100 re f Q\n", + "q .000000000000000000001 .7 .35 rg " + ".000000000000000000001 .7 .35 RG " + "200000000000000000000 w 1 0 0 1 .000000000000000000001 " + "200000000000000 cm .000000000000000000001 " + ".000000000000000000001 100 100 re f Q\n", ByteString(buf)); } @@ -165,11 +165,11 @@ TEST_F(CPDFPageContentGeneratorTest, Bug937) { TestProcessPath(&generator, &buf, pPathObj.get()); EXPECT_EQ( - "q .00000000000000000000099999997 .69999999 .34999999 rg " - ".00000000000000000000099999997 .69999999 .34999999 RG 2 w 1 0 0 1 432 " - "499999990000000 cm .00000000000000000000099999997 4.6700001 m " - ".00000000000000000000099999997 100000000000000 l " - ".000000000000099999998 3.1500001 3.5699999 2.98 53.400002 " + "q .000000000000000000001 .7 .35 rg " + ".000000000000000000001 .7 .35 RG 2 w 1 0 0 1 432 " + "500000000000000 cm .000000000000000000001 4.67 m " + ".000000000000000000001 100000000000000 l " + ".0000000000001 3.15 3.57 2.98 53.4 " "5000000000000000000 c h f Q\n", ByteString(buf)); } @@ -205,9 +205,9 @@ TEST_F(CPDFPageContentGeneratorTest, ProcessPath) { fxcrt::ostringstream buf; TestProcessPath(&generator, &buf, pPathObj.get()); EXPECT_EQ( - "q 3.102 4.6700001 m 5.4499998 .28999999 l 4.2399998 " - "3.1500001 4.6500001 2.98 3.4560001 .23999999 c 10.6000004 11.149999" - "6 l 11 12.5 l 11.46 12.6700001 11.8400002 12.96 12 13.6400003 c h f" + "q 3.102 4.67 m 5.45 .29 l 4.24 " + "3.15 4.65 2.98 3.456 .24 c 10.6 " + "11.15 l 11 12.5 l 11.46 12.67 11.84 12.96 12 13.64 c h f" " Q\n", ByteString(buf)); } @@ -242,8 +242,7 @@ TEST_F(CPDFPageContentGeneratorTest, ProcessGraphics) { ByteString path_string(buf); // Color RGB values used are integers divided by 255. - const ByteStringView kExpectedStringStart = - "q .5 .69999999 .34999999 rg 1 .89999998 0 RG /"; + const ByteStringView kExpectedStringStart = "q .5 .7 .35 rg 1 .9 0 RG /"; const ByteStringView kExpectedStringEnd = " gs 1 2 m 3 4 l 5 6 l h B Q\n"; const size_t kExpectedStringMinLength = kExpectedStringStart.GetLength() + kExpectedStringEnd.GetLength(); @@ -266,7 +265,7 @@ TEST_F(CPDFPageContentGeneratorTest, ProcessGraphics) { buf.str(""); TestProcessPath(&generator, &buf, pPathObj.get()); const ByteStringView kExpectedStringStart2 = - "q .5 .69999999 .34999999 rg 1 .89999998 0 RG 10.5 w /"; + "q .5 .7 .35 rg 1 .9 0 RG 10.5 w /"; ByteString path_string2(buf); EXPECT_EQ(kExpectedStringStart2, path_string2.First(kExpectedStringStart2.GetLength())); @@ -317,7 +316,7 @@ TEST_F(CPDFPageContentGeneratorTest, ProcessStandardText) { ByteString font_name = GetResourceNameBeforeOperator(text_string, " 10 Tf "); std::string expected = - "q .5 .69999999 .34999999 rg 1 .89999998 0 RG /"; + "q .5 .7 .35 rg 1 .9 0 RG /"; expected += gs_name.c_str(); expected += " gs 1 0 0 1 100 100 cm BT 1 0 0 1 0 0 Tm /"; @@ -451,8 +450,8 @@ TEST_F(CPDFPageContentGeneratorTest, ProcessFormWithPath) { fxcrt::ostringstream process_buf; ASSERT_TRUE(generator.ProcessPageObjects(&process_buf)); EXPECT_EQ( - "q 3.102 4.6700001 m 5.4500012 .28999999 l 4.2399998 3.14" - "99999 4.6500001 2.98 3.4560001 .23999999 c 3.102 4.6700001 l h f Q\n", + "q 3.102 4.67 m 5.4500012 .29 l 4.24 3.1499999 " + "4.65 2.98 3.456 .24 c 3.102 4.67 l h f Q\n", ByteString(process_buf)); } diff --git a/core/fpdfapi/font/cpdf_tounicodemap_unittest.cpp b/core/fpdfapi/font/cpdf_tounicodemap_unittest.cpp index 8a3f577763..5b4da257b6 100644 --- a/core/fpdfapi/font/cpdf_tounicodemap_unittest.cpp +++ b/core/fpdfapi/font/cpdf_tounicodemap_unittest.cpp @@ -392,6 +392,47 @@ TEST(CPDFToUnicodeMapTest, InsertIntoMaps) { } } +TEST(CPDFToUnicodeMapTest, SucceedingMapsSupersedePreceding) { + // Adobe TN #5014: "Code mappings (unlike codespace ranges) may overlap, but + // succeeding maps superceded preceding maps." Subset fonts commonly rely on + // this, emitting a catch-all bfrange and then overriding individual codes. + { + // bfchar entries override an earlier catch-all bfrange, including when the + // unicode is numerically above the charcode. + static constexpr uint8_t kInput1[] = + "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)); + // A charcode no later entry mentions keeps the range's mapping. + EXPECT_EQ(L"D", map.Lookup(0x44)); + } + { + // A later bfrange overrides an earlier one the same way. + static constexpr uint8_t kInput2[] = + "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"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. + static constexpr uint8_t kInput3[] = + "1 beginbfchar<41><00F3>endbfchar\n" + "1 beginbfchar<41><0041>endbfchar"; + CPDF_ToUnicodeMap map(pdfium::MakeRetain(kInput3)); + EXPECT_EQ(L"A", map.Lookup(0x41)); + } +} + TEST(CPDFToUnicodeMapTest, NonBmpUnicodeLookup) { static constexpr uint8_t kInput[] = "1 beginbfchar<01>endbfchar"; CPDF_ToUnicodeMap map(pdfium::MakeRetain(kInput)); diff --git a/core/fpdfapi/page/BUILD.gn b/core/fpdfapi/page/BUILD.gn index 85520f3f43..208451a680 100644 --- a/core/fpdfapi/page/BUILD.gn +++ b/core/fpdfapi/page/BUILD.gn @@ -157,6 +157,7 @@ pdfium_unittest_source_set("unittests") { "cpdf_colorspace_unittest.cpp", "cpdf_contentnames_unittest.cpp", "cpdf_devicecs_unittest.cpp", + "cpdf_dib_unittest.cpp", "cpdf_function_unittest.cpp", "cpdf_page_unittest.cpp", "cpdf_pageimagecache_unittest.cpp", diff --git a/core/fpdfapi/page/cpdf_contentmarks.cpp b/core/fpdfapi/page/cpdf_contentmarks.cpp index 77580f2b49..e7604f7190 100644 --- a/core/fpdfapi/page/cpdf_contentmarks.cpp +++ b/core/fpdfapi/page/cpdf_contentmarks.cpp @@ -163,12 +163,12 @@ void CPDF_ContentMarks::MarkData::AddMarkWithPropertiesHolder( marks_.push_back(std::move(pItem)); } -bool CPDF_ContentMarks::MarkData::RemoveMark(CPDF_ContentMarkItem* pMarkItem) { - for (auto it = marks_.begin(); it != marks_.end(); ++it) { - if (*it == pMarkItem) { - marks_.erase(it); - return true; - } +bool CPDF_ContentMarks::MarkData::RemoveMark(CPDF_ContentMarkItem* mark_item) { + auto it = std::ranges::find(marks_, mark_item); + if (it == marks_.end()) { + return false; } - return false; + + marks_.erase(it); + return true; } diff --git a/core/fpdfapi/page/cpdf_contentmarks.h b/core/fpdfapi/page/cpdf_contentmarks.h index 10a7c23ec6..1155932174 100644 --- a/core/fpdfapi/page/cpdf_contentmarks.h +++ b/core/fpdfapi/page/cpdf_contentmarks.h @@ -58,7 +58,7 @@ class CPDF_ContentMarks { void AddMarkWithPropertiesHolder(const ByteString& name, RetainPtr dict, const ByteString& property_name); - bool RemoveMark(CPDF_ContentMarkItem* pMarkItem); + bool RemoveMark(CPDF_ContentMarkItem* mark_item); private: MarkData(); diff --git a/core/fpdfapi/page/cpdf_dib_unittest.cpp b/core/fpdfapi/page/cpdf_dib_unittest.cpp new file mode 100644 index 0000000000..7f48d946a9 --- /dev/null +++ b/core/fpdfapi/page/cpdf_dib_unittest.cpp @@ -0,0 +1,92 @@ +// 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/fpdfapi/page/cpdf_dib.h" + +#include + +#include + +#include "core/fpdfapi/page/test_with_page_module.h" +#include "core/fpdfapi/parser/cpdf_dictionary.h" +#include "core/fpdfapi/parser/cpdf_name.h" +#include "core/fpdfapi/parser/cpdf_number.h" +#include "core/fpdfapi/parser/cpdf_stream.h" +#include "core/fpdfapi/parser/cpdf_test_document.h" +#include "core/fxcrt/data_vector.h" +#include "core/fxcrt/retain_ptr.h" +#include "core/fxcrt/span.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using CPDFDIBTest = TestWithPageModule; + +// The width every test uses. Two pixels is enough to catch a wrong stride. +constexpr int kWidth = 2; + +// Builds an uncompressed 2x1 DeviceRGB image XObject with the given sample +// data and loads it as a CPDF_DIB. The DIB stores 24bpp images as kBgr, so +// the scanline it returns is the default-decode translation of `data`. +RetainPtr LoadDeviceRgbImage(CPDF_Document* doc, + int bits_per_component, + DataVector data) { + auto dict = pdfium::MakeRetain(); + dict->SetNewFor("Type", "XObject"); + dict->SetNewFor("Subtype", "Image"); + dict->SetNewFor("Width", kWidth); + dict->SetNewFor("Height", 1); + dict->SetNewFor("BitsPerComponent", bits_per_component); + dict->SetNewFor("ColorSpace", "DeviceRGB"); + auto stream = + pdfium::MakeRetain(std::move(data), std::move(dict)); + auto dib = pdfium::MakeRetain(doc, std::move(stream)); + if (!dib->Load()) { + return nullptr; + } + return dib; +} + +} // namespace + +// Fewer than 8 bits per component: each component is read out of the bit +// stream and scaled to 8 bits. +TEST_F(CPDFDIBTest, DefaultDecode4BitsPerComponent) { + CPDF_TestDocument doc; + // Two pixels of 3 components at 4 bits each: R=0x0 G=0x8 B=0xf, then + // R=0xf G=0x0 B=0x8. + DataVector data = {0x08, 0xff, 0x08}; + RetainPtr dib = + LoadDeviceRgbImage(&doc, /*bits_per_component=*/4, std::move(data)); + ASSERT_TRUE(dib); + // Each 4-bit sample scaled by 255 / 15, stored blue, green, red. + EXPECT_THAT(dib->GetScanline(0), + testing::ElementsAre(0xff, 0x88, 0x00, 0x88, 0x00, 0xff)); +} + +// 8 bits per component is a plain RGB-to-BGR swap. +TEST_F(CPDFDIBTest, DefaultDecode8BitsPerComponent) { + CPDF_TestDocument doc; + DataVector data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; + RetainPtr dib = + LoadDeviceRgbImage(&doc, /*bits_per_component=*/8, std::move(data)); + ASSERT_TRUE(dib); + EXPECT_THAT(dib->GetScanline(0), + testing::ElementsAre(0x33, 0x22, 0x11, 0x66, 0x55, 0x44)); +} + +// 16 bits per component keeps the high byte of each big-endian sample. +TEST_F(CPDFDIBTest, DefaultDecode16BitsPerComponent) { + CPDF_TestDocument doc; + // Two pixels, samples big-endian: R=0x1122 G=0x3344 B=0x5566, then + // R=0x7788 G=0x99aa B=0xbbcc. + DataVector data = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc}; + RetainPtr dib = + LoadDeviceRgbImage(&doc, /*bits_per_component=*/16, std::move(data)); + ASSERT_TRUE(dib); + EXPECT_THAT(dib->GetScanline(0), + testing::ElementsAre(0x55, 0x33, 0x11, 0xbb, 0x99, 0x77)); +} diff --git a/core/fpdfapi/page/cpdf_image.cpp b/core/fpdfapi/page/cpdf_image.cpp index 45eba80ed3..4c120f9e16 100644 --- a/core/fpdfapi/page/cpdf_image.cpp +++ b/core/fpdfapi/page/cpdf_image.cpp @@ -30,6 +30,7 @@ #include "core/fxcodec/jpeg/jpegmodule.h" #include "core/fxcodec/png/pngmodule.h" #include "core/fxcrt/check.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" @@ -361,7 +362,7 @@ void CPDF_Image::SetImage(const RetainPtr& pBitmap) { } else if (bpp == 8) { size_t palette_size = pBitmap->GetRequiredPaletteSize(); if (palette_size > 0) { - DCHECK(palette_size <= 256); + DCHECK_LE(palette_size, 256); auto pCS = document_->NewIndirect(); pCS->AppendNew("Indexed"); pCS->AppendNew("DeviceRGB"); diff --git a/core/fpdfapi/page/cpdf_streamcontentparser.cpp b/core/fpdfapi/page/cpdf_streamcontentparser.cpp index 69f28f5818..7576e3d2fc 100644 --- a/core/fpdfapi/page/cpdf_streamcontentparser.cpp +++ b/core/fpdfapi/page/cpdf_streamcontentparser.cpp @@ -42,6 +42,7 @@ #include "core/fxcrt/autonuller.h" #include "core/fxcrt/bytestring.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/containers/contains.h" #include "core/fxcrt/epdf_tls.h" @@ -1027,12 +1028,12 @@ CPDF_ImageObject* CPDF_StreamContentParser::AddImageObject( } std::vector CPDF_StreamContentParser::GetColors() const { - DCHECK(param_count_ > 0); + DCHECK_GT(param_count_, 0); return GetNumbers(param_count_); } std::vector CPDF_StreamContentParser::GetNamedColors() const { - DCHECK(param_count_ > 0); + DCHECK_GT(param_count_, 0); const uint32_t nvalues = param_count_ - 1; std::vector values(nvalues); for (size_t i = 0; i < nvalues; ++i) { @@ -1834,7 +1835,7 @@ uint32_t CPDF_StreamContentParser::Parse( uint32_t start_offset, uint32_t max_cost, const std::vector& stream_start_offsets) { - DCHECK(start_offset < pData.size()); + DCHECK_LT(start_offset, pData.size()); // Parsing will be done from within |pDataStart|. pdfium::span pDataStart = pData.subspan(start_offset); diff --git a/core/fpdfapi/page/cpdf_streamparser.cpp b/core/fpdfapi/page/cpdf_streamparser.cpp index 97e663c6bf..4f8f6ccd4c 100644 --- a/core/fpdfapi/page/cpdf_streamparser.cpp +++ b/core/fpdfapi/page/cpdf_streamparser.cpp @@ -20,13 +20,14 @@ #include "core/fpdfapi/parser/cpdf_number.h" #include "core/fpdfapi/parser/cpdf_stream.h" #include "core/fpdfapi/parser/cpdf_string.h" +#include "core/fpdfapi/parser/cpdf_syntax_parser.h" #include "core/fpdfapi/parser/fpdf_parser_decode.h" #include "core/fpdfapi/parser/fpdf_parser_utility.h" #include "core/fxcodec/data_and_bytes_consumed.h" #include "core/fxcodec/jpeg/jpegmodule.h" #include "core/fxcodec/scanlinedecoder.h" #include "core/fxcrt/autorestorer.h" -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/data_vector.h" #include "core/fxcrt/fx_extension.h" #include "core/fxcrt/fx_safe_types.h" @@ -39,7 +40,6 @@ namespace { -const uint32_t kMaxNestedParsingLevel = 512; const size_t kMaxStringLength = 32767; const char kTrue[] = "true"; @@ -86,13 +86,13 @@ uint32_t DecodeInlineStream(pdfium::span src_span, RetainPtr pParam, uint32_t orig_size) { // |decoder| should not be an abbreviation. - DCHECK(decoder != "A85"); - DCHECK(decoder != "AHx"); - DCHECK(decoder != "CCF"); - DCHECK(decoder != "DCT"); - DCHECK(decoder != "Fl"); - DCHECK(decoder != "LZW"); - DCHECK(decoder != "RL"); + DCHECK_NE(decoder, "A85"); + DCHECK_NE(decoder, "AHx"); + DCHECK_NE(decoder, "CCF"); + DCHECK_NE(decoder, "DCT"); + DCHECK_NE(decoder, "Fl"); + DCHECK_NE(decoder, "LZW"); + DCHECK_NE(decoder, "RL"); if (decoder == "FlateDecode") { return FlateOrLZWDecode(/*use_lzw=*/false, src_span, pParam.Get(), @@ -340,40 +340,26 @@ CPDF_StreamParser::ElementType CPDF_StreamParser::ParseNextElement() { } RetainPtr CPDF_StreamParser::ReadNextObject( - bool bAllowNestedArray, - bool bInArray, - uint32_t dwRecursionLevel) { - bool bIsNumber; + bool allow_nested_array, + bool in_array, + uint32_t recursion_level) { // Must get the next word before returning to avoid infinite loops. - GetNextWord(bIsNumber); - if (!word_size_ || dwRecursionLevel > kMaxNestedParsingLevel) { + const bool is_number = GetNextWord(); + if (!word_size_ || + recursion_level > CPDF_SyntaxParser::kParserMaxRecursionDepth) { return nullptr; } - if (bIsNumber) { + if (is_number) { word_buffer_[word_size_] = 0; return pdfium::MakeRetain(GetWord()); } - int first_char = word_buffer_[0]; - if (first_char == '/') { - ByteString name = PDF_NameDecode(GetWord().Substr(1)); - return pdfium::MakeRetain(pool_, name); - } - - if (first_char == '(') { - return pdfium::MakeRetain(pool_, ReadString()); - } - - if (first_char == '<') { - if (word_size_ == 1) { - return pdfium::MakeRetain(pool_, ReadHexString(), - CPDF_String::DataType::kIsHex); - } - + const uint8_t first_char = word_buffer_[0]; + if (first_char == '<' && word_size_ > 1) { auto dict = pdfium::MakeRetain(pool_); while (true) { - GetNextWord(bIsNumber); + GetNextWord(); if (word_size_ == 2 && word_buffer_[0] == '>') { break; } @@ -383,35 +369,55 @@ RetainPtr CPDF_StreamParser::ReadNextObject( } ByteString key = PDF_NameDecode(GetWord().Substr(1)); - RetainPtr pObj = - ReadNextObject(true, bInArray, dwRecursionLevel + 1); - if (!pObj) { + RetainPtr object = ReadNextObject( + /*allow_nested_array=*/true, in_array, recursion_level + 1); + if (!object) { return nullptr; } - dict->SetFor(key, std::move(pObj)); + dict->SetFor(key, std::move(object)); } return dict; } if (first_char == '[') { - if ((!bAllowNestedArray && bInArray)) { + if (!allow_nested_array && in_array) { return nullptr; } - auto pArray = pdfium::MakeRetain(); + auto array = pdfium::MakeRetain(); while (true) { - RetainPtr pObj = - ReadNextObject(bAllowNestedArray, true, dwRecursionLevel + 1); - if (pObj) { - pArray->Append(std::move(pObj)); + RetainPtr object = ReadNextObject( + allow_nested_array, /*in_array=*/true, recursion_level + 1); + if (object) { + array->Append(std::move(object)); continue; } if (!word_size_ || word_buffer_[0] == ']') { break; } } - return pArray; + return array; + } + + return ReadLeafObject(); +} + +RetainPtr CPDF_StreamParser::ReadLeafObject() { + const uint8_t first_char = word_buffer_[0]; + if (first_char == '/') { + ByteString name = PDF_NameDecode(GetWord().Substr(1)); + return pdfium::MakeRetain(pool_, name); + } + + if (first_char == '(') { + return pdfium::MakeRetain(pool_, ReadString()); + } + + if (first_char == '<') { + CHECK_EQ(word_size_, 1); // Caller handled all other cases. + return pdfium::MakeRetain(pool_, ReadHexString(), + CPDF_String::DataType::kIsHex); } if (GetWord() == kFalse) { @@ -427,18 +433,17 @@ RetainPtr CPDF_StreamParser::ReadNextObject( } // TODO(npm): the following methods are almost identical in cpdf_syntaxparser -void CPDF_StreamParser::GetNextWord(bool& bIsNumber) { +bool CPDF_StreamParser::GetNextWord() { word_size_ = 0; - bIsNumber = true; if (!PositionIsInBounds()) { - return; + return true; } uint8_t ch = buf_[pos_++]; while (true) { while (PDFCharIsWhitespace(ch)) { if (!PositionIsInBounds()) { - return; + return true; } ch = buf_[pos_++]; } @@ -449,7 +454,7 @@ void CPDF_StreamParser::GetNextWord(bool& bIsNumber) { while (true) { if (!PositionIsInBounds()) { - return; + return true; } ch = buf_[pos_++]; if (PDFCharIsLineEnding(ch)) { @@ -459,17 +464,16 @@ void CPDF_StreamParser::GetNextWord(bool& bIsNumber) { } if (PDFCharIsDelimiter(ch)) { - bIsNumber = false; word_buffer_[word_size_++] = ch; if (ch == '/') { while (true) { if (!PositionIsInBounds()) { - return; + return false; } ch = buf_[pos_++]; if (!PDFCharIsOther(ch) && !PDFCharIsNumeric(ch)) { pos_--; - return; + return false; } if (word_size_ < kMaxWordLength) { word_buffer_[word_size_++] = ch; @@ -477,7 +481,7 @@ void CPDF_StreamParser::GetNextWord(bool& bIsNumber) { } } else if (ch == '<') { if (!PositionIsInBounds()) { - return; + return false; } ch = buf_[pos_++]; if (ch == '<') { @@ -487,7 +491,7 @@ void CPDF_StreamParser::GetNextWord(bool& bIsNumber) { } } else if (ch == '>') { if (!PositionIsInBounds()) { - return; + return false; } ch = buf_[pos_++]; if (ch == '>') { @@ -496,18 +500,19 @@ void CPDF_StreamParser::GetNextWord(bool& bIsNumber) { pos_--; } } - return; + return false; } + bool is_number = true; while (true) { if (word_size_ < kMaxWordLength) { word_buffer_[word_size_++] = ch; } if (!PDFCharIsNumeric(ch)) { - bIsNumber = false; + is_number = false; } if (!PositionIsInBounds()) { - return; + return is_number; } ch = buf_[pos_++]; @@ -516,6 +521,7 @@ void CPDF_StreamParser::GetNextWord(bool& bIsNumber) { break; } } + return is_number; } ByteString CPDF_StreamParser::ReadString() { diff --git a/core/fpdfapi/page/cpdf_streamparser.h b/core/fpdfapi/page/cpdf_streamparser.h index 4dd36d62f0..95b399210a 100644 --- a/core/fpdfapi/page/cpdf_streamparser.h +++ b/core/fpdfapi/page/cpdf_streamparser.h @@ -39,9 +39,9 @@ class CPDF_StreamParser { uint32_t GetPos() const { return pos_; } void SetPos(uint32_t pos) { pos_ = pos; } const RetainPtr& GetObject() const { return last_obj_; } - RetainPtr ReadNextObject(bool bAllowNestedArray, - bool bInArray, - uint32_t dwRecursionLevel); + RetainPtr ReadNextObject(bool allow_nested_array, + bool in_array, + uint32_t recursion_level); RetainPtr ReadInlineStream(CPDF_Document* doc, RetainPtr dict, const CPDF_Object* pCSObj); @@ -50,7 +50,11 @@ class CPDF_StreamParser { friend class CPDFStreamParserTest_ReadHexString_Test; static constexpr uint32_t kMaxWordLength = 255; - void GetNextWord(bool& bIsNumber); + // Returns true if the word consists entirely of numeric characters, or if no + // word was read (e.g. at EOF). Returns false otherwise. + bool GetNextWord(); + + RetainPtr ReadLeafObject(); ByteString ReadString(); DataVector ReadHexString(); bool PositionIsInBounds() const; diff --git a/core/fpdfapi/page/cpdf_textobject.cpp b/core/fpdfapi/page/cpdf_textobject.cpp index 35fbfce15f..76ef5495e1 100644 --- a/core/fpdfapi/page/cpdf_textobject.cpp +++ b/core/fpdfapi/page/cpdf_textobject.cpp @@ -11,6 +11,7 @@ #include "core/fpdfapi/font/cpdf_cidfont.h" #include "core/fpdfapi/font/cpdf_font.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_coordinates.h" #include "core/fxcrt/span.h" #include "core/fxcrt/span_util.h" @@ -55,7 +56,7 @@ size_t CPDF_TextObject::CountItems() const { } CPDF_TextObject::Item CPDF_TextObject::GetItemInfo(size_t index) const { - DCHECK(index < char_codes_.size()); + DCHECK_LT(index, char_codes_.size()); Item info; info.char_code_ = char_codes_[index]; @@ -231,7 +232,7 @@ void CPDF_TextObject::SetSegments(pdfium::span strings, ByteStringView segment = strings[i].AsStringView(); size_t offset = 0; while (offset < segment.GetLength()) { - DCHECK(index < char_codes_.size()); + DCHECK_LT(index, char_codes_.size()); char_codes_[index++] = font->GetNextChar(segment, &offset); } if (i != segments_count - 1) { diff --git a/core/fpdfapi/parser/cpdf_cross_ref_table.cpp b/core/fpdfapi/parser/cpdf_cross_ref_table.cpp index 70862b8cb2..2670e5d90d 100644 --- a/core/fpdfapi/parser/cpdf_cross_ref_table.cpp +++ b/core/fpdfapi/parser/cpdf_cross_ref_table.cpp @@ -8,7 +8,6 @@ #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_parser.h" -#include "core/fxcrt/check_op.h" #include "core/fxcrt/containers/contains.h" // static @@ -36,20 +35,22 @@ CPDF_CrossRefTable::CPDF_CrossRefTable(RetainPtr trailer, CPDF_CrossRefTable::~CPDF_CrossRefTable() = default; -void CPDF_CrossRefTable::AddCompressed(uint32_t obj_num, +bool CPDF_CrossRefTable::AddCompressed(uint32_t obj_num, uint32_t archive_obj_num, uint32_t archive_obj_index) { - CHECK_LE(obj_num, CPDF_Parser::kMaxObjectNumber); - CHECK_LE(archive_obj_num, CPDF_Parser::kMaxObjectNumber); + if (obj_num > CPDF_Parser::kMaxObjectNumber || + archive_obj_num > CPDF_Parser::kMaxObjectNumber) { + return false; + } auto& info = objects_info_[obj_num]; if (info.gennum > 0) { - return; + return true; } // Don't add known object streams to object streams. if (info.is_object_stream_flag) { - return; + return true; } info.type = ObjectType::kCompressed; @@ -58,32 +59,39 @@ void CPDF_CrossRefTable::AddCompressed(uint32_t obj_num, info.gennum = 0; objects_info_[archive_obj_num].is_object_stream_flag = true; + return true; } -void CPDF_CrossRefTable::AddNormal(uint32_t obj_num, +bool CPDF_CrossRefTable::AddNormal(uint32_t obj_num, uint16_t gen_num, bool is_object_stream, FX_FILESIZE pos) { - CHECK_LE(obj_num, CPDF_Parser::kMaxObjectNumber); + if (obj_num > CPDF_Parser::kMaxObjectNumber) { + return false; + } auto& info = objects_info_[obj_num]; if (info.gennum > gen_num) { - return; + return true; } info.type = ObjectType::kNormal; info.is_object_stream_flag |= is_object_stream; info.gennum = gen_num; info.pos = pos; + return true; } -void CPDF_CrossRefTable::SetFree(uint32_t obj_num, uint16_t gen_num) { - CHECK_LE(obj_num, CPDF_Parser::kMaxObjectNumber); +bool CPDF_CrossRefTable::SetFree(uint32_t obj_num, uint16_t gen_num) { + if (obj_num > CPDF_Parser::kMaxObjectNumber) { + return false; + } auto& info = objects_info_[obj_num]; info.type = ObjectType::kFree; info.gennum = gen_num; info.pos = 0; + return true; } void CPDF_CrossRefTable::SetTrailer(RetainPtr trailer, diff --git a/core/fpdfapi/parser/cpdf_cross_ref_table.h b/core/fpdfapi/parser/cpdf_cross_ref_table.h index c2d1328e35..9eac7787c7 100644 --- a/core/fpdfapi/parser/cpdf_cross_ref_table.h +++ b/core/fpdfapi/parser/cpdf_cross_ref_table.h @@ -50,14 +50,14 @@ class CPDF_CrossRefTable { uint32_t trailer_object_number); ~CPDF_CrossRefTable(); - void AddCompressed(uint32_t obj_num, + bool AddCompressed(uint32_t obj_num, uint32_t archive_obj_num, uint32_t archive_obj_index); - void AddNormal(uint32_t obj_num, + bool AddNormal(uint32_t obj_num, uint16_t gen_num, bool is_object_stream, FX_FILESIZE pos); - void SetFree(uint32_t obj_num, uint16_t gen_num); + bool SetFree(uint32_t obj_num, uint16_t gen_num); void SetTrailer(RetainPtr trailer, uint32_t trailer_object_number); diff --git a/core/fpdfapi/parser/cpdf_document.cpp b/core/fpdfapi/parser/cpdf_document.cpp index ab93e677cd..f27215cafa 100644 --- a/core/fpdfapi/parser/cpdf_document.cpp +++ b/core/fpdfapi/parser/cpdf_document.cpp @@ -331,7 +331,7 @@ void CPDF_Document::LoadPages() { uint32_t first_page_num = linearized_header->GetFirstPageNo(); uint32_t page_count = linearized_header->GetPageCount(); - DCHECK(first_page_num < page_count); + DCHECK_LT(first_page_num, page_count); // EmbedPDF(layer-seam): a layer keeps its own page list. ResizePageList(page_count); SetPageObjNumAt(first_page_num, objnum); diff --git a/core/fpdfapi/parser/cpdf_number_unittest.cpp b/core/fpdfapi/parser/cpdf_number_unittest.cpp index 8e7c78dbd8..0bf5071c8e 100644 --- a/core/fpdfapi/parser/cpdf_number_unittest.cpp +++ b/core/fpdfapi/parser/cpdf_number_unittest.cpp @@ -65,7 +65,7 @@ TEST(CPDFNumber, WriteToFloat) { // `number` cannot be represented as a float without losing precision. auto number = pdfium::MakeRetain(-77.037232f); ASSERT_TRUE(number->WriteTo(&output_stream, /*encryptor=*/nullptr)); - EXPECT_EQ(" -77.037231", output_stream.str()); + EXPECT_EQ(" -77.03723", output_stream.str()); } { ByteStringArchiveStream output_stream; @@ -79,7 +79,7 @@ TEST(CPDFNumber, WriteToFloat) { auto number = pdfium::MakeRetain(std::numeric_limits::min()); ASSERT_TRUE(number->WriteTo(&output_stream, /*encryptor=*/nullptr)); - EXPECT_EQ(" .0000000000000000000000000000000000000117549435", + EXPECT_EQ(" .000000000000000000000000000000000000011754944", output_stream.str()); } } diff --git a/core/fpdfapi/parser/cpdf_object_unittest.cpp b/core/fpdfapi/parser/cpdf_object_unittest.cpp index 34e2972973..2aed796907 100644 --- a/core/fpdfapi/parser/cpdf_object_unittest.cpp +++ b/core/fpdfapi/parser/cpdf_object_unittest.cpp @@ -209,8 +209,8 @@ class PDFObjectsTest : public testing::Test { TEST_F(PDFObjectsTest, GetString) { static constexpr auto direct_obj_results = std::to_array( - {"false", "true", "1245", "9.0034504", "A simple test", "\t\n", "space", - "", "", "", ""}); + {"false", "true", "1245", "9.00345", "A simple test", "\t\n", "space", "", + "", "", ""}); // Check for direct objects. for (size_t i = 0; i < direct_objs_.size(); ++i) { EXPECT_EQ(direct_obj_results[i], direct_objs_[i]->GetString()); @@ -554,9 +554,8 @@ TEST(PDFArrayTest, GetTypeAt) { for (size_t i = 0; i < vals.size(); ++i) { arr->InsertNewAt(i, vals[i]); } - static constexpr auto expected_str = - std::to_array({"0", "0", "10", "10", ".034499999", - "897.34003", "-2.5", "-1", "-345", "0"}); + static constexpr auto expected_str = std::to_array( + {"0", "0", "10", "10", ".0345", "897.34", "-2.5", "-1", "-345", "0"}); for (size_t i = 0; i < vals.size(); ++i) { TestArrayAccessors(arr.Get(), i, // Array and index. expected_str[i], // String value. @@ -731,8 +730,8 @@ TEST(PDFArrayTest, GetTypeAt) { arr->InsertNewAt(13, &object_holder, stream_val->GetObjNum()); static constexpr auto expected_str = std::to_array( - {"true", "false", "0", "-1234", "2345", ".050000001", "", - "It is a test!", "NAME", "test", "", "", "", ""}); + {"true", "false", "0", "-1234", "2345", ".05", "", "It is a test!", + "NAME", "test", "", "", "", ""}); static constexpr auto expected_int = std::to_array( {1, 0, 0, -1234, 2345, 0, 0, 0, 0, 0, 0, 0, 0, 0}); static constexpr auto expected_float = std::to_array( diff --git a/core/fpdfapi/parser/cpdf_parser.cpp b/core/fpdfapi/parser/cpdf_parser.cpp index 07e4111f3a..2fb455322b 100644 --- a/core/fpdfapi/parser/cpdf_parser.cpp +++ b/core/fpdfapi/parser/cpdf_parser.cpp @@ -697,30 +697,36 @@ bool CPDF_Parser::LoadCrossRefTable(FX_FILESIZE pos, bool skip) { return false; } - MergeCrossRefObjectsData(objects); - return true; + return MergeCrossRefObjectsData(objects); } -void CPDF_Parser::MergeCrossRefObjectsData( +bool CPDF_Parser::MergeCrossRefObjectsData( const std::vector& objects) { for (const auto& obj : objects) { switch (obj.info.type) { case ObjectType::kFree: - if (obj.info.gennum > 0) { - cross_ref_table_->SetFree(obj.obj_num, obj.info.gennum); + if (obj.info.gennum > 0 && + !cross_ref_table_->SetFree(obj.obj_num, obj.info.gennum)) { + return false; } break; case ObjectType::kNormal: - cross_ref_table_->AddNormal(obj.obj_num, obj.info.gennum, - obj.info.is_object_stream_flag, - obj.info.pos); + if (!cross_ref_table_->AddNormal(obj.obj_num, obj.info.gennum, + obj.info.is_object_stream_flag, + obj.info.pos)) { + return false; + } break; case ObjectType::kCompressed: - cross_ref_table_->AddCompressed(obj.obj_num, obj.info.archive.obj_num, - obj.info.archive.obj_index); + if (!cross_ref_table_->AddCompressed(obj.obj_num, + obj.info.archive.obj_num, + obj.info.archive.obj_index)) { + return false; + } break; } } + return true; } bool CPDF_Parser::FindAllCrossReferenceTablesAndStream( @@ -837,18 +843,15 @@ bool CPDF_Parser::RebuildCrossRef() { pStream->GetObjNum())); } - if (obj_num <= kMaxObjectNumber) { - cross_ref_table->AddNormal(obj_num, gen_num, /*is_object_stream=*/false, - obj_pos); + if (cross_ref_table->AddNormal(obj_num, gen_num, + /*is_object_stream=*/false, obj_pos)) { const auto object_stream = CPDF_ObjectStream::Create(std::move(pStream)); if (object_stream) { const auto& object_info = object_stream->object_info(); for (size_t i = 0; i < object_info.size(); ++i) { const auto& info = object_info[i]; - if (info.obj_num <= kMaxObjectNumber) { - cross_ref_table->AddCompressed(info.obj_num, obj_num, i); - } + cross_ref_table->AddCompressed(info.obj_num, obj_num, i); } } } diff --git a/core/fpdfapi/parser/cpdf_parser.h b/core/fpdfapi/parser/cpdf_parser.h index b3675036ad..8ee2e6c440 100644 --- a/core/fpdfapi/parser/cpdf_parser.h +++ b/core/fpdfapi/parser/cpdf_parser.h @@ -258,7 +258,7 @@ class CPDF_Parser { uint32_t count, std::vector* out_objects); bool ParseCrossRefTable(std::vector* out_objects); - void MergeCrossRefObjectsData(const std::vector& objects); + bool MergeCrossRefObjectsData(const std::vector& objects); bool InitSyntaxParser(RetainPtr validator); bool ParseFileVersion(); diff --git a/core/fpdfapi/parser/cpdf_parser_embeddertest.cpp b/core/fpdfapi/parser/cpdf_parser_embeddertest.cpp index 2b333a2f5c..5fd4b8f95d 100644 --- a/core/fpdfapi/parser/cpdf_parser_embeddertest.cpp +++ b/core/fpdfapi/parser/cpdf_parser_embeddertest.cpp @@ -82,7 +82,5 @@ TEST_F(CPDFParserEmbedderTest, InvalidDictionaryKeys) { } TEST_F(CPDFParserEmbedderTest, Bug457855936) { - // TODO(crbug.com/457855936): After this is fixed, change to ASSERT_FALSE as - // it should now fail gracefully. - ASSERT_DEATH(OpenDocument("bug_457855936.pdf"), ""); + EXPECT_FALSE(OpenDocument("bug_457855936.pdf")); } diff --git a/core/fpdfapi/parser/cpdf_security_handler.cpp b/core/fpdfapi/parser/cpdf_security_handler.cpp index a3718b6fda..4bda1586ed 100644 --- a/core/fpdfapi/parser/cpdf_security_handler.cpp +++ b/core/fpdfapi/parser/cpdf_security_handler.cpp @@ -380,7 +380,7 @@ bool CPDF_SecurityHandler::LoadDict(const CPDF_Dictionary* pEncryptDict, bool CPDF_SecurityHandler::AES256_CheckPassword(const ByteString& password, bool bOwner) { DCHECK(encrypt_dict_); - DCHECK(revision_ >= 5); + DCHECK_GE(revision_, 5); ByteString okey = encrypt_dict_->GetByteStringFor("O"); if (okey.GetLength() < 48) { diff --git a/core/fpdfapi/parser/cpdf_syntax_parser.cpp b/core/fpdfapi/parser/cpdf_syntax_parser.cpp index a8fbe314da..fe960164a9 100644 --- a/core/fpdfapi/parser/cpdf_syntax_parser.cpp +++ b/core/fpdfapi/parser/cpdf_syntax_parser.cpp @@ -84,7 +84,7 @@ class ReadableSubStream final : public IFX_SeekableReadStream { // static // EmbedPDF(thread-confined-globals): each thread parses with its own depth. -EPDF_TLS int CPDF_SyntaxParser::s_CurrentRecursionDepth = 0; +EPDF_TLS uint32_t CPDF_SyntaxParser::s_CurrentRecursionDepth = 0; // static std::unique_ptr CPDF_SyntaxParser::CreateForTesting( @@ -107,7 +107,7 @@ CPDF_SyntaxParser::CPDF_SyntaxParser(RetainPtr validator, : file_access_(std::move(validator)), header_offset_(HeaderOffset), file_len_(file_access_->GetSize()) { - DCHECK(header_offset_ <= file_len_); + DCHECK_LE(header_offset_, file_len_); } CPDF_SyntaxParser::~CPDF_SyntaxParser() = default; @@ -552,7 +552,7 @@ RetainPtr CPDF_SyntaxParser::GetObjectBody( RetainPtr CPDF_SyntaxParser::GetObjectBodyInternal( CPDF_IndirectObjectHolder* pObjList, ParseType parse_type) { - AutoRestorer depth_restorer(&s_CurrentRecursionDepth); + AutoRestorer depth_restorer(&s_CurrentRecursionDepth); if (++s_CurrentRecursionDepth > kParserMaxRecursionDepth) { return nullptr; } diff --git a/core/fpdfapi/parser/cpdf_syntax_parser.h b/core/fpdfapi/parser/cpdf_syntax_parser.h index e39ecf7ce5..45f059424e 100644 --- a/core/fpdfapi/parser/cpdf_syntax_parser.h +++ b/core/fpdfapi/parser/cpdf_syntax_parser.h @@ -39,6 +39,8 @@ class CPDF_SyntaxParser { bool is_number; }; + static constexpr uint32_t kParserMaxRecursionDepth = 64; + static std::unique_ptr CreateForTesting( RetainPtr pFileAccess, FX_FILESIZE HeaderOffset); @@ -98,9 +100,8 @@ class CPDF_SyntaxParser { friend class CPDF_DataAvail; friend class cpdf_syntax_parser_ReadHexString_Test; - static constexpr int kParserMaxRecursionDepth = 64; // EmbedPDF(thread-confined-globals): each thread parses with its own depth. - static EPDF_TLS int s_CurrentRecursionDepth; + static EPDF_TLS uint32_t s_CurrentRecursionDepth; bool ReadBlockAt(FX_FILESIZE read_pos); bool GetCharAtBackward(FX_FILESIZE pos, uint8_t* ch); diff --git a/core/fpdfapi/parser/fpdf_parser_utility.cpp b/core/fpdfapi/parser/fpdf_parser_utility.cpp index 20db20666f..5019791510 100644 --- a/core/fpdfapi/parser/fpdf_parser_utility.cpp +++ b/core/fpdfapi/parser/fpdf_parser_utility.cpp @@ -19,6 +19,7 @@ #include "core/fpdfapi/parser/cpdf_string.h" #include "core/fpdfapi/parser/fpdf_parser_decode.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/fx_extension.h" #include "core/fxcrt/fx_stream.h" @@ -150,7 +151,7 @@ ByteString PDF_NameEncode(const ByteString& orig) { std::vector ReadArrayElementsToVector(const CPDF_Array* pArray, size_t nCount) { DCHECK(pArray); - DCHECK(pArray->size() >= nCount); + DCHECK_GE(pArray->size(), nCount); std::vector ret(nCount); for (size_t i = 0; i < nCount; ++i) { ret[i] = pArray->GetFloatAt(i); diff --git a/core/fpdfdoc/cpdf_annot.cpp b/core/fpdfdoc/cpdf_annot.cpp index 677ef974ad..aa2cae3ecc 100644 --- a/core/fpdfdoc/cpdf_annot.cpp +++ b/core/fpdfdoc/cpdf_annot.cpp @@ -29,6 +29,7 @@ #include "core/fpdfapi/render/cpdf_renderoptions.h" #include "core/fpdfdoc/cpdf_appearancegenerator.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxge/cfx_fillrenderoptions.h" #include "core/fxge/cfx_graphstatedata.h" #include "core/fxge/cfx_path.h" @@ -447,7 +448,7 @@ std::optional CPDF_Annot::GetPopupAnnotRect() const { CFX_FloatRect CPDF_Annot::RectFromQuadPointsArray(const CPDF_Array* pArray, size_t nIndex) { DCHECK(pArray); - DCHECK(nIndex < pArray->size() / 8); + DCHECK_LT(nIndex, pArray->size() / 8); // EmbedPDF(text-geometry): an oriented quad's corners can come in any // order, so the bounds take all four points and skip non-finite ones. diff --git a/core/fpdfdoc/cpdf_bookmark.cpp b/core/fpdfdoc/cpdf_bookmark.cpp index cf53dd68f0..ac8d9b1aef 100644 --- a/core/fpdfdoc/cpdf_bookmark.cpp +++ b/core/fpdfdoc/cpdf_bookmark.cpp @@ -68,6 +68,10 @@ std::optional> CPDF_Bookmark::GetColor() const { return {{red, green, blue}}; } +int CPDF_Bookmark::GetStyle() const { + return dict_ ? dict_->GetIntegerFor("F") : 0; +} + int CPDF_Bookmark::GetCount() const { return dict_->GetIntegerFor("Count"); } diff --git a/core/fpdfdoc/cpdf_bookmark.h b/core/fpdfdoc/cpdf_bookmark.h index e4f8c26915..7ac372abb9 100644 --- a/core/fpdfdoc/cpdf_bookmark.h +++ b/core/fpdfdoc/cpdf_bookmark.h @@ -31,6 +31,7 @@ class CPDF_Bookmark { CPDF_Dest GetDest(CPDF_Document* document) const; CPDF_Action GetAction() const; std::optional> GetColor() const; + int GetStyle() const; int GetCount() const; private: diff --git a/core/fpdfdoc/cpvt_section.cpp b/core/fpdfdoc/cpvt_section.cpp index 02ec128060..b03c3e1c53 100644 --- a/core/fpdfdoc/cpvt_section.cpp +++ b/core/fpdfdoc/cpvt_section.cpp @@ -723,17 +723,14 @@ CPVT_FloatRect CPVT_Section::OutputLines(const CPVT_FloatRect& rect) const { line->line_info_.fLineX = fPosX - fMinX; line->line_info_.fLineY = fPosY - fMinY; - if (!bidi_resolver) { + int32_t line_start = line->line_info_.nBeginWordIndex; + if (line_start < 0) { fPosY -= line->line_info_.fLineDescent; continue; } - int32_t line_start = line->line_info_.nBeginWordIndex; // `nEndWordIndex` is inclusive. Thus, `line_start == nEndWordIndex` implies - // a single-word line (length 1), except for the special case where both are - // -1 (representing an empty line), which also computes to length 1 but - // safely yields an empty span due to internal clamping in - // GetWordRangeIteratorPair(). + // a single-word line (length 1). int32_t line_length = line->line_info_.nEndWordIndex - line_start + 1; auto position_word = [&fPosX, fMinX, fPosY, fMinY, this]( @@ -744,8 +741,12 @@ CPVT_FloatRect CPVT_Section::OutputLines(const CPVT_FloatRect& rect) const { fPosX += vt_->GetWordWidth(word); }; + CHECK(bidi_resolver); std::vector visual_runs = bidi_resolver->GetVisualRunsForLine(line_start, line_length); + if (visual_runs.empty()) { + visual_runs.push_back({line_start, line_length, /*is_rtl=*/false}); + } for (const CFX_BidiResolver::ResolvedRun& run : visual_runs) { pdfium::span> run_words = diff --git a/core/fpdfdoc/cpvt_section_unittest.cpp b/core/fpdfdoc/cpvt_section_unittest.cpp index 0b849ac8bd..50856339c5 100644 --- a/core/fpdfdoc/cpvt_section_unittest.cpp +++ b/core/fpdfdoc/cpvt_section_unittest.cpp @@ -13,15 +13,39 @@ #include "core/fpdfdoc/cpvt_stub_provider.h" #include "core/fpdfdoc/cpvt_variabletext.h" #include "core/fxcrt/cfx_bidi_resolver.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_codepage.h" -#include "core/fxcrt/notreached.h" #include "core/fxcrt/retain_ptr.h" #include "testing/gtest/include/gtest/gtest.h" namespace { + constexpr uint16_t kHebrewAlef = 0x05D0; constexpr uint16_t kHebrewBet = 0x05D1; constexpr uint16_t kHebrewGimel = 0x05D2; + +enum class TextContent { kEnglishAndHebrew, kHebrewAndEnglish }; + +struct LayoutTestData { + TextContent content; + CFX_BidiResolver::ParagraphDirection direction; + pdfium::span expected_visual_order; +}; + +// For `TextContent::kEnglishAndHebrew`: `kAuto` uses the Unicode Bidirectional +// Algorithm, which resolves to LTR because the first strong character is +// English. The RTL run [w4, sp4, w5] (indices 6, 7, 8) has its words ordered +// from right to left. +constexpr int kEnglishAndHebrewLtr[] = {0, 1, 2, 3, 4, 5, 8, 7, 6}; +constexpr int kEnglishAndHebrewRtl[] = {8, 7, 6, 5, 0, 1, 2, 3, 4}; + +// For `TextContent::kHebrewAndEnglish`: `kAuto` uses the Unicode Bidirectional +// Algorithm, which resolves to RTL because the first strong character is +// Hebrew. The RTL runs [w0, sp0, w1] and [w4, sp4, w5] have their words ordered +// from right to left. +constexpr int kHebrewAndEnglishRtl[] = {6, 7, 8, 5, 4, 3, 2, 1, 0}; +constexpr int kHebrewAndEnglishLtr[] = {4, 3, 2, 1, 0, 5, 6, 7, 8}; + } // namespace class CPVT_SectionTest : public testing::Test { @@ -65,78 +89,10 @@ class CPVT_SectionTest : public testing::Test { kHebrewGimel, ' ', 'A', ' ', 'B'}); } - void AssertEnglishAndHebrewLayout(const CPVT_Section& section, - CFX_BidiResolver::ParagraphDirection dir) { - std::vector expected_visual_order; - if (dir == CFX_BidiResolver::ParagraphDirection::kAuto || - dir == CFX_BidiResolver::ParagraphDirection::kLeftToRight) { - // `kAuto` uses the Unicode Bidirectional Algorithm, which resolves to LTR - // because the first strong character is English. - // The RTL run [w4, sp4, w5] (indices 6, 7, 8) has its words ordered from - // right to left. - expected_visual_order = {0, 1, 2, 3, 4, 5, 8, 7, 6}; - } else { - expected_visual_order = {8, 7, 6, 5, 0, 1, 2, 3, 4}; - } - - ASSERT_EQ(static_cast(expected_visual_order.size()), - section.GetWordArraySize()); - - for (size_t i = 0; i < expected_visual_order.size() - 1; ++i) { - EXPECT_LT(section.GetWordFromArray(expected_visual_order[i])->fWordX, - section.GetWordFromArray(expected_visual_order[i + 1])->fWordX); - } - } - - void AssertHebrewAndEnglishLayout(const CPVT_Section& section, - CFX_BidiResolver::ParagraphDirection dir) { - std::vector expected_visual_order; - if (dir == CFX_BidiResolver::ParagraphDirection::kLeftToRight) { - expected_visual_order = {4, 3, 2, 1, 0, 5, 6, 7, 8}; - } else if (dir == CFX_BidiResolver::ParagraphDirection::kAuto || - dir == CFX_BidiResolver::ParagraphDirection::kRightToLeft) { - // `kAuto` uses the Unicode Bidirectional Algorithm, which resolves to RTL - // because the first strong character is Hebrew. - expected_visual_order = {6, 7, 8, 5, 4, 3, 2, 1, 0}; - } - - ASSERT_EQ(static_cast(expected_visual_order.size()), - section.GetWordArraySize()); - - for (size_t i = 0; i < expected_visual_order.size() - 1; ++i) { - EXPECT_LT(section.GetWordFromArray(expected_visual_order[i])->fWordX, - section.GetWordFromArray(expected_visual_order[i + 1])->fWordX); - } - } void SetVariableTextDefaults(CPVT_VariableText& vt) { vt.SetFontSize(10.0f); vt.SetPlateRect(CFX_FloatRect(0, 0, 1000, 1000)); } - - enum class TextContent { kEnglishAndHebrew, kHebrewAndEnglish }; - - void TestTextLayout(TextContent content, - CFX_BidiResolver::ParagraphDirection direction) { - CPVT_VariableText vt(provider_.get()); - SetVariableTextDefaults(vt); - vt.SetTextDirection(direction); - vt.Initialize(); - - CPVT_Section section(&vt); - switch (content) { - case TextContent::kEnglishAndHebrew: - PopulateSectionWithEnglishAndHebrew(section); - section.Rearrange(); - AssertEnglishAndHebrewLayout(section, direction); - return; - case TextContent::kHebrewAndEnglish: - PopulateSectionWithHebrewAndEnglish(section); - section.Rearrange(); - AssertHebrewAndEnglishLayout(section, direction); - return; - } - NOTREACHED(); - } void PopulateSectionWithHello(CPVT_Section& section) { section.SetPlace(CPVT_WordPlace(0, 0, -1)); @@ -164,6 +120,10 @@ class CPVT_SectionTest : public testing::Test { std::unique_ptr provider_; }; +class CPVT_SectionLayoutTest + : public CPVT_SectionTest, + public testing::WithParamInterface {}; + TEST_F(CPVT_SectionTest, ClearLeftWords) { CPVT_VariableText vt(provider_.get()); vt.Initialize(); @@ -235,35 +195,53 @@ TEST_F(CPVT_SectionTest, ClearAllWords) { EXPECT_EQ(0, section.GetWordArraySize()); } -TEST_F(CPVT_SectionTest, OutputLines_EnglishAndHebrew_AutoDirection) { - TestTextLayout(TextContent::kEnglishAndHebrew, - CFX_BidiResolver::ParagraphDirection::kAuto); -} - -TEST_F(CPVT_SectionTest, OutputLines_EnglishAndHebrew_ExplicitLeftToRight) { - TestTextLayout(TextContent::kEnglishAndHebrew, - CFX_BidiResolver::ParagraphDirection::kLeftToRight); -} - -TEST_F(CPVT_SectionTest, OutputLines_EnglishAndHebrew_ExplicitRightToLeft) { - TestTextLayout(TextContent::kEnglishAndHebrew, - CFX_BidiResolver::ParagraphDirection::kRightToLeft); -} +TEST_P(CPVT_SectionLayoutTest, OutputLines) { + const LayoutTestData& data = GetParam(); + CPVT_VariableText vt(provider_.get()); + SetVariableTextDefaults(vt); + vt.SetTextDirection(data.direction); + vt.Initialize(); -TEST_F(CPVT_SectionTest, OutputLines_HebrewAndEnglish_AutoDirection) { - TestTextLayout(TextContent::kHebrewAndEnglish, - CFX_BidiResolver::ParagraphDirection::kAuto); -} + CPVT_Section section(&vt); + if (data.content == TextContent::kEnglishAndHebrew) { + PopulateSectionWithEnglishAndHebrew(section); + } else { + CHECK_EQ(data.content, TextContent::kHebrewAndEnglish); + PopulateSectionWithHebrewAndEnglish(section); + } + section.Rearrange(); -TEST_F(CPVT_SectionTest, OutputLines_HebrewAndEnglish_ExplicitLeftToRight) { - TestTextLayout(TextContent::kHebrewAndEnglish, - CFX_BidiResolver::ParagraphDirection::kLeftToRight); + ASSERT_EQ(static_cast(data.expected_visual_order.size()), + section.GetWordArraySize()); + for (size_t i = 0; i < data.expected_visual_order.size() - 1; ++i) { + EXPECT_LT( + section.GetWordFromArray(data.expected_visual_order[i])->fWordX, + section.GetWordFromArray(data.expected_visual_order[i + 1])->fWordX); + } } -TEST_F(CPVT_SectionTest, OutputLines_HebrewAndEnglish_ExplicitRightToLeft) { - TestTextLayout(TextContent::kHebrewAndEnglish, - CFX_BidiResolver::ParagraphDirection::kRightToLeft); -} +INSTANTIATE_TEST_SUITE_P( + CPVT_SectionTest, + CPVT_SectionLayoutTest, + testing::Values( + LayoutTestData{TextContent::kEnglishAndHebrew, + CFX_BidiResolver::ParagraphDirection::kAuto, + kEnglishAndHebrewLtr}, + LayoutTestData{TextContent::kEnglishAndHebrew, + CFX_BidiResolver::ParagraphDirection::kLeftToRight, + kEnglishAndHebrewLtr}, + LayoutTestData{TextContent::kEnglishAndHebrew, + CFX_BidiResolver::ParagraphDirection::kRightToLeft, + kEnglishAndHebrewRtl}, + LayoutTestData{TextContent::kHebrewAndEnglish, + CFX_BidiResolver::ParagraphDirection::kAuto, + kHebrewAndEnglishRtl}, + LayoutTestData{TextContent::kHebrewAndEnglish, + CFX_BidiResolver::ParagraphDirection::kLeftToRight, + kHebrewAndEnglishLtr}, + LayoutTestData{TextContent::kHebrewAndEnglish, + CFX_BidiResolver::ParagraphDirection::kRightToLeft, + kHebrewAndEnglishRtl})); TEST_F(CPVT_SectionTest, OutputLines_Multiline_EnglishAndHebrew) { CPVT_VariableText vt(provider_.get()); @@ -300,3 +278,48 @@ TEST_F(CPVT_SectionTest, OutputLines_Multiline_EnglishAndHebrew) { section.GetWordFromArray(i + 1)->fWordX); } } + +TEST_F(CPVT_SectionTest, OutputLines_EmptySection) { + CPVT_VariableText vt(provider_.get()); + SetVariableTextDefaults(vt); + vt.Initialize(); + + CPVT_Section section(&vt); + EXPECT_EQ(0, section.GetWordArraySize()); + EXPECT_EQ(0, section.GetLineArraySize()); + + CPVT_FloatRect rect = section.Rearrange(); + + // OutputLines() computes non-zero line height based on font metrics. + EXPECT_GT(rect.Height(), 0.0f); + + // After calling Rearrange(), there is now an empty line, even though there + // are no words. + EXPECT_EQ(0, section.GetWordArraySize()); + EXPECT_EQ(1, section.GetLineArraySize()); + + const CPVT_Section::Line* line = section.GetLineFromArray(0); + ASSERT_TRUE(line); + EXPECT_EQ(-1, line->line_info_.nBeginWordIndex); + EXPECT_EQ(-1, line->line_info_.nEndWordIndex); + EXPECT_FLOAT_EQ(0.0f, line->line_info_.fLineWidth); +} + +TEST_F(CPVT_SectionTest, OutputLines_ParagraphSeparator) { + CPVT_VariableText vt(provider_.get()); + SetVariableTextDefaults(vt); + vt.Initialize(); + + CPVT_Section section(&vt); + // 0x001D (Group Separator) has Bidi_Class=Paragraph_Separator in Unicode. + // When processed by ICU within a section line, ubidi_countRuns() resolves to + // zero visual runs. + PopulateSectionWithText(section, {0x001D, 0x1D1D}); + section.Rearrange(); + + ASSERT_EQ(2, section.GetWordArraySize()); + // Layout should safely fall back to sequential LTR ordering (monotonically + // increasing X coordinates) rather than failing BiDi run extraction. + EXPECT_LT(section.GetWordFromArray(0)->fWordX, + section.GetWordFromArray(1)->fWordX); +} diff --git a/core/fpdfdoc/cpvt_variabletext.cpp b/core/fpdfdoc/cpvt_variabletext.cpp index 35a7ce9d7a..7df6ccf312 100644 --- a/core/fpdfdoc/cpvt_variabletext.cpp +++ b/core/fpdfdoc/cpvt_variabletext.cpp @@ -98,12 +98,16 @@ float CPVT_VariableText::Iterator::GetLineCaretX(const CPVT_Line& line) { SetAt(old_place); }; SetAt(line.lineplace); - NextWord(); - CPVT_Word first_word; - bool is_rtl = GetWord(first_word) && - GetWordPlace().nLineIndex == line.lineplace.nLineIndex && - first_word.is_rtl(); - return is_rtl ? line.ptLine.x + line.fLineWidth : line.ptLine.x; + + if (NextWord()) { + CPVT_Word first_word; + if (GetWord(first_word) && GetWordPlace().LineCmp(line.lineplace) == 0 && + first_word.is_rtl()) { + return line.ptLine.x + line.fLineWidth; + } + } + + return line.ptLine.x; } bool CPVT_VariableText::Iterator::NextWord() { diff --git a/core/fpdftext/cpdf_textpage.cpp b/core/fpdftext/cpdf_textpage.cpp index 177b62a84d..dbb586ed99 100644 --- a/core/fpdftext/cpdf_textpage.cpp +++ b/core/fpdftext/cpdf_textpage.cpp @@ -46,8 +46,8 @@ constexpr std::array, 3> kUnicodeDataNormalizationMap4}}; float NormalizeThreshold(float threshold, int t1, int t2, int t3) { - DCHECK(t1 < t2); - DCHECK(t2 < t3); + DCHECK_LT(t1, t2); + DCHECK_LT(t2, t3); if (threshold < t1) { return threshold / 2.0f; } @@ -541,7 +541,7 @@ std::vector CPDF_TextPage::GetRectArray(int start, if (count < 0 || start + count > number_of_chars) { count = number_of_chars - start; } - DCHECK(count > 0); + DCHECK_GT(count, 0); const CPDF_TextObject* text_object = nullptr; CFX_FloatRect rect; diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index f679ff6534..f56e94eccc 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -5,6 +5,46 @@ import("../../pdfium.gni") import("../../testing/test.gni") +if (pdf_enable_rust_bmp || pdf_enable_rust_png) { + import("//build/rust/rust_static_library.gni") +} + +if (pdf_enable_rust_bmp || pdf_enable_rust_png) { + config("pdfium_rust_ffi_config") { + include_dirs = [ "$target_gen_dir/../.." ] + } +} + +if (pdf_enable_rust_bmp) { + rust_static_library("rust_bmp_ffi") { + crate_name = "rust_bmp_ffi" + allow_unsafe = true + crate_root = "bmp/rust_bmp_ffi.rs" + sources = [ crate_root ] + cxx_bindings = [ crate_root ] + public_configs = [ ":pdfium_rust_ffi_config" ] + deps = [ + "//build/rust:cxx_cppdeps", + "//third_party/rust/image/v0_25:lib", + ] + } +} + +if (pdf_enable_rust_png) { + rust_static_library("pdfium_rust_png_ffi") { + crate_name = "pdfium_rust_png_ffi" + allow_unsafe = true + crate_root = "png/rust_png_ffi.rs" + sources = [ crate_root ] + cxx_bindings = [ crate_root ] + public_configs = [ ":pdfium_rust_ffi_config" ] + deps = [ + "//build/rust:cxx_cppdeps", + "//third_party/rust/png/v0_18:lib", + ] + } +} + source_set("fxcodec") { sources = [ "basic/basicmodule.cpp", @@ -58,8 +98,6 @@ source_set("fxcodec") { "jbig2/jbig2_symbol_dict.h", "jbig2/jbig2_trd_proc.cpp", "jbig2/jbig2_trd_proc.h", - "jpeg/jpeg_common.c", - "jpeg/jpeg_common.h", "jpeg/jpeg_writer.cpp", "jpeg/jpeg_writer.h", "jpeg/jpegmodule.cpp", @@ -84,7 +122,6 @@ source_set("fxcodec") { "../../third_party:png", "../../third_party:zlib", "../fxge", - "//third_party:jpeg", ] defines = [] if (pdf_enable_brotli) { @@ -98,68 +135,67 @@ source_set("fxcodec") { sources += [ "cfx_codec_memory.cpp", "cfx_codec_memory.h", - "jpeg/libjpeg_jpeg_context.cpp", - "jpeg/libjpeg_jpeg_context.h", + "gif/cfx_gif.cpp", + "gif/cfx_gif.h", + "gif/cfx_gifcontext.cpp", + "gif/cfx_gifcontext.h", + "gif/lzw_decompressor.cpp", + "gif/lzw_decompressor.h", "progressive_decoder.cpp", "progressive_decoder.h", "progressive_decoder_context.cpp", "progressive_decoder_context.h", + "progressive_decoder_context_delegate.h", + "tiff/libtiff_tiff_context.cpp", + "tiff/libtiff_tiff_context.h", ] - if (pdf_enable_xfa_bmp) { - sources += [ "bmp/bmp_decoder_delegate.h" ] - if (pdf_enable_rust_bmp) { - sources += [ - "bmp/skia_bmp_context.cpp", - "bmp/skia_bmp_context.h", - ] - deps += [ "//skia" ] - } else { - sources += [ - "bmp/cfx_bmpcontext.cpp", - "bmp/cfx_bmpcontext.h", - "bmp/cfx_bmpdecompressor.cpp", - "bmp/cfx_bmpdecompressor.h", - "bmp/fx_bmp.h", - ] - } + deps += [ "../../third_party:tiff" ] + if (pdf_enable_rust_bmp) { + sources += [ + "bmp/rust_bmp_context.cpp", + "bmp/rust_bmp_context.h", + ] + deps += [ + ":rust_bmp_ffi", + "//build/rust:cxx_cppdeps", + ] + } else { + sources += [ + "bmp/cfx_bmpcontext.cpp", + "bmp/cfx_bmpcontext.h", + "bmp/cfx_bmpdecompressor.cpp", + "bmp/cfx_bmpdecompressor.h", + "bmp/fx_bmp.h", + ] } - if (pdf_enable_xfa_gif) { + if (pdf_enable_rust_jpeg) { sources += [ - "gif/cfx_gif.cpp", - "gif/cfx_gif.h", - "gif/cfx_gifcontext.cpp", - "gif/cfx_gifcontext.h", - "gif/lzw_decompressor.cpp", - "gif/lzw_decompressor.h", + "jpeg/skia_jpeg_context.cpp", + "jpeg/skia_jpeg_context.h", + ] + deps += [ "//skia" ] + } else { + sources += [ + "jpeg/libjpeg_jpeg_context.cpp", + "jpeg/libjpeg_jpeg_context.h", ] } - if (pdf_enable_xfa_png) { - sources += [ "png/png_decoder_delegate.h" ] - # TODO(https://crbug.com/444045690): Remove `pdf_enable_rust_png` from the - # condition below once this build mode has been tested and stabilized. - # (Chromium already sets `pdf_use_skia_override = true` so having an extra - # condition avoids affecting the Chromium behavior.) - if (pdf_use_skia && pdf_enable_rust_png) { - sources += [ - "png/skia_png_context.cpp", - "png/skia_png_context.h", - ] - deps += [ "//skia" ] - } else { - sources += [ - "png/libpng_png_context.cpp", - "png/libpng_png_context.h", - ] - deps += [ "../../third_party:png" ] - } - } - if (pdf_enable_xfa_tiff) { + if (pdf_enable_rust_png) { + sources += [ + "png/rust_png_context.cpp", + "png/rust_png_context.h", + ] + deps += [ + ":pdfium_rust_png_ffi", + "//build/rust:cxx_cppdeps", + ] + } else { sources += [ - "tiff/libtiff_tiff_context.cpp", - "tiff/libtiff_tiff_context.h", + "png/libpng_png_context.cpp", + "png/libpng_png_context.h", ] - deps += [ "../../third_party:tiff" ] + deps += [ "../../third_party:png" ] } if (pdf_use_skia) { sources += [ @@ -168,6 +204,25 @@ 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", + ] + deps += [ "//skia" ] + } visibility = [ "../../*" ] } @@ -178,6 +233,7 @@ pdfium_unittest_source_set("unittests") { "flate/flatemodule_unittest.cpp", "jbig2/jbig2_bit_stream_unittest.cpp", "jbig2/jbig2_image_unittest.cpp", + "jpeg/jpegmodule_unittest.cpp", "jpx/jpx_unittest.cpp", ] deps = [ @@ -188,14 +244,12 @@ pdfium_unittest_source_set("unittests") { pdfium_root_dir = "../../" if (pdf_enable_xfa) { - sources += [ "progressive_decoder_unittest.cpp" ] + sources += [ + "gif/cfx_gifcontext_unittest.cpp", + "gif/lzw_decompressor_unittest.cpp", + "progressive_decoder_unittest.cpp", + ] deps += [ "../fxge" ] - if (pdf_enable_xfa_gif) { - sources += [ - "gif/cfx_gifcontext_unittest.cpp", - "gif/lzw_decompressor_unittest.cpp", - ] - } 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 2033e4f89b..0f04d459be 100644 --- a/core/fxcodec/basic/basicmodule.cpp +++ b/core/fxcodec/basic/basicmodule.cpp @@ -11,7 +11,7 @@ #include "core/fxcodec/scanlinedecoder.h" #include "core/fxcrt/byteorder.h" -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/data_vector.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/numerics/safe_conversions.h" @@ -81,11 +81,9 @@ bool RLScanlineDecoder::CheckDestSize() { break; } } - if (((uint32_t)orig_width_ * comps_ * bpc_ * orig_height_ + 7) / 8 > - dest_size) { - return false; - } - return true; + FX_SAFE_UINT32 bytes = line_bytes_; + bytes *= orig_height_; + return bytes.IsValid() && bytes.ValueOrDie() <= dest_size; } bool RLScanlineDecoder::Create(pdfium::span src_buf, @@ -148,7 +146,7 @@ pdfium::span RLScanlineDecoder::GetNextLine() { fxcrt::Copy(src_buf_.subspan(src_offset_, copy_len), scan_span.subspan(col_pos)); col_pos += copy_len; - UpdateOperator((uint8_t)copy_len); + UpdateOperator(pdfium::checked_cast(copy_len)); } else if (operator_ > 128) { int fill = 0; if (src_offset_ < src_buf_.size()) { @@ -161,7 +159,7 @@ pdfium::span RLScanlineDecoder::GetNextLine() { } std::ranges::fill(scan_span.subspan(col_pos, duplicate_len), fill); col_pos += duplicate_len; - UpdateOperator((uint8_t)duplicate_len); + UpdateOperator(pdfium::checked_cast(duplicate_len)); } else { eod_ = true; break; @@ -187,7 +185,7 @@ void RLScanlineDecoder::UpdateOperator(uint8_t used_bytes) { return; } if (operator_ < 128) { - DCHECK((uint32_t)operator_ + 1 >= used_bytes); + DCHECK_GE(operator_ + 1, used_bytes); if (used_bytes == operator_ + 1) { src_offset_ += used_bytes; GetNextOperator(); @@ -201,7 +199,7 @@ void RLScanlineDecoder::UpdateOperator(uint8_t used_bytes) { return; } uint8_t count = 257 - operator_; - DCHECK((uint32_t)count >= used_bytes); + DCHECK_GE(count, used_bytes); if (used_bytes == count) { src_offset_++; GetNextOperator(); @@ -361,7 +359,7 @@ DataVector BasicModule::A85Encode( uint32_t val = 0; int count = 0; while (pos < src_span.size()) { - val += (uint32_t)(src_span[pos]) << (8 * (3 - count)); + val += static_cast(src_span[pos]) << (8 * (3 - count)); count++; pos++; } diff --git a/core/fxcodec/bmp/DEPS b/core/fxcodec/bmp/DEPS index 71e6e63d92..b4d51b75ae 100644 --- a/core/fxcodec/bmp/DEPS +++ b/core/fxcodec/bmp/DEPS @@ -1,6 +1,5 @@ specific_include_rules = { "skia_bmp_context\.cpp": [ - '+third_party/skia/experimental/rust_bmp', '+third_party/skia/include', ], } diff --git a/core/fxcodec/bmp/bmp_decoder_delegate.h b/core/fxcodec/bmp/bmp_decoder_delegate.h deleted file mode 100644 index bdb9276d9c..0000000000 --- a/core/fxcodec/bmp/bmp_decoder_delegate.h +++ /dev/null @@ -1,27 +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_BMP_BMP_DECODER_DELEGATE_H_ -#define CORE_FXCODEC_BMP_BMP_DECODER_DELEGATE_H_ - -#include - -#include "core/fxcrt/span.h" - -namespace fxcodec { - -class BmpDecoderDelegate { - public: - virtual ~BmpDecoderDelegate() = default; - - virtual bool BmpInputImagePositionBuf(uint32_t rcd_pos) = 0; - virtual void BmpReadScanline(uint32_t row_num, - pdfium::span row_buf) = 0; -}; - -} // namespace fxcodec - -using BmpDecoderDelegate = fxcodec::BmpDecoderDelegate; - -#endif // CORE_FXCODEC_BMP_BMP_DECODER_DELEGATE_H_ diff --git a/core/fxcodec/bmp/cfx_bmpcontext.cpp b/core/fxcodec/bmp/cfx_bmpcontext.cpp index dbe71aef5c..51022a7cb2 100644 --- a/core/fxcodec/bmp/cfx_bmpcontext.cpp +++ b/core/fxcodec/bmp/cfx_bmpcontext.cpp @@ -8,7 +8,6 @@ #include -#include "core/fxcodec/bmp/bmp_decoder_delegate.h" #include "core/fxcodec/cfx_codec_memory.h" #include "core/fxcodec/fx_codec.h" #include "core/fxcodec/fx_codec_def.h" @@ -16,7 +15,7 @@ namespace fxcodec { -CFX_BmpContext::CFX_BmpContext(BmpDecoderDelegate* pDelegate) +CFX_BmpContext::CFX_BmpContext(ProgressiveDecoderContextDelegate* pDelegate) : bmp_(this), delegate_(pDelegate) {} CFX_BmpContext::~CFX_BmpContext() = default; @@ -29,15 +28,13 @@ void CFX_BmpContext::Input(RetainPtr codec_memory) { bmp_.SetInputBuffer(std::move(codec_memory)); } -ProgressiveDecoderContext::Status CFX_BmpContext::DecodeImage( - size_t frame_index) { +ProgressiveDecoderContext::Status CFX_BmpContext::DecodeImage() { return bmp_.DecodeImage(); } ProgressiveDecoderContext::Status CFX_BmpContext::ReadHeader( int32_t* width, int32_t* height, - bool* tb_flag, int32_t* components, pdfium::span* palette, CFX_DIBAttribute* attribute) { @@ -50,7 +47,6 @@ ProgressiveDecoderContext::Status CFX_BmpContext::ReadHeader( *width = bmp_.width(); *height = bmp_.height(); - *tb_flag = bmp_.img_tb_flag(); *components = bmp_.components(); *palette = bmp_.palette(); attribute->dpi_unit_ = CFX_DIBAttribute::kResUnitMeter; diff --git a/core/fxcodec/bmp/cfx_bmpcontext.h b/core/fxcodec/bmp/cfx_bmpcontext.h index 856a375dc8..3e3b350e84 100644 --- a/core/fxcodec/bmp/cfx_bmpcontext.h +++ b/core/fxcodec/bmp/cfx_bmpcontext.h @@ -16,28 +16,27 @@ namespace fxcodec { -class BmpDecoderDelegate; class CFX_DIBAttribute; +class ProgressiveDecoderContextDelegate; class CFX_BmpContext final : public ProgressiveDecoderContext { public: - explicit CFX_BmpContext(BmpDecoderDelegate* pDelegate); + explicit CFX_BmpContext(ProgressiveDecoderContextDelegate* pDelegate); ~CFX_BmpContext() override; // ProgressiveDecoderContext: FX_FILESIZE GetAvailInput() const override; void Input(RetainPtr codec_memory) override; - Status DecodeImage(size_t frame_index) override; + Status DecodeImage() override; Status ReadHeader(int32_t* width, int32_t* height, - bool* tb_flag, int32_t* components, pdfium::span* palette, CFX_DIBAttribute* attribute); CFX_BmpDecompressor bmp_; - UnownedPtr const delegate_; + UnownedPtr const delegate_; }; } // namespace fxcodec diff --git a/core/fxcodec/bmp/cfx_bmpdecompressor.cpp b/core/fxcodec/bmp/cfx_bmpdecompressor.cpp index 8bcbc2e1e5..086920ec82 100644 --- a/core/fxcodec/bmp/cfx_bmpdecompressor.cpp +++ b/core/fxcodec/bmp/cfx_bmpdecompressor.cpp @@ -12,9 +12,10 @@ #include #include -#include "core/fxcodec/bmp/bmp_decoder_delegate.h" #include "core/fxcodec/bmp/cfx_bmpcontext.h" #include "core/fxcodec/cfx_codec_memory.h" +#include "core/fxcodec/progressive_decoder_context.h" +#include "core/fxcodec/progressive_decoder_context_delegate.h" #include "core/fxcrt/byteorder.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/data_vector.h" @@ -63,12 +64,13 @@ CFX_BmpDecompressor::~CFX_BmpDecompressor() = default; void CFX_BmpDecompressor::ReadNextScanline() { uint32_t row = img_tb_flag_ ? row_num_ : (height_ - 1 - row_num_); - context_->delegate_->BmpReadScanline(row, out_row_buffer_); + context_->delegate_->ResampleScanline(row, out_row_buffer_); ++row_num_; } bool CFX_BmpDecompressor::GetDataPosition(uint32_t rcd_pos) { - return context_->delegate_->BmpInputImagePositionBuf(rcd_pos); + FXCODEC_STATUS dummy_status = FXCODEC_STATUS::kError; + return context_->delegate_->ReadMoreData(rcd_pos, &dummy_status); } ProgressiveDecoderContext::Status CFX_BmpDecompressor::ReadHeader() { diff --git a/core/fxcodec/bmp/cfx_bmpdecompressor.h b/core/fxcodec/bmp/cfx_bmpdecompressor.h index 075f5f45ff..5a379f33bd 100644 --- a/core/fxcodec/bmp/cfx_bmpdecompressor.h +++ b/core/fxcodec/bmp/cfx_bmpdecompressor.h @@ -39,7 +39,6 @@ class CFX_BmpDecompressor { uint32_t width() const { return width_; } uint32_t height() const { return height_; } int32_t components() const { return components_; } - bool img_tb_flag() const { return img_tb_flag_; } int32_t dpi_x() const { return dpi_x_; } int32_t dpi_y() const { return dpi_y_; } diff --git a/core/fxcodec/bmp/rust_bmp_context.cpp b/core/fxcodec/bmp/rust_bmp_context.cpp new file mode 100644 index 0000000000..677a8625e9 --- /dev/null +++ b/core/fxcodec/bmp/rust_bmp_context.cpp @@ -0,0 +1,198 @@ +// 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/bmp/rust_bmp_context.h" + +#include +#include + +#include "core/fxcodec/bmp/rust_bmp_ffi.rs.h" +#include "core/fxcodec/cfx_codec_memory.h" +#include "core/fxcodec/fx_codec.h" +#include "core/fxcodec/progressive_decoder_context_delegate.h" +#include "core/fxcrt/byteorder.h" +#include "core/fxcrt/check.h" +#include "core/fxcrt/fx_safe_types.h" +#include "core/fxcrt/span.h" +#include "core/fxge/dib/fx_dib.h" + +namespace fxcodec { + +namespace { + +constexpr size_t kMinHeaderBytes = 18; +constexpr size_t kHeaderSizeOffset = 14; +constexpr size_t kMaxUncompressedBytes = 1024 * 1024 * 1024; // 1 GiB + +// Malformed BMP files may specify a data offset that is before the end of the +// header. Clamp the offset to ensure it points past the header. +void ClampBmpHeaderOffset(pdfium::span header, size_t total_size) { + if (header.size() >= kMinHeaderBytes && header[0] == 'B' && + header[1] == 'M') { + uint32_t bi_size = + fxcrt::GetUInt32LSBFirst(header.subspan()); + FX_SAFE_UINT32 safe_min_off = bi_size; + safe_min_off += kHeaderSizeOffset; + uint32_t off_bits = fxcrt::GetUInt32LSBFirst(header.subspan<10, 4>()); + if (safe_min_off.IsValid()) { + uint32_t min_off = safe_min_off.ValueOrDie(); + if (off_bits < min_off && min_off <= total_size) { + fxcrt::PutUInt32LSBFirst(min_off, header.subspan<10, 4>()); + } + } + } +} + +} // namespace + +RustBmpContext::RustBmpContext(ProgressiveDecoderContextDelegate* delegate) + : delegate_(delegate) {} + +RustBmpContext::~RustBmpContext() = default; + +void RustBmpContext::SetCodecMemory(RetainPtr codec_memory) { + codec_memory_ = std::move(codec_memory); +} + +FX_FILESIZE RustBmpContext::GetAvailInput() const { + if (!codec_memory_) { + return 0; + } + return codec_memory_->GetSize(); +} + +void RustBmpContext::Input(RetainPtr codec_memory) { + SetCodecMemory(std::move(codec_memory)); +} + +ProgressiveDecoderContext::Status RustBmpContext::DecodeImage() { + return ContinueDecode(); +} + +ProgressiveDecoderContext::Status RustBmpContext::ReadHeader( + int32_t* width, + int32_t* height, + int32_t* components, + pdfium::span* palette, + CFX_DIBAttribute* attribute) { + if (header_read_) { + *width = static_cast(width_); + *height = static_cast(height_); + *components = components_; + *palette = {}; + if (attribute) { + attribute->dpi_unit_ = CFX_DIBAttribute::kResUnitMeter; + attribute->x_dpi_ = dpi_x_; + attribute->y_dpi_ = dpi_y_; + } + return ProgressiveDecoderContext::Status::kSuccess; + } + + const auto span = codec_memory_->GetBufferSpan(); + if (span.size() < kMinHeaderBytes) { + return ProgressiveDecoderContext::Status::kContinue; + } + + ClampBmpHeaderOffset(codec_memory_->GetBufferSpan(), span.size()); + + rust_bmp::BmpHeaderInfo header_info{}; + rust::Slice src_slice(span); + rust_bmp::DecodeStatus status = + rust_bmp::read_bmp_info(src_slice, header_info); + if (status == rust_bmp::DecodeStatus::Continue) { + return ProgressiveDecoderContext::Status::kContinue; + } + if (status == rust_bmp::DecodeStatus::Error) { + return ProgressiveDecoderContext::Status::kError; + } + + FX_SAFE_SIZE_T safe_uncompressed = header_info.width; + safe_uncompressed *= header_info.height; + safe_uncompressed *= header_info.components; + if (!safe_uncompressed.IsValid() || + safe_uncompressed.ValueOrDie() > kMaxUncompressedBytes) { + return ProgressiveDecoderContext::Status::kError; + } + + width_ = header_info.width; + height_ = header_info.height; + components_ = header_info.components; + + if (span.size() >= 46) { + uint32_t bi_size = + fxcrt::GetUInt32LSBFirst(span.subspan()); + if (bi_size >= 40) { + dpi_x_ = + static_cast(fxcrt::GetUInt32LSBFirst(span.subspan<38, 4>())); + dpi_y_ = + static_cast(fxcrt::GetUInt32LSBFirst(span.subspan<42, 4>())); + } + } + + *width = static_cast(width_); + *height = static_cast(height_); + *components = components_; + *palette = {}; + if (attribute) { + attribute->dpi_unit_ = CFX_DIBAttribute::kResUnitMeter; + attribute->x_dpi_ = dpi_x_; + attribute->y_dpi_ = dpi_y_; + } + header_read_ = true; + return ProgressiveDecoderContext::Status::kSuccess; +} + +ProgressiveDecoderContext::Status RustBmpContext::StartDecode() { + ProgressiveDecoderContextDelegate::Format format = + (components_ == 4) ? ProgressiveDecoderContextDelegate::Format::kArgb + : ProgressiveDecoderContextDelegate::Format::kRgb; + if (!delegate_->PrepareDirectOutput(width_, height_, format)) { + return ProgressiveDecoderContext::Status::kError; + } + + return ContinueDecode(); +} + +ProgressiveDecoderContext::Status RustBmpContext::ContinueDecode() { + pdfium::span src_span = codec_memory_->GetBufferSpan(); + rust::Slice src_slice(src_span); + + FX_SAFE_SIZE_T safe_row_bytes = width_; + safe_row_bytes *= components_; + safe_row_bytes += 3; + safe_row_bytes /= 4; + safe_row_bytes *= 4; + if (!safe_row_bytes.IsValid()) { + return ProgressiveDecoderContext::Status::kError; + } + size_t row_bytes = safe_row_bytes.ValueOrDie(); + + FX_SAFE_SIZE_T buf_size = row_bytes; + buf_size *= height_; + if (!buf_size.IsValid()) { + return ProgressiveDecoderContext::Status::kError; + } + + std::vector decoded_buf(buf_size.ValueOrDie()); + rust::Slice out_slice(decoded_buf); + rust_bmp::DecodeStatus status = + rust_bmp::decode_bmp_to_buf(src_slice, out_slice, row_bytes); + if (status == rust_bmp::DecodeStatus::Continue) { + return ProgressiveDecoderContext::Status::kContinue; + } + if (status == rust_bmp::DecodeStatus::Error) { + return ProgressiveDecoderContext::Status::kError; + } + + pdfium::span buf_span(decoded_buf); + for (size_t y = 0; y < height_; ++y) { + pdfium::span row_span = + buf_span.subspan(y * row_bytes, row_bytes); + delegate_->ResampleScanline(static_cast(y), row_span); + } + + return ProgressiveDecoderContext::Status::kSuccess; +} + +} // namespace fxcodec diff --git a/core/fxcodec/bmp/rust_bmp_context.h b/core/fxcodec/bmp/rust_bmp_context.h new file mode 100644 index 0000000000..b89ec88e6a --- /dev/null +++ b/core/fxcodec/bmp/rust_bmp_context.h @@ -0,0 +1,57 @@ +// 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_BMP_RUST_BMP_CONTEXT_H_ +#define CORE_FXCODEC_BMP_RUST_BMP_CONTEXT_H_ + +#include + +#include "core/fxcodec/progressive_decoder_context.h" +#include "core/fxcrt/fx_system.h" +#include "core/fxcrt/retain_ptr.h" +#include "core/fxcrt/span.h" +#include "core/fxcrt/unowned_ptr.h" +#include "core/fxge/dib/fx_dib.h" + +namespace fxcodec { + +class CFX_DIBAttribute; +class ProgressiveDecoderContextDelegate; + +class RustBmpContext final : public ProgressiveDecoderContext { + public: + explicit RustBmpContext(ProgressiveDecoderContextDelegate* delegate); + ~RustBmpContext() override; + + void SetCodecMemory(RetainPtr codec_memory); + + // ProgressiveDecoderContext: + FX_FILESIZE GetAvailInput() const override; + void Input(RetainPtr codec_memory) override; + ProgressiveDecoderContext::Status DecodeImage() override; + + ProgressiveDecoderContext::Status ReadHeader( + int32_t* width, + int32_t* height, + int32_t* components, + pdfium::span* palette, + CFX_DIBAttribute* attribute); + + ProgressiveDecoderContext::Status StartDecode(); + ProgressiveDecoderContext::Status ContinueDecode(); + + private: + bool header_read_ = false; + uint32_t width_ = 0; + uint32_t height_ = 0; + int32_t components_ = 0; + int32_t dpi_x_ = 0; + int32_t dpi_y_ = 0; + UnownedPtr const delegate_; + RetainPtr codec_memory_; +}; + +} // namespace fxcodec + +#endif // CORE_FXCODEC_BMP_RUST_BMP_CONTEXT_H_ diff --git a/core/fxcodec/bmp/rust_bmp_ffi.rs b/core/fxcodec/bmp/rust_bmp_ffi.rs new file mode 100644 index 0000000000..4a14c41852 --- /dev/null +++ b/core/fxcodec/bmp/rust_bmp_ffi.rs @@ -0,0 +1,147 @@ +// 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 image::codecs::bmp::BmpDecoder; +use image::{ImageDecoder, ImageFormat}; +use std::io::Cursor; + +#[cxx::bridge(namespace = "fxcodec::rust_bmp")] +mod ffi { + pub struct BmpHeaderInfo { + pub width: u32, + pub height: u32, + pub components: i32, + } + + #[derive(Debug, PartialEq)] + pub enum DecodeStatus { + Success, + Continue, + Error, + } + + extern "Rust" { + fn read_bmp_info(src: &[u8], info: &mut BmpHeaderInfo) -> DecodeStatus; + fn decode_bmp_to_buf(src: &[u8], out_buf: &mut [u8], out_pitch: usize) -> DecodeStatus; + } +} + +fn get_bmp_bpp(src: &[u8]) -> u16 { + if src.len() < 18 { + return 24; + } + let header_size = u32::from_le_bytes(src[14..18].try_into().unwrap()); + if header_size == 12 { + if src.len() >= 26 { + return u16::from_le_bytes(src[24..26].try_into().unwrap()); + } + } else if header_size >= 16 && src.len() >= 30 { + return u16::from_le_bytes(src[28..30].try_into().unwrap()); + } + 24 +} + +const MAX_UNCOMPRESSED_BYTES: u64 = 1024 * 1024 * 1024; // 1 GiB + +fn is_32bpp_or_has_alpha(src: &[u8], has_alpha: bool) -> bool { + has_alpha || get_bmp_bpp(src) == 32 +} + +fn read_bmp_info(src: &[u8], info: &mut ffi::BmpHeaderInfo) -> ffi::DecodeStatus { + let decoder = match BmpDecoder::new(Cursor::new(src)) { + Ok(d) => d, + Err(image::ImageError::IoError(io_err)) + if io_err.kind() == std::io::ErrorKind::UnexpectedEof => + { + return ffi::DecodeStatus::Continue; + } + Err(_) => return ffi::DecodeStatus::Error, + }; + let (width, height) = decoder.dimensions(); + if width == 0 || height == 0 { + return ffi::DecodeStatus::Error; + } + let components = + if is_32bpp_or_has_alpha(src, decoder.color_type().has_alpha()) { 4 } else { 3 }; + let uncompressed_size = (width as u64) + .checked_mul(height as u64) + .and_then(|pixels| pixels.checked_mul(components as u64)); + if uncompressed_size.is_none_or(|size| size > MAX_UNCOMPRESSED_BYTES) { + return ffi::DecodeStatus::Error; + } + info.width = width; + info.height = height; + info.components = components; + ffi::DecodeStatus::Success +} + +fn decode_bmp_to_buf(src: &[u8], dst: &mut [u8], dst_pitch: usize) -> ffi::DecodeStatus { + let img = match image::load_from_memory_with_format(src, ImageFormat::Bmp) { + Ok(i) => i, + Err(image::ImageError::IoError(io_err)) + if io_err.kind() == std::io::ErrorKind::UnexpectedEof => + { + return ffi::DecodeStatus::Continue; + } + Err(_) => return ffi::DecodeStatus::Error, + }; + let width = img.width() as usize; + let height = img.height() as usize; + if width == 0 || height == 0 { + return ffi::DecodeStatus::Error; + } + + if is_32bpp_or_has_alpha(src, img.color().has_alpha()) { + let rgba = img.to_rgba8(); + let raw_pixels = rgba.as_raw(); + let row_bytes = width * 4; + if raw_pixels.len() < height * row_bytes { + return ffi::DecodeStatus::Error; + } + + for y in 0..height { + let src_row = &raw_pixels[y * row_bytes..(y + 1) * row_bytes]; + let dst_start = y * dst_pitch; + if dst_start + row_bytes > dst.len() { + return ffi::DecodeStatus::Error; + } + let dst_slice = &mut dst[dst_start..dst_start + row_bytes]; + for x in 0..width { + let r = src_row[x * 4]; + let g = src_row[x * 4 + 1]; + let b = src_row[x * 4 + 2]; + let a = src_row[x * 4 + 3]; + dst_slice[x * 4] = b; + dst_slice[x * 4 + 1] = g; + dst_slice[x * 4 + 2] = r; + dst_slice[x * 4 + 3] = a; + } + } + } else { + let rgb = img.to_rgb8(); + let raw_pixels = rgb.as_raw(); + let row_bytes = width * 3; + if raw_pixels.len() < height * row_bytes { + return ffi::DecodeStatus::Error; + } + + for y in 0..height { + let src_row = &raw_pixels[y * row_bytes..(y + 1) * row_bytes]; + let dst_start = y * dst_pitch; + if dst_start + row_bytes > dst.len() { + return ffi::DecodeStatus::Error; + } + let dst_slice = &mut dst[dst_start..dst_start + row_bytes]; + for x in 0..width { + let r = src_row[x * 3]; + let g = src_row[x * 3 + 1]; + let b = src_row[x * 3 + 2]; + dst_slice[x * 3] = b; + dst_slice[x * 3 + 1] = g; + dst_slice[x * 3 + 2] = r; + } + } + } + ffi::DecodeStatus::Success +} diff --git a/core/fxcodec/bmp/skia_bmp_context.cpp b/core/fxcodec/bmp/skia_bmp_context.cpp deleted file mode 100644 index da4d9113a0..0000000000 --- a/core/fxcodec/bmp/skia_bmp_context.cpp +++ /dev/null @@ -1,363 +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/bmp/skia_bmp_context.h" - -#include -#include -#include - -#include "core/fxcodec/bmp/bmp_decoder_delegate.h" -#include "core/fxcodec/cfx_codec_memory.h" -#include "core/fxcodec/codec_memory_sk_stream.h" -#include "core/fxcodec/fx_codec.h" -#include "core/fxcrt/byteorder.h" -#include "core/fxcrt/check.h" -#include "core/fxcrt/check_op.h" -#include "core/fxcrt/fx_safe_types.h" -#include "core/fxge/calculate_pitch.h" -#include "third_party/skia/experimental/rust_bmp/decoder/SkBmpRustDecoder.h" -#include "third_party/skia/include/codec/SkCodec.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 { - -constexpr size_t kMinHeaderBytes = 18; -constexpr size_t kHeaderSizeOffset = 14; - -// Malformed BMP files may specify a data offset that is before the end of the -// header. Clamp the offset to ensure it points past the header. -void ClampBmpHeaderOffset(pdfium::span header, - size_t total_size) { - if (header[0] == 'B' && header[1] == 'M') { - uint32_t bi_size = - fxcrt::GetUInt32LSBFirst(header.subspan()); - FX_SAFE_UINT32 safe_min_off = bi_size; - safe_min_off += kHeaderSizeOffset; - uint32_t off_bits = fxcrt::GetUInt32LSBFirst(header.subspan<10, 4>()); - if (safe_min_off.IsValid()) { - uint32_t min_off = safe_min_off.ValueOrDie(); - if (off_bits < min_off && min_off <= total_size) { - fxcrt::PutUInt32LSBFirst(min_off, header.subspan<10, 4>()); - } - } - } -} - -} // namespace - -SkiaBmpContext::SkiaBmpContext(BmpDecoderDelegate* delegate) - : delegate_(delegate) {} - -SkiaBmpContext::~SkiaBmpContext() = default; - -void SkiaBmpContext::SetCodecMemory(RetainPtr codec_memory) { - codec_memory_ = std::move(codec_memory); -} - -FX_FILESIZE SkiaBmpContext::GetAvailInput() const { - if (!codec_memory_) { - return 0; - } - return codec_memory_->GetSize(); -} - -void SkiaBmpContext::Input(RetainPtr codec_memory) { - SetCodecMemory(std::move(codec_memory)); -} - -ProgressiveDecoderContext::Status SkiaBmpContext::ReadHeader( - int32_t* width, - int32_t* height, - bool* tb_flag, - int32_t* components, - pdfium::span* palette, - CFX_DIBAttribute* pAttribute) { - if (header_read_) { - SkImageInfo info = decoder_->getInfo(); - *width = info.width(); - *height = info.height(); - *tb_flag = top_down_; - *components = components_; - *palette = {}; - return ProgressiveDecoderContext::Status::kSuccess; - } - - const auto span = codec_memory_->GetBufferSpan(); - if (span.size() < kMinHeaderBytes) { - return ProgressiveDecoderContext::Status::kContinue; - } - - // There are enough bytes to construct and initialize a decoder, - // if not already created by a previous call. - if (!decoder_) { - // Clamp malformed BMP data offsets prior to decoding. - ClampBmpHeaderOffset( - codec_memory_->GetBufferSpan().first(), span.size()); - codec_memory_->Seek(0); - auto stream = std::make_unique(codec_memory_); - SkCodec::Result result = SkCodec::kInvalidInput; - decoder_ = SkBmpRustDecoder::Decode(std::move(stream), &result); - if (result == SkCodec::kIncompleteInput) { - codec_memory_->Seek(0); - decoder_.reset(); - return ProgressiveDecoderContext::Status::kContinue; - } - if (result != SkCodec::kSuccess || !decoder_) { - decoder_.reset(); - return ProgressiveDecoderContext::Status::kError; - } - } - - SkImageInfo info = decoder_->getInfo(); - *width = info.width(); - *height = info.height(); - - // Extract metadata from the DIB header. - uint32_t bpp = 24; - uint32_t bi_size = - fxcrt::GetUInt32LSBFirst(span.subspan()); - uint32_t bi_compression = 0; - uint32_t bi_clr_used = 0; - if (bi_size == 12) { - if (span.size() < 26) { - return ProgressiveDecoderContext::Status::kContinue; - } - bpp = fxcrt::GetUInt16LSBFirst(span.subspan<24, 2>()); - } else if (bi_size >= 40) { - if (span.size() < 50) { - return ProgressiveDecoderContext::Status::kContinue; - } - top_down_ = static_cast( - fxcrt::GetUInt32LSBFirst(span.subspan<22, 4>())) < 0; - bpp = fxcrt::GetUInt16LSBFirst(span.subspan<28, 2>()); - bi_compression = fxcrt::GetUInt32LSBFirst(span.subspan<30, 4>()); - bi_clr_used = fxcrt::GetUInt32LSBFirst(span.subspan<46, 4>()); - } else { - return ProgressiveDecoderContext::Status::kError; - } - - if (bpp != 1 && bpp != 4 && bpp != 8 && bpp != 16 && bpp != 24 && bpp != 32) { - return ProgressiveDecoderContext::Status::kError; - } - if (bpp <= 8 && bi_clr_used > (1u << bpp)) { - return ProgressiveDecoderContext::Status::kError; - } - if (bi_compression > 6) { - return ProgressiveDecoderContext::Status::kError; - } - - bpp_ = bpp; - bi_size_ = bi_size; - bi_compression_ = bi_compression; - bi_clr_used_ = bi_clr_used; - - *tb_flag = top_down_; - components_ = (bpp == 32) ? 4 : 3; - *components = components_; - *palette = {}; - if (pAttribute) { - pAttribute->dpi_unit_ = CFX_DIBAttribute::kResUnitMeter; - pAttribute->x_dpi_ = 0; - pAttribute->y_dpi_ = 0; - } - header_read_ = true; - return ProgressiveDecoderContext::Status::kSuccess; -} - -ProgressiveDecoderContext::Status SkiaBmpContext::DecodeImage( - size_t frame_index) { - if (!header_read_ || !decoder_) { - return ProgressiveDecoderContext::Status::kError; - } - if (!ValidatePaletteIndices()) { - return ProgressiveDecoderContext::Status::kError; - } - - ProgressiveDecoderContext::Status status = StartIncrementalDecode(); - if (status != ProgressiveDecoderContext::Status::kSuccess) { - return status; - } - - int rows_decoded = 0; - // Decode available scanlines into the internal buffer. - SkCodec::Result result = decoder_->incrementalDecode(&rows_decoded); - - // Forward newly decoded scanlines to the delegate. - if (rows_decoded > rows_forwarded_) { - int width = decoder_->getInfo().width(); - int height = decoder_->getInfo().height(); - std::optional maybe_src_row_bytes = - fxge::CalculatePitch32(32, width); - std::optional maybe_dst_row_bytes = - fxge::CalculatePitch32(components_ == 4 ? 32 : 24, width); - if (!maybe_src_row_bytes.has_value() || !maybe_dst_row_bytes.has_value()) { - return ProgressiveDecoderContext::Status::kError; - } - size_t src_row_bytes = maybe_src_row_bytes.value(); - size_t dst_row_bytes = maybe_dst_row_bytes.value(); - std::vector row_buffer; - if (components_ != 4) { - row_buffer.resize(dst_row_bytes); - } - - int start_row = top_down_ ? rows_forwarded_ : (height - rows_decoded); - int end_row = top_down_ ? rows_decoded : (height - rows_forwarded_); - if (start_row < 0) { - return ProgressiveDecoderContext::Status::kError; - } - FX_SAFE_SIZE_T safe_src_row_end = static_cast(start_row); - safe_src_row_end *= src_row_bytes; - for (int r = start_row; r < end_row; ++r) { - size_t src_row_start = safe_src_row_end.ValueOrDie(); - safe_src_row_end += src_row_bytes; - if (!safe_src_row_end.IsValid() || - safe_src_row_end.ValueOrDie() > decoded_image_buf_.size()) { - return ProgressiveDecoderContext::Status::kError; - } - auto src_row = pdfium::span(decoded_image_buf_) - .subspan(src_row_start, src_row_bytes); - uint32_t dest_row = static_cast(r); - if (components_ == 4) { - delegate_->BmpReadScanline(dest_row, src_row); - } else { - Forward3ComponentRow(dest_row, src_row, row_buffer, width); - } - } - rows_forwarded_ = rows_decoded; - } - - if (result == SkCodec::kSuccess) { - return ProgressiveDecoderContext::Status::kSuccess; - } - if (result == SkCodec::kIncompleteInput) { - return ProgressiveDecoderContext::Status::kContinue; - } - return ProgressiveDecoderContext::Status::kError; -} - -bool SkiaBmpContext::ValidatePaletteIndices() { - if (bpp_ > 8 || bi_compression_ != 0 || bi_clr_used_ == 0) { - return true; - } - // Reject palette index references that exceed the palette size. - uint32_t num_colors = bi_clr_used_; - if (num_colors >= (1u << bpp_)) { - return true; - } - FX_SAFE_SIZE_T safe_off = bi_size_; - safe_off += kHeaderSizeOffset; - safe_off += num_colors * 4; - if (!safe_off.IsValid()) { - return false; - } - size_t off = safe_off.ValueOrDie(); - const auto buf = codec_memory_->GetBufferSpan(); - if (buf.size() >= kHeaderSizeOffset) { - uint32_t off_bits = fxcrt::GetUInt32LSBFirst(buf.subspan<10, 4>()); - if (off_bits >= off && off_bits <= buf.size()) { - off = off_bits; - } - } - int width = decoder_->getInfo().width(); - int height = decoder_->getInfo().height(); - std::optional maybe_row_stride = - fxge::CalculatePitch32(bpp_, width); - if (!maybe_row_stride.has_value()) { - return false; - } - FX_SAFE_SIZE_T safe_row_off = off; - size_t row_stride = maybe_row_stride.value(); - for (int y = 0; y < height; ++y, safe_row_off += row_stride) { - if (!safe_row_off.IsValid()) { - return false; - } - size_t row_off = safe_row_off.ValueOrDie(); - if (row_off >= buf.size()) { - break; - } - auto row_span = buf.subspan(row_off); - if (bpp_ == 8) { - size_t valid_bytes = - std::min(static_cast(width), row_span.size()); - for (size_t x = 0; x < valid_bytes; ++x) { - if (row_span[x] >= num_colors) { - return false; - } - } - } else if (bpp_ == 4) { - for (int x = 0; x < width; ++x) { - size_t byte_idx = static_cast(x) / 2; - if (byte_idx >= row_span.size()) { - break; - } - uint8_t val = (x % 2 == 0) ? (row_span[byte_idx] >> 4) - : (row_span[byte_idx] & 0x0f); - if (val >= num_colors) { - return false; - } - } - } else { - CHECK_EQ(bpp_, 1u); - for (int x = 0; x < width; ++x) { - size_t byte_idx = static_cast(x) / 8; - if (byte_idx >= row_span.size()) { - break; - } - uint8_t val = (row_span[byte_idx] >> (7 - (x % 8))) & 1; - if (val >= num_colors) { - return false; - } - } - } - } - return true; -} - -ProgressiveDecoderContext::Status SkiaBmpContext::StartIncrementalDecode() { - // Initialize incremental decoding into the destination buffer. - if (decode_started_) { - return ProgressiveDecoderContext::Status::kSuccess; - } - SkImageInfo dst_info = decoder_->getInfo() - .makeColorType(kBGRA_8888_SkColorType) - .makeAlphaType(decoder_->getInfo().alphaType()); - size_t row_bytes = dst_info.minRowBytes(); - FX_SAFE_SIZE_T safe_buf_size = dst_info.height(); - safe_buf_size *= row_bytes; - if (!safe_buf_size.IsValid()) { - return ProgressiveDecoderContext::Status::kError; - } - decoded_image_buf_.resize(safe_buf_size.ValueOrDie()); - SkCodec::Result result = decoder_->startIncrementalDecode( - dst_info, decoded_image_buf_.data(), row_bytes); - if (result == SkCodec::kIncompleteInput) { - return ProgressiveDecoderContext::Status::kContinue; - } - if (result != SkCodec::kSuccess) { - return ProgressiveDecoderContext::Status::kError; - } - decode_started_ = true; - return ProgressiveDecoderContext::Status::kSuccess; -} - -void SkiaBmpContext::Forward3ComponentRow(uint32_t dest_row, - pdfium::span src_row, - pdfium::span row_buffer, - int width) { - CHECK_EQ(components_, 3); - for (int x = 0; x < width; ++x) { - row_buffer[x * 3 + 0] = src_row[x * 4 + 0]; - row_buffer[x * 3 + 1] = src_row[x * 4 + 1]; - row_buffer[x * 3 + 2] = src_row[x * 4 + 2]; - } - delegate_->BmpReadScanline(dest_row, row_buffer); -} - -} // namespace fxcodec diff --git a/core/fxcodec/bmp/skia_bmp_context.h b/core/fxcodec/bmp/skia_bmp_context.h deleted file mode 100644 index ef70f68b60..0000000000 --- a/core/fxcodec/bmp/skia_bmp_context.h +++ /dev/null @@ -1,73 +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_BMP_SKIA_BMP_CONTEXT_H_ -#define CORE_FXCODEC_BMP_SKIA_BMP_CONTEXT_H_ - -#include - -#include -#include - -#include "core/fxcodec/cfx_codec_memory.h" -#include "core/fxcodec/progressive_decoder_context.h" -#include "core/fxcrt/retain_ptr.h" -#include "core/fxcrt/span.h" -#include "core/fxcrt/unowned_ptr.h" -#include "core/fxge/dib/fx_dib.h" - -class SkCodec; - -namespace fxcodec { - -class BmpDecoderDelegate; -class CFX_DIBAttribute; - -class SkiaBmpContext final : public ProgressiveDecoderContext { - public: - explicit SkiaBmpContext(BmpDecoderDelegate* delegate); - ~SkiaBmpContext() override; - - void SetCodecMemory(RetainPtr codec_memory); - - // ProgressiveDecoderContext: - FX_FILESIZE GetAvailInput() const override; - void Input(RetainPtr codec_memory) override; - - ProgressiveDecoderContext::Status ReadHeader( - int32_t* width, - int32_t* height, - bool* tb_flag, - int32_t* components, - pdfium::span* palette, - CFX_DIBAttribute* pAttribute); - - ProgressiveDecoderContext::Status DecodeImage(size_t frame_index) override; - - private: - bool ValidatePaletteIndices(); - ProgressiveDecoderContext::Status StartIncrementalDecode(); - void Forward3ComponentRow(uint32_t dest_row, - pdfium::span src_row, - pdfium::span row_buffer, - int width); - - UnownedPtr const delegate_; - std::unique_ptr decoder_; - RetainPtr codec_memory_; - std::vector decoded_image_buf_; - int rows_forwarded_ = 0; - int components_ = 3; - uint32_t bpp_ = 24; - uint32_t bi_size_ = 40; - uint32_t bi_compression_ = 0; - uint32_t bi_clr_used_ = 0; - bool top_down_ = false; - bool header_read_ = false; - bool decode_started_ = false; -}; - -} // namespace fxcodec - -#endif // CORE_FXCODEC_BMP_SKIA_BMP_CONTEXT_H_ diff --git a/core/fxcodec/fax/faxmodule.cpp b/core/fxcodec/fax/faxmodule.cpp index d21267aca9..4acd313072 100644 --- a/core/fxcodec/fax/faxmodule.cpp +++ b/core/fxcodec/fax/faxmodule.cpp @@ -17,7 +17,6 @@ #include "build/build_config.h" #include "core/fxcodec/scanlinedecoder.h" #include "core/fxcrt/binary_buffer.h" -#include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/data_vector.h" @@ -61,7 +60,7 @@ int FindBit(pdfium::span data_buf, int max_pos, int start_pos, bool bit) { - DCHECK(start_pos >= 0); + DCHECK_GE(start_pos, 0); if (start_pos >= max_pos) { return max_pos; } diff --git a/core/fxcodec/flate/flatemodule.cpp b/core/fxcodec/flate/flatemodule.cpp index f4dbbbc909..0c3cbfe623 100644 --- a/core/fxcodec/flate/flatemodule.cpp +++ b/core/fxcodec/flate/flatemodule.cpp @@ -327,7 +327,7 @@ uint8_t GetUpperLeftValue(pdfium::span span, return 0; } -uint8_t PathPredictor(uint8_t a, uint8_t b, uint8_t c) { +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); @@ -338,11 +338,11 @@ uint8_t PathPredictor(uint8_t a, uint8_t b, uint8_t c) { return pb <= pc ? b : c; } -void PNG_PredictLine(pdfium::span dest_span, - pdfium::span src_span, - pdfium::span last_span, - size_t row_size, - uint32_t bytes_per_pixel) { +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); @@ -375,7 +375,7 @@ void PNG_PredictLine(pdfium::span dest_span, 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] + PathPredictor(left, up, upper_left); + remaining_src_span[i] + PaethPredictor(left, up, upper_left); } break; } @@ -386,13 +386,13 @@ void PNG_PredictLine(pdfium::span dest_span, } } -std::optional> PNG_Predictor( - int Colors, - int BitsPerComponent, - int Columns, +std::optional> PngPredictor( + int colors, + int bits_per_component, + int columns, pdfium::span src_span) { const uint32_t row_size = - fxge::CalculatePitch8(BitsPerComponent, Colors, Columns).value_or(0); + fxge::CalculatePitch8(bits_per_component, colors, columns).value_or(0); if (row_size == 0) { return std::nullopt; } @@ -416,12 +416,12 @@ std::optional> PNG_Predictor( 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 * BitsPerComponent + 7) / 8; + 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); - PNG_PredictLine(remaining_dest_span, remaining_src_span, prev_dest_span, - remaining_row_size, bytes_per_pixel); + 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); @@ -429,12 +429,12 @@ std::optional> PNG_Predictor( return dest_buf; } -void TIFF_PredictLine(pdfium::span dest_span, - int BitsPerComponent, - int Colors, - int Columns) { - if (BitsPerComponent == 1) { - int row_bits = std::min(BitsPerComponent * Colors * Columns, +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; @@ -452,28 +452,28 @@ void TIFF_PredictLine(pdfium::span dest_span, } return; } - int BytesPerPixel = BitsPerComponent * Colors / 8; - if (BitsPerComponent == 16) { - for (size_t i = BytesPerPixel; i + 1 < dest_span.size(); i += 2) { - uint16_t pixel = (dest_span[i - BytesPerPixel] << 8) | - dest_span[i - BytesPerPixel + 1]; + 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 = BytesPerPixel; i < dest_span.size(); i++) { - dest_span[i] += dest_span[i - BytesPerPixel]; + for (size_t i = bytes_per_pixel; i < dest_span.size(); i++) { + dest_span[i] += dest_span[i - bytes_per_pixel]; } } } -bool TIFF_Predictor(int Colors, - int BitsPerComponent, - int Columns, - pdfium::span data_span) { +bool TiffPredictor(int colors, + int bits_per_component, + int columns, + pdfium::span data_span) { const uint32_t row_size = - fxge::CalculatePitch8(BitsPerComponent, Colors, Columns).value_or(0); + fxge::CalculatePitch8(bits_per_component, colors, columns).value_or(0); if (row_size == 0) { return false; } @@ -481,7 +481,7 @@ bool TIFF_Predictor(int Colors, while (!data_span.empty()) { auto row_span = data_span.first(std::min(row_size, data_span.size())); - TIFF_PredictLine(row_span, BitsPerComponent, Colors, Columns); + TiffPredictLine(row_span, bits_per_component, colors, columns); data_span = data_span.subspan(row_span.size()); } return true; @@ -506,13 +506,14 @@ DataAndBytesConsumed FlateUncompress(pdfium::span src_buf, return uncompressor.TakeResult(); } -enum class PredictorType : uint8_t { kNone, kFlate, kPng }; +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::kFlate; + return PredictorType::kTiff; } return PredictorType::kNone; } @@ -672,15 +673,15 @@ void FlatePredictorScanlineDecoder::GetNextLineWithPredictedPitch() { fxge::CalculatePitch8OrDie(bits_per_component_, colors_, columns_); const uint32_t bytes_per_pixel = (bits_per_component_ * colors_ + 7) / 8; FlateOutput(flate_.get(), predict_raw_); - PNG_PredictLine(scanline_, predict_raw_, last_line_, row_size, - bytes_per_pixel); + PngPredictLine(scanline_, predict_raw_, last_line_, row_size, + bytes_per_pixel); fxcrt::Copy(scanline_.first(predict_pitch_), last_line_.span()); break; } - case PredictorType::kFlate: { + case PredictorType::kTiff: { FlateOutput(flate_.get(), scanline_); - TIFF_PredictLine(scanline_.first(predict_pitch_), bpc_, comps_, - output_width_); + TiffPredictLine(scanline_.first(predict_pitch_), bpc_, comps_, + output_width_); break; } case PredictorType::kNone: { @@ -706,18 +707,18 @@ void FlatePredictorScanlineDecoder::GetNextLineWithoutPredictedPitch() { case PredictorType::kPng: { while (bytes_to_go) { FlateOutput(flate_.get(), predict_raw_); - PNG_PredictLine(predict_buffer_, predict_raw_, last_line_, row_size, - bytes_per_pixel); + PngPredictLine(predict_buffer_, predict_raw_, last_line_, row_size, + bytes_per_pixel); fxcrt::Copy(predict_buffer_.span(), last_line_.span()); bytes_to_go = CopyAndAdvanceLine(bytes_to_go); } break; } - case PredictorType::kFlate: { + case PredictorType::kTiff: { while (bytes_to_go) { FlateOutput(flate_.get(), predict_buffer_); - TIFF_PredictLine(predict_buffer_, bits_per_component_, colors_, - columns_); + TiffPredictLine(predict_buffer_, bits_per_component_, colors_, + columns_); bytes_to_go = CopyAndAdvanceLine(bytes_to_go); } break; @@ -900,14 +901,14 @@ DataAndBytesConsumed FlateModule::FlateOrLZWDecode( } case PredictorType::kPng: { std::optional> result = - PNG_Predictor(Colors, BitsPerComponent, Columns, dest_buf); + 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::kFlate: { - bool ret = TIFF_Predictor(Colors, BitsPerComponent, Columns, dest_buf); + case PredictorType::kTiff: { + bool ret = TiffPredictor(Colors, BitsPerComponent, Columns, dest_buf); return {std::move(dest_buf), ret ? bytes_consumed : FX_INVALID_OFFSET}; } } diff --git a/core/fxcodec/fx_codec_def.h b/core/fxcodec/fx_codec_def.h index 257007a4f4..4aa84683fd 100644 --- a/core/fxcodec/fx_codec_def.h +++ b/core/fxcodec/fx_codec_def.h @@ -20,18 +20,10 @@ enum class FXCODEC_STATUS { enum FXCODEC_IMAGE_TYPE { FXCODEC_IMAGE_UNKNOWN = 0, FXCODEC_IMAGE_JPG, -#ifdef PDF_ENABLE_XFA_BMP FXCODEC_IMAGE_BMP, -#endif // PDF_ENABLE_XFA_BMP -#ifdef PDF_ENABLE_XFA_PNG FXCODEC_IMAGE_PNG, -#endif // PDF_ENABLE_XFA_PNG -#ifdef PDF_ENABLE_XFA_GIF FXCODEC_IMAGE_GIF, -#endif // PDF_ENABLE_XFA_GIF -#ifdef PDF_ENABLE_XFA_TIFF FXCODEC_IMAGE_TIFF, -#endif // PDF_ENABLE_XFA_TIFF FXCODEC_IMAGE_MAX }; #endif // PDF_ENABLE_XFA diff --git a/core/fxcodec/gif/cfx_gifcontext.cpp b/core/fxcodec/gif/cfx_gifcontext.cpp index dca7fb1756..435b8b243c 100644 --- a/core/fxcodec/gif/cfx_gifcontext.cpp +++ b/core/fxcodec/gif/cfx_gifcontext.cpp @@ -16,10 +16,16 @@ #include #include "core/fxcodec/cfx_codec_memory.h" +#include "core/fxcodec/progressive_decoder_context_delegate.h" #include "core/fxcrt/byteorder.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/data_vector.h" +#include "core/fxcrt/fx_safe_types.h" +#include "core/fxcrt/stl_util.h" #include "core/fxcrt/to_underlying.h" +#include "core/fxcrt/zip.h" +#include "core/fxge/dib/cfx_dibitmap.h" +#include "core/fxge/dib/fx_dib.h" namespace fxcodec { @@ -42,41 +48,112 @@ std::optional GifSignatureFromByte(uint8_t signature) { } // namespace -CFX_GifContext::CFX_GifContext(Delegate* delegate) : delegate_(delegate) {} +CFX_GifContext::CFX_GifContext(ProgressiveDecoderContextDelegate* delegate) + : delegate_(delegate) {} CFX_GifContext::~CFX_GifContext() = default; -ProgressiveDecoderContext::Status CFX_GifContext::ReadHeader( - int* width, - int* height, - pdfium::span* pal_pp, - int* bg_index) { +ProgressiveDecoderContext::Status CFX_GifContext::ReadHeader(int* width, + int* height) { ProgressiveDecoderContext::Status ret = ReadHeader(); if (ret != ProgressiveDecoderContext::Status::kSuccess) { return ret; } *width = width_; *height = height_; - *pal_pp = global_palette_; - *bg_index = bc_index_; return ProgressiveDecoderContext::Status::kSuccess; } void CFX_GifContext::ReadScanline(int32_t row_num, pdfium::span row_buf) { - delegate_->GifReadScanline(row_num, row_buf); + int32_t line = row_num + img_top_; + if (line < 0 || line >= height_) { + return; + } + const size_t img_width = static_cast(img_width_); + const pdfium::span row_span = row_buf.first(img_width); + if (!bitmap_->IsAlphaFormat()) { + for (auto& byte_ref : row_span) { + if (byte_ref == trans_index_) { + byte_ref = bc_index_; + } + } + } + int32_t pal_index = bc_index_; + if (trans_index_ != -1 && bitmap_->IsAlphaFormat()) { + pal_index = trans_index_; + } + std::ranges::fill(scanline_buf_, pal_index); + fxcrt::Copy( + row_span, + pdfium::span(scanline_buf_).subspan(static_cast(img_left_))); + delegate_->ResampleScanline(line, scanline_buf_); } bool CFX_GifContext::GetRecordPosition(uint32_t cur_pos, - int32_t left, - int32_t top, - int32_t width, - int32_t height, + int32_t sub_left, + int32_t sub_top, + int32_t sub_width, pdfium::span pal, int32_t trans_index) { - return delegate_->GifInputRecordPositionBuf( - cur_pos, FX_RECT(left, top, left + width, top + height), pal, - trans_index); + img_left_ = sub_left; + img_top_ = sub_top; + img_width_ = sub_width; + trans_index_ = trans_index; + FX_SAFE_INT32 safe_size = width_; + int aligned_width = FxAlignToBoundary<4>(safe_size).ValueOrDie(); + scanline_buf_.resize(aligned_width); + if (pal.empty()) { + pal = global_palette_; + } + if (pal.empty()) { + return false; + } + std::vector argb_pal(pal.size()); + for (auto [in, out] : fxcrt::Zip(pal, argb_pal)) { + out = ArgbEncode(0xff, in.r, in.g, in.b); + } + int32_t pal_index = bc_index_; + if (trans_index_ >= static_cast(argb_pal.size())) { + trans_index_ = -1; + } + if (trans_index_ != -1) { + argb_pal[trans_index_] &= 0x00ffffff; + if (bitmap_ && bitmap_->IsAlphaFormat()) { + pal_index = trans_index_; + } + } + if (pal_index >= static_cast(argb_pal.size())) { + return false; + } + if (!delegate_->PrepareScanlineResampling( + width_, height_, ProgressiveDecoderContextDelegate::Format::k8bppRgb, + argb_pal, argb_pal[pal_index])) { + return false; + } + FXCODEC_STATUS dummy_status = FXCODEC_STATUS::kError; + return delegate_->ReadMoreData(cur_pos, &dummy_status); +} + +FXCODEC_STATUS CFX_GifContext::StartDecode(RetainPtr bitmap) { + bitmap_ = std::move(bitmap); + return FXCODEC_STATUS::kDecodeToBeContinued; +} + +FXCODEC_STATUS CFX_GifContext::ContinueDecode() { + ProgressiveDecoderContext::Status read_res = DecodeImage(); + while (read_res == ProgressiveDecoderContext::Status::kContinue) { + FXCODEC_STATUS error_status = FXCODEC_STATUS::kDecodeFinished; + if (!delegate_->ReadMoreData(std::nullopt, &error_status)) { + bitmap_ = nullptr; + return error_status; + } + read_res = DecodeImage(); + } + bitmap_ = nullptr; + return read_res == ProgressiveDecoderContext::Status::kSuccess + ? FXCODEC_STATUS::kDecodeFinished + : FXCODEC_STATUS::kError; } ProgressiveDecoderContext::Status CFX_GifContext::ReadHeader() { @@ -191,13 +268,13 @@ ProgressiveDecoderContext::Status CFX_GifContext::GetFrame() { } } -ProgressiveDecoderContext::Status CFX_GifContext::DecodeImage( - size_t frame_index) { - if (frame_index >= images_.size()) { +ProgressiveDecoderContext::Status CFX_GifContext::DecodeImage() { + if (images_.empty()) { return ProgressiveDecoderContext::Status::kError; } - CFX_GifImage* gif_image = images_[frame_index].get(); + // Always render frame 0. + CFX_GifImage* gif_image = images_.front().get(); if (gif_image->image_info.height == 0) { return ProgressiveDecoderContext::Status::kError; } @@ -212,10 +289,10 @@ ProgressiveDecoderContext::Status CFX_GifContext::DecodeImage( CFX_GifGraphicControlExtension* gif_img_gce = gif_image->image_GCE.get(); pdfium::span pLocalPalette = gif_image->local_palettes; if (!gif_img_gce) { - bool bRes = GetRecordPosition( - gif_image->data_pos, gif_image->image_info.left, - gif_image->image_info.top, gif_image->image_info.width, - gif_image->image_info.height, pLocalPalette, -1); + bool bRes = + GetRecordPosition(gif_image->data_pos, gif_image->image_info.left, + gif_image->image_info.top, + gif_image->image_info.width, pLocalPalette, -1); if (!bRes) { gif_image->row_buffer.clear(); return ProgressiveDecoderContext::Status::kError; @@ -223,8 +300,7 @@ ProgressiveDecoderContext::Status CFX_GifContext::DecodeImage( } else { bool bRes = GetRecordPosition( gif_image->data_pos, gif_image->image_info.left, - gif_image->image_info.top, gif_image->image_info.width, - gif_image->image_info.height, pLocalPalette, + gif_image->image_info.top, gif_image->image_info.width, pLocalPalette, gif_image->image_GCE->gce_flags.transparency ? static_cast(gif_image->image_GCE->trans_index) : -1); @@ -522,7 +598,7 @@ ProgressiveDecoderContext::Status CFX_GifContext::DecodeImageInfo() { } gif_image->code_exp = code_size; - gif_image->data_pos = delegate_->GifCurrentPosition(); + gif_image->data_pos = delegate_->GetCurrentInputPosition(); gif_image->image_GCE = nullptr; if (graphic_control_extension_.get()) { if (graphic_control_extension_->gce_flags.transparency) { diff --git a/core/fxcodec/gif/cfx_gifcontext.h b/core/fxcodec/gif/cfx_gifcontext.h index b35ba339a3..818aeaf436 100644 --- a/core/fxcodec/gif/cfx_gifcontext.h +++ b/core/fxcodec/gif/cfx_gifcontext.h @@ -10,50 +10,37 @@ #include #include +#include "core/fxcodec/fx_codec_def.h" #include "core/fxcodec/gif/cfx_gif.h" #include "core/fxcodec/gif/lzw_decompressor.h" #include "core/fxcodec/progressive_decoder_context.h" #include "core/fxcrt/compiler_specific.h" +#include "core/fxcrt/data_vector.h" #include "core/fxcrt/fx_coordinates.h" #include "core/fxcrt/retain_ptr.h" #include "core/fxcrt/span.h" #include "core/fxcrt/unowned_ptr.h" class CFX_CodecMemory; +class CFX_DIBitmap; namespace fxcodec { +class ProgressiveDecoderContextDelegate; + class CFX_GifContext : public ProgressiveDecoderContext { public: - class Delegate { - public: - virtual ~Delegate() = default; - - virtual uint32_t GifCurrentPosition() const = 0; - virtual bool GifInputRecordPositionBuf(uint32_t rcd_pos, - const FX_RECT& img_rc, - pdfium::span pal_ptr, - int32_t trans_index) = 0; - virtual void GifReadScanline(int32_t row_num, - pdfium::span row_buf) = 0; - }; - - explicit CFX_GifContext(Delegate* delegate); + explicit CFX_GifContext(ProgressiveDecoderContextDelegate* delegate); ~CFX_GifContext() override; void ReadScanline(int32_t row_num, pdfium::span row_buf); bool GetRecordPosition(uint32_t cur_pos, - int32_t left, - int32_t top, - int32_t width, - int32_t height, + int32_t sub_left, + int32_t sub_top, + int32_t sub_width, pdfium::span pal, int32_t trans_index); - ProgressiveDecoderContext::Status ReadHeader( - int* width, - int* height, - pdfium::span* pal_pp, - int* bg_index); + ProgressiveDecoderContext::Status ReadHeader(int* width, int* height); ProgressiveDecoderContext::Status ReadHeader(); ProgressiveDecoderContext::Status GetFrame(); ProgressiveDecoderContext::Status LoadFrame(size_t frame_num); @@ -61,12 +48,16 @@ class CFX_GifContext : public ProgressiveDecoderContext { // ProgressiveDecoderContext: FX_FILESIZE GetAvailInput() const override; void Input(RetainPtr codec_memory) override; - Status DecodeImage(size_t frame_index) override; + Status DecodeImage() override; + + FXCODEC_STATUS StartDecode(RetainPtr bitmap); + FXCODEC_STATUS ContinueDecode(); void SetInputBuffer(RetainPtr codec_memory); size_t GetFrameNum() const { return images_.size(); } - UnownedPtr const delegate_; + UnownedPtr const delegate_; + RetainPtr bitmap_; std::vector global_palette_; uint8_t global_palette_exp_ = 0; uint32_t img_row_offset_ = 0; @@ -77,6 +68,11 @@ class CFX_GifContext : public ProgressiveDecoderContext { std::unique_ptr lzw_decompressor_; int width_ = 0; int height_ = 0; + int img_left_ = 0; + int img_top_ = 0; + int img_width_ = 0; + int trans_index_ = -1; + DataVector scanline_buf_; uint8_t bc_index_ = 0; uint8_t global_sort_flag_ = 0; uint8_t global_color_resolution_ = 0; diff --git a/core/fxcodec/jbig2/jbig2_context.cpp b/core/fxcodec/jbig2/jbig2_context.cpp index 88b47b070c..219946e010 100644 --- a/core/fxcodec/jbig2/jbig2_context.cpp +++ b/core/fxcodec/jbig2/jbig2_context.cpp @@ -21,7 +21,7 @@ #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.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fixed_size_data_vector.h" #include "core/fxcrt/fx_memory_wrappers.h" #include "core/fxcrt/fx_safe_types.h" @@ -1297,8 +1297,8 @@ std::vector CJBig2_Context::DecodeSymbolIDHuffmanTable( } const CJBig2_HuffmanTable* CJBig2_Context::GetHuffmanTable(size_t idx) { - DCHECK(idx > 0); - DCHECK(idx < CJBig2_HuffmanTable::kNumHuffmanTables); + DCHECK_GT(idx, 0); + DCHECK_LT(idx, CJBig2_HuffmanTable::kNumHuffmanTables); if (!huffman_tables_[idx].get()) { huffman_tables_[idx] = std::make_unique(idx); } diff --git a/core/fxcodec/jbig2/jbig2_huffman_table.cpp b/core/fxcodec/jbig2/jbig2_huffman_table.cpp index a456773273..f040a09a9b 100644 --- a/core/fxcodec/jbig2/jbig2_huffman_table.cpp +++ b/core/fxcodec/jbig2/jbig2_huffman_table.cpp @@ -13,6 +13,7 @@ #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" @@ -246,7 +247,7 @@ void CJBig2_HuffmanTable::ExtendBuffers(bool increment) { } size += 16; - DCHECK(NTEMP < size); + DCHECK_LT(NTEMP, size); CODES.resize(size); RANGELEN.resize(size); RANGELOW.resize(size); diff --git a/core/fxcodec/jpeg/DEPS b/core/fxcodec/jpeg/DEPS index 88af2417fc..a78ca5aada 100644 --- a/core/fxcodec/jpeg/DEPS +++ b/core/fxcodec/jpeg/DEPS @@ -4,3 +4,12 @@ include_rules = [ '+third_party/libjpeg_turbo/jerror.h', '+third_party/libjpeg_turbo/jpeglib.h', ] + +specific_include_rules = { + "skia_jpeg_context\.(cpp|h)": [ + '+third_party/skia/include', + ], + "skia_scanline_decoder\.(cpp|h)": [ + '+third_party/skia/include', + ], +} diff --git a/core/fxcodec/jpeg/jpegmodule.cpp b/core/fxcodec/jpeg/jpegmodule.cpp index 031c5bab32..3cf8818a96 100644 --- a/core/fxcodec/jpeg/jpegmodule.cpp +++ b/core/fxcodec/jpeg/jpegmodule.cpp @@ -1,4 +1,4 @@ -// Copyright 2014 The PDFium Authors +// 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. @@ -6,405 +6,33 @@ #include "core/fxcodec/jpeg/jpegmodule.h" -#include #include #include -#include -#include #include "build/build_config.h" -#include "core/fxcodec/cfx_codec_memory.h" -#include "core/fxcodec/jpeg/jpeg_common.h" +#include "core/fxcodec/jpeg/libjpeg_scanline_decoder.h" #include "core/fxcodec/scanlinedecoder.h" -#include "core/fxcrt/check.h" -#include "core/fxcrt/check_op.h" +#include "core/fxcrt/span.h" + +#if defined(PDF_ENABLE_RUST_JPEG) +#include "core/fxcodec/jpeg/skia_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/data_vector.h" +#include "core/fxcrt/fx_memory.h" #include "core/fxcrt/fx_safe_types.h" -#include "core/fxcrt/raw_span.h" +#include "core/fxcrt/numerics/safe_conversions.h" +#include "core/fxcrt/retain_ptr.h" #include "core/fxge/dib/cfx_dibbase.h" #include "core/fxge/dib/fx_dib.h" - -static pdfium::span JpegScanSOI( - pdfium::span src_span) { - DCHECK(!src_span.empty()); - - 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; -} - -static bool JpegLoadInfo(pdfium::span src_span, - JpegModule::ImageInfo* pInfo) { - src_span = JpegScanSOI(src_span); - - JpegCommon jpeg_common = {}; - jpeg_common.error_mgr.error_exit = jpeg_common_error_fatal; - jpeg_common.error_mgr.emit_message = jpeg_common_error_do_nothing_int; - jpeg_common.error_mgr.output_message = jpeg_common_error_do_nothing; - jpeg_common.error_mgr.format_message = jpeg_common_error_do_nothing_char; - jpeg_common.error_mgr.reset_error_mgr = jpeg_common_error_do_nothing; - jpeg_common.error_mgr.trace_level = 0; - jpeg_common.cinfo.err = &jpeg_common.error_mgr; - jpeg_common.cinfo.client_data = &jpeg_common; - if (!jpeg_common_create_decompress(&jpeg_common)) { - return false; - } - - jpeg_common.source_mgr.init_source = jpeg_common_src_do_nothing; - jpeg_common.source_mgr.term_source = jpeg_common_src_do_nothing; - jpeg_common.source_mgr.skip_input_data = jpeg_common_src_skip_data_or_trap; - jpeg_common.source_mgr.fill_input_buffer = jpeg_common_src_fill_buffer; - jpeg_common.source_mgr.resync_to_restart = jpeg_common_src_resync; - jpeg_common.source_mgr.bytes_in_buffer = src_span.size(); - jpeg_common.source_mgr.next_input_byte = src_span.data(); - jpeg_common.cinfo.src = &jpeg_common.source_mgr; - if (jpeg_common_read_header(&jpeg_common, TRUE) != JPEG_HEADER_OK) { - jpeg_common_destroy_decompress(&jpeg_common); - return false; - } - pInfo->width = jpeg_common.cinfo.image_width; - pInfo->height = jpeg_common.cinfo.image_height; - pInfo->num_components = jpeg_common.cinfo.num_components; - pInfo->color_transform = jpeg_common.cinfo.jpeg_color_space == JCS_YCbCr || - jpeg_common.cinfo.jpeg_color_space == JCS_YCCK; - pInfo->bits_per_components = jpeg_common.cinfo.data_precision; - jpeg_common_destroy_decompress(&jpeg_common); - return true; -} +#endif // BUILDFLAG(IS_WIN) namespace fxcodec { -namespace { - -constexpr size_t kKnownBadHeaderWithInvalidHeightByteOffsetStarts[] = {94, 163}; - -class JpegDecoder final : public ScanlineDecoder { - public: - JpegDecoder(); - ~JpegDecoder() override; - - bool Create(pdfium::span src_span, - uint32_t width, - uint32_t height, - int nComps, - bool ColorTransform, - uint32_t scale_denom); - - // ScanlineDecoder: - [[nodiscard]] bool Rewind() override; - pdfium::span GetNextLine() override; - uint32_t GetSrcOffset() override; - - bool InitDecode(bool bAcceptKnownBadHeader); - - private: - void CalcPitch(); - void InitDecompressSrc(); - - // Only called when initial jpeg_read_header() fails. - bool HasKnownBadHeaderWithInvalidHeight(size_t dimension_offset) const; - - // Is a JPEG SOFn marker, which is defined as 0xff, 0xc[0-9a-f]. - bool IsSofSegment(size_t marker_offset) const; - - // Patch up the in-memory JPEG header for known bad JPEGs. - void PatchUpKnownBadHeaderWithInvalidHeight(size_t dimension_offset); - - // Patch up the JPEG trailer, even if it is correct. - void PatchUpTrailer(); - - pdfium::span GetWritableSrcData(); - - // For a given invalid height byte offset in - // |kKnownBadHeaderWithInvalidHeightByteOffsetStarts|, the SOFn marker should - // be this many bytes before that. - static constexpr size_t kSofMarkerByteOffset = 5; - - JpegCommon common_ = {}; - pdfium::raw_span src_span_; - DataVector scanline_buf_; - bool decompress_created_ = false; - bool started_ = false; - bool jpeg_transform_ = false; - uint32_t scale_denom_ = 1; -}; - -// Returns the output dimension libjpeg produces for `dim` when decoding at -// 1/`scale_denom` scale. Matches jpeg_core_output_dimensions() for the -// 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; -} - -JpegDecoder::JpegDecoder() = default; - -JpegDecoder::~JpegDecoder() { - if (decompress_created_) { - jpeg_common_destroy_decompress(&common_); - } - - // Span in superclass can't outlive our buffer. - last_scanline_ = pdfium::span(); -} - -bool JpegDecoder::InitDecode(bool bAcceptKnownBadHeader) { - common_.cinfo.err = &common_.error_mgr; - common_.cinfo.client_data = &common_; - if (!jpeg_common_create_decompress(&common_)) { - return false; - } - decompress_created_ = true; - common_.cinfo.image_width = orig_width_; - common_.cinfo.image_height = orig_height_; - InitDecompressSrc(); - if (jpeg_common_read_header(&common_, TRUE) != JPEG_HEADER_OK) { - std::optional known_bad_header_offset; - if (bAcceptKnownBadHeader) { - for (size_t offset : kKnownBadHeaderWithInvalidHeightByteOffsetStarts) { - if (HasKnownBadHeaderWithInvalidHeight(offset)) { - known_bad_header_offset = offset; - break; - } - } - } - jpeg_common_destroy_decompress(&common_); - decompress_created_ = false; - if (!known_bad_header_offset.has_value()) { - return false; - } - PatchUpKnownBadHeaderWithInvalidHeight(known_bad_header_offset.value()); - if (!jpeg_common_create_decompress(&common_)) { - return false; - } - decompress_created_ = true; - common_.cinfo.image_width = orig_width_; - common_.cinfo.image_height = orig_height_; - InitDecompressSrc(); - if (jpeg_common_read_header(&common_, TRUE) != JPEG_HEADER_OK) { - jpeg_common_destroy_decompress(&common_); - decompress_created_ = false; - return false; - } - } - if (common_.cinfo.saw_Adobe_marker) { - jpeg_transform_ = true; - } - - if (common_.cinfo.num_components == 3 && !jpeg_transform_) { - common_.cinfo.out_color_space = common_.cinfo.jpeg_color_space; - } - - orig_width_ = common_.cinfo.image_width; - orig_height_ = common_.cinfo.image_height; - - // Reduced-size (scale_denom) decoding collapses each 8x8 DCT block toward its - // low-frequency content, ultimately its average at 1/8. If a dimension is not - // a multiple of the MCU size, the encoder had to pad the partial edge blocks; - // that padding is not required to replicate the edge (some encoders fill it - // with black), and reduced decoding cannot crop it back out, so it would - // contaminate the visible edge pixels (e.g. a too-dark right/bottom fringe). - // Only decode at reduced size when there are no padded edge blocks in any - // component, which makes the result exact regardless of the encoder. The MCU - // size in pixels is max_samp_factor * DCTSIZE; derive it from the per- - // component sampling factors, which are valid immediately after read_header. - // See https://crbug.com/890745 and - // https://github.com/libjpeg-turbo/libjpeg-turbo/issues/297. - int max_h_samp = 1; - int max_v_samp = 1; - // SAFETY: libjpeg guarantees `comp_info` points to `num_components` entries - // once the header has been read. - auto comp_info = UNSAFE_BUFFERS( - pdfium::span(common_.cinfo.comp_info, - static_cast(common_.cinfo.num_components))); - for (const jpeg_component_info& comp : comp_info) { - max_h_samp = std::max(max_h_samp, comp.h_samp_factor); - max_v_samp = std::max(max_v_samp, comp.v_samp_factor); - } - if (orig_width_ % (max_h_samp * DCTSIZE) != 0 || - orig_height_ % (max_v_samp * DCTSIZE) != 0) { - // Reset to 1 (no reduction). This is equivalent to using - // common_.cinfo.scale_denom, which libjpeg leaves at its default of 1 until - // jpeg_start_decompress(); that has not run yet here. - scale_denom_ = 1; - } - - output_width_ = ScaledJpegSize(orig_width_, scale_denom_); - output_height_ = ScaledJpegSize(orig_height_, scale_denom_); - return true; -} - -bool JpegDecoder::Create(pdfium::span src_span, - uint32_t width, - uint32_t height, - int nComps, - bool ColorTransform, - uint32_t scale_denom) { - // 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 - // and lead to a scanline-buffer overflow. - DCHECK(scale_denom == 1 || scale_denom == 2 || scale_denom == 4 || - scale_denom == 8); - scale_denom_ = scale_denom; - src_span_ = JpegScanSOI(src_span); - if (src_span_.size() < 2) { - return false; - } - - PatchUpTrailer(); - - common_.error_mgr.error_exit = jpeg_common_error_fatal; - common_.error_mgr.emit_message = jpeg_common_error_do_nothing_int; - common_.error_mgr.output_message = jpeg_common_error_do_nothing; - common_.error_mgr.format_message = jpeg_common_error_do_nothing_char; - common_.error_mgr.reset_error_mgr = jpeg_common_error_do_nothing; - common_.source_mgr.init_source = jpeg_common_src_do_nothing; - common_.source_mgr.term_source = jpeg_common_src_do_nothing; - common_.source_mgr.skip_input_data = jpeg_common_src_skip_data_or_trap; - common_.source_mgr.fill_input_buffer = jpeg_common_src_fill_buffer; - common_.source_mgr.resync_to_restart = jpeg_common_src_resync; - jpeg_transform_ = ColorTransform; - output_width_ = orig_width_ = width; - output_height_ = orig_height_ = height; - if (!InitDecode(/*bAcceptKnownBadHeader=*/true)) { - return false; - } - - if (common_.cinfo.num_components < nComps) { - return false; - } - - if (common_.cinfo.image_width < width) { - return false; - } - - CalcPitch(); - scanline_buf_ = DataVector(pitch_); - comps_ = common_.cinfo.num_components; - bpc_ = 8; - started_ = false; - return true; -} - -bool JpegDecoder::Rewind() { - if (started_) { - jpeg_common_destroy_decompress(&common_); - if (!InitDecode(/*bAcceptKnownBadHeader=*/false)) { - return false; - } - } - // `Created()` validated `scale_denom_` and, for non-MCU-aligned images, - // reset it to 1 in `InitDecode()` (called from `Create()`, and again just - // above when re-decoding), so it is safe to use as-is here. - common_.cinfo.scale_denom = scale_denom_; - output_width_ = ScaledJpegSize(orig_width_, scale_denom_); - output_height_ = ScaledJpegSize(orig_height_, scale_denom_); - if (!jpeg_common_start_decompress(&common_)) { - jpeg_common_destroy_decompress(&common_); - return false; - } - // `CalcPitch()` sized the scanline buffer for `output_width_` columns, - // so libjpeg must not produce more than that per scanline. - CHECK_LE(static_cast(common_.cinfo.output_width), output_width_); - started_ = true; - return true; -} - -pdfium::span JpegDecoder::GetNextLine() { - uint8_t* row_array[] = {scanline_buf_.data()}; - int nlines = jpeg_common_read_scanlines(&common_, row_array, 1u); - if (nlines <= 0) { - return pdfium::span(); - } - return scanline_buf_; -} - -uint32_t JpegDecoder::GetSrcOffset() { - return static_cast(src_span_.size() - - common_.source_mgr.bytes_in_buffer); -} - -void JpegDecoder::CalcPitch() { - // `output_width_` must be finalized (post scale/alignment decision in - // `InitDecode()`) before this runs; the sole caller is `Create()`, after - // `InitDecode()`. - DCHECK_GT(output_width_, 0); - pitch_ = static_cast(output_width_) * common_.cinfo.num_components; - pitch_ += 3; - pitch_ /= 4; - pitch_ *= 4; -} - -void JpegDecoder::InitDecompressSrc() { - common_.cinfo.src = &common_.source_mgr; - common_.source_mgr.bytes_in_buffer = src_span_.size(); - common_.source_mgr.next_input_byte = src_span_.data(); -} - -bool JpegDecoder::HasKnownBadHeaderWithInvalidHeight( - size_t dimension_offset) const { - // Perform lots of possibly redundant checks to make sure this has no false - // positives. - bool bDimensionChecks = - common_.cinfo.err->msg_code == JERR_IMAGE_TOO_BIG && - common_.cinfo.image_width < JPEG_MAX_DIMENSION && - common_.cinfo.image_height == 0xffff && orig_width_ > 0 && - orig_width_ <= JPEG_MAX_DIMENSION && orig_height_ > 0 && - orig_height_ <= JPEG_MAX_DIMENSION; - if (!bDimensionChecks) { - return false; - } - - if (src_span_.size() <= dimension_offset + 3u) { - return false; - } - - if (!IsSofSegment(dimension_offset - kSofMarkerByteOffset)) { - return false; - } - - const auto pHeaderDimensions = src_span_.subspan(dimension_offset); - uint8_t nExpectedWidthByte1 = (orig_width_ >> 8) & 0xff; - uint8_t nExpectedWidthByte2 = orig_width_ & 0xff; - // Height high byte, height low byte, width high byte, width low byte. - return pHeaderDimensions[0] == 0xff && pHeaderDimensions[1] == 0xff && - pHeaderDimensions[2] == nExpectedWidthByte1 && - pHeaderDimensions[3] == nExpectedWidthByte2; -} - -bool JpegDecoder::IsSofSegment(size_t marker_offset) const { - const auto pHeaderMarker = src_span_.subspan(marker_offset); - return pHeaderMarker[0] == 0xff && pHeaderMarker[1] >= 0xc0 && - pHeaderMarker[1] <= 0xcf; -} - -void JpegDecoder::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; -} - -void JpegDecoder::PatchUpTrailer() { - auto pData = GetWritableSrcData(); - pData[src_span_.size() - 2] = 0xff; - pData[src_span_.size() - 1] = 0xd9; -} - -pdfium::span JpegDecoder::GetWritableSrcData() { - // SAFETY: const_cast<> doesn't change size. - return UNSAFE_BUFFERS( - pdfium::span(const_cast(src_span_.data()), src_span_.size())); -} - -} // namespace - // static std::unique_ptr JpegModule::CreateDecoder( pdfium::span src_span, @@ -413,26 +41,27 @@ std::unique_ptr JpegModule::CreateDecoder( int nComps, bool ColorTransform, uint32_t scale_denom) { - DCHECK(!src_span.empty()); - - auto pDecoder = std::make_unique(); - if (!pDecoder->Create(src_span, width, height, nComps, ColorTransform, - scale_denom)) { - return nullptr; +#if defined(PDF_ENABLE_RUST_JPEG) + auto decoder = SkiaScanlineDecoder::Create(src_span, width, height, nComps, + ColorTransform, scale_denom); + if (decoder) { + return decoder; } - - return pDecoder; +#endif + return LibjpegScanlineDecoder::Create(src_span, width, height, nComps, + ColorTransform, scale_denom); } // static std::optional JpegModule::LoadInfo( pdfium::span src_span) { - ImageInfo info; - if (!JpegLoadInfo(src_span, &info)) { - return std::nullopt; +#if defined(PDF_ENABLE_RUST_JPEG) + auto info = SkiaScanlineDecoder::LoadInfo(src_span); + if (info.has_value()) { + return info; } - - return info; +#endif + return LibjpegScanlineDecoder::LoadInfo(src_span); } #if BUILDFLAG(IS_WIN) diff --git a/core/fxcodec/jpeg/jpegmodule_unittest.cpp b/core/fxcodec/jpeg/jpegmodule_unittest.cpp new file mode 100644 index 0000000000..f8e7403b67 --- /dev/null +++ b/core/fxcodec/jpeg/jpegmodule_unittest.cpp @@ -0,0 +1,149 @@ +// 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/jpegmodule.h" + +#include + +#include +#include +#include +#include + +#include "core/fxcodec/scanlinedecoder.h" +#include "core/fxcrt/span.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/utils/file_util.h" +#include "testing/utils/path_service.h" + +namespace fxcodec { + +namespace { + +std::vector LoadMonaLisa() { + std::string file_path = PathService::GetTestFilePath("mona_lisa.jpg"); + return GetFileContents(file_path.c_str()); +} + +} // namespace + +TEST(JpegModuleTest, LoadInfoInvalid) { + EXPECT_FALSE(JpegModule::LoadInfo({}).has_value()); + + std::vector invalid = {0x00, 0x01, 0x02, 0x03}; + EXPECT_FALSE(JpegModule::LoadInfo(invalid).has_value()); +} + +TEST(JpegModuleTest, LoadInfoValid) { + std::vector jpeg_data = LoadMonaLisa(); + ASSERT_FALSE(jpeg_data.empty()); + + std::optional info = JpegModule::LoadInfo(jpeg_data); + ASSERT_TRUE(info.has_value()); + EXPECT_EQ(120u, info->width); + EXPECT_EQ(120u, info->height); + EXPECT_EQ(3, info->num_components); + EXPECT_EQ(8, info->bits_per_components); +} + +TEST(JpegModuleTest, CreateDecoderInvalid) { + EXPECT_FALSE(JpegModule::CreateDecoder({}, 120, 120, 3, true, 1)); + + std::vector invalid = {0x00, 0x01, 0x02, 0x03}; + EXPECT_FALSE(JpegModule::CreateDecoder(invalid, 120, 120, 3, true, 1)); +} + +TEST(JpegModuleTest, CreateDecoderValid) { + std::vector jpeg_data = LoadMonaLisa(); + ASSERT_FALSE(jpeg_data.empty()); + + std::unique_ptr decoder = + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 1); + ASSERT_TRUE(decoder); + EXPECT_EQ(120, decoder->GetWidth()); + EXPECT_EQ(120, decoder->GetHeight()); + EXPECT_EQ(3, decoder->CountComps()); + EXPECT_EQ(8, decoder->GetBPC()); + + for (int line = 0; line < 120; ++line) { + pdfium::span scanline = decoder->GetScanline(line); + EXPECT_FALSE(scanline.empty()) << "line " << line; + EXPECT_GE(scanline.size(), 360u) << "line " << line; + } + + // Out of range scanline line index. + EXPECT_TRUE(decoder->GetScanline(120).empty()); + + // Rewind and re-read line 0. + pdfium::span scanline0 = decoder->GetScanline(0); + EXPECT_FALSE(scanline0.empty()); + EXPECT_GE(scanline0.size(), 360u); +} + +// EmbedPDF(reads-dont-write): a JPEG without its end marker decodes as if it +// had one, from a patched copy; the source bytes, an image stream's own, stay +// as they were. +TEST(JpegModuleTest, DecodeWithoutTrailerLeavesSourceAlone) { + std::vector jpeg_data = LoadMonaLisa(); + ASSERT_GE(jpeg_data.size(), 2u); + ASSERT_EQ(0xff, jpeg_data[jpeg_data.size() - 2]); + ASSERT_EQ(0xd9, jpeg_data[jpeg_data.size() - 1]); + jpeg_data[jpeg_data.size() - 2] = 0x00; + jpeg_data[jpeg_data.size() - 1] = 0x00; + const std::vector original = jpeg_data; + + std::unique_ptr decoder = + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 1); + ASSERT_TRUE(decoder); + for (int line = 0; line < 120; ++line) { + EXPECT_FALSE(decoder->GetScanline(line).empty()) << "line " << line; + } + EXPECT_EQ(original, jpeg_data); +} + +TEST(JpegModuleTest, CreateDecoderDownscaled) { + std::vector jpeg_data = LoadMonaLisa(); + ASSERT_FALSE(jpeg_data.empty()); + + // 1/2 scale (scale_denom = 2). + std::unique_ptr decoder2 = + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 2); + ASSERT_TRUE(decoder2); + EXPECT_EQ(60, decoder2->GetWidth()); + EXPECT_EQ(60, decoder2->GetHeight()); + + for (int line = 0; line < 60; ++line) { + pdfium::span scanline = decoder2->GetScanline(line); + EXPECT_FALSE(scanline.empty()) << "line " << line; + EXPECT_GE(scanline.size(), 180u) << "line " << line; + } + + // 1/4 scale (scale_denom = 4). + std::unique_ptr decoder4 = + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 4); + ASSERT_TRUE(decoder4); + EXPECT_EQ(30, decoder4->GetWidth()); + EXPECT_EQ(30, decoder4->GetHeight()); + + for (int line = 0; line < 30; ++line) { + pdfium::span scanline = decoder4->GetScanline(line); + EXPECT_FALSE(scanline.empty()) << "line " << line; + EXPECT_GE(scanline.size(), 90u) << "line " << line; + } + + // 1/8 scale (scale_denom = 8). + std::unique_ptr decoder8 = + JpegModule::CreateDecoder(jpeg_data, 120, 120, 3, true, 8); + ASSERT_TRUE(decoder8); + EXPECT_EQ(15, decoder8->GetWidth()); + EXPECT_EQ(15, decoder8->GetHeight()); + + for (int line = 0; line < 15; ++line) { + pdfium::span scanline = decoder8->GetScanline(line); + EXPECT_FALSE(scanline.empty()) << "line " << line; + EXPECT_GE(scanline.size(), 45u) << "line " << line; + } +} + +} // namespace fxcodec diff --git a/core/fxcodec/jpeg/libjpeg_jpeg_context.cpp b/core/fxcodec/jpeg/libjpeg_jpeg_context.cpp index 77d048bfe6..64cb274447 100644 --- a/core/fxcodec/jpeg/libjpeg_jpeg_context.cpp +++ b/core/fxcodec/jpeg/libjpeg_jpeg_context.cpp @@ -5,29 +5,56 @@ #include "core/fxcodec/jpeg/libjpeg_jpeg_context.h" #include +#include +#include #include "core/fxcodec/cfx_codec_memory.h" #include "core/fxcodec/fx_codec.h" +#include "core/fxcodec/progressive_decoder_context_delegate.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/fx_memory.h" +#include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/span.h" #include "core/fxge/dib/cfx_dibbase.h" +#include "core/fxge/dib/cfx_dibitmap.h" #include "core/fxge/dib/fx_dib.h" namespace fxcodec { - namespace { void JpegLoadAttribute(const jpeg_decompress_struct& info, - CFX_DIBAttribute* pAttribute) { - pAttribute->x_dpi_ = info.X_density; - pAttribute->y_dpi_ = info.Y_density; - pAttribute->dpi_unit_ = + CFX_DIBAttribute* attribute) { + CHECK(attribute); + attribute->x_dpi_ = info.X_density; + attribute->y_dpi_ = info.Y_density; + attribute->dpi_unit_ = static_cast(info.density_unit); } +void RGB2BGR(pdfium::span buffer) { + for (size_t i = 0; i + 2 < buffer.size(); i += 3) { + std::swap(buffer[i], buffer[i + 2]); + } +} + +ProgressiveDecoderContextDelegate::Format GetCodecFormat(int num_components) { + switch (num_components) { + case 1: + return ProgressiveDecoderContextDelegate::Format::k8bppGray; + case 3: + return ProgressiveDecoderContextDelegate::Format::kRgb; + case 4: + return ProgressiveDecoderContextDelegate::Format::kCmyk; + default: + return ProgressiveDecoderContextDelegate::Format::kInvalid; + } +} + } // namespace -LibjpegJpegContext::LibjpegJpegContext() { +LibjpegJpegContext::LibjpegJpegContext( + ProgressiveDecoderContextDelegate* delegate) + : delegate_(delegate) { common_.cinfo.client_data = &common_; common_.cinfo.err = &common_.error_mgr; @@ -51,7 +78,9 @@ LibjpegJpegContext::LibjpegJpegContext() { } LibjpegJpegContext::~LibjpegJpegContext() { - jpeg_destroy_decompress(&common_.cinfo); + if (create_ok_) { + jpeg_destroy_decompress(&common_.cinfo); + } } FX_FILESIZE LibjpegJpegContext::GetAvailInput() const { @@ -59,6 +88,7 @@ FX_FILESIZE LibjpegJpegContext::GetAvailInput() const { } void LibjpegJpegContext::Input(RetainPtr codec_memory) { + codec_memory_ = codec_memory; pdfium::span src_buf = codec_memory->GetUnconsumedSpan(); if (common_.skip_size) { if (common_.skip_size > src_buf.size()) { @@ -73,16 +103,67 @@ void LibjpegJpegContext::Input(RetainPtr codec_memory) { common_.source_mgr.bytes_in_buffer = src_buf.size(); } +void LibjpegJpegContext::SyncCodecMemory() { + codec_memory_->Seek(codec_memory_->GetSize() - + common_.source_mgr.bytes_in_buffer); +} + +FXCODEC_STATUS LibjpegJpegContext::StartDecode() { + decode_row_ = 0; + while (!StartScanline()) { + FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; + SyncCodecMemory(); + if (!delegate_->ReadMoreData(std::nullopt, &error_status)) { + return error_status; + } + } + 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; + } + return FXCODEC_STATUS::kDecodeToBeContinued; +} + +FXCODEC_STATUS LibjpegJpegContext::ContinueDecode() { + const bool is_rgb = common_.cinfo.num_components == 3; + while (true) { + int err_code = ReadScanline(line_buf_.data()); + if (err_code == LibjpegJpegContext::kFatal) { + return FXCODEC_STATUS::kError; + } + if (err_code != LibjpegJpegContext::kOk) { + FXCODEC_STATUS error_status = FXCODEC_STATUS::kDecodeFinished; + SyncCodecMemory(); + if (delegate_->ReadMoreData(std::nullopt, &error_status)) { + continue; + } + return error_status; + } + if (is_rgb) { + RGB2BGR(pdfium::span(line_buf_).first( + static_cast(common_.cinfo.image_width) * 3)); + } + delegate_->ResampleScanline(decode_row_++, line_buf_); + if (decode_row_ >= static_cast(common_.cinfo.image_height)) { + return FXCODEC_STATUS::kDecodeFinished; + } + } +} + int LibjpegJpegContext::ReadHeader(int* width, int* height, int* nComps, - CFX_DIBAttribute* pAttribute) { - DCHECK(pAttribute); - - int ret = jpeg_common_read_header(&common_, TRUE); - if (ret == -1) { + CFX_DIBAttribute* attribute) { + DCHECK(create_ok_); + if (setjmp(common_.jmpbuf)) { return kFatal; } + int ret = jpeg_read_header(&common_.cinfo, true); if (ret == JPEG_SUSPENDED) { return kNeedsMoreInput; } @@ -92,7 +173,7 @@ int LibjpegJpegContext::ReadHeader(int* width, *width = common_.cinfo.image_width; *height = common_.cinfo.image_height; *nComps = common_.cinfo.num_components; - JpegLoadAttribute(common_.cinfo, pAttribute); + JpegLoadAttribute(common_.cinfo, attribute); return kOk; } diff --git a/core/fxcodec/jpeg/libjpeg_jpeg_context.h b/core/fxcodec/jpeg/libjpeg_jpeg_context.h index 15096c7ccd..a3abe7a247 100644 --- a/core/fxcodec/jpeg/libjpeg_jpeg_context.h +++ b/core/fxcodec/jpeg/libjpeg_jpeg_context.h @@ -5,13 +5,18 @@ #ifndef CORE_FXCODEC_JPEG_LIBJPEG_JPEG_CONTEXT_H_ #define CORE_FXCODEC_JPEG_LIBJPEG_JPEG_CONTEXT_H_ +#include + +#include "core/fxcodec/fx_codec_def.h" #include "core/fxcodec/jpeg/jpeg_common.h" #include "core/fxcodec/progressive_decoder_context.h" #include "core/fxcrt/retain_ptr.h" +#include "core/fxcrt/unowned_ptr.h" namespace fxcodec { class CFX_DIBAttribute; +class ProgressiveDecoderContextDelegate; class LibjpegJpegContext final : public ProgressiveDecoderContext { public: @@ -21,22 +26,33 @@ class LibjpegJpegContext final : public ProgressiveDecoderContext { static constexpr int kError = 1; static constexpr int kNeedsMoreInput = 2; - LibjpegJpegContext(); + explicit LibjpegJpegContext(ProgressiveDecoderContextDelegate* delegate); ~LibjpegJpegContext() override; // ProgressiveDecoderContext: FX_FILESIZE GetAvailInput() const override; void Input(RetainPtr codec_memory) override; + FXCODEC_STATUS StartDecode(); + FXCODEC_STATUS ContinueDecode(); + int ReadHeader(int* width, int* height, int* nComps, - CFX_DIBAttribute* pAttribute); + CFX_DIBAttribute* attribute); bool StartScanline(); int ReadScanline(uint8_t* dest_buf); JpegCommon common_ = {}; bool create_ok_ = false; + + private: + void SyncCodecMemory(); + + UnownedPtr const delegate_; + RetainPtr codec_memory_; + std::vector line_buf_; + int decode_row_ = 0; }; } // namespace fxcodec diff --git a/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp b/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp new file mode 100644 index 0000000000..ba6264b725 --- /dev/null +++ b/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp @@ -0,0 +1,382 @@ +// 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. + +// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com + +#include "core/fxcodec/jpeg/libjpeg_scanline_decoder.h" + +#include +#include +#include +#include + +#include "core/fxcodec/jpeg/jpeg_common.h" +#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" +#include "core/fxcrt/compiler_specific.h" +#include "core/fxcrt/data_vector.h" +#include "core/fxcrt/fx_safe_types.h" +#include "core/fxcrt/raw_span.h" + +namespace fxcodec { + +namespace { + +constexpr size_t kKnownBadHeaderWithInvalidHeightByteOffsetStarts[] = {94, 163}; + +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; +} + +// Returns the output dimension libjpeg produces for `dim` when decoding at +// 1/`scale_denom` scale. Matches jpeg_core_output_dimensions() for the +// 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; +} + +} // namespace + +// static +std::unique_ptr LibjpegScanlineDecoder::Create( + pdfium::span src_span, + uint32_t width, + uint32_t height, + int nComps, + bool ColorTransform, + uint32_t scale_denom) { + auto decoder = std::make_unique(); + if (!decoder->CreateImpl(src_span, width, height, nComps, ColorTransform, + scale_denom)) { + return nullptr; + } + return decoder; +} + +LibjpegScanlineDecoder::LibjpegScanlineDecoder() = default; + +LibjpegScanlineDecoder::~LibjpegScanlineDecoder() { + if (decompress_created_) { + jpeg_common_destroy_decompress(&common_); + } + + // Span in superclass can't outlive our buffer. + last_scanline_ = pdfium::span(); +} + +bool LibjpegScanlineDecoder::InitDecode(bool bAcceptKnownBadHeader) { + common_.cinfo.err = &common_.error_mgr; + common_.cinfo.client_data = &common_; + if (!jpeg_common_create_decompress(&common_)) { + return false; + } + decompress_created_ = true; + common_.cinfo.image_width = orig_width_; + common_.cinfo.image_height = orig_height_; + InitDecompressSrc(); + if (jpeg_common_read_header(&common_, TRUE) != JPEG_HEADER_OK) { + std::optional known_bad_header_offset; + if (bAcceptKnownBadHeader) { + for (size_t offset : kKnownBadHeaderWithInvalidHeightByteOffsetStarts) { + if (HasKnownBadHeaderWithInvalidHeight(offset)) { + known_bad_header_offset = offset; + break; + } + } + } + jpeg_common_destroy_decompress(&common_); + decompress_created_ = false; + if (!known_bad_header_offset.has_value()) { + return false; + } + PatchUpKnownBadHeaderWithInvalidHeight(known_bad_header_offset.value()); + if (!jpeg_common_create_decompress(&common_)) { + return false; + } + decompress_created_ = true; + common_.cinfo.image_width = orig_width_; + common_.cinfo.image_height = orig_height_; + InitDecompressSrc(); + if (jpeg_common_read_header(&common_, TRUE) != JPEG_HEADER_OK) { + jpeg_common_destroy_decompress(&common_); + decompress_created_ = false; + return false; + } + } + if (common_.cinfo.saw_Adobe_marker) { + jpeg_transform_ = true; + } + + if (common_.cinfo.num_components == 3 && !jpeg_transform_) { + common_.cinfo.out_color_space = common_.cinfo.jpeg_color_space; + } + + orig_width_ = common_.cinfo.image_width; + orig_height_ = common_.cinfo.image_height; + + // Reduced-size (scale_denom) decoding collapses each 8x8 DCT block toward its + // low-frequency content, ultimately its average at 1/8. If a dimension is not + // a multiple of the MCU size, the encoder had to pad the partial edge blocks; + // that padding is not required to replicate the edge (some encoders fill it + // with black), and reduced decoding cannot crop it back out, so it would + // contaminate the visible edge pixels (e.g. a too-dark right/bottom fringe). + // Only decode at reduced size when there are no padded edge blocks in any + // component, which makes the result exact regardless of the encoder. The MCU + // size in pixels is max_samp_factor * DCTSIZE; derive it from the per- + // component sampling factors, which are valid immediately after read_header. + // See https://crbug.com/890745 and + // https://github.com/libjpeg-turbo/libjpeg-turbo/issues/297. + int max_h_samp = 1; + int max_v_samp = 1; + // SAFETY: libjpeg guarantees `comp_info` points to `num_components` entries + // once the header has been read. + auto comp_info = UNSAFE_BUFFERS( + pdfium::span(common_.cinfo.comp_info, + static_cast(common_.cinfo.num_components))); + for (const jpeg_component_info& comp : comp_info) { + max_h_samp = std::max(max_h_samp, comp.h_samp_factor); + max_v_samp = std::max(max_v_samp, comp.v_samp_factor); + } + if (orig_width_ % (max_h_samp * DCTSIZE) != 0 || + orig_height_ % (max_v_samp * DCTSIZE) != 0) { + // Reset to 1 (no reduction). This is equivalent to using + // common_.cinfo.scale_denom, which libjpeg leaves at its default of 1 until + // jpeg_start_decompress(); that has not run yet here. + scale_denom_ = 1; + } + + output_width_ = ScaledJpegSize(orig_width_, scale_denom_); + output_height_ = ScaledJpegSize(orig_height_, scale_denom_); + return true; +} + +bool LibjpegScanlineDecoder::CreateImpl(pdfium::span src_span, + uint32_t width, + uint32_t height, + int nComps, + bool ColorTransform, + uint32_t scale_denom) { + // 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 + // and lead to a scanline-buffer overflow. + DCHECK(scale_denom == 1 || scale_denom == 2 || scale_denom == 4 || + scale_denom == 8); + scale_denom_ = scale_denom; + src_span_ = JpegScanSOI(src_span); + if (src_span_.size() < 2) { + return false; + } + + PatchUpTrailer(); + + common_.error_mgr.error_exit = jpeg_common_error_fatal; + common_.error_mgr.emit_message = jpeg_common_error_do_nothing_int; + common_.error_mgr.output_message = jpeg_common_error_do_nothing; + common_.error_mgr.format_message = jpeg_common_error_do_nothing_char; + common_.error_mgr.reset_error_mgr = jpeg_common_error_do_nothing; + common_.source_mgr.init_source = jpeg_common_src_do_nothing; + common_.source_mgr.term_source = jpeg_common_src_do_nothing; + common_.source_mgr.skip_input_data = jpeg_common_src_skip_data_or_trap; + common_.source_mgr.fill_input_buffer = jpeg_common_src_fill_buffer; + common_.source_mgr.resync_to_restart = jpeg_common_src_resync; + jpeg_transform_ = ColorTransform; + output_width_ = orig_width_ = width; + output_height_ = orig_height_ = height; + if (!InitDecode(/*bAcceptKnownBadHeader=*/true)) { + return false; + } + + if (common_.cinfo.num_components < nComps) { + return false; + } + + if (common_.cinfo.image_width < width) { + return false; + } + + CalcPitch(); + scanline_buf_ = DataVector(pitch_); + comps_ = common_.cinfo.num_components; + bpc_ = 8; + started_ = false; + return true; +} + +bool LibjpegScanlineDecoder::Rewind() { + if (started_) { + jpeg_common_destroy_decompress(&common_); + if (!InitDecode(/*bAcceptKnownBadHeader=*/false)) { + return false; + } + } + // `CreateImpl()` validated `scale_denom_` and, for non-MCU-aligned images, + // reset it to 1 in `InitDecode()` (called from `CreateImpl()`, and again just + // above when re-decoding), so it is safe to use as-is here. + common_.cinfo.scale_denom = scale_denom_; + output_width_ = ScaledJpegSize(orig_width_, scale_denom_); + output_height_ = ScaledJpegSize(orig_height_, scale_denom_); + if (!jpeg_common_start_decompress(&common_)) { + jpeg_common_destroy_decompress(&common_); + return false; + } + // `CalcPitch()` sized the scanline buffer for `output_width_` columns, + // so libjpeg must not produce more than that per scanline. + CHECK_LE(static_cast(common_.cinfo.output_width), output_width_); + started_ = true; + return true; +} + +pdfium::span LibjpegScanlineDecoder::GetNextLine() { + uint8_t* row_array[] = {scanline_buf_.data()}; + int nlines = jpeg_common_read_scanlines(&common_, row_array, 1u); + if (nlines <= 0) { + return pdfium::span(); + } + return scanline_buf_; +} + +uint32_t LibjpegScanlineDecoder::GetSrcOffset() { + return static_cast(src_span_.size() - + common_.source_mgr.bytes_in_buffer); +} + +void LibjpegScanlineDecoder::CalcPitch() { + // `output_width_` must be finalized (post scale/alignment decision in + // `InitDecode()`) before this runs; the sole caller is `CreateImpl()`, after + // `InitDecode()`. + DCHECK_GT(output_width_, 0); + pitch_ = static_cast(output_width_) * common_.cinfo.num_components; + pitch_ += 3; + pitch_ /= 4; + pitch_ *= 4; +} + +void LibjpegScanlineDecoder::InitDecompressSrc() { + common_.cinfo.src = &common_.source_mgr; + common_.source_mgr.bytes_in_buffer = src_span_.size(); + common_.source_mgr.next_input_byte = src_span_.data(); +} + +bool LibjpegScanlineDecoder::HasKnownBadHeaderWithInvalidHeight( + size_t dimension_offset) const { + // Perform lots of possibly redundant checks to make sure this has no false + // positives. + bool bDimensionChecks = + common_.cinfo.err->msg_code == JERR_IMAGE_TOO_BIG && + common_.cinfo.image_width < JPEG_MAX_DIMENSION && + common_.cinfo.image_height == 0xffff && orig_width_ > 0 && + orig_width_ <= JPEG_MAX_DIMENSION && orig_height_ > 0 && + orig_height_ <= JPEG_MAX_DIMENSION; + if (!bDimensionChecks) { + return false; + } + + if (src_span_.size() <= dimension_offset + 3u) { + return false; + } + + if (!IsSofSegment(dimension_offset - kSofMarkerByteOffset)) { + return false; + } + + const auto pHeaderDimensions = src_span_.subspan(dimension_offset); + uint8_t nExpectedWidthByte1 = (orig_width_ >> 8) & 0xff; + uint8_t nExpectedWidthByte2 = orig_width_ & 0xff; + // Height high byte, height low byte, width high byte, width low byte. + return pHeaderDimensions[0] == 0xff && pHeaderDimensions[1] == 0xff && + pHeaderDimensions[2] == nExpectedWidthByte1 && + pHeaderDimensions[3] == nExpectedWidthByte2; +} + +bool LibjpegScanlineDecoder::IsSofSegment(size_t marker_offset) const { + const auto pHeaderMarker = src_span_.subspan(marker_offset); + return pHeaderMarker[0] == 0xff && pHeaderMarker[1] >= 0xc0 && + pHeaderMarker[1] <= 0xcf; +} + +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; +} + +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) { + return; + } + auto pData = GetWritableSrcData(); + pData[src_span_.size() - 2] = 0xff; + pData[src_span_.size() - 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_); + } + return patched_src_; +} + +// static +std::optional LibjpegScanlineDecoder::LoadInfo( + pdfium::span src_span) { + src_span = JpegScanSOI(src_span); + + JpegCommon jpeg_common = {}; + jpeg_common.error_mgr.error_exit = jpeg_common_error_fatal; + jpeg_common.error_mgr.emit_message = jpeg_common_error_do_nothing_int; + jpeg_common.error_mgr.output_message = jpeg_common_error_do_nothing; + jpeg_common.error_mgr.format_message = jpeg_common_error_do_nothing_char; + jpeg_common.error_mgr.reset_error_mgr = jpeg_common_error_do_nothing; + jpeg_common.error_mgr.trace_level = 0; + jpeg_common.cinfo.err = &jpeg_common.error_mgr; + jpeg_common.cinfo.client_data = &jpeg_common; + if (!jpeg_common_create_decompress(&jpeg_common)) { + return std::nullopt; + } + + jpeg_common.source_mgr.init_source = jpeg_common_src_do_nothing; + jpeg_common.source_mgr.term_source = jpeg_common_src_do_nothing; + jpeg_common.source_mgr.skip_input_data = jpeg_common_src_skip_data_or_trap; + jpeg_common.source_mgr.fill_input_buffer = jpeg_common_src_fill_buffer; + jpeg_common.source_mgr.resync_to_restart = jpeg_common_src_resync; + jpeg_common.source_mgr.bytes_in_buffer = src_span.size(); + jpeg_common.source_mgr.next_input_byte = src_span.data(); + jpeg_common.cinfo.src = &jpeg_common.source_mgr; + if (jpeg_common_read_header(&jpeg_common, TRUE) != JPEG_HEADER_OK) { + jpeg_common_destroy_decompress(&jpeg_common); + return std::nullopt; + } + + JpegModule::ImageInfo info; + info.width = jpeg_common.cinfo.image_width; + info.height = jpeg_common.cinfo.image_height; + info.num_components = jpeg_common.cinfo.num_components; + info.color_transform = jpeg_common.cinfo.jpeg_color_space == JCS_YCbCr || + jpeg_common.cinfo.jpeg_color_space == JCS_YCCK; + info.bits_per_components = jpeg_common.cinfo.data_precision; + jpeg_common_destroy_decompress(&jpeg_common); + return info; +} + +} // namespace fxcodec diff --git a/core/fxcodec/jpeg/libjpeg_scanline_decoder.h b/core/fxcodec/jpeg/libjpeg_scanline_decoder.h new file mode 100644 index 0000000000..fa729b9477 --- /dev/null +++ b/core/fxcodec/jpeg/libjpeg_scanline_decoder.h @@ -0,0 +1,82 @@ +// 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_LIBJPEG_SCANLINE_DECODER_H_ +#define CORE_FXCODEC_JPEG_LIBJPEG_SCANLINE_DECODER_H_ + +#include +#include + +#include +#include + +#include "core/fxcodec/jpeg/jpeg_common.h" +#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" + +namespace fxcodec { + +class LibjpegScanlineDecoder final : public ScanlineDecoder { + public: + static std::unique_ptr Create( + pdfium::span src_span, + uint32_t width, + uint32_t height, + int nComps, + bool ColorTransform, + uint32_t scale_denom); + + static std::optional LoadInfo( + pdfium::span src_span); + + LibjpegScanlineDecoder(); + ~LibjpegScanlineDecoder() override; + + // ScanlineDecoder: + [[nodiscard]] bool Rewind() override; + pdfium::span GetNextLine() override; + uint32_t GetSrcOffset() override; + + private: + bool CreateImpl(pdfium::span src_span, + uint32_t width, + uint32_t height, + int nComps, + bool ColorTransform, + uint32_t scale_denom); + 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. + 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 + // |kKnownBadHeaderWithInvalidHeightByteOffsetStarts|, the SOFn marker should + // be this many bytes before that. + static constexpr size_t kSofMarkerByteOffset = 5; + + JpegCommon common_ = {}; + 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; + uint32_t scale_denom_ = 1; +}; + +} // namespace fxcodec + +#endif // CORE_FXCODEC_JPEG_LIBJPEG_SCANLINE_DECODER_H_ diff --git a/core/fxcodec/jpeg/skia_jpeg_context.cpp b/core/fxcodec/jpeg/skia_jpeg_context.cpp new file mode 100644 index 0000000000..7d508c3d68 --- /dev/null +++ b/core/fxcodec/jpeg/skia_jpeg_context.cpp @@ -0,0 +1,149 @@ +// 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 new file mode 100644 index 0000000000..2392a533f8 --- /dev/null +++ b/core/fxcodec/jpeg/skia_jpeg_context.h @@ -0,0 +1,57 @@ +// 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 new file mode 100644 index 0000000000..875601cc26 --- /dev/null +++ b/core/fxcodec/jpeg/skia_scanline_decoder.cpp @@ -0,0 +1,290 @@ +// 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/jpeg/skia_scanline_decoder.h b/core/fxcodec/jpeg/skia_scanline_decoder.h new file mode 100644 index 0000000000..e96550e909 --- /dev/null +++ b/core/fxcodec/jpeg/skia_scanline_decoder.h @@ -0,0 +1,60 @@ +// 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_SCANLINE_DECODER_H_ +#define CORE_FXCODEC_JPEG_SKIA_SCANLINE_DECODER_H_ + +#include + +#include +#include + +#include "core/fxcodec/jpeg/jpegmodule.h" +#include "core/fxcodec/scanlinedecoder.h" +#include "core/fxcrt/data_vector.h" +#include "core/fxcrt/span.h" + +class SkCodec; + +namespace fxcodec { + +class SkiaScanlineDecoder final : public ScanlineDecoder { + public: + static std::unique_ptr Create( + pdfium::span src_span, + uint32_t width, + uint32_t height, + int num_components, + bool color_transform, + uint32_t scale_denom); + + static std::optional LoadInfo( + pdfium::span src_span); + + SkiaScanlineDecoder(); + ~SkiaScanlineDecoder() override; + + // ScanlineDecoder: + [[nodiscard]] bool Rewind() override; + pdfium::span GetNextLine() override; + uint32_t GetSrcOffset() override; + + private: + bool CreateImpl(pdfium::span src_span, + uint32_t width, + uint32_t height, + int num_components, + bool color_transform, + uint32_t scale_denom); + void CalcPitch(); + + std::unique_ptr decoder_; + DataVector scanline_buf_; + DataVector row_decode_buf_; + uint32_t scale_denom_ = 1; +}; + +} // namespace fxcodec + +#endif // CORE_FXCODEC_JPEG_SKIA_SCANLINE_DECODER_H_ diff --git a/core/fxcodec/png/libpng_png_context.cpp b/core/fxcodec/png/libpng_png_context.cpp index cd09992cfb..8dd1d963ab 100644 --- a/core/fxcodec/png/libpng_png_context.cpp +++ b/core/fxcodec/png/libpng_png_context.cpp @@ -7,11 +7,15 @@ #include #include +#include + #include "core/fxcodec/cfx_codec_memory.h" +#include "core/fxcodec/progressive_decoder_context_delegate.h" #include "core/fxcrt/check.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/retain_ptr.h" #include "core/fxcrt/span.h" +#include "core/fxge/dib/cfx_dibitmap.h" #ifdef USE_SYSTEM_LIBPNG #include @@ -19,6 +23,12 @@ #include "third_party/libpng/png.h" #endif +namespace { + +constexpr double kPngGamma = 2.2; + +} // namespace + extern "C" { void _png_error_data(png_structp png_ptr, png_const_charp error_msg) { @@ -59,20 +69,22 @@ void _png_get_header_func(png_structp png_ptr, png_infop info_ptr) { context->number_of_passes_ = png_set_interlace_handling(png_ptr); context->height_ = height; - double gamma = 1.0; - if (!context->delegate_->PngReadHeader(width, height, &gamma)) { + // Notifies the delegate of image dimensions and metadata. + if (!context->delegate_->PrepareDirectOutput( + width, height, + fxcodec::ProgressiveDecoderContextDelegate::Format::kArgb)) { // Note that `png_error` function is marked as `PNG_NORETURN`. png_error(context->png_, "Read Header Callback Error"); } int intent; if (png_get_sRGB(png_ptr, info_ptr, &intent)) { - png_set_gamma(png_ptr, gamma, 0.45455); + png_set_gamma(png_ptr, kPngGamma, 0.45455); } else { double image_gamma; if (png_get_gAMA(png_ptr, info_ptr, &image_gamma)) { - png_set_gamma(png_ptr, gamma, image_gamma); + png_set_gamma(png_ptr, kPngGamma, image_gamma); } else { - png_set_gamma(png_ptr, gamma, 0.45455); + png_set_gamma(png_ptr, kPngGamma, 0.45455); } } if (!(libpng_color_type & PNG_COLOR_MASK_COLOR)) { @@ -93,18 +105,17 @@ void _png_get_row_func(png_structp png_ptr, int pass) { auto* context = reinterpret_cast( png_get_progressive_ptr(png_ptr)); - if (!context) { + if (!context || !context->bitmap_) { return; } - pdfium::span dst_buf = - context->delegate_->PngAskScanlineBuf(row_num); + pdfium::span dst_buf = context->delegate_->AskScanlineBuf(row_num); CHECK(!dst_buf.empty()); png_progressive_combine_row(png_ptr, dst_buf.data(), new_row); if ((pass == (context->number_of_passes_ - 1)) && (row_num == (context->height_ - 1))) { - context->delegate_->PngFinishedDecoding(); + context->finished_ = true; } } @@ -124,8 +135,8 @@ int _png_set_read_and_error_fns(png_structrp png_ptr, namespace fxcodec { -LibpngPngContext::LibpngPngContext(PngDecoderDelegate* pDelegate) - : delegate_(pDelegate) { +LibpngPngContext::LibpngPngContext(ProgressiveDecoderContextDelegate* delegate) + : delegate_(delegate) { png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!png_) { @@ -147,7 +158,44 @@ LibpngPngContext::~LibpngPngContext() { nullptr); } -bool LibpngPngContext::ContinueDecode(RetainPtr codec_memory) { +void LibpngPngContext::Input(RetainPtr codec_memory) { + codec_memory_ = std::move(codec_memory); +} + +FXCODEC_STATUS LibpngPngContext::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 LibpngPngContext::ContinueDecode() { + FXCODEC_STATUS status = FXCODEC_STATUS::kDecodeFinished; + while (!finished_) { + if (!ProcessData(codec_memory_)) { + status = FXCODEC_STATUS::kError; + break; + } + if (finished_) { + break; + } + status = FXCODEC_STATUS::kError; + if (!delegate_->ReadMoreData(std::nullopt, &status)) { + break; + } + } + bitmap_ = nullptr; + return finished_ ? FXCODEC_STATUS::kDecodeFinished : status; +} + +bool LibpngPngContext::ReadHeader(RetainPtr codec_memory) { + return ProcessData(std::move(codec_memory)); +} + +bool LibpngPngContext::ProcessData(RetainPtr codec_memory) { if (setjmp(png_jmpbuf(png_))) { return false; } diff --git a/core/fxcodec/png/libpng_png_context.h b/core/fxcodec/png/libpng_png_context.h index d1e5bcb9e5..ab43963cb0 100644 --- a/core/fxcodec/png/libpng_png_context.h +++ b/core/fxcodec/png/libpng_png_context.h @@ -8,32 +8,47 @@ #include #include "core/fxcodec/cfx_codec_memory.h" -#include "core/fxcodec/png/png_decoder_delegate.h" +#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" #include "core/fxcrt/unowned_ptr_exclusion.h" +#include "core/fxge/dib/cfx_dibitmap.h" struct png_info_def; struct png_struct_def; namespace fxcodec { +class ProgressiveDecoderContextDelegate; + class LibpngPngContext final : public ProgressiveDecoderContext { public: static constexpr size_t kPngErrorSize = 256; - explicit LibpngPngContext(PngDecoderDelegate* pDelegate); + explicit LibpngPngContext(ProgressiveDecoderContextDelegate* delegate); ~LibpngPngContext() override; - bool ContinueDecode(RetainPtr codec_memory); + // ProgressiveDecoderContext: + void Input(RetainPtr codec_memory) override; + + FXCODEC_STATUS StartDecode(RetainPtr bitmap); + FXCODEC_STATUS ContinueDecode(); + + bool ReadHeader(RetainPtr codec_memory); UNOWNED_PTR_EXCLUSION png_struct_def* png_ = nullptr; UNOWNED_PTR_EXCLUSION png_info_def* info_ = nullptr; - UnownedPtr const delegate_; + UnownedPtr const delegate_; + RetainPtr bitmap_; + RetainPtr codec_memory_; char last_error_[kPngErrorSize] = {}; uint32_t height_ = 0; int number_of_passes_ = 0; + bool finished_ = false; + + private: + bool ProcessData(RetainPtr codec_memory); }; } // namespace fxcodec diff --git a/core/fxcodec/png/png_decoder_delegate.h b/core/fxcodec/png/png_decoder_delegate.h deleted file mode 100644 index 1d73af9a18..0000000000 --- a/core/fxcodec/png/png_decoder_delegate.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2025 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_PNG_PNG_DECODER_DELEGATE_H_ -#define CORE_FXCODEC_PNG_PNG_DECODER_DELEGATE_H_ - -#include - -#include "core/fxcrt/span.h" - -namespace fxcodec { - -// Abstract interface used by `libpng_png_context.h` and `skia_png_context.h`. -class PngDecoderDelegate { - public: - virtual ~PngDecoderDelegate() = default; - - // Called by `PngDecoder` after decoding the image header with - // `width` and `height` that specify image dimensions in pixels - // - // Implementation should: - // - // * Return `true` upon success (and `false` otherwise) - // * Set `*gamma` to the target gamma to decode with - // * TODO(crbug.com/355630556): Add out parameter for desired alpha-premul. - virtual bool PngReadHeader(int width, int height, double* gamma) = 0; - - // Called by `PngDecoder` to ask where to write decoded BGRA/8 pixels. - // Implementation should return a span to the buffer where decoder can write - // decoded pixels from the given `line`. - // - // `PngDecoder` guarantees that `0 <= line && line < height` - // (`height` that was earlier passed to `PngReadHeader`). - virtual pdfium::span PngAskScanlineBuf(int line) = 0; - - // Called by `PngDecoder` to ask where to write decoded BGRA/8 pixels. - // Implementation should return a span to the buffer where decoder can write - // all the decoded pixels (i.e. covering all rows/lines of the image). - virtual pdfium::span PngAskImageBuf() = 0; - - // Called by `PngDecoder` to communicate that all pixels have been decoded. - virtual void PngFinishedDecoding() = 0; -}; - -} // namespace fxcodec - -#endif // CORE_FXCODEC_PNG_PNG_DECODER_DELEGATE_H_ diff --git a/core/fxcodec/png/rust_png_context.cpp b/core/fxcodec/png/rust_png_context.cpp new file mode 100644 index 0000000000..5135e13942 --- /dev/null +++ b/core/fxcodec/png/rust_png_context.cpp @@ -0,0 +1,113 @@ +// 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/png/rust_png_context.h" + +#include + +#include "core/fxcodec/cfx_codec_memory.h" +#include "core/fxcodec/png/rust_png_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/span.h" +#include "core/fxge/dib/cfx_dibitmap.h" + +namespace fxcodec { + +RustPngContext::RustPngContext(ProgressiveDecoderContextDelegate* delegate) + : delegate_(delegate) {} + +RustPngContext::~RustPngContext() = default; + +bool RustPngContext::ReadHeader() { + if (state_ == State::kGotHeader || state_ == State::kFinishedDecoding) { + return true; + } + if (state_ == State::kError || !codec_memory_) { + return false; + } + CHECK_EQ(state_, State::kNoDecoder); + + rust_png::PngHeaderInfo header_info{}; + pdfium::span src_span = codec_memory_->GetBufferSpan(); + rust::Slice src_slice(src_span); + if (!rust_png::read_png_info(src_slice, header_info)) { + return false; + } + + width_ = header_info.width; + height_ = header_info.height; + if (!delegate_->PrepareDirectOutput( + width_, height_, ProgressiveDecoderContextDelegate::Format::kArgb)) { + state_ = State::kError; + return false; + } + state_ = State::kGotHeader; + return true; +} + +bool RustPngContext::ProcessData() { + if (state_ == State::kNoDecoder) { + if (!ReadHeader()) { + return false; + } + } + + if (state_ == State::kGotHeader) { + pdfium::span src_span = codec_memory_->GetBufferSpan(); + rust::Slice src_slice(src_span); + pdfium::span dst_buffer = delegate_->AskImageBuf(); + uint32_t pitch = bitmap_->GetPitch(); + rust::Slice dst_slice(dst_buffer); + if (!rust_png::decode_png_to_buf(src_slice, dst_slice, pitch)) { + return false; + } + state_ = State::kFinishedDecoding; + return true; + } + + CHECK_EQ(state_, State::kFinishedDecoding); + return true; +} + +void RustPngContext::Input(RetainPtr codec_memory) { + codec_memory_ = std::move(codec_memory); +} + +FXCODEC_STATUS RustPngContext::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 RustPngContext::ContinueDecode() { + FXCODEC_STATUS status = FXCODEC_STATUS::kDecodeFinished; + while (state_ != State::kFinishedDecoding) { + if (!ProcessData()) { + status = FXCODEC_STATUS::kError; + if (!delegate_->ReadMoreData(std::nullopt, &status)) { + if (status != FXCODEC_STATUS::kDecodeToBeContinued) { + state_ = State::kError; + } + break; + } + continue; + } + if (state_ == State::kFinishedDecoding) { + break; + } + } + if (status != FXCODEC_STATUS::kDecodeToBeContinued) { + bitmap_ = nullptr; + } + return state_ == State::kFinishedDecoding ? FXCODEC_STATUS::kDecodeFinished + : status; +} + +} // namespace fxcodec diff --git a/core/fxcodec/png/rust_png_context.h b/core/fxcodec/png/rust_png_context.h new file mode 100644 index 0000000000..296f3e1b8a --- /dev/null +++ b/core/fxcodec/png/rust_png_context.h @@ -0,0 +1,55 @@ +// 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_PNG_RUST_PNG_CONTEXT_H_ +#define CORE_FXCODEC_PNG_RUST_PNG_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_DIBitmap; + +namespace fxcodec { + +class ProgressiveDecoderContextDelegate; + +class RustPngContext final : public ProgressiveDecoderContext { + public: + explicit RustPngContext(ProgressiveDecoderContextDelegate* delegate); + ~RustPngContext() override; + + bool ReadHeader(); + bool has_error() const { return state_ == State::kError; } + + // ProgressiveDecoderContext: + void Input(RetainPtr codec_memory) override; + + FXCODEC_STATUS StartDecode(RetainPtr bitmap); + FXCODEC_STATUS ContinueDecode(); + + private: + enum class State { + kNoDecoder, + kGotHeader, + kFinishedDecoding, + kError, + }; + + bool ProcessData(); + + State state_ = State::kNoDecoder; + uint32_t width_ = 0; + uint32_t height_ = 0; + UnownedPtr const delegate_; + RetainPtr codec_memory_; + RetainPtr bitmap_; +}; + +} // namespace fxcodec + +#endif // CORE_FXCODEC_PNG_RUST_PNG_CONTEXT_H_ diff --git a/core/fxcodec/png/rust_png_ffi.rs b/core/fxcodec/png/rust_png_ffi.rs new file mode 100644 index 0000000000..ef8853a2a8 --- /dev/null +++ b/core/fxcodec/png/rust_png_ffi.rs @@ -0,0 +1,180 @@ +// 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 std::io::Cursor; + +#[cxx::bridge(namespace = "fxcodec::rust_png")] +mod ffi { + pub struct PngHeaderInfo { + pub width: u32, + pub height: u32, + } + + extern "Rust" { + fn read_png_info(src: &[u8], info: &mut PngHeaderInfo) -> bool; + fn decode_png_to_buf(src: &[u8], dst: &mut [u8], dst_pitch: usize) -> bool; + } +} + +fn read_png_info(src: &[u8], info: &mut ffi::PngHeaderInfo) -> bool { + if src.len() >= 24 + && &src[0..8] == b"\x89PNG\r\n\x1a\n" + && &src[12..16] == b"IHDR" + && let (Ok(w), Ok(h)) = (src[16..20].try_into(), src[20..24].try_into()) + { + let width = u32::from_be_bytes(w); + let height = u32::from_be_bytes(h); + if width > 0 && height > 0 { + info.width = width; + info.height = height; + return true; + } + } + + let cursor = Cursor::new(src); + let decoder = png::Decoder::new(cursor); + let reader = match decoder.read_info() { + Ok(r) => r, + Err(_) => return false, + }; + let header = reader.info(); + if header.width == 0 || header.height == 0 { + return false; + } + info.width = header.width; + info.height = header.height; + true +} + +fn decode_png_to_buf(src: &[u8], dst: &mut [u8], dst_pitch: usize) -> bool { + let cursor = Cursor::new(src); + let mut decoder = png::Decoder::new(cursor); + decoder.set_transformations(png::Transformations::EXPAND | png::Transformations::STRIP_16); + let mut reader = match decoder.read_info() { + Ok(r) => r, + Err(_) => return false, + }; + let buf_size = match reader.output_buffer_size() { + Some(s) => s, + None => return false, + }; + let mut buf = vec![0; buf_size]; + let output_info = match reader.next_frame(&mut buf) { + Ok(info) => info, + Err(_) => return false, + }; + let width = output_info.width as usize; + let height = output_info.height as usize; + if width == 0 || height == 0 { + return false; + } + let line_size = output_info.line_size; + if line_size < width { + return false; + } + let bytes_per_pixel = line_size / width; + if bytes_per_pixel == 0 || bytes_per_pixel > 4 { + return false; + } + if let Some(total_src_bytes) = line_size.checked_mul(height) { + if total_src_bytes > buf.len() { + return false; + } + } else { + return false; + } + + let row_dst_bytes = match width.checked_mul(4) { + Some(b) => b, + None => return false, + }; + + // Standard default file gamma for sRGB (1.0 / 2.2). + const DEFAULT_FILE_GAMMA: f64 = 0.45455; + let file_gamma: f64 = if reader.info().srgb.is_some() { + DEFAULT_FILE_GAMMA + } else if let Some(gama) = reader.info().gama_chunk { + let val = gama.into_value() as f64; + if val > 0.0 { + val + } else { + DEFAULT_FILE_GAMMA + } + } else { + DEFAULT_FILE_GAMMA + }; + + const DISPLAY_GAMMA: f64 = 2.2; + let mut gamma_lut = [0u8; 256]; + let exponent = 1.0 / (file_gamma * DISPLAY_GAMMA); + if (exponent - 1.0).abs() < 1e-4 { + for (i, item) in gamma_lut.iter_mut().enumerate() { + *item = i as u8; + } + } else { + for (i, item) in gamma_lut.iter_mut().enumerate() { + let v = (i as f64) / 255.0; + *item = (v.powf(exponent) * 255.0).round().clamp(0.0, 255.0) as u8; + } + } + + for y in 0..height { + let src_start = y * line_size; + let src_row = &buf[src_start..src_start + line_size]; + let dst_start = match y.checked_mul(dst_pitch) { + Some(s) => s, + None => return false, + }; + let dst_end = match dst_start.checked_add(row_dst_bytes) { + Some(e) => e, + None => return false, + }; + if dst_end > dst.len() { + return false; + } + let dst_slice = &mut dst[dst_start..dst_end]; + if bytes_per_pixel == 1 { + for x in 0..width { + let g = gamma_lut[src_row[x] as usize]; + dst_slice[x * 4] = g; + dst_slice[x * 4 + 1] = g; + dst_slice[x * 4 + 2] = g; + dst_slice[x * 4 + 3] = 255; + } + } else if bytes_per_pixel == 2 { + for x in 0..width { + let g = gamma_lut[src_row[x * 2] as usize]; + let a = src_row[x * 2 + 1]; + dst_slice[x * 4] = g; + dst_slice[x * 4 + 1] = g; + dst_slice[x * 4 + 2] = g; + dst_slice[x * 4 + 3] = a; + } + } else if bytes_per_pixel == 3 { + for x in 0..width { + let r = gamma_lut[src_row[x * 3] as usize]; + let g = gamma_lut[src_row[x * 3 + 1] as usize]; + let b = gamma_lut[src_row[x * 3 + 2] as usize]; + dst_slice[x * 4] = b; + dst_slice[x * 4 + 1] = g; + dst_slice[x * 4 + 2] = r; + dst_slice[x * 4 + 3] = 255; + } + } else if bytes_per_pixel == 4 { + for x in 0..width { + let r = gamma_lut[src_row[x * 4] as usize]; + let g = gamma_lut[src_row[x * 4 + 1] as usize]; + let b = gamma_lut[src_row[x * 4 + 2] as usize]; + let a = src_row[x * 4 + 3]; + dst_slice[x * 4] = b; + dst_slice[x * 4 + 1] = g; + dst_slice[x * 4 + 2] = r; + dst_slice[x * 4 + 3] = a; + } + } else { + return false; + } + } + true +} diff --git a/core/fxcodec/png/skia_png_context.cpp b/core/fxcodec/png/skia_png_context.cpp deleted file mode 100644 index 144c1b3c61..0000000000 --- a/core/fxcodec/png/skia_png_context.cpp +++ /dev/null @@ -1,143 +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/png/skia_png_context.h" - -#include - -#include "core/fxcodec/codec_memory_sk_stream.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 "third_party/skia/include/codec/SkCodec.h" -#include "third_party/skia/include/core/SkColorSpace.h" -#include "third_party/skia/include/core/SkColorType.h" -#include "third_party/skia/include/core/SkImageInfo.h" -#include "third_party/skia/include/core/SkRefCnt.h" -#include "third_party/skia/include/core/SkStream.h" - -#ifdef PDF_ENABLE_RUST_PNG -#include "third_party/skia/include/codec/SkPngRustDecoder.h" -#else -#include "third_party/skia/include/codec/SkPngDecoder.h" -#endif - -namespace fxcodec { - -namespace { - -sk_sp GetTargetColorSpace(double gamma) { - const skcms_TransferFunction fn = { - .g = static_cast(1.0 / gamma), - .a = 1.0f, - }; - return SkColorSpace::MakeRGB(fn, SkNamedGamut::kSRGB); -} - -} // namespace - -SkiaPngContext::SkiaPngContext(PngDecoderDelegate* delegate) - : delegate_(delegate) {} - -SkiaPngContext::~SkiaPngContext() = default; - -bool SkiaPngContext::ContinueDecode(RetainPtr codec_memory) { - // `ProgressiveDecoder` guarantees that all calls to `ContinueDecode` use the - // same `codec_memory`. Therefore `SkiaPngContext` expects that - // `codec_memory` passed to `CodecMemorySkStream` below remains the right one - // to use going forward. - if (!codec_memory_) { - codec_memory_ = codec_memory; - } - CHECK_EQ(&*codec_memory_, &*codec_memory); - - switch (state_) { - case State::kNoDecoder: { - CHECK(!decoder_); - auto stream = - std::make_unique(std::move(codec_memory)); - SkCodec::Result result = SkCodec::kSuccess; -#ifdef PDF_ENABLE_RUST_PNG - decoder_ = SkPngRustDecoder::Decode(std::move(stream), &result); -#else - decoder_ = SkPngDecoder::Decode(std::move(stream), &result); -#endif - switch (result) { - case SkCodec::kSuccess: { - SkImageInfo info = decoder_->getInfo(); - if (!delegate_->PngReadHeader(info.width(), info.height(), - &target_gamma_)) { - decoder_.reset(); - state_ = State::kError; - return false; - } - state_ = State::kGotDecoder; - break; // continue decoding - } - case SkCodec::kIncompleteInput: - // Rewind to start from the beginning of input when retrying later. - // This will also prompt `ProgressiveDecoder::ReadMoreData` to grow - // the `codec_memory_` as needed. - codec_memory_->Seek(0); - return true; // retry when called later with more data - default: - decoder_.reset(); - state_ = State::kError; - return false; // fatal error - } - break; - } - case State::kGotDecoder: - case State::kStartedDecode: - break; - case State::kFinishedDecoding: - case State::kError: - NOTREACHED(); - } - - if (state_ == State::kGotDecoder) { - SkImageInfo dst_info = - decoder_->getInfo() - .makeColorSpace(GetTargetColorSpace(target_gamma_)) - .makeColorType(kBGRA_8888_SkColorType); - - pdfium::span dst_buffer = delegate_->PngAskImageBuf(); - FX_SAFE_SIZE_T row_bytes = dst_buffer.size(); - row_bytes /= dst_info.height(); - - SkCodec::Result result = decoder_->startIncrementalDecode( - dst_info, dst_buffer.data(), row_bytes.ValueOrDie()); - switch (result) { - case SkCodec::kSuccess: - state_ = State::kStartedDecode; - break; // continue decoding - case SkCodec::kIncompleteInput: - return true; // retry when called later with more data - default: - decoder_.reset(); - state_ = State::kError; - return false; // fatal error - } - } - - CHECK_EQ(state_, State::kStartedDecode); - SkCodec::Result result = decoder_->incrementalDecode(nullptr); - switch (result) { - case SkCodec::kSuccess: - decoder_.reset(); - delegate_->PngFinishedDecoding(); - state_ = State::kFinishedDecoding; - return true; // finished decoding - case SkCodec::kIncompleteInput: - return true; // retry when called later with more data - default: - decoder_.reset(); - state_ = State::kError; - return false; // fatal error - } -} - -} // namespace fxcodec diff --git a/core/fxcodec/png/skia_png_context.h b/core/fxcodec/png/skia_png_context.h deleted file mode 100644 index f010bce38d..0000000000 --- a/core/fxcodec/png/skia_png_context.h +++ /dev/null @@ -1,88 +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_PNG_SKIA_PNG_CONTEXT_H_ -#define CORE_FXCODEC_PNG_SKIA_PNG_CONTEXT_H_ - -#include - -#include "core/fxcodec/cfx_codec_memory.h" -#include "core/fxcodec/png/png_decoder_delegate.h" -#include "core/fxcodec/progressive_decoder_context.h" -#include "core/fxcrt/retain_ptr.h" -#include "core/fxcrt/unowned_ptr.h" - -class SkCodec; - -namespace fxcodec { - -class SkiaPngContext final : public ProgressiveDecoderContext { - public: - // Caller needs to guarantee that `delegate` lives longer than - // `SkiaPngContext`. - explicit SkiaPngContext(PngDecoderDelegate* delegate); - ~SkiaPngContext() override; - - // Starts or resumes decoding `codec_memory`. - // - // Returns `false` upon failure. Returns `true` when either 1) the whole - // image has been successfully decoded or 2) the image has been partially - // decoded but decoding should be continued/retried when more input data - // is available. - // - // Communicates image metadata (once read/available) via - // `PngDecoderDelegate::PngReadHeader`. Writes decoded BGRA pixels to the - // buffer provided via `PngDecoderDelegate::PngAskImageBuf`. - bool ContinueDecode(RetainPtr codec_memory); - - private: - enum class State { - // `decoder_` is null. - // - // This is the initial state. - kNoDecoder, - - // `decoder_` is not null and `delegate_->PngReadHeader` succeeded - // and provided `target_gamma_`. - // - // `startIncrementalDecode` didn't run, or returned `kIncompleteInput`. - kGotDecoder, - - // `decoder_` is not null, got `target_gamma_`, and `startIncrementalDecode` - // has already suceeded. - // - // `incrementalDecode` didn't run yet, or returned `kIncompleteInput`. - kStartedDecode, - - // `decoder_` is null. All pixels have been decoded to - // `delegate_->PngAskImageBuf`. - // - // This is a terminal state. - kFinishedDecoding, - - // `decoder_` is null. A non-recoverable (i.e. non-`kIncompleteInput`-kind) - // error has been encountered. - // - // This is a terminal state. - kError, - }; - State state_ = State::kNoDecoder; - - UnownedPtr const delegate_; - std::unique_ptr decoder_; - double target_gamma_ = 0.0; - - // `CFX_CodecMemory` received in `ContinueDecode` may get wrapped in - // `CodecMemorySkStream` and become transitively owned by `decoder_`. This - // class retains `codec_memory_` to `CHECK` that all calls to `ContinueDecode` - // use the same `CFX_CodecMemory` - this helps to ensure that `decoder_` and - // `CodecMemorySkStream` won't accidentally use stale input data if a future, - // hypothetical refactoring of `ProgressiveDecoder` changes how it manages - // `CFX_CodecMemory`. - RetainPtr codec_memory_; -}; - -} // namespace fxcodec - -#endif // CORE_FXCODEC_PNG_SKIA_PNG_CONTEXT_H_ diff --git a/core/fxcodec/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index 997eb9eeb5..ad07c35263 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -11,9 +11,9 @@ #include #include "build/build_config.h" -#include "core/fxcodec/cfx_codec_memory.h" -#include "core/fxcodec/jpeg/libjpeg_jpeg_context.h" +#include "core/fxcodec/gif/cfx_gifcontext.h" #include "core/fxcodec/progressive_decoder_context.h" +#include "core/fxcodec/tiff/libtiff_tiff_context.h" #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" @@ -26,38 +26,32 @@ #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" -#ifdef PDF_ENABLE_XFA_BMP +#ifndef PDF_ENABLE_XFA +#error "XFA only" +#endif + #if defined(PDF_ENABLE_RUST_BMP) -#include "core/fxcodec/bmp/skia_bmp_context.h" +#include "core/fxcodec/bmp/rust_bmp_context.h" #else #include "core/fxcodec/bmp/cfx_bmpcontext.h" #endif -#endif // PDF_ENABLE_XFA_BMP -#ifdef PDF_ENABLE_XFA_GIF -#include "core/fxcodec/gif/cfx_gifcontext.h" -#endif // PDF_ENABLE_XFA_GIF - -#ifdef PDF_ENABLE_XFA_PNG -#include "core/fxcodec/png/png_decoder_delegate.h" -// TODO(https://crbug.com/444045690): Remove `pdf_enable_rust_png` from the -// condition below once this build mode has been tested and stabilized. -// (Chromium already sets `pdf_use_skia_override = true` so having an extra -// condition avoids affecting the Chromium behavior.) -#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) -#include "core/fxcodec/png/skia_png_context.h" +#if defined(PDF_ENABLE_RUST_JPEG) +#include "core/fxcodec/jpeg/skia_jpeg_context.h" #else -#include "core/fxcodec/png/libpng_png_context.h" +#include "core/fxcodec/jpeg/libjpeg_jpeg_context.h" #endif -#endif // PDF_ENABLE_XFA_PNG -#ifdef PDF_ENABLE_XFA_TIFF -#include "core/fxcodec/tiff/libtiff_tiff_context.h" -#endif // PDF_ENABLE_XFA_TIFF +#if defined(PDF_ENABLE_RUST_PNG) +#include "core/fxcodec/png/rust_png_context.h" +#else +#include "core/fxcodec/png/libpng_png_context.h" +#endif namespace fxcodec { @@ -69,29 +63,28 @@ std::unique_ptr CreateDecoderContext( FXCODEC_IMAGE_TYPE type, ProgressiveDecoder* delegate) { switch (type) { -#ifdef PDF_ENABLE_XFA_BMP case FXCODEC_IMAGE_BMP: #if defined(PDF_ENABLE_RUST_BMP) - return std::make_unique(delegate); + return std::make_unique(delegate); #else return std::make_unique(delegate); #endif -#endif // PDF_ENABLE_XFA_BMP -#ifdef PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_GIF: return std::make_unique(delegate); -#endif // PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_JPG: { - auto context = std::make_unique(); +#if defined(PDF_ENABLE_RUST_JPEG) + return std::make_unique(delegate); +#else + auto context = std::make_unique(delegate); if (!context->create_ok_) { return nullptr; } return context; +#endif } -#ifdef PDF_ENABLE_XFA_PNG case FXCODEC_IMAGE_PNG: -#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) - return std::make_unique(delegate); +#if defined(PDF_ENABLE_RUST_PNG) + return std::make_unique(delegate); #else { auto context = std::make_unique(delegate); @@ -101,215 +94,105 @@ std::unique_ptr CreateDecoderContext( return context; } #endif -#endif // PDF_ENABLE_XFA_PNG default: return nullptr; } } -#ifdef PDF_ENABLE_XFA_PNG -#if BUILDFLAG(IS_APPLE) -const double kPngGamma = 1.7; -#else -const double kPngGamma = 2.2; -#endif // BUILDFLAG(IS_APPLE) -#endif // PDF_ENABLE_XFA_PNG - -void RGB2BGR(uint8_t* buffer, int width = 1) { - if (buffer && width > 0) { - uint8_t temp; - int i = 0; - int j = 0; - UNSAFE_TODO({ - for (; i < width; i++, j += 3) { - temp = buffer[j]; - buffer[j] = buffer[j + 2]; - buffer[j + 2] = temp; - } - }); - } -} - } // namespace ProgressiveDecoder::ProgressiveDecoder() = default; ProgressiveDecoder::~ProgressiveDecoder() = default; -#ifdef PDF_ENABLE_XFA_PNG -bool ProgressiveDecoder::PngReadHeader(int width, int height, double* gamma) { - if (!device_bitmap_) { - got_png_metadata_ = true; - src_width_ = width; - src_height_ = height; - - // PNG decoder always decodes into BGRA. - src_bits_per_component_ = 8; - src_components_count_ = 4; - src_format_ = FXCodec_Argb; - +bool ProgressiveDecoder::ReadMoreData(std::optional rcd_pos, + FXCODEC_STATUS* err_status) { + if (rcd_pos.has_value()) { + offset_ = rcd_pos.value(); + codec_memory_->Seek(codec_memory_->GetSize()); + } + size_t unconsumed_bytes = codec_memory_->GetUnconsumedSpan().size(); + if (!ReadMoreDataInternal(unconsumed_bytes, err_status)) { return false; } - - CHECK_EQ(width, src_width_); - CHECK_EQ(height, src_height_); - CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); - *gamma = kPngGamma; + if (context_) { + context_->Input(codec_memory_); + } return true; } -pdfium::span ProgressiveDecoder::PngAskScanlineBuf(int line) { - CHECK_GE(line, 0); - CHECK_LT(line, src_height_); - CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); - CHECK_EQ(src_format_, FXCodec_Argb); - return device_bitmap_->GetWritableScanline(line); -} - -pdfium::span ProgressiveDecoder::PngAskImageBuf() { - CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); - CHECK_EQ(src_format_, FXCodec_Argb); - return device_bitmap_->GetWritableBuffer(); -} - -void ProgressiveDecoder::PngFinishedDecoding() { - status_ = FXCODEC_STATUS::kDecodeFinished; -} -#endif // PDF_ENABLE_XFA_PNG - -#ifdef PDF_ENABLE_XFA_GIF -uint32_t ProgressiveDecoder::GifCurrentPosition() const { +uint32_t ProgressiveDecoder::GetCurrentInputPosition() const { uint32_t remain_size = - pdfium::checked_cast(context_->GetAvailInput()); + context_ ? pdfium::checked_cast(context_->GetAvailInput()) : 0; return offset_ - remain_size; } -bool ProgressiveDecoder::GifInputRecordPositionBuf( - uint32_t rcd_pos, - const FX_RECT& img_rc, - pdfium::span pal_span, - int32_t trans_index) { - offset_ = rcd_pos; - - FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; - codec_memory_->Seek(codec_memory_->GetSize()); - if (!GifReadMoreData(&error_status)) { - return false; - } - - if (pal_span.empty()) { - pal_span = gif_palette_; - } - if (pal_span.empty()) { +bool ProgressiveDecoder::PrepareScanlineResampling( + int src_width, + int src_height, + Format src_format, + pdfium::span palette, + std::optional fill_argb) { + if (!device_bitmap_) { return false; } - src_palette_.resize(pal_span.size()); - for (size_t i = 0; i < pal_span.size(); i++) { - src_palette_[i] = - ArgbEncode(0xff, pal_span[i].r, pal_span[i].g, pal_span[i].b); - } - gif_trans_index_ = trans_index; - gif_frame_rect_ = img_rc; - int32_t pal_index = gif_bg_index_; - RetainPtr pDevice = device_bitmap_; - if (trans_index >= static_cast(pal_span.size())) { - trans_index = -1; - } - if (trans_index != -1) { - src_palette_[trans_index] &= 0x00ffffff; - if (pDevice->IsAlphaFormat()) { - pal_index = trans_index; - } + src_width_ = src_width; + src_height_ = src_height; + src_format_ = src_format; + SetTransMethod(); + decode_buf_.resize(GetScanlineSize()); + FXDIB_ResampleOptions options; + options.bInterpolateBilinear = true; + weight_horz_.CalculateWeights(src_width_, 0, src_width_, src_width_, 0, + src_width_, options); + if (!palette.empty()) { + device_bitmap_->SetPalette(palette); + src_palette_ = DataVector(palette.begin(), palette.end()); } - if (pal_index >= static_cast(pal_span.size())) { - return false; - } - int startX = 0; - int startY = 0; - int sizeX = src_width_; - int sizeY = src_height_; - const int bytes_per_pixel = pDevice->GetBPP() / 8; - FX_ARGB argb = src_palette_[pal_index]; - for (int row = 0; row < sizeY; row++) { - pdfium::span scan_span = - pDevice->GetWritableScanline(row + startY) - .subspan(static_cast(startX * bytes_per_pixel)); - switch (trans_method_) { - case TransformMethod::k8BppRgbToRgbNoAlpha: { - uint8_t* pScanline = scan_span.data(); - UNSAFE_TODO({ - for (int col = 0; col < sizeX; col++) { - *pScanline++ = FXARGB_B(argb); - *pScanline++ = FXARGB_G(argb); - *pScanline++ = FXARGB_R(argb); - pScanline += bytes_per_pixel - 3; - } - }); - break; - } - case TransformMethod::k8BppRgbToArgb: { - for (int col = 0; col < sizeX; col++) { - FXARGB_SetDIB(scan_span.first<4u>(), argb); - scan_span = scan_span.subspan<4u>(); - } - break; - } - default: - break; - } + if (fill_argb.has_value()) { + device_bitmap_->Clear(fill_argb.value()); } return true; } -void ProgressiveDecoder::GifReadScanline(int32_t row_num, - pdfium::span row_buf) { - RetainPtr pDIBitmap = device_bitmap_; - const size_t img_width = static_cast(gif_frame_rect_.Width()); - const pdfium::span row_span = row_buf.first(img_width); - if (!pDIBitmap->IsAlphaFormat()) { - for (auto& byte_ref : row_span) { - if (byte_ref == gif_trans_index_) { - byte_ref = gif_bg_index_; - } - } - } - int32_t pal_index = gif_bg_index_; - if (gif_trans_index_ != -1 && device_bitmap_->IsAlphaFormat()) { - pal_index = gif_trans_index_; - } - const int32_t left = gif_frame_rect_.left; - const pdfium::span decode_span = decode_buf_; - std::ranges::fill(decode_span.first(static_cast(src_width_)), - pal_index); - fxcrt::Copy(row_span, decode_span.subspan(static_cast(left))); - int32_t line = row_num + gif_frame_rect_.top; +void ProgressiveDecoder::ResampleScanline( + int line, + pdfium::span src_span) { + CHECK(device_bitmap_); if (line < 0 || line >= src_height_) { return; } - ResampleScanline(pDIBitmap, line, decode_span, src_format_); + int scanline_size = GetScanlineSize(); + fxcrt::Copy(src_span.first(static_cast(scanline_size)), decode_buf_); + ResampleScanline(device_bitmap_, line, decode_buf_, src_format_); } -#endif // PDF_ENABLE_XFA_GIF -#ifdef PDF_ENABLE_XFA_BMP -bool ProgressiveDecoder::BmpInputImagePositionBuf(uint32_t rcd_pos) { - offset_ = rcd_pos; - FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; - return BmpReadMoreData(context_.get(), &error_status); +bool ProgressiveDecoder::PrepareDirectOutput(int src_width, + int src_height, + Format src_format) { + if (src_width <= 0 || src_height <= 0 || src_format != Format::kArgb) { + return false; + } + src_width_ = src_width; + src_height_ = src_height; + src_format_ = src_format; + src_bits_per_component_ = 8; + src_components_count_ = 4; + return true; } -void ProgressiveDecoder::BmpReadScanline(uint32_t row_num, - pdfium::span row_buf) { - RetainPtr pDIBitmap = device_bitmap_; - DCHECK(pDIBitmap); - - int scanline_size = GetScanlineSize(); - fxcrt::Copy(row_buf.first(static_cast(scanline_size)), decode_buf_); - - if (row_num >= static_cast(src_height_)) { - return; - } +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); + return device_bitmap_->GetWritableScanline(line); +} - ResampleScanline(pDIBitmap, row_num, decode_buf_, src_format_); +pdfium::span ProgressiveDecoder::AskImageBuf() { + CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); + CHECK_EQ(src_format_, Format::kArgb); + return device_bitmap_->GetWritableBuffer(); } bool ProgressiveDecoder::BmpDetectImageTypeInBuffer( @@ -319,24 +202,22 @@ bool ProgressiveDecoder::BmpDetectImageTypeInBuffer( pBmcontext->Input(codec_memory_); #if defined(PDF_ENABLE_RUST_BMP) - auto* ctx = static_cast(pBmcontext.get()); + auto* ctx = static_cast(pBmcontext.get()); #else auto* ctx = static_cast(pBmcontext.get()); #endif pdfium::span palette; - ProgressiveDecoderContext::Status read_result = - ctx->ReadHeader(&src_width_, &src_height_, &bmp_is_top_bottom_, - &src_components_count_, &palette, pAttribute); + ProgressiveDecoderContext::Status read_result = ctx->ReadHeader( + &src_width_, &src_height_, &src_components_count_, &palette, pAttribute); while (read_result == ProgressiveDecoderContext::Status::kContinue) { FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; if (!BmpReadMoreData(pBmcontext.get(), &error_status)) { status_ = error_status; return false; } - read_result = - ctx->ReadHeader(&src_width_, &src_height_, &bmp_is_top_bottom_, - &src_components_count_, &palette, pAttribute); + read_result = ctx->ReadHeader(&src_width_, &src_height_, + &src_components_count_, &palette, pAttribute); } if (read_result != ProgressiveDecoderContext::Status::kSuccess) { @@ -347,15 +228,15 @@ bool ProgressiveDecoder::BmpDetectImageTypeInBuffer( FXDIB_Format format = FXDIB_Format::kInvalid; switch (src_components_count_) { case 1: - src_format_ = FXCodec_8bppRgb; + src_format_ = Format::k8bppRgb; format = FXDIB_Format::k8bppRgb; break; case 3: - src_format_ = FXCodec_Rgb; + src_format_ = Format::kRgb; format = FXDIB_Format::kBgr; break; case 4: - src_format_ = FXCodec_Rgb32; + src_format_ = Format::kRgb32; format = FXDIB_Format::kBgrx; break; default: @@ -401,7 +282,7 @@ bool ProgressiveDecoder::BmpReadMoreData(ProgressiveDecoderContext* bmp_context, if (!avail_input.IsValid()) { return false; } - if (!ReadMoreData(avail_input.ValueOrDie(), err_status)) { + if (!ReadMoreDataInternal(avail_input.ValueOrDie(), err_status)) { return false; } bmp_context->Input(codec_memory_); @@ -420,7 +301,7 @@ FXCODEC_STATUS ProgressiveDecoder::BmpStartDecode() { } FXCODEC_STATUS ProgressiveDecoder::BmpContinueDecode() { - ProgressiveDecoderContext::Status read_res = context_->DecodeImage(0); + ProgressiveDecoderContext::Status read_res = context_->DecodeImage(); while (read_res == ProgressiveDecoderContext::Status::kContinue) { FXCODEC_STATUS error_status = FXCODEC_STATUS::kDecodeFinished; if (!BmpReadMoreData(context_.get(), &error_status)) { @@ -429,7 +310,7 @@ FXCODEC_STATUS ProgressiveDecoder::BmpContinueDecode() { status_ = error_status; return status_; } - read_res = context_->DecodeImage(0); + read_res = context_->DecodeImage(); } device_bitmap_ = nullptr; @@ -439,9 +320,7 @@ FXCODEC_STATUS ProgressiveDecoder::BmpContinueDecode() { : FXCODEC_STATUS::kError; return status_; } -#endif // PDF_ENABLE_XFA_BMP -#ifdef PDF_ENABLE_XFA_GIF bool ProgressiveDecoder::GifReadMoreData(FXCODEC_STATUS* err_status) { // TODO(lukasza): Can this just use // `codec_memory_->GetUnconsumedSpan().size()`? (IIUC this is what @@ -451,7 +330,7 @@ bool ProgressiveDecoder::GifReadMoreData(FXCODEC_STATUS* err_status) { if (!avail_input.IsValid()) { return false; } - if (!ReadMoreData(avail_input.ValueOrDie(), err_status)) { + if (!ReadMoreDataInternal(avail_input.ValueOrDie(), err_status)) { return false; } @@ -465,7 +344,7 @@ bool ProgressiveDecoder::GifDetectImageTypeInBuffer() { src_components_count_ = 1; auto* ctx = static_cast(context_.get()); ProgressiveDecoderContext::Status readResult = - ctx->ReadHeader(&src_width_, &src_height_, &gif_palette_, &gif_bg_index_); + ctx->ReadHeader(&src_width_, &src_height_); while (readResult == ProgressiveDecoderContext::Status::kContinue) { FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; if (!GifReadMoreData(&error_status)) { @@ -473,8 +352,7 @@ bool ProgressiveDecoder::GifDetectImageTypeInBuffer() { status_ = error_status; return false; } - readResult = ctx->ReadHeader(&src_width_, &src_height_, &gif_palette_, - &gif_bg_index_); + readResult = ctx->ReadHeader(&src_width_, &src_height_); } if (readResult == ProgressiveDecoderContext::Status::kSuccess) { src_bits_per_component_ = 8; @@ -486,51 +364,32 @@ bool ProgressiveDecoder::GifDetectImageTypeInBuffer() { } FXCODEC_STATUS ProgressiveDecoder::GifStartDecode() { - src_format_ = FXCodec_8bppRgb; + src_format_ = Format::k8bppRgb; SetTransMethod(); decode_buf_.resize(GetScanlineSize()); FXDIB_ResampleOptions options; options.bInterpolateBilinear = true; weight_horz_.CalculateWeights(src_width_, 0, src_width_, src_width_, 0, src_width_, options); - frame_cur_ = 0; - status_ = FXCODEC_STATUS::kDecodeToBeContinued; + auto* ctx = static_cast(context_.get()); + status_ = ctx->StartDecode(device_bitmap_); return status_; } FXCODEC_STATUS ProgressiveDecoder::GifContinueDecode() { - ProgressiveDecoderContext::Status readRes = context_->DecodeImage(frame_cur_); - while (readRes == ProgressiveDecoderContext::Status::kContinue) { - FXCODEC_STATUS error_status = FXCODEC_STATUS::kDecodeFinished; - if (!GifReadMoreData(&error_status)) { - device_bitmap_ = nullptr; - file_ = nullptr; - status_ = error_status; - return status_; - } - readRes = context_->DecodeImage(frame_cur_); - } - - if (readRes == ProgressiveDecoderContext::Status::kSuccess) { - device_bitmap_ = nullptr; - file_ = nullptr; - status_ = FXCODEC_STATUS::kDecodeFinished; - return status_; - } - + auto* ctx = static_cast(context_.get()); + status_ = ctx->ContinueDecode(); // Success or error, never continue. device_bitmap_ = nullptr; file_ = nullptr; - status_ = FXCODEC_STATUS::kError; return status_; } -#endif // PDF_ENABLE_XFA_GIF bool ProgressiveDecoder::JpegReadMoreData(FXCODEC_STATUS* err_status) { FX_SAFE_SIZE_T avail_input = context_->GetAvailInput(); if (!avail_input.IsValid()) { return false; } - if (!ReadMoreData(avail_input.ValueOrDie(), err_status)) { + if (!ReadMoreDataInternal(avail_input.ValueOrDie(), err_status)) { return false; } context_->Input(codec_memory_); @@ -546,6 +405,23 @@ bool ProgressiveDecoder::JpegDetectImageTypeInBuffer( } context_->Input(codec_memory_); +#if defined(PDF_ENABLE_RUST_JPEG) + auto* ctx = static_cast(context_.get()); + src_width_ = 0; + src_height_ = 0; + if (ctx->ReadHeader(codec_memory_)) { + FXCODEC_STATUS status = FXCODEC_STATUS::kError; + while (src_width_ == 0 && ReadMoreData(std::nullopt, &status)) { + if (!ctx->ReadHeader(codec_memory_)) { + break; + } + } + } + + bool got_metadata = (src_width_ > 0 && src_height_ > 0); + context_.reset(); + return got_metadata; +#else auto* ctx = static_cast(context_.get()); while (1) { int read_result = ctx->ReadHeader(&src_width_, &src_height_, @@ -570,88 +446,53 @@ bool ProgressiveDecoder::JpegDetectImageTypeInBuffer( NOTREACHED(); } } +#endif } FXCODEC_STATUS ProgressiveDecoder::JpegStartDecode() { - auto* ctx = static_cast(context_.get()); - while (!ctx->StartScanline()) { - // Maybe it needs more data. - FXCODEC_STATUS error_status = FXCODEC_STATUS::kError; - if (!JpegReadMoreData(&error_status)) { - device_bitmap_ = nullptr; - file_ = nullptr; - status_ = error_status; - return status_; - } +#if defined(PDF_ENABLE_RUST_JPEG) + context_ = CreateDecoderContext(FXCODEC_IMAGE_JPG, this); + if (!context_) { + device_bitmap_ = nullptr; + file_ = nullptr; + status_ = FXCODEC_STATUS::kError; + return status_; } - decode_buf_.resize(GetScanlineSize()); - FXDIB_ResampleOptions options; - options.bInterpolateBilinear = true; - weight_horz_.CalculateWeights(src_width_, 0, src_width_, src_width_, 0, - src_width_, options); - switch (src_components_count_) { - case 1: - src_format_ = FXCodec_8bppGray; - break; - case 3: - src_format_ = FXCodec_Rgb; - break; - case 4: - src_format_ = FXCodec_Cmyk; - break; + context_->Input(codec_memory_); + auto* ctx = static_cast(context_.get()); + status_ = ctx->StartDecode(device_bitmap_); + return status_; +#else + status_ = static_cast(context_.get())->StartDecode(); + if (status_ == FXCODEC_STATUS::kError) { + device_bitmap_ = nullptr; + file_ = nullptr; + context_.reset(); } - SetTransMethod(); - status_ = FXCODEC_STATUS::kDecodeToBeContinued; return status_; +#endif } FXCODEC_STATUS ProgressiveDecoder::JpegContinueDecode() { - auto* ctx = static_cast(context_.get()); - while (true) { - int err_code = ctx->ReadScanline(decode_buf_.data()); - if (err_code == LibjpegJpegContext::kFatal) { - context_.reset(); - status_ = FXCODEC_STATUS::kError; - return FXCODEC_STATUS::kError; - } - if (err_code != LibjpegJpegContext::kOk) { - // Maybe it needs more data. - FXCODEC_STATUS error_status = FXCODEC_STATUS::kDecodeFinished; - if (JpegReadMoreData(&error_status)) { - continue; - } - device_bitmap_ = nullptr; - file_ = nullptr; - status_ = error_status; - return status_; - } - if (src_format_ == FXCodec_Rgb) { - RGB2BGR(UNSAFE_TODO(decode_buf_.data()), src_width_); - } - if (src_row_ >= src_height_) { - device_bitmap_ = nullptr; - file_ = nullptr; - status_ = FXCODEC_STATUS::kDecodeFinished; - return status_; - } - Resample(device_bitmap_, src_row_, decode_buf_.data(), src_format_); - src_row_++; - } -} - -#ifdef PDF_ENABLE_XFA_PNG -bool ProgressiveDecoder::PngReadMoreData() { - size_t unconsumed_bytes = codec_memory_->GetUnconsumedSpan().size(); - if (!ReadMoreData(unconsumed_bytes, &status_)) { - return false; +#if defined(PDF_ENABLE_RUST_JPEG) + auto* ctx = static_cast(context_.get()); + status_ = ctx->ContinueDecode(); + if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) { + device_bitmap_ = nullptr; + file_ = nullptr; } - -#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) - auto* ctx = static_cast(context_.get()); + return status_; #else - auto* ctx = static_cast(context_.get()); + status_ = static_cast(context_.get())->ContinueDecode(); + if (status_ == FXCODEC_STATUS::kError) { + context_.reset(); + } + if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) { + device_bitmap_ = nullptr; + file_ = nullptr; + } + return status_; #endif - return ctx->ContinueDecode(codec_memory_); } bool ProgressiveDecoder::PngDetectImageTypeInBuffer() { @@ -661,25 +502,36 @@ bool ProgressiveDecoder::PngDetectImageTypeInBuffer() { return false; } -#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) - auto* ctx = static_cast(context_.get()); + src_width_ = 0; + src_height_ = 0; + +#if defined(PDF_ENABLE_RUST_PNG) + context_->Input(codec_memory_); + auto* ctx = static_cast(context_.get()); + FXCODEC_STATUS status = FXCODEC_STATUS::kError; + while (src_width_ == 0) { + if (ctx->ReadHeader()) { + break; + } + if (ctx->has_error() || !ReadMoreData(std::nullopt, &status)) { + break; + } + } #else auto* ctx = static_cast(context_.get()); -#endif - - // Keep feeding more input into the decoder until either the decoder 1) fails, - // or 2) calls `PngReadHeader` to indicate that it `got_png_metadata_`. - if (ctx->ContinueDecode(codec_memory_)) { - while (!got_png_metadata_ && PngReadMoreData()) { + if (ctx->ReadHeader(codec_memory_)) { + FXCODEC_STATUS status = FXCODEC_STATUS::kError; + while (src_width_ == 0 && ReadMoreData(std::nullopt, &status)) { + if (!ctx->ReadHeader(codec_memory_)) { + break; + } } } +#endif - // Return `got_png_metadata_` and ignore any failures that the decoder may - // have reported. (In particular ignore the failure that `PngReadHeader` - // reports when there is no `device_bitmap_` - e.g. during image type - // detection.) + bool got_metadata = (src_width_ > 0 && src_height_ > 0); context_.reset(); - return got_png_metadata_; + return got_metadata; } FXCODEC_STATUS ProgressiveDecoder::PngStartDecode() { @@ -690,42 +542,30 @@ FXCODEC_STATUS ProgressiveDecoder::PngStartDecode() { status_ = FXCODEC_STATUS::kError; return status_; } - - // No need to resample/transform pixels when decoding PNGs, because 1) - // `device_bitmap_` for PNGs is always kBgra and 2) the decoder always outputs - // `FXCodec_Argb` (the same format). In other words, PNG code path doesn't - // need to use an intermediate `decode_buf_` to transform the pixels via - // `SetTransMethod` and `ResampleScanline`. - CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); - CHECK_EQ(src_format_, FXCodec_Argb); - - // Discard old/stale data from `codec_memory_` and restart reading the `file_` - // from `offset_` 0. - codec_memory_->Seek(codec_memory_->GetSize()); - offset_ = 0; - - status_ = FXCODEC_STATUS::kDecodeToBeContinued; + context_->Input(codec_memory_); +#if defined(PDF_ENABLE_RUST_PNG) + auto* ctx = static_cast(context_.get()); +#else + auto* ctx = static_cast(context_.get()); +#endif + status_ = ctx->StartDecode(device_bitmap_); return status_; } FXCODEC_STATUS ProgressiveDecoder::PngContinueDecode() { - while (status_ == FXCODEC_STATUS::kDecodeToBeContinued) { - if (!PngReadMoreData()) { - status_ = FXCODEC_STATUS::kError; - break; - } +#if defined(PDF_ENABLE_RUST_PNG) + auto* ctx = static_cast(context_.get()); +#else + auto* ctx = static_cast(context_.get()); +#endif + status_ = ctx->ContinueDecode(); + if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) { + device_bitmap_ = nullptr; + file_ = nullptr; } - - context_.reset(); - device_bitmap_ = nullptr; - file_ = nullptr; - CHECK(status_ == FXCODEC_STATUS::kDecodeFinished || - status_ == FXCODEC_STATUS::kError); return status_; } -#endif // PDF_ENABLE_XFA_PNG -#ifdef PDF_ENABLE_XFA_TIFF bool ProgressiveDecoder::TiffDetectImageTypeFromFile( CFX_DIBAttribute* pAttribute) { auto ctx = std::make_unique(); @@ -756,15 +596,12 @@ FXCODEC_STATUS ProgressiveDecoder::TiffContinueDecode() { file_ = nullptr; return status_; } -#endif // PDF_ENABLE_XFA_TIFF bool ProgressiveDecoder::DetectImageType(FXCODEC_IMAGE_TYPE imageType, CFX_DIBAttribute* pAttribute) { -#ifdef PDF_ENABLE_XFA_TIFF if (imageType == FXCODEC_IMAGE_TIFF) { return TiffDetectImageTypeFromFile(pAttribute); } -#endif // PDF_ENABLE_XFA_TIFF size_t size = pdfium::checked_cast( std::min(file_->GetSize(), kBlockSize)); @@ -781,30 +618,22 @@ bool ProgressiveDecoder::DetectImageType(FXCODEC_IMAGE_TYPE imageType, return JpegDetectImageTypeInBuffer(pAttribute); } -#ifdef PDF_ENABLE_XFA_BMP if (imageType == FXCODEC_IMAGE_BMP) { return BmpDetectImageTypeInBuffer(pAttribute); } -#endif // PDF_ENABLE_XFA_BMP - -#ifdef PDF_ENABLE_XFA_GIF if (imageType == FXCODEC_IMAGE_GIF) { return GifDetectImageTypeInBuffer(); } -#endif // PDF_ENABLE_XFA_GIF - -#ifdef PDF_ENABLE_XFA_PNG if (imageType == FXCODEC_IMAGE_PNG) { return PngDetectImageTypeInBuffer(); } -#endif // PDF_ENABLE_XFA_PNG status_ = FXCODEC_STATUS::kError; return false; } -bool ProgressiveDecoder::ReadMoreData(size_t unconsumed_bytes, - FXCODEC_STATUS* err_status) { +bool ProgressiveDecoder::ReadMoreDataInternal(size_t unconsumed_bytes, + FXCODEC_STATUS* err_status) { // Check for EOF. if (offset_ >= static_cast(file_->GetSize())) { return false; @@ -910,21 +739,21 @@ void ProgressiveDecoder::SetTransMethod() { NOTREACHED(); case FXDIB_Format::kBgr: { switch (src_format_) { - case FXCodec_Invalid: + case Format::kInvalid: trans_method_ = TransformMethod::kInvalid; break; - case FXCodec_8bppGray: + case Format::k8bppGray: trans_method_ = TransformMethod::k8BppGrayToRgbMaybeAlpha; break; - case FXCodec_8bppRgb: + case Format::k8bppRgb: trans_method_ = TransformMethod::k8BppRgbToRgbNoAlpha; break; - case FXCodec_Rgb: - case FXCodec_Rgb32: - case FXCodec_Argb: + case Format::kRgb: + case Format::kRgb32: + case Format::kArgb: trans_method_ = TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha; break; - case FXCodec_Cmyk: + case Format::kCmyk: trans_method_ = TransformMethod::kCmykToRgbMaybeAlpha; break; } @@ -933,27 +762,27 @@ void ProgressiveDecoder::SetTransMethod() { case FXDIB_Format::kBgrx: case FXDIB_Format::kBgra: { switch (src_format_) { - case FXCodec_Invalid: + case Format::kInvalid: trans_method_ = TransformMethod::kInvalid; break; - case FXCodec_8bppGray: + case Format::k8bppGray: trans_method_ = TransformMethod::k8BppGrayToRgbMaybeAlpha; break; - case FXCodec_8bppRgb: + case Format::k8bppRgb: if (device_bitmap_->GetFormat() == FXDIB_Format::kBgra) { trans_method_ = TransformMethod::k8BppRgbToArgb; } else { trans_method_ = TransformMethod::k8BppRgbToRgbNoAlpha; } break; - case FXCodec_Rgb: - case FXCodec_Rgb32: + case Format::kRgb: + case Format::kRgb32: trans_method_ = TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha; break; - case FXCodec_Cmyk: + case Format::kCmyk: trans_method_ = TransformMethod::kCmykToRgbMaybeAlpha; break; - case FXCodec_Argb: + case Format::kArgb: trans_method_ = TransformMethod::kArgbToArgb; break; } @@ -972,10 +801,11 @@ void ProgressiveDecoder::ResampleScanline( const RetainPtr& pDeviceBitmap, int dest_line, pdfium::span src_span, - FXCodec_Format src_format) { + Format src_format) { uint8_t* src_scan = src_span.data(); uint8_t* dest_scan = pDeviceBitmap->GetWritableScanline(dest_line).data(); - const int src_bytes_per_pixel = (src_format & 0xff) / 8; + const auto src_bytes_per_pixel = + (fxcrt::to_underlying(src_format) & 0xff) / 8; const int dest_bytes_per_pixel = pDeviceBitmap->GetBPP() / 8; for (int dest_col = 0; dest_col < src_width_; dest_col++) { CStretchEngine::PixelWeight* pPixelWeights = @@ -1117,7 +947,7 @@ void ProgressiveDecoder::ResampleScanline( void ProgressiveDecoder::Resample(const RetainPtr& pDeviceBitmap, int32_t src_line, uint8_t* src_scan, - FXCodec_Format src_format) { + Format src_format) { if (src_line < 0 || src_line >= src_height_) { return; } @@ -1128,16 +958,17 @@ void ProgressiveDecoder::Resample(const RetainPtr& pDeviceBitmap, FXDIB_Format ProgressiveDecoder::GetBitmapFormat() const { switch (image_type_) { case FXCODEC_IMAGE_JPG: -#ifdef PDF_ENABLE_XFA_BMP +#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: -#endif // PDF_ENABLE_XFA_BMP return GetBitsPerPixel() <= 24 ? FXDIB_Format::kBgr : FXDIB_Format::kBgrx; -#ifdef PDF_ENABLE_XFA_PNG case FXCODEC_IMAGE_PNG: -#endif // PDF_ENABLE_XFA_PNG -#ifdef PDF_ENABLE_XFA_TIFF case FXCODEC_IMAGE_TIFF: -#endif // PDF_ENABLE_XFA_TIFF default: // TODO(crbug.com/355630556): Consider adding support for // `FXDIB_Format::kBgraPremul` @@ -1152,20 +983,13 @@ std::pair ProgressiveDecoder::GetFrames() { } switch (image_type_) { -#ifdef PDF_ENABLE_XFA_BMP - case FXCODEC_IMAGE_BMP: -#endif // PDF_ENABLE_XFA_BMP case FXCODEC_IMAGE_JPG: -#ifdef PDF_ENABLE_XFA_PNG + case FXCODEC_IMAGE_BMP: case FXCODEC_IMAGE_PNG: -#endif // PDF_ENABLE_XFA_PNG -#ifdef PDF_ENABLE_XFA_TIFF case FXCODEC_IMAGE_TIFF: -#endif // PDF_ENABLE_XFA_TIFF frame_number_ = 1; status_ = FXCODEC_STATUS::kDecodeReady; return {status_, 1}; -#ifdef PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_GIF: { auto* ctx = static_cast(context_.get()); while (true) { @@ -1195,7 +1019,6 @@ std::pair ProgressiveDecoder::GetFrames() { return {status_, 0}; } } -#endif // PDF_ENABLE_XFA_GIF default: return {FXCODEC_STATUS::kError, 0}; } @@ -1224,28 +1047,19 @@ FXCODEC_STATUS ProgressiveDecoder::StartDecode(RetainPtr bitmap) { return FXCODEC_STATUS::kError; } - frame_cur_ = 0; device_bitmap_ = std::move(bitmap); switch (image_type_) { -#ifdef PDF_ENABLE_XFA_BMP + case FXCODEC_IMAGE_JPG: + return JpegStartDecode(); case FXCODEC_IMAGE_BMP: return BmpStartDecode(); -#endif // PDF_ENABLE_XFA_BMP -#ifdef PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_GIF: return GifStartDecode(); -#endif // PDF_ENABLE_XFA_GIF - case FXCODEC_IMAGE_JPG: - return JpegStartDecode(); -#ifdef PDF_ENABLE_XFA_PNG case FXCODEC_IMAGE_PNG: return PngStartDecode(); -#endif // PDF_ENABLE_XFA_PNG -#ifdef PDF_ENABLE_XFA_TIFF case FXCODEC_IMAGE_TIFF: status_ = FXCODEC_STATUS::kDecodeToBeContinued; return status_; -#endif // PDF_ENABLE_XFA_TIFF default: return FXCODEC_STATUS::kError; } @@ -1259,22 +1073,14 @@ FXCODEC_STATUS ProgressiveDecoder::ContinueDecode() { switch (image_type_) { case FXCODEC_IMAGE_JPG: return JpegContinueDecode(); -#ifdef PDF_ENABLE_XFA_BMP case FXCODEC_IMAGE_BMP: return BmpContinueDecode(); -#endif // PDF_ENABLE_XFA_BMP -#ifdef PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_GIF: return GifContinueDecode(); -#endif // PDF_ENABLE_XFA_GIF -#ifdef PDF_ENABLE_XFA_PNG case FXCODEC_IMAGE_PNG: return PngContinueDecode(); -#endif // PDF_ENABLE_XFA_PNG -#ifdef PDF_ENABLE_XFA_TIFF case FXCODEC_IMAGE_TIFF: return TiffContinueDecode(); -#endif // PDF_ENABLE_XFA_TIFF default: return FXCODEC_STATUS::kError; } diff --git a/core/fxcodec/progressive_decoder.h b/core/fxcodec/progressive_decoder.h index 672ed7d54e..42733c12c0 100644 --- a/core/fxcodec/progressive_decoder.h +++ b/core/fxcodec/progressive_decoder.h @@ -14,26 +14,18 @@ #include #include "core/fxcodec/fx_codec_def.h" -#include "core/fxcodec/jpeg/jpegmodule.h" +#include "core/fxcodec/progressive_decoder_context_delegate.h" #include "core/fxcrt/data_vector.h" #include "core/fxcrt/retain_ptr.h" #include "core/fxcrt/span.h" -#include "core/fxcrt/unowned_ptr_exclusion.h" #include "core/fxge/dib/cstretchengine.h" #include "core/fxge/dib/fx_dib.h" -#ifdef PDF_ENABLE_XFA_BMP -#include "core/fxcodec/bmp/bmp_decoder_delegate.h" -#endif // PDF_ENABLE_XFA_BMP - -#ifdef PDF_ENABLE_XFA_GIF -#include "core/fxcodec/gif/cfx_gifcontext.h" -#endif // PDF_ENABLE_XFA_GIF - -#ifdef PDF_ENABLE_XFA_PNG -#include "core/fxcodec/png/png_decoder_delegate.h" -#endif // PDF_ENABLE_XFA_PNG +#ifndef PDF_ENABLE_XFA +#error "XFA only" +#endif +class CFX_CodecMemory; class CFX_DIBitmap; class IFX_SeekableReadStream; @@ -42,29 +34,9 @@ namespace fxcodec { class CFX_DIBAttribute; class ProgressiveDecoderContext; -class Dummy {}; // Placeholder to work around C++ syntax issues - -class ProgressiveDecoder final : -#ifdef PDF_ENABLE_XFA_BMP - public BmpDecoderDelegate, -#endif // PDF_ENABLE_XFA_BMP -#ifdef PDF_ENABLE_XFA_GIF - public CFX_GifContext::Delegate, -#endif // PDF_ENABLE_XFA_GIF -#ifdef PDF_ENABLE_XFA_PNG - public PngDecoderDelegate, -#endif // PDF_ENABLE_XFA_PNG - public Dummy { +class ProgressiveDecoder final : public ProgressiveDecoderContextDelegate { public: - enum FXCodec_Format { - FXCodec_Invalid = 0, - FXCodec_8bppGray = 0x108, - FXCodec_8bppRgb = 0x008, - FXCodec_Rgb = 0x018, - FXCodec_Rgb32 = 0x020, - FXCodec_Argb = 0x220, - FXCodec_Cmyk = 0x120 - }; + using Format = ProgressiveDecoderContextDelegate::Format; ProgressiveDecoder(); ~ProgressiveDecoder() override; @@ -84,30 +56,22 @@ class ProgressiveDecoder final : FXCODEC_STATUS ContinueDecode(); -#ifdef PDF_ENABLE_XFA_PNG - // PngDecoderDelegate - bool PngReadHeader(int width, int height, double* gamma) override; - pdfium::span PngAskScanlineBuf(int line) override; - pdfium::span PngAskImageBuf() override; - void PngFinishedDecoding() override; -#endif // PDF_ENABLE_XFA_PNG - -#ifdef PDF_ENABLE_XFA_GIF - // CFX_GifContext::Delegate - uint32_t GifCurrentPosition() const override; - bool GifInputRecordPositionBuf(uint32_t rcd_pos, - const FX_RECT& img_rc, - pdfium::span pal_span, - int32_t trans_index) override; - void GifReadScanline(int32_t row_num, pdfium::span row_buf) override; -#endif // PDF_ENABLE_XFA_GIF - -#ifdef PDF_ENABLE_XFA_BMP - // BmpDecoder::Delegate - bool BmpInputImagePositionBuf(uint32_t rcd_pos) override; - void BmpReadScanline(uint32_t row_num, - pdfium::span row_buf) override; -#endif // PDF_ENABLE_XFA_BMP + // ProgressiveDecoderContextDelegate: + bool ReadMoreData(std::optional rcd_pos, + FXCODEC_STATUS* err_status) override; + uint32_t GetCurrentInputPosition() const override; + bool PrepareScanlineResampling(int src_width, + int src_height, + Format src_format, + pdfium::span palette, + std::optional fill_argb) override; + void ResampleScanline(int line, + pdfium::span src_span) override; + bool PrepareDirectOutput(int src_width, + int src_height, + Format src_format) override; + pdfium::span AskScanlineBuf(int line) override; + pdfium::span AskImageBuf() override; private: using WeightTable = CStretchEngine::WeightTable; @@ -122,32 +86,23 @@ class ProgressiveDecoder final : kArgbToArgb, }; -#ifdef PDF_ENABLE_XFA_BMP bool BmpReadMoreData(ProgressiveDecoderContext* bmp_context, FXCODEC_STATUS* err_status); bool BmpDetectImageTypeInBuffer(CFX_DIBAttribute* pAttribute); FXCODEC_STATUS BmpStartDecode(); FXCODEC_STATUS BmpContinueDecode(); -#endif // PDF_ENABLE_XFA_BMP -#ifdef PDF_ENABLE_XFA_GIF bool GifReadMoreData(FXCODEC_STATUS* err_status); bool GifDetectImageTypeInBuffer(); FXCODEC_STATUS GifStartDecode(); FXCODEC_STATUS GifContinueDecode(); -#endif // PDF_ENABLE_XFA_GIF -#ifdef PDF_ENABLE_XFA_PNG - bool PngReadMoreData(); bool PngDetectImageTypeInBuffer(); FXCODEC_STATUS PngStartDecode(); FXCODEC_STATUS PngContinueDecode(); -#endif // PDF_ENABLE_XFA_PNG -#ifdef PDF_ENABLE_XFA_TIFF bool TiffDetectImageTypeFromFile(CFX_DIBAttribute* pAttribute); FXCODEC_STATUS TiffContinueDecode(); -#endif // PDF_ENABLE_XFA_TIFF bool JpegReadMoreData(FXCODEC_STATUS* err_status); bool JpegDetectImageTypeInBuffer(CFX_DIBAttribute* pAttribute); @@ -170,18 +125,19 @@ class ProgressiveDecoder final : // // Reads start at `offset_` inside the file. The `offset_` will be update as // needed. - bool ReadMoreData(size_t unconsumed_bytes, FXCODEC_STATUS* err_status); + bool ReadMoreDataInternal(size_t unconsumed_bytes, + FXCODEC_STATUS* err_status); void SetTransMethod(); void ResampleScanline(const RetainPtr& pDeviceBitmap, int32_t dest_line, pdfium::span src_span, - FXCodec_Format src_format); + Format src_format); void Resample(const RetainPtr& pDeviceBitmap, int32_t src_line, uint8_t* src_scan, - FXCodec_Format src_format); + Format src_format); // Computes the size of a single decoded image row (in bytes). // @@ -197,9 +153,6 @@ class ProgressiveDecoder final : DataVector decode_buf_; DataVector src_palette_; std::unique_ptr context_; -#ifdef PDF_ENABLE_XFA_PNG - bool got_png_metadata_ = false; -#endif // PDF_ENABLE_XFA_PNG uint32_t offset_ = 0; WeightTable weight_horz_; int src_width_ = 0; @@ -207,19 +160,8 @@ class ProgressiveDecoder final : int src_components_count_ = 0; // e.g. 4 for RGBA, or 3 for RGB int src_bits_per_component_ = 0; // how many bits per channel TransformMethod trans_method_; - int src_row_ = 0; - FXCodec_Format src_format_ = FXCodec_Invalid; + Format src_format_ = Format::kInvalid; size_t frame_number_ = 0; - size_t frame_cur_ = 0; -#ifdef PDF_ENABLE_XFA_GIF - int gif_bg_index_ = 0; - pdfium::span gif_palette_; - int gif_trans_index_ = -1; - FX_RECT gif_frame_rect_; -#endif // PDF_ENABLE_XFA_GIF -#ifdef PDF_ENABLE_XFA_BMP - bool bmp_is_top_bottom_ = false; -#endif // PDF_ENABLE_XFA_BMP }; } // namespace fxcodec diff --git a/core/fxcodec/progressive_decoder_context.cpp b/core/fxcodec/progressive_decoder_context.cpp index 03a53f1757..a4d3ca8e82 100644 --- a/core/fxcodec/progressive_decoder_context.cpp +++ b/core/fxcodec/progressive_decoder_context.cpp @@ -17,8 +17,7 @@ FX_FILESIZE ProgressiveDecoderContext::GetAvailInput() const { void ProgressiveDecoderContext::Input(RetainPtr codec_memory) { } -ProgressiveDecoderContext::Status ProgressiveDecoderContext::DecodeImage( - size_t frame_index) { +ProgressiveDecoderContext::Status ProgressiveDecoderContext::DecodeImage() { return Status::kError; } diff --git a/core/fxcodec/progressive_decoder_context.h b/core/fxcodec/progressive_decoder_context.h index 785ffe36f5..2a03d73353 100644 --- a/core/fxcodec/progressive_decoder_context.h +++ b/core/fxcodec/progressive_decoder_context.h @@ -32,9 +32,7 @@ class ProgressiveDecoderContext { virtual FX_FILESIZE GetAvailInput() const; virtual void Input(RetainPtr codec_memory); - // Decoders that do not handle multiple frames will ignore the - // `frame_index` argument. Callers should pass 0 in such cases. - virtual Status DecodeImage(size_t frame_index); + virtual Status DecodeImage(); }; } // namespace fxcodec diff --git a/core/fxcodec/progressive_decoder_context_delegate.h b/core/fxcodec/progressive_decoder_context_delegate.h new file mode 100644 index 0000000000..c3bcae8efa --- /dev/null +++ b/core/fxcodec/progressive_decoder_context_delegate.h @@ -0,0 +1,76 @@ +// 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_PROGRESSIVE_DECODER_CONTEXT_DELEGATE_H_ +#define CORE_FXCODEC_PROGRESSIVE_DECODER_CONTEXT_DELEGATE_H_ + +#include + +#include + +#include "core/fxcodec/fx_codec_def.h" +#include "core/fxcrt/span.h" +#include "core/fxge/dib/fx_dib.h" + +namespace fxcodec { + +class ProgressiveDecoderContextDelegate { + public: + // Do not use `FXDIB_Format` here since these decoders can return pixel + // formats not supported by it. + enum class Format : uint16_t { + kInvalid = 0, + k8bppGray = 0x108, + k8bppRgb = 0x008, + kRgb = 0x018, + kRgb32 = 0x020, + kArgb = 0x220, + kCmyk = 0x120, + }; + + virtual ~ProgressiveDecoderContextDelegate() = default; + + // I/O: Refills input buffer from the file stream when decoder runs out of + // data. If rcd_pos has a value, sets offset_ to rcd_pos before refilling. + virtual bool ReadMoreData(std::optional rcd_pos, + FXCODEC_STATUS* err_status) = 0; + + // Returns the current reading offset in the underlying input stream. + virtual uint32_t GetCurrentInputPosition() const = 0; + + // Scanline Resampling Output: + // Prepares resampling weights and scanline buffer (for codecs that perform + // resampling), and optionally sets the color palette and fills the + // destination bitmap with a background color. + // Returns true to proceed with decoding, or false to abort (e.g. when + // no destination bitmap is available). + virtual bool PrepareScanlineResampling( + int src_width, + int src_height, + Format src_format, + pdfium::span palette = {}, + std::optional fill_argb = std::nullopt) = 0; + + // Resamples a single decoded scanline into the device bitmap (e.g., BMP, + // GIF, JPEG). + virtual void ResampleScanline(int line, + pdfium::span src_span) = 0; + + // Direct In-Place Output: + // Notifies the delegate of image dimensions and metadata for codecs that + // write directly into the device bitmap without scanline resampling (e.g., + // PNG). + virtual bool PrepareDirectOutput(int src_width, + int src_height, + Format src_format) = 0; + + // Returns a writable span for a scanline or the entire image in the device + // bitmap without intermediate scanline resampling (e.g., PNG). + virtual pdfium::span AskScanlineBuf(int line) = 0; + virtual pdfium::span AskImageBuf() = 0; +}; + +} // namespace fxcodec + +#endif // CORE_FXCODEC_PROGRESSIVE_DECODER_CONTEXT_DELEGATE_H_ diff --git a/core/fxcodec/progressive_decoder_unittest.cpp b/core/fxcodec/progressive_decoder_unittest.cpp index 173c292763..719df58b6e 100644 --- a/core/fxcodec/progressive_decoder_unittest.cpp +++ b/core/fxcodec/progressive_decoder_unittest.cpp @@ -29,6 +29,7 @@ namespace fxcodec { namespace { +using ::testing::AnyOf; using ::testing::ElementsAre; using ::testing::ElementsAreArray; @@ -52,7 +53,6 @@ using ProgressiveDecoderTest = testing::Test; } // namespace -#ifdef PDF_ENABLE_XFA_BMP TEST_F(ProgressiveDecoderTest, Indexed8Bmp) { static constexpr uint8_t kInput[] = { 0x42, 0x4d, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, @@ -119,7 +119,13 @@ TEST_F(ProgressiveDecoderTest, Indexed8BmpWithInvalidIndex) { ASSERT_EQ(1u, frames); status = DecodeToBitmap(decoder, bitmap); +#if defined(PDF_ENABLE_RUST_BMP) + // The Rust image crate recovers from out-of-range palette indices and + // succeeds. + EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status); +#else EXPECT_EQ(FXCODEC_STATUS::kError, status); +#endif } TEST_F(ProgressiveDecoderTest, Direct24Bmp) { @@ -258,6 +264,169 @@ TEST_F(ProgressiveDecoderTest, BmpWithDataOffsetAfterEndOfHeader) { EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0x00)); } +TEST_F(ProgressiveDecoderTest, TruncatedBmp) { + // Truncate the input before the pixel data is complete. + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); +#if defined(PDF_ENABLE_RUST_BMP) + // Skia decoder does not detect truncation failure until decoding the bitmap. + ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status); + + ASSERT_EQ(1, decoder.GetWidth()); + ASSERT_EQ(1, decoder.GetHeight()); + ASSERT_EQ(FXDIB_Format::kBgr, decoder.GetBitmapFormat()); + + auto bitmap = pdfium::MakeRetain(); + ASSERT_TRUE(bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), + decoder.GetBitmapFormat())); + + size_t frames; + std::tie(status, frames) = decoder.GetFrames(); + ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status); + ASSERT_EQ(1u, frames); + + status = DecodeToBitmap(decoder, bitmap); + EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status); +#else + EXPECT_EQ(FXCODEC_STATUS::kError, status); +#endif +} + +TEST_F(ProgressiveDecoderTest, CorruptBmp) { + // Invalid BMP header signature. + static constexpr uint8_t kInput[] = {0x42, 0x4e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); + EXPECT_EQ(FXCODEC_STATUS::kError, status); +} + +TEST_F(ProgressiveDecoderTest, Os2Bmp) { + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x18, 0x00, 0xc0, 0x80, 0x40, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); + ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status); + + ASSERT_EQ(1, decoder.GetWidth()); + ASSERT_EQ(1, decoder.GetHeight()); + ASSERT_EQ(FXDIB_Format::kBgr, decoder.GetBitmapFormat()); + + auto bitmap = pdfium::MakeRetain(); + ASSERT_TRUE(bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), + decoder.GetBitmapFormat())); + + size_t frames; + std::tie(status, frames) = decoder.GetFrames(); + ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status); + ASSERT_EQ(1u, frames); + + status = DecodeToBitmap(decoder, bitmap); + EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status); + EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0x00)); +} + +TEST_F(ProgressiveDecoderTest, Indexed1Bmp) { + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x11, + 0x22, 0x33, 0x00, 0xc0, 0x80, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); + ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status); + + ASSERT_EQ(1, decoder.GetWidth()); + ASSERT_EQ(1, decoder.GetHeight()); + ASSERT_EQ(FXDIB_Format::kBgr, decoder.GetBitmapFormat()); + + auto bitmap = pdfium::MakeRetain(); + ASSERT_TRUE(bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), + decoder.GetBitmapFormat())); + + size_t frames; + std::tie(status, frames) = decoder.GetFrames(); + ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status); + ASSERT_EQ(1u, frames); + + status = DecodeToBitmap(decoder, bitmap); + EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status); + EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0x00)); +} + +TEST_F(ProgressiveDecoderTest, Indexed4Bmp) { + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + // 16 color entries: + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + // Pixel data (nibble 1 selects index 1): + 0x10, 0x00, 0x00, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); + ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status); + + ASSERT_EQ(1, decoder.GetWidth()); + ASSERT_EQ(1, decoder.GetHeight()); + ASSERT_EQ(FXDIB_Format::kBgr, decoder.GetBitmapFormat()); + + auto bitmap = pdfium::MakeRetain(); + ASSERT_TRUE(bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), + decoder.GetBitmapFormat())); + + size_t frames; + std::tie(status, frames) = decoder.GetFrames(); + ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status); + ASSERT_EQ(1u, frames); + + status = DecodeToBitmap(decoder, bitmap); + EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status); + EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0x00)); +} + TEST_F(ProgressiveDecoderTest, LargeBmp) { // Construct a 24-bit BMP larger than `kBlockSize` (4096 bytes). static constexpr uint8_t kWidth = 37; @@ -311,9 +480,234 @@ TEST_F(ProgressiveDecoderTest, LargeBmp) { EXPECT_EQ(0, scanline[kScanlineSize - 1]); } } -#endif // PDF_ENABLE_XFA_BMP -#ifdef PDF_ENABLE_XFA_GIF +TEST_F(ProgressiveDecoderTest, TopDown24Bmp) { + // Top-down BMP has negative height. + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x80, 0x40, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); + ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status); + + ASSERT_EQ(1, decoder.GetWidth()); + ASSERT_EQ(1, decoder.GetHeight()); + ASSERT_EQ(FXDIB_Format::kBgr, decoder.GetBitmapFormat()); + + auto bitmap = pdfium::MakeRetain(); + ASSERT_TRUE(bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), + decoder.GetBitmapFormat())); + + size_t frames; + std::tie(status, frames) = decoder.GetFrames(); + ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status); + ASSERT_EQ(1u, frames); + + status = DecodeToBitmap(decoder, bitmap); + EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status); + EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0x00)); +} + +TEST_F(ProgressiveDecoderTest, IntMinHeightBmp) { + // Height = INT32_MIN (-2147483648). + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x80, 0x40, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); + EXPECT_EQ(FXCODEC_STATUS::kError, status); +} + +TEST_F(ProgressiveDecoderTest, Direct16BitfieldsBmp) { + // 16-bit BMP with 5-6-5 bitfields (compression = 3). + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Red mask: 0x0000F800 + 0x00, 0xf8, 0x00, 0x00, + // Green mask: 0x000007E0 + 0xe0, 0x07, 0x00, 0x00, + // Blue mask: 0x0000001F + 0x1f, 0x00, 0x00, 0x00, + // Pixel: 0xF81F (Magenta: R=0x1F, G=0x00, B=0x1F) + 0x1f, 0xf8, 0x00, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); + ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status); + + ASSERT_EQ(1, decoder.GetWidth()); + ASSERT_EQ(1, decoder.GetHeight()); + ASSERT_EQ(FXDIB_Format::kBgr, decoder.GetBitmapFormat()); + + auto bitmap = pdfium::MakeRetain(); + ASSERT_TRUE(bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), + decoder.GetBitmapFormat())); + + size_t frames; + std::tie(status, frames) = decoder.GetFrames(); + ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status); + ASSERT_EQ(1u, frames); + + status = DecodeToBitmap(decoder, bitmap); + EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status); +#if defined(PDF_ENABLE_RUST_BMP) + // Rust/Skia decoders expand 5-bit channels to full 8-bit dynamic range + // (0xFF). + EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xff, 0x00, 0xff, 0x00)); +#else + // C++ decoder shifts 5-bit channels by 3 without replication (0xF8). + EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xf8, 0x00, 0xf8, 0x00)); +#endif +} + +TEST_F(ProgressiveDecoderTest, Direct16OverlappingMasksBitfieldsBmp) { + // 16-bit BMP with overlapping bitfields masks. + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // Red mask: 0x0000F800 + 0x00, 0xf8, 0x00, 0x00, + // Overlapping Green mask: 0x0000F800 + 0x00, 0xf8, 0x00, 0x00, + // Blue mask: 0x0000001F + 0x1f, 0x00, 0x00, 0x00, + // Pixel: + 0x1f, 0xf8, 0x00, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); +#if defined(PDF_ENABLE_RUST_BMP) + // Skia decoder allows header loading for overlapping masks. + EXPECT_EQ(FXCODEC_STATUS::kFrameReady, status); +#else + EXPECT_EQ(FXCODEC_STATUS::kError, status); +#endif +} + +TEST_F(ProgressiveDecoderTest, Rle8Bmp) { + // 8-bit RLE compressed BMP (compression = 1). + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + // Palette (2 colors): + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x80, 0x40, 0x00, + // RLE8 data (1 pixel of color 1, then end of bitmap): + 0x01, 0x01, 0x00, 0x01}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); + ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status); + + ASSERT_EQ(1, decoder.GetWidth()); + ASSERT_EQ(1, decoder.GetHeight()); + ASSERT_EQ(FXDIB_Format::kBgr, decoder.GetBitmapFormat()); + + auto bitmap = pdfium::MakeRetain(); + ASSERT_TRUE(bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), + decoder.GetBitmapFormat())); + + size_t frames; + std::tie(status, frames) = decoder.GetFrames(); + ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status); + ASSERT_EQ(1u, frames); + + status = DecodeToBitmap(decoder, bitmap); + EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status); + EXPECT_THAT(bitmap->GetScanline(0), ElementsAre(0xc0, 0x80, 0x40, 0x00)); +} + +TEST_F(ProgressiveDecoderTest, ExcessiveDimensionsBmp) { + // Dimensions 65536 x 65536. + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); + EXPECT_EQ(FXCODEC_STATUS::kError, status); +} + +TEST_F(ProgressiveDecoderTest, Excessive2GBUncompressedBmp) { + // Dimensions 23171 x 23171 with 32bpp exceeds 2 GB uncompressed buffer size. + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x83, 0x5a, 0x00, 0x00, + 0x83, 0x5a, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); + EXPECT_EQ(FXCODEC_STATUS::kError, status); +} + +TEST_F(ProgressiveDecoderTest, Excessive1GBUncompressedBmp) { + // Dimensions 16385 x 16384 with 32bpp exceeds 1 GB uncompressed buffer size. + static constexpr uint8_t kInput[] = { + 0x42, 0x4d, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_BMP, &attr, true); + EXPECT_EQ(FXCODEC_STATUS::kError, status); +} + TEST_F(ProgressiveDecoderTest, Gif87a) { static constexpr uint8_t kInput[] = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x01, @@ -492,9 +886,7 @@ TEST_F(ProgressiveDecoderTest, GifDecodeAcrossSubblocks) { ElementsAre(0xc0, 0x80, 0x40, 0xff, 0xc0, 0x80, 0x40, 0xff, 0xc0, 0x80, 0x40, 0xff, 0xc0, 0x80, 0x40, 0xff)); } -#endif // PDF_ENABLE_XFA_GIF -#ifdef PDF_ENABLE_XFA_PNG // `kGreenPng` has been taken from Chromium's // `//third_party/blink/renderer/platform/testing/data/green.png`. constexpr std::array kGreenPng = { @@ -628,6 +1020,54 @@ TEST_F(ProgressiveDecoderTest, BigPng) { EXPECT_THAT(bitmap->GetScanline(0).first(4u), ElementsAre(0x00, 0xFF, 0x00, 0xFF)); } -#endif + +// 1x1 8-bit grayscale PNG with a gAMA chunk of 100,000 (gamma 1.0) and pixel +// value 128 (0x80). +TEST_F(ProgressiveDecoderTest, PngGammaChunk) { + static constexpr uint8_t kInput[] = { + // Signature (8 bytes) + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, + // IHDR chunk (25 bytes): 1x1, 8-bit grayscale + 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x7e, 0x9b, + 0x55, + // gAMA chunk (16 bytes): value 100,000 (1.0 in 100k fixed-point) + 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x01, 0x86, 0xa0, + 0x31, 0xe8, 0x96, 0x5f, + // IDAT chunk (22 bytes): filter=0, pixel=0x80 (128) + 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x68, + 0x00, 0x00, 0x00, 0x82, 0x00, 0x81, 0xda, 0x45, 0x08, 0x3b, + // IEND chunk (12 bytes) + 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82}; + + ProgressiveDecoder decoder; + + auto source = pdfium::MakeRetain(kInput); + CFX_DIBAttribute attr; + FXCODEC_STATUS status = + decoder.LoadImageInfo(std::move(source), FXCODEC_IMAGE_PNG, &attr, true); + ASSERT_EQ(FXCODEC_STATUS::kFrameReady, status); + + ASSERT_EQ(1, decoder.GetWidth()); + ASSERT_EQ(1, decoder.GetHeight()); + ASSERT_EQ(FXDIB_Format::kBgra, decoder.GetBitmapFormat()); + + auto bitmap = pdfium::MakeRetain(); + ASSERT_TRUE(bitmap->Create(decoder.GetWidth(), decoder.GetHeight(), + decoder.GetBitmapFormat())); + + size_t frames; + std::tie(status, frames) = decoder.GetFrames(); + ASSERT_EQ(FXCODEC_STATUS::kDecodeReady, status); + ASSERT_EQ(1u, frames); + + status = DecodeToBitmap(decoder, bitmap); + EXPECT_EQ(FXCODEC_STATUS::kDecodeFinished, status); + // Input 0x80 with file_gamma=1.0 and display_gamma=2.2 yields 0xBA (186 + // floor) or 0xBB (187 rounded). + EXPECT_THAT(bitmap->GetScanline(0).first(4u), + AnyOf(ElementsAre(0xBA, 0xBA, 0xBA, 0xFF), + ElementsAre(0xBB, 0xBB, 0xBB, 0xFF))); +} } // namespace fxcodec diff --git a/core/fxcodec/tiff/libtiff_tiff_context.h b/core/fxcodec/tiff/libtiff_tiff_context.h index 49ac458ada..7953101cdb 100644 --- a/core/fxcodec/tiff/libtiff_tiff_context.h +++ b/core/fxcodec/tiff/libtiff_tiff_context.h @@ -14,8 +14,8 @@ #include "core/fxcodec/progressive_decoder_context.h" #include "core/fxcrt/retain_ptr.h" -#ifndef PDF_ENABLE_XFA_TIFF -#error "TIFF must be enabled" +#ifndef PDF_ENABLE_XFA +#error "XFA must be enabled" #endif struct tiff; diff --git a/core/fxcrt/BUILD.gn b/core/fxcrt/BUILD.gn index 32b06eacab..060ea838d0 100644 --- a/core/fxcrt/BUILD.gn +++ b/core/fxcrt/BUILD.gn @@ -13,9 +13,8 @@ source_set("fxcrt") { "../../fpdfsdk/*", "../../fxbarcode:*", "../../fxjs:*", - "../../testing:*", + "../../testing/*", "../../testing/fuzzers/*", - "../../testing/image_diff/*", "../../third_party:fx_agg", "../../third_party:fx_lcms2", "../../third_party:fx_libopenjpeg", diff --git a/core/fxcrt/cfx_bidi_resolver.cpp b/core/fxcrt/cfx_bidi_resolver.cpp index 50e302e358..59f13a3b1c 100644 --- a/core/fxcrt/cfx_bidi_resolver.cpp +++ b/core/fxcrt/cfx_bidi_resolver.cpp @@ -10,7 +10,12 @@ #include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/ptr_util.h" + +#if defined(USE_SYSTEM_ICUUC) +#include +#else #include "third_party/icu/source/common/unicode/ubidi.h" +#endif void CFX_BidiResolver::UBiDiDeleter::operator()(UBiDi* bidi) const { ubidi_close(bidi); @@ -44,8 +49,8 @@ CFX_BidiResolver::CFX_BidiResolver(std::u16string utf16_text, std::numeric_limits::max() / kIcuRunSize); UErrorCode status = U_ZERO_ERROR; - ScopedUBiDi bidi( - ubidi_openSized(static_cast(utf16_text_.size()), 0, &status)); + ScopedUBiDi bidi(ubidi_openSized(static_cast(utf16_text_.size()), + /*maxRunCount=*/0, &status)); if (U_FAILURE(status)) { return; } @@ -58,8 +63,8 @@ CFX_BidiResolver::CFX_BidiResolver(std::u16string utf16_text, } ubidi_setPara(bidi.get(), utf16_text_.data(), - static_cast(utf16_text_.size()), para_level, nullptr, - &status); + static_cast(utf16_text_.size()), para_level, + /*embeddingLevels=*/nullptr, &status); if (U_FAILURE(status)) { return; } @@ -72,18 +77,20 @@ CFX_BidiResolver::~CFX_BidiResolver() = default; std::vector CFX_BidiResolver::GetVisualRunsForLine(int line_start, int line_length) const { std::vector runs; - if (line_length <= 0) { + if (line_start < 0 || line_length <= 0) { return runs; } UErrorCode status = U_ZERO_ERROR; - ScopedUBiDi line_bidi(ubidi_openSized(line_length, 0, &status)); + ScopedUBiDi line_bidi( + ubidi_openSized(line_length, /*maxRunCount=*/0, &status)); if (U_FAILURE(status)) { return runs; } FX_SAFE_INT32 safe_line_end = pdfium::CheckAdd(line_start, line_length); - if (!safe_line_end.IsValid()) { + if (!safe_line_end.IsValid() || + safe_line_end.ValueOrDie() > utf16_text_.size()) { return runs; } diff --git a/core/fxcrt/cfx_bidi_resolver.h b/core/fxcrt/cfx_bidi_resolver.h index 16e50dda0a..13ec3b4764 100644 --- a/core/fxcrt/cfx_bidi_resolver.h +++ b/core/fxcrt/cfx_bidi_resolver.h @@ -13,7 +13,7 @@ struct UBiDi; // Wraps ICU's UBiDi engine to extract visual text runs from a logical // paragraph. -class CFX_BidiResolver { +class CFX_BidiResolver final { public: enum class ParagraphDirection { kAuto, kLeftToRight, kRightToLeft }; @@ -23,6 +23,7 @@ class CFX_BidiResolver { bool is_rtl; }; + // Returns a new instance on success, or nullptr if ICU initialization fails. static std::unique_ptr Create(std::u16string utf16_text, ParagraphDirection direction); diff --git a/core/fxcrt/cfx_bidi_resolver_unittest.cpp b/core/fxcrt/cfx_bidi_resolver_unittest.cpp index 3469969aa7..7e6726e301 100644 --- a/core/fxcrt/cfx_bidi_resolver_unittest.cpp +++ b/core/fxcrt/cfx_bidi_resolver_unittest.cpp @@ -4,17 +4,30 @@ #include "core/fxcrt/cfx_bidi_resolver.h" +#include + +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +using testing::ElementsAre; + +bool operator==(const CFX_BidiResolver::ResolvedRun& lhs, + const CFX_BidiResolver::ResolvedRun& rhs) { + return lhs.start == rhs.start && lhs.length == rhs.length && + lhs.is_rtl == rhs.is_rtl; +} + +void PrintTo(const CFX_BidiResolver::ResolvedRun& run, std::ostream* os) { + *os << "ResolvedRun{start=" << run.start << ", length=" << run.length + << ", is_rtl=" << (run.is_rtl ? "true" : "false") << "}"; +} + TEST(fxcrt, BidiResolverPureLTR) { auto resolver = CFX_BidiResolver::Create( u"abc", CFX_BidiResolver::ParagraphDirection::kLeftToRight); ASSERT_TRUE(resolver); - auto runs = resolver->GetVisualRunsForLine(0, 3); - ASSERT_EQ(1u, runs.size()); - EXPECT_EQ(0, runs[0].start); - EXPECT_EQ(3, runs[0].length); - EXPECT_FALSE(runs[0].is_rtl); + EXPECT_THAT(resolver->GetVisualRunsForLine(0, 3), + ElementsAre(CFX_BidiResolver::ResolvedRun{0, 3, false})); } TEST(fxcrt, BidiResolverPureRTL) { @@ -23,11 +36,8 @@ TEST(fxcrt, BidiResolverPureRTL) { u"\x05D0\x05D1\x05D2", CFX_BidiResolver::ParagraphDirection::kRightToLeft); ASSERT_TRUE(resolver); - auto runs = resolver->GetVisualRunsForLine(0, 3); - ASSERT_EQ(1u, runs.size()); - EXPECT_EQ(0, runs[0].start); - EXPECT_EQ(3, runs[0].length); - EXPECT_TRUE(runs[0].is_rtl); + EXPECT_THAT(resolver->GetVisualRunsForLine(0, 3), + ElementsAre(CFX_BidiResolver::ResolvedRun{0, 3, true})); } TEST(fxcrt, BidiResolverMixed) { @@ -35,20 +45,11 @@ TEST(fxcrt, BidiResolverMixed) { u"ab \x05D0\x05D1 cd", CFX_BidiResolver::ParagraphDirection::kLeftToRight); ASSERT_TRUE(resolver); - auto runs = resolver->GetVisualRunsForLine(0, 8); - ASSERT_EQ(3u, runs.size()); // "ab " (LTR) -> "Aleph Bet" (RTL) -> " cd" (LTR) - EXPECT_EQ(0, runs[0].start); - EXPECT_EQ(3, runs[0].length); - EXPECT_FALSE(runs[0].is_rtl); - - EXPECT_EQ(3, runs[1].start); - EXPECT_EQ(2, runs[1].length); - EXPECT_TRUE(runs[1].is_rtl); - - EXPECT_EQ(5, runs[2].start); - EXPECT_EQ(3, runs[2].length); - EXPECT_FALSE(runs[2].is_rtl); + EXPECT_THAT(resolver->GetVisualRunsForLine(0, 8), + ElementsAre(CFX_BidiResolver::ResolvedRun{0, 3, false}, + CFX_BidiResolver::ResolvedRun{3, 2, true}, + CFX_BidiResolver::ResolvedRun{5, 3, false})); } TEST(fxcrt, BidiResolverLineWrap) { @@ -56,11 +57,8 @@ TEST(fxcrt, BidiResolverLineWrap) { u"abc def ghi", CFX_BidiResolver::ParagraphDirection::kLeftToRight); ASSERT_TRUE(resolver); // Get runs for the middle word "def " - auto runs = resolver->GetVisualRunsForLine(4, 4); - ASSERT_EQ(1u, runs.size()); - EXPECT_EQ(4, runs[0].start); - EXPECT_EQ(4, runs[0].length); - EXPECT_FALSE(runs[0].is_rtl); + EXPECT_THAT(resolver->GetVisualRunsForLine(4, 4), + ElementsAre(CFX_BidiResolver::ResolvedRun{4, 4, false})); } TEST(fxcrt, BidiResolverEmpty) { @@ -69,3 +67,37 @@ TEST(fxcrt, BidiResolverEmpty) { // Just proving it doesn't crash or leak memory. EXPECT_FALSE(resolver); } + +TEST(fxcrt, BidiResolverOutOfBounds) { + auto resolver = CFX_BidiResolver::Create( + u"abc", CFX_BidiResolver::ParagraphDirection::kLeftToRight); + ASSERT_TRUE(resolver); + + // Negative start + EXPECT_THAT(resolver->GetVisualRunsForLine(-1, 3), testing::IsEmpty()); + + // Negative length + EXPECT_THAT(resolver->GetVisualRunsForLine(0, -1), testing::IsEmpty()); + + // Zero length + EXPECT_THAT(resolver->GetVisualRunsForLine(0, 0), testing::IsEmpty()); + + // Start + length is past the end + EXPECT_THAT(resolver->GetVisualRunsForLine(1, 3), testing::IsEmpty()); + + // Start is past the end + EXPECT_THAT(resolver->GetVisualRunsForLine(4, 1), testing::IsEmpty()); +} + +TEST(fxcrt, BidiResolverInvalidUTF16) { + // Pass an isolated trailing surrogate (invalid UTF-16 sequence) + auto resolver = CFX_BidiResolver::Create( + u"\xDC00\xDC00", CFX_BidiResolver::ParagraphDirection::kLeftToRight); + + ASSERT_TRUE(resolver); + // ICU typically substitutes invalid bytes or treats them as neutral. + // Because the paragraph direction is LTR, these neutral characters + // inherit the base direction and resolve to a single LTR run. + EXPECT_THAT(resolver->GetVisualRunsForLine(0, 2), + ElementsAre(CFX_BidiResolver::ResolvedRun{0, 2, false})); +} diff --git a/core/fxcrt/cfx_bitstream.cpp b/core/fxcrt/cfx_bitstream.cpp index 633346586e..3c6f01c706 100644 --- a/core/fxcrt/cfx_bitstream.cpp +++ b/core/fxcrt/cfx_bitstream.cpp @@ -24,8 +24,8 @@ void CFX_BitStream::ByteAlign() { } uint32_t CFX_BitStream::GetBits(uint32_t nBits) { - DCHECK(nBits > 0); - DCHECK(nBits <= 32); + DCHECK_GT(nBits, 0); + DCHECK_LE(nBits, 32); if (nBits > bit_size_ || bit_pos_ > bit_size_ - nBits) { return 0; } diff --git a/core/fxcrt/cfx_datetime.cpp b/core/fxcrt/cfx_datetime.cpp index a91997bbee..f7cc965bc4 100644 --- a/core/fxcrt/cfx_datetime.cpp +++ b/core/fxcrt/cfx_datetime.cpp @@ -7,7 +7,7 @@ #include "core/fxcrt/cfx_datetime.h" #include "build/build_config.h" -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_extension.h" #include "core/fxcrt/fx_system.h" #include "core/fxcrt/span.h" @@ -26,7 +26,7 @@ constexpr int32_t kDaysPerYear = 365; constexpr int32_t kDaysPerLeapYear = 366; int32_t DaysBeforeMonthInYear(int32_t iYear, uint8_t iMonth) { - DCHECK(iYear != 0); + DCHECK_NE(iYear, 0); pdfium::span p = FX_IsLeapYear(iYear) ? pdfium::span(kDaysBeforeLeapMonth) : pdfium::span(kDaysBeforeMonth); @@ -35,7 +35,7 @@ int32_t DaysBeforeMonthInYear(int32_t iYear, uint8_t iMonth) { } int32_t DaysInYear(int32_t iYear) { - DCHECK(iYear != 0); + DCHECK_NE(iYear, 0); return FX_IsLeapYear(iYear) ? kDaysPerLeapYear : kDaysPerYear; } @@ -43,11 +43,11 @@ int64_t DateToDays(int32_t iYear, uint8_t iMonth, uint8_t iDay, bool bIncludeThisDay) { - DCHECK(iYear != 0); - DCHECK(iMonth >= 1); - DCHECK(iMonth <= 12); - DCHECK(iDay >= 1); - DCHECK(iDay <= FX_DaysInMonth(iYear, iMonth)); + DCHECK_NE(iYear, 0); + DCHECK_GE(iMonth, 1); + DCHECK_LE(iMonth, 12); + DCHECK_GE(iDay, 1); + DCHECK_LE(iDay, FX_DaysInMonth(iYear, iMonth)); int64_t iDays = DaysBeforeMonthInYear(iYear, iMonth); iDays += iDay; @@ -68,7 +68,7 @@ int64_t DateToDays(int32_t iYear, } // namespace uint8_t FX_DaysInMonth(int32_t iYear, uint8_t iMonth) { - DCHECK(iYear != 0); + DCHECK_NE(iYear, 0); pdfium::span p = FX_IsLeapYear(iYear) ? pdfium::span(kDaysPerLeapMonth) : pdfium::span(kDaysPerMonth); @@ -77,7 +77,7 @@ uint8_t FX_DaysInMonth(int32_t iYear, uint8_t iMonth) { } bool FX_IsLeapYear(int32_t iYear) { - DCHECK(iYear != 0); + DCHECK_NE(iYear, 0); return ((iYear % 4) == 0 && (iYear % 100) != 0) || (iYear % 400) == 0; } diff --git a/core/fxcrt/css/cfx_cssstyleselector.cpp b/core/fxcrt/css/cfx_cssstyleselector.cpp index 7fd33c6d77..049b86cacc 100644 --- a/core/fxcrt/css/cfx_cssstyleselector.cpp +++ b/core/fxcrt/css/cfx_cssstyleselector.cpp @@ -10,6 +10,7 @@ #include #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/containers/adapters.h" #include "core/fxcrt/css/cfx_csscolorvalue.h" #include "core/fxcrt/css/cfx_csscomputedstyle.h" @@ -27,7 +28,7 @@ CFX_CSSStyleSelector::CFX_CSSStyleSelector() = default; CFX_CSSStyleSelector::~CFX_CSSStyleSelector() = default; void CFX_CSSStyleSelector::SetDefaultFontSize(float fFontSize) { - DCHECK(fFontSize > 0); + DCHECK_GT(fFontSize, 0); default_font_size_ = fFontSize; } diff --git a/core/fxcrt/fx_unicode.cpp b/core/fxcrt/fx_unicode.cpp index 60a8a20ed5..340c6087fd 100644 --- a/core/fxcrt/fx_unicode.cpp +++ b/core/fxcrt/fx_unicode.cpp @@ -157,7 +157,7 @@ wchar_t GetMirrorChar(wchar_t wch) { BidiClass GetBidiClass(wchar_t wch) { uint16_t prop = GetUnicodeProperties(wch); uint16_t result = (prop & kBidiClassBitMask) >> kBidiClassBitPos; - DCHECK(result <= static_cast(BidiClass::kPDF)); + DCHECK_LE(result, static_cast(BidiClass::kPDF)); return static_cast(result); } @@ -166,14 +166,14 @@ BidiClass GetBidiClass(wchar_t wch) { CharType GetCharType(wchar_t wch) { uint16_t prop = GetExtendedUnicodeProperties(wch); uint16_t result = (prop & kCharTypeBitMask) >> kCharTypeBitPos; - DCHECK(result <= static_cast(CharType::kArabic)); + DCHECK_LE(result, static_cast(CharType::kArabic)); return static_cast(result); } BreakProperty GetBreakProperty(wchar_t wch) { uint16_t prop = GetExtendedUnicodeProperties(wch); uint16_t result = (prop & kBreakTypeBitMask) >> kBreakTypeBitPos; - DCHECK(result <= static_cast(BreakProperty::kTB)); + DCHECK_LE(result, static_cast(BreakProperty::kTB)); return static_cast(result); } diff --git a/core/fxcrt/string_data_template.cpp b/core/fxcrt/string_data_template.cpp index 1606f001e7..b723b8c2e5 100644 --- a/core/fxcrt/string_data_template.cpp +++ b/core/fxcrt/string_data_template.cpp @@ -10,7 +10,6 @@ #include -#include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_memory.h" #include "core/fxcrt/fx_safe_types.h" @@ -39,7 +38,7 @@ RetainPtr> StringDataTemplate::Create( nSize &= ~15; size_t totalSize = nSize.ValueOrDie(); size_t usableLen = (totalSize - overhead) / sizeof(CharType); - DCHECK(usableLen >= nLen); + DCHECK_GE(usableLen, nLen); void* pData = FX_StringAlloc(char, totalSize); return pdfium::WrapRetain(new (pData) StringDataTemplate(nLen, usableLen)); diff --git a/core/fxge/BUILD.gn b/core/fxge/BUILD.gn index abab9a4515..4f1fc59977 100644 --- a/core/fxge/BUILD.gn +++ b/core/fxge/BUILD.gn @@ -244,8 +244,8 @@ if (pdf_enable_fontations) { features = [ "agl" ] deps = [ "//build/rust:cxx_cppdeps", - "//third_party/rust/read_fonts/v0_40:lib", - "//third_party/rust/skrifa/v0_43:lib", + "//third_party/rust/read_fonts/v0_43:lib", + "//third_party/rust/skrifa/v0_46:lib", ] configs += [ ":skrifa_cxx_config" ] } diff --git a/core/fxge/cfx_renderdevice.h b/core/fxge/cfx_renderdevice.h index 8576d4d223..4114b98840 100644 --- a/core/fxge/cfx_renderdevice.h +++ b/core/fxge/cfx_renderdevice.h @@ -19,8 +19,6 @@ #include "core/fxge/dib/fx_dib.h" #include "core/fxge/renderdevicedriver_iface.h" -// EmbedPDF(bug-fix): HDC needs windows.h; win32-x64 builds failed without it. -// Upstream's 3abda657c (Aug 10 2026), taken early; S7 brings the same lines. #if BUILDFLAG(IS_WIN) #include #endif diff --git a/core/fxge/dib/cfx_scanlinecompositor.cpp b/core/fxge/dib/cfx_scanlinecompositor.cpp index 2849e73d21..498f80a929 100644 --- a/core/fxge/dib/cfx_scanlinecompositor.cpp +++ b/core/fxge/dib/cfx_scanlinecompositor.cpp @@ -412,18 +412,43 @@ void CompositeRow_Bgr2Bgra_NoBlend_Clip(pdfium::span dest_span, }); } +// Copies a source pixel into a BGRA destination, forcing it opaque. The +// source may carry an alpha channel or not; it is ignored either way. +template +void CopyPixelToOpaqueBgra(const SrcPixelStruct& input, + DestPixelStruct& output) { + output.blue = input.blue; + output.green = input.green; + output.red = input.red; + output.alpha = 255; +} + +template +void CopyRowToOpaqueBgra(pdfium::span dest_span, + pdfium::span src_span) { + for (auto [dest, src] : fxcrt::Zip(dest_span, src_span)) { + CopyPixelToOpaqueBgra(src, dest); + } +} + +// Picks the source pixel layout for the row above. A kBgr source has 3-byte +// pixels and a kBgrx source 4-byte ones, whose 4th byte is ignored. template void CompositeRow_Bgr2Bgra_NoBlend_NoClip( pdfium::span dest_span, pdfium::span src_span, - size_t src_Bpp) { - for (auto& dest : dest_span) { - dest.blue = src_span[0]; - dest.green = src_span[1]; - dest.red = src_span[2]; - dest.alpha = 255; - src_span = src_span.subspan(src_Bpp); + int src_Bpp) { + const size_t width = dest_span.size(); + if (src_Bpp == 3) { + CopyRowToOpaqueBgra(dest_span, + fxcrt::reinterpret_span>( + src_span.first(width * 3))); + return; } + CHECK_EQ(src_Bpp, 4); + CopyRowToOpaqueBgra(dest_span, + fxcrt::reinterpret_span>( + src_span.first(width * 4))); } template @@ -613,7 +638,12 @@ void CompositeRowBgra2Bgra(pdfium::span> src_span, for (auto [input, output] : fxcrt::Zip(src_span, dest_span)) { const uint8_t src_alpha = CompositePixelBgra2BgraCommon(input, /*clip=*/255, output); - if (src_alpha != 0) { + if (src_alpha == 255) { + // Fully opaque source: AlphaUnion() would be 255, the ratio 255, and + // AlphaMerge(..., 255) returns the source, so the general path in + // CompositePixelBgra2BgraNoBlend() reduces to a plain copy. + CopyPixelToOpaqueBgra(input, output); + } else if (src_alpha != 0) { CompositePixelBgra2BgraNoBlend(input, src_alpha, output); } } @@ -647,7 +677,10 @@ void CompositeRowBgra2Bgra(pdfium::span> src_span, fxcrt::Zip(src_span, clip_span, dest_span)) { const uint8_t src_alpha = CompositePixelBgra2BgraCommon(input, clip, output); - if (src_alpha != 0) { + if (src_alpha == 255) { + // Same plain-copy reduction as the no-clip loop above. + CopyPixelToOpaqueBgra(input, output); + } else if (src_alpha != 0) { CompositePixelBgra2BgraNoBlend(input, src_alpha, output); } } diff --git a/core/fxge/dib/cstretchengine.cpp b/core/fxge/dib/cstretchengine.cpp index 98b407da19..8d0bd0f81b 100644 --- a/core/fxge/dib/cstretchengine.cpp +++ b/core/fxge/dib/cstretchengine.cpp @@ -13,9 +13,13 @@ #include #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" +#include "core/fxcrt/fx_2d_size.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/fx_system.h" #include "core/fxcrt/pauseindicator_iface.h" +#include "core/fxcrt/span_util.h" +#include "core/fxcrt/zip.h" #include "core/fxge/calculate_pitch.h" #include "core/fxge/dib/cfx_dibbase.h" #include "core/fxge/dib/cfx_dibitmap.h" @@ -109,64 +113,6 @@ void StretchOpaqueRow(pdfium::span src, } } -// The vertical pass for one destination row of 3 channels, reading the -// horizontal pass's rows `src_top` onwards from `inter` (`pitch` bytes each) -// for the destination columns `left` to `right`. Each channel is the sum the -// general loop computes, added in two halves. -void StretchVertRow(pdfium::span inter, - size_t pitch, - int src_top, - const CStretchEngine::PixelWeight* weights, - int bytes_per_pixel, - int columns, - pdfium::span dest) { - const int count = weights->src_end_ - weights->src_start_ + 1; - if (count > 0) { - const size_t first = static_cast(weights->src_start_ - src_top); - CHECK_LE((first + count - 1) * pitch + - static_cast(columns) * bytes_per_pixel, - inter.size()); - } - CHECK_LE(static_cast(columns) * bytes_per_pixel, dest.size()); - // SAFETY: the checks above keep every row and column read in `inter`, and - // every pixel written in `dest`. - UNSAFE_BUFFERS({ - const uint32_t* weight = weights->weights_; - const uint8_t* rows = - inter.data() + - static_cast(weights->src_start_ - src_top) * pitch; - uint8_t* out = dest.data(); - for (int col = 0; col < columns; ++col) { - const uint8_t* pixel = rows + static_cast(col) * bytes_per_pixel; - uint32_t b0 = 0; - uint32_t g0 = 0; - uint32_t r0 = 0; - uint32_t b1 = 0; - uint32_t g1 = 0; - uint32_t r1 = 0; - int i = 0; - for (; i + 1 < count; i += 2) { - b0 += weight[i] * pixel[0]; - g0 += weight[i] * pixel[1]; - r0 += weight[i] * pixel[2]; - b1 += weight[i + 1] * pixel[pitch]; - g1 += weight[i + 1] * pixel[pitch + 1]; - r1 += weight[i + 1] * pixel[pitch + 2]; - pixel += 2 * pitch; - } - if (i < count) { - b0 += weight[i] * pixel[0]; - g0 += weight[i] * pixel[1]; - r0 += weight[i] * pixel[2]; - } - out[0] = CStretchEngine::PixelFromFixed(b0 + b1); - out[1] = CStretchEngine::PixelFromFixed(g0 + g1); - out[2] = CStretchEngine::PixelFromFixed(r0 + r1); - out += bytes_per_pixel; - } - }); -} - size_t TotalBytesForWeightCount(size_t weight_count) { // Always room for one weight even for empty ranges due to declaration // of weights_[1] in the header. Don't shrink below this since @@ -258,12 +204,12 @@ bool CStretchEngine::WeightTable::CalculateWeights( // Always room for one weight per size calculation. pixel_weights.weights_[0] = kFixedPointOne; } else { - UNSAFE_TODO({ - pixel_weights.weights_[1] = - FixedFromDouble(src_pos - pixel_weights.src_start_ - 0.5f); - pixel_weights.weights_[0] = - kFixedPointOne - pixel_weights.weights_[1]; - }); + const uint32_t second_weight = + FixedFromDouble(src_pos - pixel_weights.src_start_ - 0.5f); + pixel_weights.SetWeightForPosition(pixel_weights.src_start_, + kFixedPointOne - second_weight); + pixel_weights.SetWeightForPosition(pixel_weights.src_start_ + 1, + second_weight); } } else { int pixel_pos = static_cast(floor(src_pos)); @@ -285,7 +231,10 @@ bool CStretchEngine::WeightTable::CalculateWeights( start_i = std::max(start_i, src_min); end_i = std::min(end_i, src_max - 1); if (start_i > end_i) { - start_i = std::min(start_i, src_max - 1); + // `start_i` is already at least `src_min`, so the lower bound only + // matters when the source range is empty: `src_max` is then 0 and + // clamping from above alone would store a negative position. + start_i = std::clamp(start_i, 0, std::max(src_max - 1, 0)); pixel_weights.SetStartEnd(start_i, start_i, weight_count); continue; } @@ -320,7 +269,7 @@ bool CStretchEngine::WeightTable::CalculateWeights( const CStretchEngine::PixelWeight* CStretchEngine::WeightTable::GetPixelWeight( int pixel) const { - DCHECK(pixel >= dest_min_); + DCHECK_GE(pixel, dest_min_); return reinterpret_cast( &weight_tables_[(pixel - dest_min_) * item_size_bytes_]); } @@ -469,7 +418,7 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { return true; } - int Bpp = dest_bpp_ / 8; + const int Bpp = dest_bpp_ / 8; static const int kStrechPauseRows = 10; int rows_to_go = kStrechPauseRows; for (; cur_row_ < src_clip_.bottom; ++cur_row_) { @@ -481,9 +430,7 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { rows_to_go = kStrechPauseRows; } - // EmbedPDF(render-speedup): StretchOpaqueRow() reads the row as a span. - pdfium::span src_row = source_->GetScanline(cur_row_); - const uint8_t* src_scan = src_row.data(); + pdfium::span src_row_span = source_->GetScanline(cur_row_); pdfium::span dest_span = inter_buf_.subspan( (cur_row_ - src_clip_.top) * inter_pitch_, inter_pitch_); size_t dest_span_index = 0; @@ -493,6 +440,8 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { 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, @@ -500,23 +449,21 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { // needs its bits one by one. const BitRun run = g_specialized_loops_enabled - ? ClassifyBits(src_scan, pWeights->src_start_, + ? ClassifyBits(src_row_span.data(), pWeights->src_start_, pWeights->src_end_) : BitRun::kMixed; if (run == BitRun::kSet) { - uint32_t weights = 0; - for (int j = pWeights->src_start_; j <= pWeights->src_end_; ++j) { - weights += pWeights->GetWeightForPosition(j); + uint32_t weight_sum = 0; + for (uint32_t weight : weights) { + weight_sum += weight; } - dest_a = weights * 255; + dest_a = weight_sum * 255; } else if (run == BitRun::kMixed) { - for (int j = pWeights->src_start_; j <= pWeights->src_end_; ++j) { - uint32_t pixel_weight = pWeights->GetWeightForPosition(j); - UNSAFE_TODO({ - if (src_scan[j / 8] & (1 << (7 - j % 8))) { - dest_a += pixel_weight * 255; - } - }); + 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); @@ -526,39 +473,62 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { case TransformMethod::k8BppTo8Bpp: { for (int col = dest_clip_.left; col < dest_clip_.right; ++col) { const PixelWeight* pWeights = weight_table_.GetPixelWeight(col); + pdfium::span weights = pWeights->GetWeights(); + // One validated view of exactly the source bytes this column taps. + auto src_window = + src_row_span.subspan(pWeights->GetSrcStart(), weights.size()); uint32_t dest_a = 0; - for (int j = pWeights->src_start_; j <= pWeights->src_end_; ++j) { - uint32_t pixel_weight = pWeights->GetWeightForPosition(j); - UNSAFE_TODO({ dest_a += pixel_weight * src_scan[j]; }); + // See the matching unrolls in the kManyBpptoManyBpp case below: + // one and two taps are the common trip counts and the loop is all + // overhead there. Tap order is unchanged. + switch (weights.size()) { + case 1: + dest_a = weights[0] * src_window[0]; + break; + case 2: + dest_a = weights[0] * src_window[0] + weights[1] * src_window[1]; + break; + default: + for (auto [pixel_weight, src_byte] : + fxcrt::Zip(weights, src_window)) { + dest_a += pixel_weight * src_byte; + } + break; } dest_span[dest_span_index++] = PixelFromFixed(dest_a); } break; } case TransformMethod::k8BppToManyBpp: { + // 3 bytes per destination pixel, written contiguously. + auto dest_pixels = + fxcrt::reinterpret_span>(dest_span); + size_t dest_index = 0; for (int col = dest_clip_.left; col < dest_clip_.right; ++col) { const PixelWeight* pWeights = weight_table_.GetPixelWeight(col); + pdfium::span weights = pWeights->GetWeights(); + auto src_window = + src_row_span.subspan(pWeights->GetSrcStart(), weights.size()); uint32_t dest_r = 0; uint32_t dest_g = 0; uint32_t dest_b = 0; - for (int j = pWeights->src_start_; j <= pWeights->src_end_; ++j) { - uint32_t pixel_weight = pWeights->GetWeightForPosition(j); - UNSAFE_TODO({ - FX_ARGB argb = src_palette_[src_scan[j]]; - if (dest_format_ == FXDIB_Format::kBgr) { - dest_r += pixel_weight * static_cast(argb >> 16); - dest_g += pixel_weight * static_cast(argb >> 8); - dest_b += pixel_weight * static_cast(argb); - } else { - dest_b += pixel_weight * static_cast(argb >> 24); - dest_g += pixel_weight * static_cast(argb >> 16); - dest_r += pixel_weight * static_cast(argb >> 8); - } - }); + for (auto [pixel_weight, src_byte] : + fxcrt::Zip(weights, src_window)) { + FX_ARGB argb = src_palette_[src_byte]; + if (dest_format_ == FXDIB_Format::kBgr) { + dest_r += pixel_weight * static_cast(argb >> 16); + dest_g += pixel_weight * static_cast(argb >> 8); + dest_b += pixel_weight * static_cast(argb); + } else { + dest_b += pixel_weight * static_cast(argb >> 24); + dest_g += pixel_weight * static_cast(argb >> 16); + dest_r += pixel_weight * static_cast(argb >> 8); + } } - dest_span[dest_span_index++] = PixelFromFixed(dest_b); - dest_span[dest_span_index++] = PixelFromFixed(dest_g); - dest_span[dest_span_index++] = PixelFromFixed(dest_r); + FX_BGR_STRUCT& dest = dest_pixels[dest_index++]; + dest.blue = PixelFromFixed(dest_b); + dest.green = PixelFromFixed(dest_g); + dest.red = PixelFromFixed(dest_r); } break; } @@ -567,59 +537,116 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { // loop. if (g_specialized_loops_enabled && (Bpp == 3 || Bpp == 4)) { if (Bpp == 3) { - StretchOpaqueRow<3>(src_row, dest_span, weight_table_, + StretchOpaqueRow<3>(src_row_span, dest_span, weight_table_, dest_clip_.left, dest_clip_.right); } else { - StretchOpaqueRow<4>(src_row, dest_span, weight_table_, + StretchOpaqueRow<4>(src_row_span, dest_span, weight_table_, dest_clip_.left, dest_clip_.right); } break; } - for (int col = dest_clip_.left; col < dest_clip_.right; ++col) { - const PixelWeight* pWeights = weight_table_.GetPixelWeight(col); - uint32_t dest_r = 0; - uint32_t dest_g = 0; - uint32_t dest_b = 0; - for (int j = pWeights->src_start_; j <= pWeights->src_end_; ++j) { - uint32_t pixel_weight = pWeights->GetWeightForPosition(j); - UNSAFE_TODO({ - const uint8_t* src_pixel = src_scan + j * Bpp; - dest_b += pixel_weight * (*src_pixel++); - dest_g += pixel_weight * (*src_pixel++); - dest_r += pixel_weight * (*src_pixel); - }); + // `Bpp` is the source pixel stride as well as the destination's: + // this method is only selected for >8bpp sources, and + // CFX_ImageStretcher::GetStretchedFormat() maps such sources to the + // same format on the destination side, so both are kBgr (3 bytes) or + // kBgrx (4 bytes, whose 4th byte this ignores). The generic lambda + // below is instantiated once per branch, so each instantiation gets a + // compile-time source pixel size. + auto stretch_row_from = [this, &dest_span](auto src_pixels) { + // The destination pixels have the same layout as the source + // pixels: 3 bytes fully written, or 4 bytes of which the 4th is + // left untouched. + using DestPixel = + std::remove_const_t; + auto dest_pixels = fxcrt::reinterpret_span(dest_span); + size_t dest_index = 0; + for (int col = dest_clip_.left; col < dest_clip_.right; ++col) { + const PixelWeight* pWeights = weight_table_.GetPixelWeight(col); + pdfium::span weights = pWeights->GetWeights(); + auto window = + src_pixels.subspan(pWeights->GetSrcStart(), weights.size()); + uint32_t dest_r = 0; + uint32_t dest_g = 0; + uint32_t dest_b = 0; + // One and two taps are the overwhelmingly common cases -- one is + // no scaling in this axis, two is bilinear -- and at that trip + // count the loop is all overhead. Unrolled explicitly; the terms + // are summed in tap order, so the result is identical. + switch (weights.size()) { + case 1: { + const auto& src0 = window[0]; + dest_b = weights[0] * src0.blue; + dest_g = weights[0] * src0.green; + dest_r = weights[0] * src0.red; + break; + } + case 2: { + const auto& src0 = window[0]; + const auto& src1 = window[1]; + dest_b = weights[0] * src0.blue + weights[1] * src1.blue; + dest_g = weights[0] * src0.green + weights[1] * src1.green; + dest_r = weights[0] * src0.red + weights[1] * src1.red; + break; + } + default: + for (auto [pixel_weight, src] : fxcrt::Zip(weights, window)) { + dest_b += pixel_weight * src.blue; + dest_g += pixel_weight * src.green; + dest_r += pixel_weight * src.red; + } + break; + } + DestPixel& dest = dest_pixels[dest_index++]; + dest.blue = PixelFromFixed(dest_b); + dest.green = PixelFromFixed(dest_g); + dest.red = PixelFromFixed(dest_r); } - dest_span[dest_span_index++] = PixelFromFixed(dest_b); - dest_span[dest_span_index++] = PixelFromFixed(dest_g); - dest_span[dest_span_index++] = PixelFromFixed(dest_r); - dest_span_index += Bpp - 3; + }; + if (Bpp == 3) { + stretch_row_from( + fxcrt::reinterpret_span>( + src_row_span)); + } else { + CHECK_EQ(Bpp, 4); + stretch_row_from( + fxcrt::reinterpret_span>( + src_row_span)); } break; } case TransformMethod::kManyBpptoManyBppWithAlpha: { - DCHECK(has_alpha_); + CHECK(has_alpha_); + // `has_alpha_` implies both source and destination are kBgra + // (asserted in the constructor), so the source pixels are 4-byte + // BGRA structs. + CHECK_EQ(Bpp, 4); + auto src_pixels = + fxcrt::reinterpret_span>( + src_row_span); + auto dest_pixels = + fxcrt::reinterpret_span>(dest_span); + size_t dest_index = 0; for (int col = dest_clip_.left; col < dest_clip_.right; ++col) { const PixelWeight* pWeights = weight_table_.GetPixelWeight(col); + pdfium::span weights = pWeights->GetWeights(); + auto window = + src_pixels.subspan(pWeights->GetSrcStart(), weights.size()); uint32_t dest_a = 0; uint32_t dest_r = 0; uint32_t dest_g = 0; uint32_t dest_b = 0; - for (int j = pWeights->src_start_; j <= pWeights->src_end_; ++j) { - UNSAFE_TODO({ - const uint8_t* src_pixel = src_scan + j * Bpp; - uint32_t pixel_weight = - pWeights->GetWeightForPosition(j) * src_pixel[3] / 255; - dest_b += pixel_weight * (*src_pixel++); - dest_g += pixel_weight * (*src_pixel++); - dest_r += pixel_weight * (*src_pixel); - dest_a += pixel_weight; - }); + for (auto [weight, src] : fxcrt::Zip(weights, window)) { + uint32_t pixel_weight = weight * src.alpha / 255; + dest_b += pixel_weight * src.blue; + dest_g += pixel_weight * src.green; + dest_r += pixel_weight * src.red; + dest_a += pixel_weight; } - dest_span[dest_span_index++] = PixelFromFixed(dest_b); - dest_span[dest_span_index++] = PixelFromFixed(dest_g); - dest_span[dest_span_index++] = PixelFromFixed(dest_r); - dest_span[dest_span_index] = PixelFromFixed(255 * dest_a); - dest_span_index += Bpp - 3; + FX_BGRA_STRUCT& dest = dest_pixels[dest_index++]; + dest.blue = PixelFromFixed(dest_b); + dest.green = PixelFromFixed(dest_g); + dest.red = PixelFromFixed(dest_r); + dest.alpha = PixelFromFixed(255 * dest_a); } break; } @@ -641,95 +668,142 @@ void CStretchEngine::StretchVert() { return; } + const int dest_cols = dest_clip_.right - dest_clip_.left; + if (dest_cols <= 0) { + return; + } + const int DestBpp = dest_bpp_ / 8; + const size_t row_bytes = Fx2DSizeOrDie(dest_cols, DestBpp); + + // Taps are the outer loop, so each one reads a contiguous row of the + // intermediate buffer into a row-wide accumulator. Per byte the taps are + // still summed in tap order, so the output is unchanged. + // + // Allocated once outside the row loop rather than constructed inside it: + // `row_bytes` uint32s is tens of kilobytes for a wide destination, and one + // allocate/free pair per destination row is a large fraction of a scanned + // page's render time. + // + // Uninitialised, because the first tap of each row assigns rather than + // accumulates and so writes every element before anything reads it. The + // one case with no first tap -- an empty weight range -- zeroes the + // accumulator explicitly below. + auto accum = FixedSizeDataVector::Uninit(row_bytes); + pdfium::span accum_span = accum.span(); + for (int row = dest_clip_.top; row < dest_clip_.bottom; ++row) { - unsigned char* dest_scan = dest_scanline_.data(); const PixelWeight* pWeights = table.GetPixelWeight(row); + pdfium::span weights = pWeights->GetWeights(); + if (!weights.empty()) { + CHECK_GE(pWeights->src_start_, src_clip_.top); + CHECK_LT(pWeights->src_end_, src_clip_.bottom); + } + + // No taps for this row, so the tap loop below writes nothing: zero the + // accumulator so the conversion switch sees defined values. + if (weights.empty()) { + std::ranges::fill(accum_span, 0u); + } + for (size_t i = 0; i < weights.size(); ++i) { + // Hoisted deliberately: `weights` and `accum_span` are both spans of + // uint32_t, so the compiler cannot prove the accumulation below does + // not alias the weight, and would otherwise reload it every iteration + // and decline to vectorize. + const uint32_t pixel_weight = weights[i]; + auto src_row = inter_buf_.subspan( + (static_cast(pWeights->src_start_ - src_clip_.top) + i) * + inter_pitch_, + row_bytes); + // The first tap assigns rather than accumulates. That initialises the + // whole accumulator, so no separate zeroing pass over the row is + // needed -- one full-width write instead of a write plus a + // read-modify-write. + if (i == 0) { + for (auto [acc, src_byte] : fxcrt::Zip(accum_span, src_row)) { + acc = pixel_weight * src_byte; + } + } else { + for (auto [acc, src_byte] : fxcrt::Zip(accum_span, src_row)) { + acc += pixel_weight * src_byte; + } + } + } + + auto dest_row = pdfium::span(dest_scanline_).first(row_bytes); switch (trans_method_) { case TransformMethod::k1BppTo8Bpp: case TransformMethod::k1BppToManyBpp: case TransformMethod::k8BppTo8Bpp: { - for (int col = dest_clip_.left; col < dest_clip_.right; ++col) { - pdfium::span src_span = - inter_buf_.subspan((col - dest_clip_.left) * DestBpp); - uint32_t dest_a = 0; - for (int j = pWeights->src_start_; j <= pWeights->src_end_; ++j) { - uint32_t pixel_weight = pWeights->GetWeightForPosition(j); - dest_a += - pixel_weight * src_span[(j - src_clip_.top) * inter_pitch_]; + // Only the first channel of each destination pixel carries data on + // these paths; any remaining bytes are left untouched. Typed pixel + // views give every width the same loop shape. The generic lambda + // below is instantiated once per branch, so each instantiation gets a + // compile-time destination pixel size. + auto write_first_channel = [](auto dest_pixels, auto acc_pixels) { + for (auto [dest, acc] : fxcrt::Zip(dest_pixels, acc_pixels)) { + dest.blue = PixelFromFixed(acc.blue); + } + }; + if (DestBpp == 1) { + for (auto [dest, acc] : fxcrt::Zip(dest_row, accum_span)) { + dest = PixelFromFixed(acc); } - UNSAFE_TODO({ - *dest_scan = PixelFromFixed(dest_a); - dest_scan += DestBpp; - }); + } else if (DestBpp == 3) { + write_first_channel( + fxcrt::reinterpret_span>(dest_row), + fxcrt::reinterpret_span>( + accum_span)); + } else { + CHECK_EQ(DestBpp, 4); + write_first_channel( + fxcrt::reinterpret_span>(dest_row), + fxcrt::reinterpret_span>( + accum_span)); } break; } case TransformMethod::k8BppToManyBpp: case TransformMethod::kManyBpptoManyBpp: { - // EmbedPDF(render-speedup): the vertical pass in a dedicated loop. - if (g_specialized_loops_enabled) { - StretchVertRow(inter_buf_, inter_pitch_, src_clip_.top, pWeights, - DestBpp, dest_clip_.Width(), dest_scanline_); - break; - } - for (int col = dest_clip_.left; col < dest_clip_.right; ++col) { - pdfium::span src_span = - inter_buf_.subspan((col - dest_clip_.left) * DestBpp); - uint32_t dest_r = 0; - uint32_t dest_g = 0; - uint32_t dest_b = 0; - for (int j = pWeights->src_start_; j <= pWeights->src_end_; ++j) { - uint32_t pixel_weight = pWeights->GetWeightForPosition(j); - pdfium::span src_pixel = src_span.subspan( - static_cast((j - src_clip_.top) * inter_pitch_), 3u); - dest_b += pixel_weight * src_pixel[0]; - dest_g += pixel_weight * src_pixel[1]; - dest_r += pixel_weight * src_pixel[2]; + // The destination is kBgr (3 bytes per pixel, fully written) or + // kBgrx (4 bytes per pixel, 4th byte left untouched); see the + // matching dispatch in ContinueStretchHorz(). + if (DestBpp == 3) { + for (auto [dest, acc] : fxcrt::Zip(dest_row, accum_span)) { + dest = PixelFromFixed(acc); + } + } else { + CHECK_EQ(DestBpp, 4); + auto dest_pixels = + fxcrt::reinterpret_span>(dest_row); + auto acc_pixels = + fxcrt::reinterpret_span>( + accum_span); + for (auto [dest, acc] : fxcrt::Zip(dest_pixels, acc_pixels)) { + dest.blue = PixelFromFixed(acc.blue); + dest.green = PixelFromFixed(acc.green); + dest.red = PixelFromFixed(acc.red); } - UNSAFE_TODO({ - dest_scan[0] = PixelFromFixed(dest_b); - dest_scan[1] = PixelFromFixed(dest_g); - dest_scan[2] = PixelFromFixed(dest_r); - dest_scan += DestBpp; - }); } break; } case TransformMethod::kManyBpptoManyBppWithAlpha: { - DCHECK(has_alpha_); - for (int col = dest_clip_.left; col < dest_clip_.right; ++col) { - pdfium::span src_span = - inter_buf_.subspan((col - dest_clip_.left) * DestBpp); - uint32_t dest_a = 0; - uint32_t dest_r = 0; - uint32_t dest_g = 0; - uint32_t dest_b = 0; - static constexpr size_t kPixelBytes = 4; - for (int j = pWeights->src_start_; j <= pWeights->src_end_; ++j) { - uint32_t pixel_weight = pWeights->GetWeightForPosition(j); - pdfium::span src_pixel = src_span.subspan( - static_cast((j - src_clip_.top) * inter_pitch_), - kPixelBytes); - dest_b += pixel_weight * src_pixel[0]; - dest_g += pixel_weight * src_pixel[1]; - dest_r += pixel_weight * src_pixel[2]; - dest_a += pixel_weight * src_pixel[3]; - } - if (dest_a) { - int r = static_cast(dest_r) * 255 / dest_a; - int g = static_cast(dest_g) * 255 / dest_a; - int b = static_cast(dest_b) * 255 / dest_a; - UNSAFE_TODO({ - dest_scan[0] = std::clamp(b, 0, 255); - dest_scan[1] = std::clamp(g, 0, 255); - dest_scan[2] = std::clamp(r, 0, 255); - }); + CHECK(has_alpha_); + CHECK_EQ(DestBpp, 4); + auto dest_pixels = + fxcrt::reinterpret_span>(dest_row); + auto acc_pixels = + fxcrt::reinterpret_span>(accum_span); + for (auto [dest, acc] : fxcrt::Zip(dest_pixels, acc_pixels)) { + if (acc.alpha) { + int r = acc.red * 255 / acc.alpha; + int g = acc.green * 255 / acc.alpha; + int b = acc.blue * 255 / acc.alpha; + dest.blue = std::clamp(b, 0, 255); + dest.green = std::clamp(g, 0, 255); + dest.red = std::clamp(r, 0, 255); } - UNSAFE_TODO({ - dest_scan[3] = PixelFromFixed(dest_a); - dest_scan += DestBpp; - }); + dest.alpha = PixelFromFixed(acc.alpha); } break; } diff --git a/core/fxge/dib/cstretchengine.h b/core/fxge/dib/cstretchengine.h index 204817d50c..586bb245db 100644 --- a/core/fxge/dib/cstretchengine.h +++ b/core/fxge/dib/cstretchengine.h @@ -60,18 +60,12 @@ class CStretchEngine { struct PixelWeight { void SetStartEnd(int src_start, int src_end, size_t weight_count) { + CHECK_GE(src_start, 0); CHECK_LT(src_end - src_start, static_cast(weight_count)); src_start_ = src_start; src_end_ = src_end; } - uint32_t GetWeightForPosition(int position) const { - CHECK_GE(position, src_start_); - CHECK_LE(position, src_end_); - // SAFETY: enforced by checks above. - return UNSAFE_BUFFERS(weights_[position - src_start_]); - } - void SetWeightForPosition(int position, uint32_t weight) { CHECK_GE(position, src_start_); CHECK_LE(position, src_end_); @@ -79,6 +73,23 @@ class CStretchEngine { UNSAFE_BUFFERS(weights_[position - src_start_] = weight); } + // The first source position this pixel taps. Non-negative, so callers + // can index a span with it directly. + size_t GetSrcStart() const { return static_cast(src_start_); } + + // One weight per source position in [src_start_, src_end_]; empty for an + // empty range. + pdfium::span GetWeights() const { + if (src_end_ < src_start_) { + return {}; + } + // SAFETY: CalculateWeights() sizes each PixelWeight for exactly + // `src_end_ - src_start_ + 1` trailing weights, and SetStartEnd() + // CHECKs that the range fits the allocation. + return UNSAFE_BUFFERS(pdfium::span( + weights_, static_cast(src_end_ - src_start_ + 1))); + } + // NOTE: relies on defined behaviour for unsigned overflow to // decrement the previous position, as needed. void RemoveLastWeightAndAdjust(uint32_t weight_change) { diff --git a/core/fxge/dib/cstretchengine_unittest.cpp b/core/fxge/dib/cstretchengine_unittest.cpp index d15965bba1..9312c5f3db 100644 --- a/core/fxge/dib/cstretchengine_unittest.cpp +++ b/core/fxge/dib/cstretchengine_unittest.cpp @@ -4,13 +4,18 @@ #include "core/fxge/dib/cstretchengine.h" +#include +#include #include +#include #include "core/fpdfapi/page/cpdf_dib.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_number.h" #include "core/fpdfapi/parser/cpdf_stream.h" +#include "core/fxge/dib/cfx_dibitmap.h" #include "core/fxge/dib/fx_dib.h" +#include "core/fxge/dib/scanlinecomposer_iface.h" #include "testing/gtest/include/gtest/gtest.h" namespace { @@ -19,10 +24,41 @@ namespace { constexpr uint32_t kTooBigSrcLen = 20; constexpr uint32_t kTooBigDestLen = 32 * 1024 * 1024 + 1; +// Captures the scanlines StretchVert() composes, so a test can tell a row that +// was zeroed apart from a copy of the row above it. StretchVert() composes into +// its caller through this interface only, so nothing test-only is needed to +// reach the output. +class ScanlineRecorder final : public ScanlineComposerIface { + public: + void ComposeScanline(int line, + pdfium::span scanline) override { + rows_[line] = std::vector(scanline.begin(), scanline.end()); + } + + bool SetInfo(int width, + int height, + FXDIB_Format src_format, + DataVector src_palette) override { + return true; + } + + bool HasRow(int line) const { return rows_.find(line) != rows_.end(); } + + bool RowIsAllZero(int line) const { + auto it = rows_.find(line); + CHECK(it != rows_.end()); + return std::ranges::all_of(it->second, + [](uint8_t byte) { return byte == 0; }); + } + + private: + std::map> rows_; +}; + uint32_t PixelWeightSum(const CStretchEngine::PixelWeight* weights) { uint32_t sum = 0; - for (int i = weights->src_start_; i <= weights->src_end_; ++i) { - sum += weights->GetWeightForPosition(i); + for (uint32_t weight : weights->GetWeights()) { + sum += weight; } return sum; } @@ -137,6 +173,96 @@ TEST(CStretchEngine, ZeroLengthSrcNoSmoothingBilinear) { ASSERT_TRUE(table.CalculateWeights(100, 0, 100, 0, 0, 0, options)); } +// A source clip box that is empty in x leaves `src_max` at 0, so the +// area-average branch clamps a destination pixel's tap position from above +// against `src_max - 1`, i.e. -1. Regression test for storing that negative +// position; the degenerate entry must contribute no weight. +TEST(CStretchEngine, EmptySourceRangeDownscale) { + FXDIB_ResampleOptions options; + CStretchEngine::WeightTable table; + ASSERT_TRUE(table.CalculateWeights(1, 0, 1, 100, 0, 0, options)); + EXPECT_EQ(0u, PixelWeightSum(table.GetPixelWeight(0))); +} + +// Same degenerate source range, reached through the constructor rather than +// by calling CalculateWeights() directly: a horizontally mirrored +// destination whose clip box maps the source's right edge onto x == 0 leaves +// a source clip of zero width but non-zero height. +TEST(CStretchEngine, MirroredDestinationEmptySourceClip) { + auto source = pdfium::MakeRetain(); + ASSERT_TRUE(source->Create(100, 50, FXDIB_Format::kBgra)); + CStretchEngine engine(nullptr, FXDIB_Format::kBgra, /*dest_width=*/-100, + /*dest_height=*/50, FX_RECT(100, 0, 200, 50), source, + FXDIB_ResampleOptions()); + EXPECT_TRUE(engine.StartStretchHorz()); +} + +// The mirror image of the case above: here the source clip degenerates at the +// right edge, so `src_min` and `src_max` are both the source width. Clamping +// the tap position up to `src_min` would put it one past the last source +// pixel, which the horizontal pass then reads. +// +// This drives the horizontal pass directly instead of via Continue(), which +// would go on to run StretchVert() and dereference the null +// ScanlineComposerIface*. The tap position is consumed by the horizontal +// pass, so stopping there still covers the case. +TEST(CStretchEngine, EmptySourceClipAtRightEdge) { + auto source = pdfium::MakeRetain(); + ASSERT_TRUE(source->Create(100, 50, FXDIB_Format::kBgra)); + CStretchEngine engine(nullptr, FXDIB_Format::kBgra, /*dest_width=*/100, + /*dest_height=*/50, FX_RECT(100, 0, 200, 50), source, + FXDIB_ResampleOptions()); + ASSERT_TRUE(engine.StartStretchHorz()); + EXPECT_FALSE(engine.ContinueStretchHorz(nullptr)); +} + +// The vertical pass can be handed an empty tap range even when the source clip +// is not degenerate, which is the case the three tests above cannot reach -- +// StartStretchHorz() rejects a zero-height source clip, so `src_min == +// src_max` never survives to StretchVert(). +// +// The geometry that does reach it: a vertically mirrored destination +// (`dest_height < 0`) whose clip box straddles the bottom edge of the +// destination. The constructor maps the clip box back to source space and then +// clamps it with `src_clip_.Intersect()`, raising `src_clip_.top` from -25 to +// 0; the per-row tap position that CalculateWeights() computes afterwards is +// not clamped the same way, so the lower destination rows land above the +// clamped clip and get `src_start = src_min`, `src_end = pixel_pos < src_min` +// -- an empty range. +// +// This matters because StretchVert()'s row-wide accumulator is allocated once +// outside the row loop and reused. A row with no taps writes nothing into it, +// so failing to zero it for that row would repeat the last row that did have +// taps. The source here is opaque white, which makes a repeated row and a +// zeroed row tell apart. +TEST(CStretchEngine, EmptySourceRangeInVerticalPass) { + auto source = pdfium::MakeRetain(); + ASSERT_TRUE(source->Create(100, 50, FXDIB_Format::kBgr)); + source->Clear(0xffffffff); + + FXDIB_ResampleOptions options; + options.bNoSmoothing = true; + + ScanlineRecorder recorder; + CStretchEngine engine(&recorder, FXDIB_Format::kBgr, /*dest_width=*/100, + /*dest_height=*/-50, FX_RECT(0, 25, 100, 75), source, + options); + ASSERT_TRUE(engine.StartStretchHorz()); + ASSERT_FALSE(engine.ContinueStretchHorz(nullptr)); + engine.StretchVert(); + + // Destination rows 25..49 tap source rows 24..0 and must be opaque; the + // clip box runs to 75, and rows 50..74 tap nothing. + for (int line = 0; line < 25; ++line) { + ASSERT_TRUE(recorder.HasRow(line)) << "line " << line; + EXPECT_FALSE(recorder.RowIsAllZero(line)) << "line " << line; + } + for (int line = 25; line < 50; ++line) { + ASSERT_TRUE(recorder.HasRow(line)) << "line " << line; + EXPECT_TRUE(recorder.RowIsAllZero(line)) << "line " << line; + } +} + TEST(CStretchEngine, ZeroLengthDest) { FXDIB_ResampleOptions options; CStretchEngine::WeightTable table; diff --git a/core/fxge/skrifa/src/main.rs b/core/fxge/skrifa/src/main.rs index 41db28b6c2..d71999984f 100644 --- a/core/fxge/skrifa/src/main.rs +++ b/core/fxge/skrifa/src/main.rs @@ -160,6 +160,7 @@ use skrifa_ffi::{ PsEncodingKind, UnicodeRange, }; +#[allow(clippy::large_enum_variant)] pub enum SkrifaFont<'a> { Sfnt(Sfnt<'a>), Type1(Type1Font), @@ -226,13 +227,12 @@ pub fn new_font<'a>(data: &'a [u8], index: u32) -> Box> { let charset = cff.charset(); let encoding = cff.encoding(); let subfonts = (0..cff.num_subfonts()).map(|i| cff.subfont(i, &[]).ok()).collect(); - let unicode_cmap = if let Some(charset) = charset.as_ref() { - Some(PsCharmap::from_glyph_names(charset.iter().filter_map(|(gid, sid)| { - Some((gid, core::str::from_utf8(cff.string(sid)?).ok()?)) - }))) - } else { - None - }; + let unicode_cmap = + charset.as_ref().map(|charset| { + PsCharmap::from_glyph_names(charset.iter().filter_map(|(gid, sid)| { + Some((gid, core::str::from_utf8(cff.string(sid)?).ok()?)) + })) + }); let cid_count = if cff.is_cid() { charset.as_ref().and_then(|cs| { cs.iter() diff --git a/docs/embedpdf/syncs/2026-10-12-33a499ea1.md b/docs/embedpdf/syncs/2026-10-12-33a499ea1.md index 6381f605aa..f9a85be211 100644 --- a/docs/embedpdf/syncs/2026-10-12-33a499ea1.md +++ b/docs/embedpdf/syncs/2026-10-12-33a499ea1.md @@ -196,6 +196,11 @@ The darwin-arm64 exports gain exactly this one. `test:redaction-perf` 10 of 10; licensing passes. Bindings since the S3 run: only added functions (`FPDFPageObj_AddExistingMark` and `FPDFTextObj_SetFontSize` from S4, `FPDFBookmark_GetColor`); no signature changed. +- **Correction (Oct 13, S7):** this sync's CI run (38307562377) built `embedpdf/main`, not this + branch: the workflow's `ref` input defaults to `embedpdf/main` and the dispatch didn't pass + one. S5 was not CI-tested before merging. On main it failed to build win32-x64 and win32-arm64 + (`HDC` without ``, fixed in S6) and both musl targets (its `build/` listed `ar` as a + build input; S6's `build/` roll fixed that). Main at S6 (run 38317148338) passes on every target. - changed expectations, each with its reason: - `FPDFEditEmbedderTest.EmptyCreation`: our expected bytes (our save writes `/Info` before `/Pages`), objects 2 and 4 seven bytes later: the creation date carries a time zone now diff --git a/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md b/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md new file mode 100644 index 0000000000..e131315084 --- /dev/null +++ b/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md @@ -0,0 +1,160 @@ +# Sync 2026-10-13: upstream f4facdd26..cfe3a2413 (121 commits) + +## Summary + +Upstream from 2026-08-02 to 2026-09-01, merged into `1b27f1fc0`. + +Upstream sped up the image stretch loops, formats content-stream numbers with Dragonbox (shortest +exact form), made the parser's recursion depth unsigned, stopped `FPDFAnnot_SetRect()` from +rewriting the appearance's `/BBox`, reorganized its codecs (Rust decoders, off for us) and made +the embedder tests' unscoped saved-page calls private. We kept the parser depth thread-local, +kept our 1-bit and opaque-row stretch loops and dropped our vertical one for upstream's, +ported our tests to scoped saved pages, gave our Skia subset `SkLogHandler`, and stopped the +JPEG decoder from patching an image stream's own bytes. + +## Likely security fixes + +- `5e7f267e0` 2026-08-17 Expose Rust decoder features and enable fuzzy matching in test runner +- `e7961d624` 2026-08-17 Add dual max delta and MSE criteria for image fuzzy matching +- `ae277bf21` 2026-08-17 Fix crash on invalid object numbers in xref table + +`ae277bf21` taken as upstream wrote it: the cross-reference table refuses object numbers above +the maximum and the load fails instead of crashing; our parser calls it exactly as upstream +does. The other two only add fuzzy image matching to upstream's test runner. + +## Conflicts (10 files, 13 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 | +| :-- | --: | :-- | --: | :-- | :-- | +| `BUILD.gn` | 1 | untagged | | `921adfa17` Reorganize testing/ directories and PNG codec structure; `813359800` Replace Skia BMP decoder with direct Rust FFI (image crate); `98f454243` Replace Skia PNG decoder with direct Rust FFI (png crate); +3 more | Both: upstream's moved test targets (`testing/pdfium_test`, `testing:pdfium_diff`, `921adfa17`) and our layer benchmark and soak tools. | +| `core/fpdfapi/edit/BUILD.gn` | 1 | untagged | | `9ce000987` Use Dragonbox to format floating-point numbers in content streams; `29fe6d617` Add unit tests for CPDFContentstreamWriteUtils | Both: upstream's new `cpdf_contentstream_write_utils_unittest.cpp` (`29fe6d617`) and our tests; the list sorted. | +| `core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp` | 1 | untagged | | `9ce000987` Use Dragonbox to format floating-point numbers in content streams | Ours (our generator writes the text matrix as `cm` before `BT` and `Tj` for unkerned text), with upstream's Dragonbox numbers: `.7 .35` and `.9` instead of `.69999999 .34999999` and `.89999998` (`9ce000987`). | +| `core/fpdfapi/parser/cpdf_document.cpp` | 1 | layer-seam | | `875172eae` Replace generic DCHECK checks with specific operator variants | Ours (layer-seam page list) with upstream's `DCHECK_LT` (`875172eae`). | +| `core/fpdfapi/parser/cpdf_syntax_parser.cpp` | 1 | thread-confined-globals | **1** | `d42d3e097` Make recursion variables unsigned in CPDF_SyntaxParser; `875172eae` Replace generic DCHECK checks with specific operator variants | Upstream's `uint32_t` (`d42d3e097`), with our `EPDF_TLS` kept (thread-confined-globals); the allowlist already names it thread-local. | +| `core/fpdfapi/parser/cpdf_syntax_parser.h` | 1 | thread-confined-globals | **1** | `d42d3e097` Make recursion variables unsigned in CPDF_SyntaxParser; `187ec5a25` Use CPDF_SyntaxParser::kParserMaxRecursionDepth in CPDF_StreamParser | As in the `.cpp`. Upstream moved `kParserMaxRecursionDepth` to the public section (`187ec5a25`); our private copy is gone. | +| `core/fxcodec/BUILD.gn` | 1 | untagged | | `9a7ad119f` Fix Rust BMP/PNG build rules for embedders; `813359800` Replace Skia BMP decoder with direct Rust FFI (image crate); `98f454243` Replace Skia PNG decoder with direct Rust FFI (png crate); +10 more | Upstream now builds `jpeg_common.*` unconditionally (`3efbe60cf` and the Rust decoder commits), which we had added ourselves for our JPEG writer: only our `jpeg_writer.*` (and our `pngmodule.*` and PNG dependency, merged cleanly) stay. | +| `core/fxge/cfx_renderdevice.h` | 1 | bug-fix | | `3abda657c` fxge: fix missing windows include in cfx_renderdevice.h | Upstream's `` include (`3abda657c`), which S6 took early; our copy and its comment go (rule bug-fix). | +| `core/fxge/dib/cstretchengine.cpp` | 4 | render-speedup | | `0f6645c0b` Reinterpret CStretchEngine destination spans as pixel structs; `c9def552a` Speed up CStretchEngine: Zip() the tap loops, interchange StretchVert; `875172eae` Replace generic DCHECK checks with specific operator variants | Upstream rewrote the stretch loops for speed (`c9def552a`, `0f6645c0b`) and replaced `GetWeightForPosition()` with a weights span. The general paths are now upstream's. Ours, behind the off/on switch (render-speedup): the 1-bit run classifier and the 3/4-byte opaque horizontal loop, ported to the span. Our vertical loop (`StretchVertRow()`) is dropped: upstream's now accumulates whole rows per tap, which vectorizes and does what ours did; keeping ours would have done the work twice. | +| `fpdfsdk/fpdf_annot.cpp` | 1 | layer-door | | `d0f00ac9c` Keep annotation AP BBox when resizing | Upstream's: `FPDFAnnot_SetRect()` no longer rewrites the normal appearance's `/BBox` (`d0f00ac9c`), since `/BBox` is the source rectangle a viewer maps into `/Rect`. Our layer-door write there went with it. | + +## Write audit (3 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/fpdfapi/page/cpdf_streamparser.cpp:378` | SetFor( | fresh | The inline-image dictionary being parsed, created a few lines above. Upstream split `ReadNextObject()` (`3cbcab325`). | +| `core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp:324` | const_cast | fixed | `PatchUpTrailer()` and the bad-header fix wrote into the source bytes through a `const_cast`: the image stream's own data, which a layer shares with its frozen base and another thread may be decoding (a read that writes, and a race). Upstream only moved this code (`a178a0ebe`). Now a patch goes into a private copy, and the usual JPEG, whose trailer is in place, isn't copied (reads-dont-write, tagged; `JpegModuleTest.DecodeWithoutTrailerLeavesSourceAlone`). | +| `core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp:326` | const_cast | fixed | As above. | + +## Upstream changes outside the conflicts + +- **Numbers in content streams (`9ce000987`).** Upstream writes floats with Dragonbox, the shortest + form that reads back exactly: `.3` instead of `.30000001`. Everything that writes content goes + through it, our content rewriter and generators too, so their output bytes change; four of our + content-rewrite expectations moved with it (Tests). +- **Saved pages in embedder tests (`17c6f449c`).** `LoadSavedPage()` / `CloseSavedPage()` became + private; our 80 calls in four test files use `LoadScopedSavedPage()`. Where a test closed the + page before closing the saved document, the page now lives in its own block. +- **Skia's `SkLog()` calls `SkLogHandler::GetInstance()`** after the roll; our `redaction_pathops` + subset builds `src/utils/SkLogHandler.cpp` too (a never-set atomic handler; see Globals). +- **JPEG decoding no longer patches an image stream's bytes** (write audit). Found by the write + audit on code upstream moved; the behaviour predates the sync. +- **Codecs reorganized** around optional Rust decoders (BMP, PNG, JPEG) and a split libjpeg + decoder; all Rust options stay off in our builds (they need Rust, and JPEG needs Skia). +- **New APIs:** `FPDFBookmark_GetStyle()`, `FPDFPath_GetBezierControlPoints()`. + +## Upstream appearance generator + +None. + +## Globals + +The heuristic's line is the parser depth, kept thread-local (above). `check-globals.py` on the +darwin-arm64 release build: ok, 255 shared writable (S6: 254), 24 thread-local (S6: 25): +- `gUserLogHandler` (Skia's `SkLogHandler.cpp`), a `std::atomic`, set only by + `SkLogHandler::SetInstance()`, which nothing calls; +- abseil's hash-table seed changed shape: its old function-local thread-local and guard + (`NextHashTableSeed()::seed`) became one thread-local counter (`RandomSeed()::counter`). + +## Build and patches + +- `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. + +Build arguments added or changed: +- `build_overrides/pdfium.gni:52`: `pdf_enable_rust_jpeg_override = false` +- `pdfium.gni:70`: `pdf_enable_rust_jpeg = pdf_enable_rust_jpeg_override` +- `pdfium.gni:114`: `"pdf_enable_rust_bmp=true requires enable_rust=true and enable_rust_cxx=true")` +- `pdfium.gni:118`: `"pdf_enable_rust_jpeg=true requires enable_rust=true, enable_rust_cxx=true, and pdf_use_skia=true")` +- `pdfium.gni:122`: `"pdf_enable_rust_png=true requires enable_rust=true and enable_rust_cxx=true")` + +All three Rust decoders stay off in our builds. + +## API diff + +- added `FPDFBookmark_GetStyle` +- added `FPDFPath_GetBezierControlPoints` + +The darwin-arm64 exports gain exactly these two. + +## Tests + +- [x] fork unit + embedder tests (darwin-arm64): 1226 unit tests pass (one new). Embedder: 1592 + tests, 9 skipped; the failures are S6's 80. Linux runs in the PR's CI. +- [x] layer mode: exit 0; 202 layer-only failures, all expected (S6: 199): four new upstream + form-fill keyboard tests (layer mode never drives the form-fill environment), one renamed. +- [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 + fork main before S1 (`b33fa892d`): `@embedpdf/engine` 34 of 1616 and `@cloudpdf/engine` 20 of + 937 fail, one more each than the baseline; `test:redaction-perf` 10 of 10 as before; licensing + passes. The new one is the same test on both engines, "exports a document to the same file on + every engine", which pins the SHA-256 of an exported bundle. Comparing the bundles from S6's + and S7's runtimes: only the two stamp appearances differ, and only in their numbers + (`218.62199` → `218.622`, `.84999102` → `.849991`: upstream's Dragonbox formatting) and the + file `/ID` that derives from them. The test's comment expects this ("a change to the canonical + drawing changes these on purpose"): the hashes in `runAnnotationExportConformance.ts` get + updated when the engine takes this runtime. Bindings: only the two new functions are added. +- changed expectations, each with its reason: + - `CPDFPageContentGeneratorTest` text case and four `EPDFContentRewriteEmbedderTest` color + space checks: numbers in Dragonbox's shortest form (`.7`, `.3`, `.4`, `.6`; `9ce000987`). + - Layer-mode writers, rerecorded: `FPDFAnnotEmbedderTest.ModifyRectQuadpointsWithAP` writes + 3 objects instead of 4: `FPDFAnnot_SetRect()` no longer rewrites the appearance (`d0f00ac9c`). +- new test: `JpegModuleTest.DecodeWithoutTrailerLeavesSourceAlone`. + +## Ledger changes proposed + +For review; none applied. + +1. **render-speedup, Where:** our stretch loops are now the 1-bit run classifier and the opaque + 3/4-byte horizontal row; the vertical pass is upstream's. Proposal: measure our horizontal + row against upstream's new unrolled loop on a scanned page, and drop ours if it is no longer + faster (less fork code in a hot file upstream keeps changing). +2. **reads-dont-write, Where:** add `libjpeg_scanline_decoder.cpp` (patches go into a private + copy). Worth reporting upstream too: decoding writes into the caller's const input, a data + race when two threads decode one image. +3. **Sync procedure: CI must build the sync branch.** `release-libpdfium.yml`'s `ref` input + defaults to `embedpdf/main`, so a dispatch on the branch without `-f ref=` builds + main. S5's and S6's runs did, so neither was CI-tested before merging: S5 broke win32-x64 + and musl on main. Proposal: in `upstream-sync.md`, trigger with + `-f ref=embedpdf/sync/`, and check the run's checkout step names the branch; or make + the default `github.ref_name`. diff --git a/docs/embedpdf/syncs/2026-10-13-f4facdd26.md b/docs/embedpdf/syncs/2026-10-13-f4facdd26.md index 88fc34fab5..89535ff970 100644 --- a/docs/embedpdf/syncs/2026-10-13-f4facdd26.md +++ b/docs/embedpdf/syncs/2026-10-13-f4facdd26.md @@ -155,6 +155,9 @@ The darwin-arm64 exports gain exactly these four. PDFs from before S3, as before. Against fork main before S1 (`b33fa892d`): the same failures, `@embedpdf/engine` 33 of 1616, `@cloudpdf/engine` 19 of 937, `test:redaction-perf` 10 of 10; licensing passes. Bindings: only the four new functions are added; no signature changed. +- **Correction (Oct 13, S7):** this sync's CI run (38313773945) built `embedpdf/main` (then S5), + not this branch: the workflow's `ref` input defaults to `embedpdf/main`. S6 itself was first + tested on main after merging, run 38317148338: source tests and all nine targets pass. - changed expectations, each with its reason: - `EPDFViewEmbedderTest.DecodedImageStoreRendersAsNewDecodes`: two cases for upstream's `jpeg_reduced_size.pdf`, widths going both ways (the store fix above). diff --git a/fpdfsdk/cpdfsdk_interactiveform.cpp b/fpdfsdk/cpdfsdk_interactiveform.cpp index 6e776f5360..0518db94a1 100644 --- a/fpdfsdk/cpdfsdk_interactiveform.cpp +++ b/fpdfsdk/cpdfsdk_interactiveform.cpp @@ -213,11 +213,8 @@ void CPDFSDK_InteractiveForm::AddMap(CPDF_FormControl* pControl, map_[pdfium::WrapUnowned(pControl)] = pWidget; } -void CPDFSDK_InteractiveForm::RemoveMap(CPDF_FormControl* pControl) { - auto it = map_.find(pControl); - if (it != map_.end()) { - map_.erase(it); - } +void CPDFSDK_InteractiveForm::RemoveMap(CPDF_FormControl* control) { + map_.erase(control); } void CPDFSDK_InteractiveForm::EnableCalculate(bool bEnabled) { diff --git a/fpdfsdk/cpdfsdk_interactiveform.h b/fpdfsdk/cpdfsdk_interactiveform.h index 2abd00ee1f..e90505df29 100644 --- a/fpdfsdk/cpdfsdk_interactiveform.h +++ b/fpdfsdk/cpdfsdk_interactiveform.h @@ -43,7 +43,7 @@ class CPDFSDK_InteractiveForm final std::vector>* widgets) const; void AddMap(CPDF_FormControl* pControl, CPDFSDK_Widget* pWidget); - void RemoveMap(CPDF_FormControl* pControl); + void RemoveMap(CPDF_FormControl* control); void EnableCalculate(bool bEnabled); bool IsCalculateEnabled() const; diff --git a/fpdfsdk/cpdfsdk_widget.cpp b/fpdfsdk/cpdfsdk_widget.cpp index 7dce9e9fb3..6b928906f3 100644 --- a/fpdfsdk/cpdfsdk_widget.cpp +++ b/fpdfsdk/cpdfsdk_widget.cpp @@ -23,6 +23,7 @@ #include "core/fpdfdoc/cpdf_iconfit.h" #include "core/fpdfdoc/cpdf_interactiveform.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/notreached.h" #include "core/fxcrt/retain_ptr.h" #include "core/fxge/cfx_fillrenderoptions.h" @@ -415,8 +416,8 @@ FormFieldType CPDFSDK_Widget::GetFieldType() const { } void CPDFSDK_Widget::SetRect(const CFX_FloatRect& rect) { - DCHECK(rect.right - rect.left >= 1.0f); - DCHECK(rect.top - rect.bottom >= 1.0f); + DCHECK_GE(rect.right - rect.left, 1.0f); + DCHECK_GE(rect.top - rect.bottom, 1.0f); GetMutableAnnotDict()->SetRectFor(pdfium::annotation::kRect, rect); } diff --git a/fpdfsdk/epdf_annot_embeddertest.cpp b/fpdfsdk/epdf_annot_embeddertest.cpp index 10b7f536fd..97d998b185 100644 --- a/fpdfsdk/epdf_annot_embeddertest.cpp +++ b/fpdfsdk/epdf_annot_embeddertest.cpp @@ -5267,14 +5267,13 @@ TEST_F(EPDFAnnotEmbedderTest, ApplyRedactionRemovesTextInMiddleOfSentence) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - std::wstring after = ExtractPageText(saved_page); - ScopedFPDFBitmap after_bitmap = RenderSavedPage(saved_page); + std::wstring after = ExtractPageText(saved_page.get()); + ScopedFPDFBitmap after_bitmap = RenderSavedPage(saved_page.get()); ASSERT_TRUE(after_bitmap); const std::string after_hash = HashBitmap(after_bitmap.get()); - CloseSavedPage(saved_page); EXPECT_NE(std::wstring::npos, after.find(L"hello")); EXPECT_EQ(std::wstring::npos, after.find(L"secret")); EXPECT_NE(std::wstring::npos, after.find(L"world")); @@ -5334,11 +5333,11 @@ TEST_F(EPDFAnnotEmbedderTest, ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - EXPECT_EQ(original_object_count + 2, FPDFPage_CountObjects(saved_page)); + EXPECT_EQ(original_object_count + 2, FPDFPage_CountObjects(saved_page.get())); { - ScopedFPDFBitmap bitmap = RenderPageOnWhite(saved_page, 612, 792); + ScopedFPDFBitmap bitmap = RenderPageOnWhite(saved_page.get(), 612, 792); ASSERT_TRUE(bitmap); EXPECT_EQ(0xFF0000FFu, GetPixelColor(bitmap.get(), 150, 392)); EXPECT_EQ(0xFFFF0000u, GetPixelColor(bitmap.get(), 150, 287)); @@ -5347,7 +5346,6 @@ TEST_F(EPDFAnnotEmbedderTest, EXPECT_EQ(0xFF0000FFu, GetPixelColor(bitmap.get(), 450, 392)); EXPECT_EQ(0xFFFF0000u, GetPixelColor(bitmap.get(), 450, 287)); } - CloseSavedPage(saved_page); } TEST_F(EPDFAnnotEmbedderTest, @@ -5386,17 +5384,16 @@ TEST_F(EPDFAnnotEmbedderTest, ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - EXPECT_EQ(original_object_count + 1, FPDFPage_CountObjects(saved_page)); + EXPECT_EQ(original_object_count + 1, FPDFPage_CountObjects(saved_page.get())); { - ScopedFPDFBitmap bitmap = RenderPageOnWhite(saved_page, 612, 792); + ScopedFPDFBitmap bitmap = RenderPageOnWhite(saved_page.get(), 612, 792); ASSERT_TRUE(bitmap); EXPECT_EQ(0xFF0000FFu, GetPixelColor(bitmap.get(), 150, 392)); EXPECT_EQ(0xFFFFFFFFu, GetPixelColor(bitmap.get(), 300, 392)); EXPECT_EQ(0xFF0000FFu, GetPixelColor(bitmap.get(), 450, 392)); } - CloseSavedPage(saved_page); } TEST_F(EPDFAnnotEmbedderTest, @@ -5459,16 +5456,15 @@ TEST_F(EPDFAnnotEmbedderTest, ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(1); + ScopedSavedPage saved_page = LoadScopedSavedPage(1); ASSERT_TRUE(saved_page); { - ScopedFPDFBitmap bitmap = RenderPageOnWhite(saved_page, 612, 792); + ScopedFPDFBitmap bitmap = RenderPageOnWhite(saved_page.get(), 612, 792); ASSERT_TRUE(bitmap); EXPECT_EQ(0xFF0000FFu, GetPixelColor(bitmap.get(), 180, 442)); EXPECT_EQ(0xFFFFFFFFu, GetPixelColor(bitmap.get(), 150, 442)); EXPECT_EQ(0xFF0000FFu, GetPixelColor(bitmap.get(), 350, 442)); } - CloseSavedPage(saved_page); } TEST_F(EPDFAnnotEmbedderTest, RotatedCaretAppearanceCarriesTransform) { @@ -5901,11 +5897,11 @@ TEST_F(EPDFAnnotEmbedderTest, EmbedSetRectPreservesRotatedAppearance) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); { ScopedFPDFAnnotation saved_annot( - FPDFPage_GetAnnot(saved_page, annot_index)); + FPDFPage_GetAnnot(saved_page.get(), annot_index)); ASSERT_TRUE(saved_annot); FS_RECTF saved_rect = {}; @@ -5929,7 +5925,6 @@ TEST_F(EPDFAnnotEmbedderTest, EmbedSetRectPreservesRotatedAppearance) { expect_rect(stream_dict->GetRectFor("BBox"), original_unrotated); expect_matrix(stream_dict->GetMatrixFor("Matrix"), original_matrix); } - CloseSavedPage(saved_page); } TEST_F(EPDFAnnotEmbedderTest, ApplyRedactionRotatedQuadSparesNeighbours) { @@ -6005,10 +6000,9 @@ TEST_F(EPDFAnnotEmbedderTest, ApplyRedactionRotatedQuadSparesNeighbours) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - std::wstring after = ExtractPageText(saved_page); - CloseSavedPage(saved_page); + std::wstring after = ExtractPageText(saved_page.get()); EXPECT_EQ(std::wstring::npos, after.find(L"SECRET")); EXPECT_NE(std::wstring::npos, after.find(L"KEEPME")); } @@ -6032,10 +6026,9 @@ TEST_F(EPDFAnnotEmbedderTest, ApplyRedactionFallsBackToRectForNonFiniteQuad) { ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - const std::wstring after = ExtractPageText(saved_page); - CloseSavedPage(saved_page); + const std::wstring after = ExtractPageText(saved_page.get()); EXPECT_NE(std::wstring::npos, after.find(L"hello")); EXPECT_EQ(std::wstring::npos, after.find(L"secret")); EXPECT_NE(std::wstring::npos, after.find(L"world")); @@ -6137,10 +6130,9 @@ TEST_F(EPDFAnnotEmbedderTest, RedactInQuadsMultipleRegionsOnOneTextObject) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - const std::wstring after = ExtractPageText(saved_page); - CloseSavedPage(saved_page); + const std::wstring after = ExtractPageText(saved_page.get()); EXPECT_EQ(std::wstring::npos, after.find(L"SECRET")) << after; EXPECT_EQ(std::wstring::npos, after.find(L"ECRET")) << after; @@ -6187,10 +6179,9 @@ TEST_F(EPDFAnnotEmbedderTest, ApplyPageRedactionsTwoMarksOnOneTextObject) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - const std::wstring after = ExtractPageText(saved_page); - CloseSavedPage(saved_page); + const std::wstring after = ExtractPageText(saved_page.get()); EXPECT_EQ(std::wstring::npos, after.find(L"SECRET")) << after; EXPECT_EQ(std::wstring::npos, after.find(L"ECRET")) << after; @@ -6230,20 +6221,19 @@ TEST_F(EPDFAnnotEmbedderTest, RedactFarFromVerticalTextLeavesItInPlace) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - EXPECT_EQ(before, ExtractPageText(saved_page)); + EXPECT_EQ(before, ExtractPageText(saved_page.get())); double left_after = 0; double right_after = 0; double bottom_after = 0; double top_after = 0; { - ScopedFPDFTextPage text_page(FPDFText_LoadPage(saved_page)); + ScopedFPDFTextPage text_page(FPDFText_LoadPage(saved_page.get())); ASSERT_TRUE(text_page); ASSERT_TRUE(FPDFText_GetCharBox(text_page.get(), 0, &left_after, &right_after, &bottom_after, &top_after)); } - CloseSavedPage(saved_page); EXPECT_NEAR(left_before, left_after, 0.01); EXPECT_NEAR(right_before, right_after, 0.01); EXPECT_NEAR(bottom_before, bottom_after, 0.01); @@ -6278,10 +6268,9 @@ TEST_F(EPDFAnnotEmbedderTest, RedactInQuadsRemovesTextInsideFormXObject) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - const std::wstring after = ExtractPageText(saved_page); - CloseSavedPage(saved_page); + const std::wstring after = ExtractPageText(saved_page.get()); EXPECT_NE(std::wstring::npos, after.find(L"Hello,")) << after; EXPECT_NE(std::wstring::npos, after.find(L"Goodbye, world!")) << after; @@ -6333,9 +6322,9 @@ TEST_F(EPDFAnnotEmbedderTest, RedactRemovesInvisibleOcrLayerText) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - const std::wstring after = ExtractPageText(saved_page); + const std::wstring after = ExtractPageText(saved_page.get()); EXPECT_EQ(std::wstring::npos, after.find(L"SECRET")) << after; EXPECT_EQ(std::wstring::npos, after.find(L"ECRET")) << after; EXPECT_EQ(std::wstring::npos, after.find(L"DAT")) << after; @@ -6343,7 +6332,7 @@ TEST_F(EPDFAnnotEmbedderTest, RedactRemovesInvisibleOcrLayerText) { const size_t surviving_a = after.find(L"A"); ASSERT_NE(std::wstring::npos, surviving_a) << after; { - ScopedFPDFTextPage text_page(FPDFText_LoadPage(saved_page)); + ScopedFPDFTextPage text_page(FPDFText_LoadPage(saved_page.get())); ASSERT_TRUE(text_page); double l = 0; double r = 0; @@ -6353,7 +6342,6 @@ TEST_F(EPDFAnnotEmbedderTest, RedactRemovesInvisibleOcrLayerText) { text_page.get(), static_cast(surviving_a), &l, &r, &b, &t)); EXPECT_NEAR(a_left_before, l, 0.05); } - CloseSavedPage(saved_page); } // Vertical writing: removing the leading glyph must re-anchor the run along @@ -6386,13 +6374,13 @@ TEST_F(EPDFAnnotEmbedderTest, RedactVerticalTextLeadingGlyphKeepsRestInPlace) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - const std::wstring after = ExtractPageText(saved_page); + const std::wstring after = ExtractPageText(saved_page.get()); EXPECT_EQ(std::wstring::npos, after.find(L"H")) << after; ASSERT_EQ(0u, after.find(L"ello")) << after; { - ScopedFPDFTextPage text_page(FPDFText_LoadPage(saved_page)); + ScopedFPDFTextPage text_page(FPDFText_LoadPage(saved_page.get())); ASSERT_TRUE(text_page); double l = 0; double r = 0; @@ -6404,7 +6392,6 @@ TEST_F(EPDFAnnotEmbedderTest, RedactVerticalTextLeadingGlyphKeepsRestInPlace) { EXPECT_NEAR(e_bottom, b, 0.05); EXPECT_NEAR(e_top, t, 0.05); } - CloseSavedPage(saved_page); } TEST_F(EPDFAnnotEmbedderTest, RedactVerticalTextMiddleGlyphsKeepsTailInPlace) { @@ -6434,16 +6421,16 @@ TEST_F(EPDFAnnotEmbedderTest, RedactVerticalTextMiddleGlyphsKeepsTailInPlace) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - const std::wstring after = ExtractPageText(saved_page); + const std::wstring after = ExtractPageText(saved_page.get()); EXPECT_EQ(std::wstring::npos, after.find(L"ll")) << after; ASSERT_EQ(0u, after.find(L"He")) << after; const size_t o_index = after.find(L"o"); ASSERT_NE(std::wstring::npos, o_index) << after; EXPECT_LT(o_index, after.find(L"World")) << after; { - ScopedFPDFTextPage text_page(FPDFText_LoadPage(saved_page)); + ScopedFPDFTextPage text_page(FPDFText_LoadPage(saved_page.get())); ASSERT_TRUE(text_page); double l = 0; double r = 0; @@ -6456,7 +6443,6 @@ TEST_F(EPDFAnnotEmbedderTest, RedactVerticalTextMiddleGlyphsKeepsTailInPlace) { EXPECT_NEAR(o_bottom, b, 0.05); EXPECT_NEAR(o_top, t, 0.05); } - CloseSavedPage(saved_page); } // Text in a font whose glyphs have NO outlines at all (empty glyph boxes, @@ -6481,17 +6467,16 @@ TEST_F(EPDFAnnotEmbedderTest, RedactRemovesTextWithEmptyGlyphBoxes) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); int text_objects = 0; - const int count = FPDFPage_CountObjects(saved_page); + const int count = FPDFPage_CountObjects(saved_page.get()); for (int i = 0; i < count; ++i) { - if (FPDFPageObj_GetType(FPDFPage_GetObject(saved_page, i)) == + if (FPDFPageObj_GetType(FPDFPage_GetObject(saved_page.get(), i)) == FPDF_PAGEOBJ_TEXT) { ++text_objects; } } - CloseSavedPage(saved_page); EXPECT_EQ(0, text_objects); EXPECT_EQ(std::string::npos, GetString().find("(SECRET)")); } @@ -6839,14 +6824,13 @@ TEST_F(EPDFAnnotEmbedderTest, ApplyRedactionPreservesInheritedColorspaceResource ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); { - ScopedFPDFBitmap bmp = RenderPageOnWhite(saved_page, 200, 200); + ScopedFPDFBitmap bmp = RenderPageOnWhite(saved_page.get(), 200, 200); EXPECT_TRUE(is_reddish(GetPixelColor(bmp.get(), 100, 100))); EXPECT_EQ(0xFF000000u, GetPixelColor(bmp.get(), 15, 185)); } - CloseSavedPage(saved_page); } TEST_F(EPDFAnnotEmbedderTest, ApplyRedactionOnNeverParsedPageRemovesText) { @@ -6867,13 +6851,12 @@ TEST_F(EPDFAnnotEmbedderTest, ApplyRedactionOnNeverParsedPageRemovesText) { ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - std::wstring after = ExtractPageText(saved_page); + std::wstring after = ExtractPageText(saved_page.get()); EXPECT_EQ(std::wstring::npos, after.find(L"secret")); EXPECT_NE(std::wstring::npos, after.find(L"hello")); EXPECT_NE(std::wstring::npos, after.find(L"world")); - CloseSavedPage(saved_page); } TEST_F(EPDFAnnotEmbedderTest, GenerateRedactAppearanceBakesLabelIntoOverlay) { diff --git a/fpdfsdk/epdf_content_rewrite_embeddertest.cpp b/fpdfsdk/epdf_content_rewrite_embeddertest.cpp index c4f22e3135..dc3d1f5be3 100644 --- a/fpdfsdk/epdf_content_rewrite_embeddertest.cpp +++ b/fpdfsdk/epdf_content_rewrite_embeddertest.cpp @@ -371,18 +371,20 @@ class EPDFContentRewriteEmbedderTest FPDF_BITMAP before, const std::vector& areas, const std::function& inspect = {}) { - FPDF_PAGE saved = LoadSavedPage(index); - ASSERT_TRUE(saved); - ScopedFPDFBitmap after = RenderAtPageSize(saved); - std::string where; - EXPECT_EQ(0, CountChangedPixelsOutside(before, after.get(), areas, &where)) - << where; - EXPECT_EQ(0, CountInkInside(after.get(), areas)); - EXPECT_EQ(L"", TextInside(saved, areas)); - if (inspect) { - inspect(saved_document(), saved); + { + ScopedSavedPage saved = LoadScopedSavedPage(index); + ASSERT_TRUE(saved); + ScopedFPDFBitmap after = RenderAtPageSize(saved.get()); + std::string where; + EXPECT_EQ(0, + CountChangedPixelsOutside(before, after.get(), areas, &where)) + << where; + EXPECT_EQ(0, CountInkInside(after.get(), areas)); + EXPECT_EQ(L"", TextInside(saved.get(), areas)); + if (inspect) { + inspect(saved_document(), saved.get()); + } } - CloseSavedPage(saved); CloseSaved(); } @@ -476,9 +478,9 @@ TEST_P(EPDFContentRewriteEmbedderTest, KeepsEveryColorSpace) { << content; EXPECT_NE(std::string::npos, content.find("/CSsep CS 1 SCN")) << content; - EXPECT_NE(std::string::npos, content.find("/CSdn cs .30000001 1 scn")) + EXPECT_NE(std::string::npos, content.find("/CSdn cs .3 1 scn")) << content; - EXPECT_NE(std::string::npos, content.find("/CSpat cs 0 .60000002 0 /")) + EXPECT_NE(std::string::npos, content.find("/CSpat cs 0 .6 0 /")) << content; }); } @@ -525,12 +527,13 @@ TEST_P(EPDFContentRewriteEmbedderTest, EditingAnAppearanceKeepsItsSpotColor) { ASSERT_TRUE(FPDFAnnot_UpdateObject(stamp.get(), object)); }); - FPDF_PAGE saved = LoadSavedPage(kAppearanceInASpotColor); - ASSERT_TRUE(saved); - ScopedFPDFBitmap after = RenderAtPageSize(saved); - EXPECT_EQ(0xff0000u, PixelAt(after.get(), 250, 535)); - EXPECT_EQ(0, CountChangedPixelsOutside(before.get(), after.get(), {bar})); - CloseSavedPage(saved); + { + ScopedSavedPage saved = LoadScopedSavedPage(kAppearanceInASpotColor); + ASSERT_TRUE(saved); + ScopedFPDFBitmap after = RenderAtPageSize(saved.get()); + EXPECT_EQ(0xff0000u, PixelAt(after.get(), 250, 535)); + EXPECT_EQ(0, CountChangedPixelsOutside(before.get(), after.get(), {bar})); + } CloseSaved(); } @@ -582,13 +585,14 @@ TEST_P(EPDFContentRewriteEmbedderTest, ASSERT_TRUE(FPDFPage_GenerateContent(page.get())); }); - FPDF_PAGE saved = LoadSavedPage(0); - ASSERT_TRUE(saved); - ScopedFPDFBitmap after = RenderAtPageSize(saved); - EXPECT_EQ(0xffffffu, PixelAt(after.get(), 150, 333)); - EXPECT_EQ(0, - CountChangedPixelsOutside(before.get(), after.get(), {underline})); - CloseSavedPage(saved); + { + ScopedSavedPage saved = LoadScopedSavedPage(0); + ASSERT_TRUE(saved); + ScopedFPDFBitmap after = RenderAtPageSize(saved.get()); + EXPECT_EQ(0xffffffu, PixelAt(after.get(), 150, 333)); + EXPECT_EQ( + 0, CountChangedPixelsOutside(before.get(), after.get(), {underline})); + } CloseSaved(); } @@ -735,10 +739,10 @@ TEST_P(EPDFContentRewriteEmbedderTest, KeepsICCBasedDefaultsAndCMYKPatterns) { kICCBasedDefaultsAndCMYKPattern, [](FPDF_DOCUMENT saved, FPDF_PAGE) { const std::string content = PageContent(saved, kICCBasedDefaultsAndCMYKPattern); - EXPECT_NE(std::string::npos, content.find("/CSicc cs .2 .40000001")) + EXPECT_NE(std::string::npos, content.find("/CSicc cs .2 .4")) << content; EXPECT_NE(std::string::npos, - content.find("/CSpatk cs 0 .60000002 1 0 /")) + content.find("/CSpatk cs 0 .6 1 0 /")) << content; RetainPtr spaces = CPDFDocumentFromFPDFDocument(saved) diff --git a/fpdfsdk/epdf_flatten_embeddertest.cpp b/fpdfsdk/epdf_flatten_embeddertest.cpp index d966fbfac7..3d641eeb97 100644 --- a/fpdfsdk/epdf_flatten_embeddertest.cpp +++ b/fpdfsdk/epdf_flatten_embeddertest.cpp @@ -144,11 +144,10 @@ TEST_F(EPDFFlattenEmbedderTest, FlattenSpecificAnnotationByHandle) { ASSERT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved_page = LoadSavedPage(0); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); ASSERT_TRUE(saved_page); - EXPECT_EQ(5, FPDFPage_GetAnnotCount(saved_page)); - EXPECT_FALSE(EPDFPage_GetAnnotByObjectNumber(saved_page, 4u)); - CloseSavedPage(saved_page); + EXPECT_EQ(5, FPDFPage_GetAnnotCount(saved_page.get())); + EXPECT_FALSE(EPDFPage_GetAnnotByObjectNumber(saved_page.get(), 4u)); } TEST_F(EPDFFlattenEmbedderTest, diff --git a/fpdfsdk/epdf_redact_embeddertest.cpp b/fpdfsdk/epdf_redact_embeddertest.cpp index c0bf251df9..7785c024f1 100644 --- a/fpdfsdk/epdf_redact_embeddertest.cpp +++ b/fpdfsdk/epdf_redact_embeddertest.cpp @@ -542,10 +542,10 @@ TEST_F(EPDFRedactEmbedderTest, NestedFormsUseTheRenderersTransformOrder) { auto before = Render(page.get()); ApplyAndSave(page.get()); - FPDF_PAGE saved = LoadSavedPage(0); + ScopedSavedPage saved = LoadScopedSavedPage(0); ASSERT_TRUE(saved); - EXPECT_EQ(L"PUBLIC", ReadText(saved)); - auto after = Render(saved); + EXPECT_EQ(L"PUBLIC", ReadText(saved.get())); + auto after = Render(saved.get()); // Independent, fixture-defined coordinates. Overlays are disabled, so // neither extraction nor rendering can hide the surviving nested word. for (int y = 440; y < 465; ++y) { @@ -555,7 +555,6 @@ TEST_F(EPDFRedactEmbedderTest, NestedFormsUseTheRenderersTransformOrder) { } ExpectSamePixels(before.get(), after.get(), 45, 275, 160, 305); ExpectNoSentinel(CPDFDocumentFromFPDFDocument(saved_document()), "SECRET"); - CloseSavedPage(saved); } TEST_F(EPDFRedactEmbedderTest, SharedImageAndMaskPreserveOtherPlacements) { @@ -567,22 +566,20 @@ TEST_F(EPDFRedactEmbedderTest, SharedImageAndMaskPreserveOtherPlacements) { EXPECT_NE(0xffffffu, original); ApplyAndSave(page.get()); - FPDF_PAGE saved = LoadSavedPage(0); + ScopedSavedPage saved = LoadScopedSavedPage(0); ASSERT_TRUE(saved); - auto after = Render(saved); + auto after = Render(saved.get()); EXPECT_EQ(0xffffffu, Pixel(after.get(), 75, 500)); ExpectSamePixels(before.get(), after.get(), 100, 450, 150, 550); ExpectSamePixels(before.get(), after.get(), 250, 450, 350, 550); - CloseSavedPage(saved); // This placement was never parsed or rendered before the mutation; caches // cannot disguise an overwrite of its shared image or soft-mask stream. - FPDF_PAGE other = LoadSavedPage(1); + ScopedSavedPage other = LoadScopedSavedPage(1); ASSERT_TRUE(other); - auto other_bitmap = Render(other); + auto other_bitmap = Render(other.get()); EXPECT_EQ(original, Pixel(other_bitmap.get(), 75, 500)); EXPECT_EQ(original, Pixel(other_bitmap.get(), 125, 500)); - CloseSavedPage(other); } TEST_F(EPDFRedactEmbedderTest, RemovesReplacementTextFromWholeAffectedSpan) { @@ -592,11 +589,11 @@ TEST_F(EPDFRedactEmbedderTest, RemovesReplacementTextFromWholeAffectedSpan) { auto before = Render(page.get()); ApplyAndSave(page.get()); - FPDF_PAGE saved = LoadSavedPage(0); + ScopedSavedPage saved = LoadScopedSavedPage(0); ASSERT_TRUE(saved); - EXPECT_EQ(L"keep\r\nsibling\r\nPUBLIC", ReadText(saved)); + EXPECT_EQ(L"keep\r\nsibling\r\nPUBLIC", ReadText(saved.get())); ExpectNoSentinel(CPDFDocumentFromFPDFDocument(saved_document()), "SECRET"); - auto after = Render(saved); + auto after = Render(saved.get()); ExpectSamePixels(before.get(), after.get(), 135, 475, 200, 505); ExpectSamePixels(before.get(), after.get(), 45, 375, 130, 405); ExpectSamePixels(before.get(), after.get(), 45, 275, 160, 305); @@ -610,7 +607,6 @@ TEST_F(EPDFRedactEmbedderTest, RemovesReplacementTextFromWholeAffectedSpan) { auto kids = paragraph->GetArrayFor("K"); EXPECT_FALSE(kids->GetDictAt(0)->KeyExist("ActualText")); EXPECT_EQ(L"PUBLIC", kids->GetDictAt(1)->GetUnicodeTextFor("ActualText")); - CloseSavedPage(saved); } TEST_F(EPDFRedactEmbedderTest, @@ -619,11 +615,10 @@ TEST_F(EPDFRedactEmbedderTest, ScopedPage page = LoadScopedPage(0); ASSERT_TRUE(page); ApplyAndSave(page.get()); - FPDF_PAGE saved = LoadSavedPage(0); + ScopedSavedPage saved = LoadScopedSavedPage(0); ASSERT_TRUE(saved); - EXPECT_EQ(L"keep\r\nsibling", ReadText(saved)); + EXPECT_EQ(L"keep\r\nsibling", ReadText(saved.get())); ExpectNoSentinel(CPDFDocumentFromFPDFDocument(saved_document()), "SECRET"); - CloseSavedPage(saved); } TEST_F(EPDFRedactEmbedderTest, @@ -637,11 +632,10 @@ TEST_F(EPDFRedactEmbedderTest, ASSERT_TRUE(FPDFAnnot_SetRect(annot.get(), &whole_span)); } ApplyAndSave(page.get()); - FPDF_PAGE saved = LoadSavedPage(0); + ScopedSavedPage saved = LoadScopedSavedPage(0); ASSERT_TRUE(saved); - EXPECT_EQ(L"PUBLIC", ReadText(saved)); + EXPECT_EQ(L"PUBLIC", ReadText(saved.get())); ExpectNoSentinel(CPDFDocumentFromFPDFDocument(saved_document()), "SECRET"); - CloseSavedPage(saved); } TEST_F(EPDFRedactEmbedderTest, SharedFormPreservesUnmarkedReplacementText) { @@ -650,14 +644,13 @@ TEST_F(EPDFRedactEmbedderTest, SharedFormPreservesUnmarkedReplacementText) { ASSERT_TRUE(page); auto before = Render(page.get()); ApplyAndSave(page.get()); - FPDF_PAGE saved = LoadSavedPage(0); + ScopedSavedPage saved = LoadScopedSavedPage(0); ASSERT_TRUE(saved); // The unmarked copy is legitimate content. A document-wide forbidden-word // assertion would be wrong here, and global property mutation loses it. - EXPECT_EQ(L"keep SECRET keep", ReadText(saved)); - auto after = Render(saved); + EXPECT_EQ(L"keep SECRET keep", ReadText(saved.get())); + auto after = Render(saved.get()); ExpectSamePixels(before.get(), after.get(), 45, 275, 220, 305); - CloseSavedPage(saved); } TEST_F(EPDFRedactEmbedderTest, SharedImagePlacementsReceiveIndependentRegions) { @@ -673,14 +666,13 @@ TEST_F(EPDFRedactEmbedderTest, SharedImagePlacementsReceiveIndependentRegions) { ASSERT_TRUE(FPDFAnnot_SetRect(annot.get(), &other_half)); } ApplyAndSave(page.get()); - FPDF_PAGE saved = LoadSavedPage(0); + ScopedSavedPage saved = LoadScopedSavedPage(0); ASSERT_TRUE(saved); - auto after = Render(saved); + auto after = Render(saved.get()); EXPECT_EQ(0xffffffu, Pixel(after.get(), 75, 500)); EXPECT_EQ(original, Pixel(after.get(), 125, 500)); EXPECT_EQ(original, Pixel(after.get(), 275, 500)); EXPECT_EQ(0xffffffu, Pixel(after.get(), 325, 500)); - CloseSavedPage(saved); } TEST_F(EPDFRedactEmbedderTest, InheritedPropertiesLeaveNoUnusedOriginalOnPage) { @@ -688,11 +680,10 @@ TEST_F(EPDFRedactEmbedderTest, InheritedPropertiesLeaveNoUnusedOriginalOnPage) { ScopedPage page = LoadScopedPage(0); ASSERT_TRUE(page); ApplyAndSave(page.get()); - FPDF_PAGE saved = LoadSavedPage(0); + ScopedSavedPage saved = LoadScopedSavedPage(0); ASSERT_TRUE(saved); - EXPECT_EQ(L"keep", ReadText(saved)); + EXPECT_EQ(L"keep", ReadText(saved.get())); ExpectNoSentinel(CPDFDocumentFromFPDFDocument(saved_document()), "SECRET"); - CloseSavedPage(saved); } // Exercise page-tree inheritance separately from Form resource inheritance. @@ -801,14 +792,12 @@ TEST_P(EPDFRedactPageTreeTest, RemovesUnusedPropertiesFromEveryAncestor) { SaveWorking(); ASSERT_TRUE(saved_document()); ExpectNoSentinel(CPDFDocumentFromFPDFDocument(saved_document()), "SECRET"); - FPDF_PAGE saved = LoadSavedPage(0); + ScopedSavedPage saved = LoadScopedSavedPage(0); ASSERT_TRUE(saved); - EXPECT_EQ(L"keep\r\nsibling\r\nPUBLIC", ReadText(saved)); - CloseSavedPage(saved); - FPDF_PAGE sibling = LoadSavedPage(1); + EXPECT_EQ(L"keep\r\nsibling\r\nPUBLIC", ReadText(saved.get())); + ScopedSavedPage sibling = LoadScopedSavedPage(1); ASSERT_TRUE(sibling); - EXPECT_EQ(L"PUBLIC", ReadText(sibling)); - CloseSavedPage(sibling); + EXPECT_EQ(L"PUBLIC", ReadText(sibling.get())); ExpectOtherLayerUnchanged(); } @@ -821,10 +810,9 @@ TEST_P(EPDFRedactPageTreeTest, PreservesAnUnmarkedSiblingUsingTheProperty) { ASSERT_TRUE(FPDFPage_GenerateContent(page.get())); SaveWorking(); ASSERT_TRUE(saved_document()); - FPDF_PAGE sibling = LoadSavedPage(1); + ScopedSavedPage sibling = LoadScopedSavedPage(1); ASSERT_TRUE(sibling); - EXPECT_EQ(L"SECRET keep sibling\r\nPUBLIC", ReadText(sibling)); - CloseSavedPage(sibling); + EXPECT_EQ(L"SECRET keep sibling\r\nPUBLIC", ReadText(sibling.get())); ExpectOtherLayerUnchanged(); } @@ -1023,12 +1011,11 @@ TEST_F(EPDFRedactEmbedderTest, AppliesOnAPageWithoutContentsOrResources) { ASSERT_TRUE(FPDF_SaveAsCopy(doc.get(), this, FPDF_NO_INCREMENTAL)); ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE saved = LoadSavedPage(0); + ScopedSavedPage saved = LoadScopedSavedPage(0); ASSERT_TRUE(saved); - auto reloaded = Render(saved); + auto reloaded = Render(saved.get()); EXPECT_EQ(0x000000u, Pixel(reloaded.get(), 80, 330)); EXPECT_EQ(0xffffffu, Pixel(reloaded.get(), 200, 100)); - CloseSavedPage(saved); } } @@ -1223,11 +1210,10 @@ TEST_F(EPDFRedactEmbedderTest, ARedactedImageKeepsItsLayerAndHowItIsDrawnOnly) { for (const char* sentinel : {"IMAGEMETASECRET", "ALTSECRET", "OPISECRET"}) { ExpectNoSentinel(saved, sentinel); } - FPDF_PAGE reloaded = LoadSavedPage(0); + ScopedSavedPage reloaded = LoadScopedSavedPage(0); ASSERT_TRUE(reloaded); - auto saved_render = Render(reloaded); + auto saved_render = Render(reloaded.get()); EXPECT_EQ(0xffffffu, Pixel(saved_render.get(), 400, 200)); - CloseSavedPage(reloaded); } } @@ -1286,13 +1272,12 @@ TEST_F(EPDFRedactEmbedderTest, AnExplicitMaskStillMasksTheReplacementImage) { ASSERT_TRUE(FPDF_SaveAsCopy(opened.doc.get(), this, FPDF_NO_INCREMENTAL)); ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE reloaded = LoadSavedPage(0); + ScopedSavedPage reloaded = LoadScopedSavedPage(0); ASSERT_TRUE(reloaded); - auto saved_render = Render(reloaded); + auto saved_render = Render(reloaded.get()); EXPECT_EQ(0xffffffu, Pixel(saved_render.get(), 140, 300)); EXPECT_NE(0xffffffu, Pixel(saved_render.get(), 240, 300)); EXPECT_EQ(0xffffffu, Pixel(saved_render.get(), 400, 300)); - CloseSavedPage(reloaded); } } } @@ -1387,12 +1372,11 @@ TEST_F(EPDFRedactEmbedderTest, CopiesOfAType3FontsContentHoldNoOtherCopies) { CPDF_Document* saved = CPDFDocumentFromFPDFDocument(saved_document()); ExpectNoSentinel(saved, "NESTEDPRIVATE"); ExpectNoSentinel(saved, "GLYPHPRIVATE"); - FPDF_PAGE reloaded = LoadSavedPage(0); + ScopedSavedPage reloaded = LoadScopedSavedPage(0); ASSERT_TRUE(reloaded); - auto saved_render = Render(reloaded); + auto saved_render = Render(reloaded.get()); EXPECT_EQ(Pixel(before.get(), 350, 250), Pixel(saved_render.get(), 350, 250)); - CloseSavedPage(reloaded); } } } @@ -1522,11 +1506,10 @@ TEST_F(EPDFRedactEmbedderTest, ARedactedImageKeepsItsSamplesAndFormat) { ASSERT_TRUE(FPDF_SaveAsCopy(opened.doc.get(), this, FPDF_NO_INCREMENTAL)); ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE reloaded = LoadSavedPage(0); + ScopedSavedPage reloaded = LoadScopedSavedPage(0); ASSERT_TRUE(reloaded); - auto saved = Render(reloaded); + auto saved = Render(reloaded.get()); ExpectSamePixels(after.get(), saved.get(), 100, 200, 500, 400); - CloseSavedPage(reloaded); } } } @@ -1673,11 +1656,10 @@ TEST_F(EPDFRedactEmbedderTest, ARedactedJpegStaysAJpeg) { ASSERT_TRUE(FPDF_SaveAsCopy(opened.doc.get(), this, FPDF_NO_INCREMENTAL)); ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE reloaded = LoadSavedPage(0); + ScopedSavedPage reloaded = LoadScopedSavedPage(0); ASSERT_TRUE(reloaded); - auto saved = Render(reloaded); + auto saved = Render(reloaded.get()); ExpectSamePixels(after.get(), saved.get(), 100, 200, 500, 400); - CloseSavedPage(reloaded); } } } @@ -1968,11 +1950,10 @@ TEST_F(EPDFRedactEmbedderTest, ARedactedJpeg2000IsWrittenAsAJpeg) { ASSERT_TRUE(FPDF_SaveAsCopy(opened.doc.get(), this, FPDF_NO_INCREMENTAL)); ScopedSavedDoc saved_doc = OpenScopedSavedDocument(); ASSERT_TRUE(saved_doc); - FPDF_PAGE reloaded = LoadSavedPage(0); + ScopedSavedPage reloaded = LoadScopedSavedPage(0); ASSERT_TRUE(reloaded); - auto saved = Render(reloaded); + auto saved = Render(reloaded.get()); ExpectSamePixels(after.get(), saved.get(), 100, 200, 500, 400); - CloseSavedPage(reloaded); } } } diff --git a/fpdfsdk/formfiller/cffl_interactiveformfiller.cpp b/fpdfsdk/formfiller/cffl_interactiveformfiller.cpp index 3e855d172f..b041ff7e58 100644 --- a/fpdfsdk/formfiller/cffl_interactiveformfiller.cpp +++ b/fpdfsdk/formfiller/cffl_interactiveformfiller.cpp @@ -629,13 +629,8 @@ bool CFFL_InteractiveFormFiller::Redo(CPDFSDK_Widget* pWidget) { return pFormField && pFormField->Redo(); } -void CFFL_InteractiveFormFiller::UnregisterFormField(CPDFSDK_Widget* pWidget) { - auto it = map_.find(pWidget); - if (it == map_.end()) { - return; - } - - map_.erase(it); +void CFFL_InteractiveFormFiller::UnregisterFormField(CPDFSDK_Widget* widget) { + map_.erase(widget); } void CFFL_InteractiveFormFiller::InvalidateRect(PerWindowData* pWidgetData, diff --git a/fpdfsdk/formfiller/cffl_interactiveformfiller.h b/fpdfsdk/formfiller/cffl_interactiveformfiller.h index 175b949d5d..461748b21f 100644 --- a/fpdfsdk/formfiller/cffl_interactiveformfiller.h +++ b/fpdfsdk/formfiller/cffl_interactiveformfiller.h @@ -213,7 +213,7 @@ class CFFL_InteractiveFormFiller final : public IPWL_FillerNotify { bool IsFillingAllowed(CPDFSDK_Widget* pWidget) const; CFFL_FormField* GetFormField(CPDFSDK_Widget* pWidget); CFFL_FormField* GetOrCreateFormField(CPDFSDK_Widget* pWidget); - void UnregisterFormField(CPDFSDK_Widget* pWidget); + void UnregisterFormField(CPDFSDK_Widget* widget); UnownedPtr const callback_iface_; WidgetToFormFillerMap map_; diff --git a/fpdfsdk/fpdf_annot.cpp b/fpdfsdk/fpdf_annot.cpp index c943fbabbe..88b871f3ca 100644 --- a/fpdfsdk/fpdf_annot.cpp +++ b/fpdfsdk/fpdf_annot.cpp @@ -981,22 +981,6 @@ FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFAnnot_SetRect(FPDF_ANNOTATION annot, // Update the "Rect" entry in the annotation dictionary. pAnnotDict->SetRectFor(pdfium::annotation::kRect, newRect); - - // If the annotation's appearance stream is defined, the annotation is of a - // type that does not have quadpoints, and the new rectangle is bigger than - // the current bounding box, then update the "BBox" entry in the AP - // dictionary too, since its "BBox" entry comes from annotation dictionary's - // "Rect" entry. - if (FPDFAnnot_HasAttachmentPoints(annot)) { - return true; - } - - // EmbedPDF(layer-door): the appearance is written: open it through the door. - RetainPtr pStream = - GetMutableAnnotAP(pAnnotDict.Get(), CPDF_Annot::AppearanceMode::kNormal); - if (pStream && newRect.Contains(pStream->GetDict()->GetRectFor("BBox"))) { - pStream->GetMutableDict()->SetRectFor("BBox", newRect); - } return true; } diff --git a/fpdfsdk/fpdf_annot_embeddertest.cpp b/fpdfsdk/fpdf_annot_embeddertest.cpp index 0c3dc8692e..1065206d17 100644 --- a/fpdfsdk/fpdf_annot_embeddertest.cpp +++ b/fpdfsdk/fpdf_annot_embeddertest.cpp @@ -18,6 +18,7 @@ #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/containers/contains.h" +#include "core/fxcrt/fx_coordinates.h" #include "core/fxcrt/fx_memcpy_wrappers.h" #include "core/fxcrt/fx_system.h" #include "core/fxcrt/span.h" @@ -33,6 +34,7 @@ #include "testing/fx_string_testhelpers.h" #include "testing/gmock/include/gmock/gmock-matchers.h" #include "testing/gtest/include/gtest/gtest.h" +#include "testing/utils/compare_coordinates.h" #include "testing/utils/hash.h" using pdfium::kAnnotationStampWithApPng; @@ -171,6 +173,47 @@ TEST_F(FPDFAnnotEmbedderTest, SetAP) { EXPECT_EQ(kStreamData, GetPlatformWString(buf.data())); } +TEST_F(FPDFAnnotEmbedderTest, SetRectKeepsAPBBox) { + ScopedFPDFDocument doc(FPDF_CreateNewDocument()); + ASSERT_TRUE(doc); + ScopedFPDFPage page(FPDFPage_New(doc.get(), 0, 100, 100)); + ASSERT_TRUE(page); + ScopedFPDFWideString ap_stream = GetFPDFWideString(kStreamData); + ASSERT_TRUE(ap_stream); + + ScopedFPDFAnnotation annot( + FPDFPage_CreateAnnot(page.get(), FPDF_ANNOT_STAMP)); + ASSERT_TRUE(annot); + + constexpr FS_RECTF kRect{0.0f, 40.0f, 100.0f, 0.0f}; + ASSERT_TRUE(FPDFAnnot_SetRect(annot.get(), &kRect)); + ASSERT_TRUE(FPDFAnnot_SetAP(annot.get(), FPDF_ANNOT_APPEARANCEMODE_NORMAL, + ap_stream.get())); + + CPDF_AnnotContext* context = CPDFAnnotContextFromFPDFAnnotation(annot.get()); + ASSERT_TRUE(context); + const CPDF_Dictionary* annot_dict = context->GetAnnotDict(); + ASSERT_TRUE(annot_dict); + RetainPtr ap_dict = + annot_dict->GetDictFor(pdfium::annotation::kAP); + ASSERT_TRUE(ap_dict); + RetainPtr stream_dict = ap_dict->GetDictFor("N"); + ASSERT_TRUE(stream_dict); + + CompareFS_RECTF(kRect, + FSRectFFromCFXFloatRect(stream_dict->GetRectFor("BBox"))); + + constexpr FS_RECTF kLargerRect{0.0f, 120.0f, 300.0f, 0.0f}; + ASSERT_TRUE(FPDFAnnot_SetRect(annot.get(), &kLargerRect)); + + FS_RECTF updated_rect; + ASSERT_TRUE(FPDFAnnot_GetRect(annot.get(), &updated_rect)); + CompareFS_RECTF(kLargerRect, updated_rect); + + CompareFS_RECTF(kRect, + FSRectFFromCFXFloatRect(stream_dict->GetRectFor("BBox"))); +} + TEST_F(FPDFAnnotEmbedderTest, SetAPWithOpacity) { ScopedFPDFDocument doc(FPDF_CreateNewDocument()); ASSERT_TRUE(doc); diff --git a/fpdfsdk/fpdf_doc.cpp b/fpdfsdk/fpdf_doc.cpp index b8dfcc7bf0..0dfb517a29 100644 --- a/fpdfsdk/fpdf_doc.cpp +++ b/fpdfsdk/fpdf_doc.cpp @@ -29,7 +29,7 @@ #include "core/fpdfdoc/cpdf_dest.h" #include "core/fpdfdoc/cpdf_linklist.h" #include "core/fpdfdoc/cpdf_pagelabel.h" -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/containers/contains.h" #include "core/fxcrt/numerics/safe_conversions.h" @@ -275,6 +275,15 @@ FPDFBookmark_GetColor(FPDF_BOOKMARK bookmark, float* R, float* G, float* B) { return true; } +FPDF_EXPORT int FPDF_CALLCONV FPDFBookmark_GetStyle(FPDF_BOOKMARK bookmark) { + if (!bookmark) { + return 0; + } + CPDF_Bookmark cpdf_bookmark( + pdfium::WrapRetain(CPDFDictionaryFromFPDFBookmark(bookmark))); + return cpdf_bookmark.GetStyle(); +} + FPDF_EXPORT unsigned long FPDF_CALLCONV FPDFAction_GetType(FPDF_ACTION action) { if (!action) { return PDFACTION_UNSUPPORTED; @@ -390,7 +399,7 @@ FPDFDest_GetView(FPDF_DEST dest, unsigned long* pNumParams, FS_FLOAT* pParams) { CPDF_Dest destination(pdfium::WrapRetain(CPDFArrayFromFPDFDest(dest))); const unsigned long nParams = pdfium::checked_cast(destination.GetNumParams()); - DCHECK(nParams <= 4); + DCHECK_LE(nParams, 4); *pNumParams = nParams; for (unsigned long i = 0; i < nParams; ++i) { UNSAFE_TODO(pParams[i]) = destination.GetParam(i); diff --git a/fpdfsdk/fpdf_doc_embeddertest.cpp b/fpdfsdk/fpdf_doc_embeddertest.cpp index 0fe497a2da..a4fad673b5 100644 --- a/fpdfsdk/fpdf_doc_embeddertest.cpp +++ b/fpdfsdk/fpdf_doc_embeddertest.cpp @@ -717,6 +717,38 @@ TEST_F(FPDFDocEmbedderTest, FindBookmarksWithColor) { EXPECT_FLOAT_EQ(blue, 0.3); } +TEST_F(FPDFDocEmbedderTest, BookmarkStyle) { + ASSERT_TRUE(OpenDocument("bookmarks_style.pdf")); + + ScopedFPDFWideString title = GetFPDFWideString(L"No Style"); + FPDF_BOOKMARK bookmark = FPDFBookmark_Find(document(), title.get()); + ASSERT_TRUE(bookmark); + EXPECT_EQ(0, FPDFBookmark_GetStyle(bookmark)); + + title = GetFPDFWideString(L"Style Is Not An Int"); + bookmark = FPDFBookmark_Find(document(), title.get()); + ASSERT_TRUE(bookmark); + EXPECT_EQ(0, FPDFBookmark_GetStyle(bookmark)); + + title = GetFPDFWideString(L"Style Is 15"); + bookmark = FPDFBookmark_Find(document(), title.get()); + ASSERT_TRUE(bookmark); + // Value is passed on to the caller as is. + EXPECT_EQ(15, FPDFBookmark_GetStyle(bookmark)); + + title = GetFPDFWideString(L"Style is Normal"); + bookmark = FPDFBookmark_Find(document(), title.get()); + ASSERT_TRUE(bookmark); + // 0 = 00 in binary, no styling. + EXPECT_EQ(0, FPDFBookmark_GetStyle(bookmark)); + + title = GetFPDFWideString(L"Style is Bold and Italic"); + bookmark = FPDFBookmark_Find(document(), title.get()); + ASSERT_TRUE(bookmark); + // 3 = 11 in binary, bold and italic. + EXPECT_EQ(3, FPDFBookmark_GetStyle(bookmark)); +} + // Check circular bookmarks will not cause infinite loop. TEST_F(FPDFDocEmbedderTest, FindBookmarksBug420) { // Open a file with circular bookmarks. diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp index 8a333e2372..f577b36936 100644 --- a/fpdfsdk/fpdf_edit_embeddertest.cpp +++ b/fpdfsdk/fpdf_edit_embeddertest.cpp @@ -2732,6 +2732,81 @@ TEST_F(FPDFEditEmbedderTest, PathsPoints) { EXPECT_FALSE(FPDFPathSegment_GetClose(nullptr)); } +TEST_F(FPDFEditEmbedderTest, PathBezierControlPoints) { + CreateNewDocument(); + + FS_POINTF first_control_point = {11.0f, 12.0f}; + FS_POINTF second_control_point = {13.0f, 14.0f}; + + EXPECT_FALSE(FPDFPath_GetBezierControlPoints(nullptr, 0, &first_control_point, + &second_control_point)); + EXPECT_FLOAT_EQ(11.0f, first_control_point.x); + EXPECT_FLOAT_EQ(12.0f, first_control_point.y); + EXPECT_FLOAT_EQ(13.0f, second_control_point.x); + EXPECT_FLOAT_EQ(14.0f, second_control_point.y); + + ScopedFPDFPageObject image(FPDFPageObj_NewImageObj(document())); + ASSERT_TRUE(image); + EXPECT_FALSE(FPDFPath_GetBezierControlPoints( + image.get(), 0, &first_control_point, &second_control_point)); + + ScopedFPDFPageObject path(FPDFPageObj_CreateNewPath(1.0f, 2.0f)); + ASSERT_TRUE(path); + ASSERT_TRUE( + FPDFPath_BezierTo(path.get(), 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f)); + ASSERT_TRUE( + FPDFPath_BezierTo(path.get(), 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f)); + ASSERT_EQ(7, FPDFPath_CountSegments(path.get())); + + EXPECT_FALSE(FPDFPath_GetBezierControlPoints( + path.get(), std::numeric_limits::max(), &first_control_point, + &second_control_point)); + EXPECT_FALSE(FPDFPath_GetBezierControlPoints( + path.get(), 7, &first_control_point, &second_control_point)); + + // Index 0 is the initial move-to point, not a cubic Bezier endpoint. + EXPECT_FALSE(FPDFPath_GetBezierControlPoints( + path.get(), 0, &first_control_point, &second_control_point)); + EXPECT_FLOAT_EQ(11.0f, first_control_point.x); + EXPECT_FLOAT_EQ(12.0f, first_control_point.y); + EXPECT_FLOAT_EQ(13.0f, second_control_point.x); + EXPECT_FLOAT_EQ(14.0f, second_control_point.y); + + EXPECT_FALSE(FPDFPath_GetBezierControlPoints( + path.get(), 1, &first_control_point, &second_control_point)); + EXPECT_FALSE(FPDFPath_GetBezierControlPoints( + path.get(), 2, &first_control_point, &second_control_point)); + EXPECT_TRUE(FPDFPath_GetBezierControlPoints( + path.get(), 3, &first_control_point, &second_control_point)); + EXPECT_FLOAT_EQ(3.0f, first_control_point.x); + EXPECT_FLOAT_EQ(4.0f, first_control_point.y); + EXPECT_FLOAT_EQ(5.0f, second_control_point.x); + EXPECT_FLOAT_EQ(6.0f, second_control_point.y); + + EXPECT_FALSE(FPDFPath_GetBezierControlPoints( + path.get(), 4, &first_control_point, &second_control_point)); + EXPECT_FALSE(FPDFPath_GetBezierControlPoints( + path.get(), 5, &first_control_point, &second_control_point)); + EXPECT_TRUE(FPDFPath_GetBezierControlPoints( + path.get(), 6, &first_control_point, &second_control_point)); + EXPECT_FLOAT_EQ(9.0f, first_control_point.x); + EXPECT_FLOAT_EQ(10.0f, first_control_point.y); + EXPECT_FLOAT_EQ(11.0f, second_control_point.x); + EXPECT_FLOAT_EQ(12.0f, second_control_point.y); + + EXPECT_FALSE(FPDFPath_GetBezierControlPoints(path.get(), 3, nullptr, + &second_control_point)); + + first_control_point = {11.0f, 12.0f}; + second_control_point = {13.0f, 14.0f}; + EXPECT_FALSE(FPDFPath_GetBezierControlPoints(path.get(), 3, + &first_control_point, nullptr)); + EXPECT_FLOAT_EQ(11.0f, first_control_point.x); + EXPECT_FLOAT_EQ(12.0f, first_control_point.y); + EXPECT_FLOAT_EQ(13.0f, second_control_point.x); + EXPECT_FLOAT_EQ(14.0f, second_control_point.y); +} + TEST_F(FPDFEditEmbedderTest, PathOnTopOfText) { // Load document with some text ASSERT_TRUE(OpenDocument("hello_world.pdf")); diff --git a/fpdfsdk/fpdf_editpath.cpp b/fpdfsdk/fpdf_editpath.cpp index 067ef2bb8a..97ef64788f 100644 --- a/fpdfsdk/fpdf_editpath.cpp +++ b/fpdfsdk/fpdf_editpath.cpp @@ -145,6 +145,46 @@ FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_BezierTo(FPDF_PAGEOBJECT path, return true; } +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPath_GetBezierControlPoints(FPDF_PAGEOBJECT path, + size_t index, + FS_POINTF* first_control_point, + FS_POINTF* second_control_point) { + auto* path_object = CPDFPathObjectFromFPDFPageObject(path); + if (!path_object || !first_control_point || !second_control_point) { + return false; + } + + pdfium::span points = path_object->path().GetPoints(); + if (index >= points.size()) { + return false; + } + + const CFX_Path::Point& path_point = points[index]; + if (path_point.type_ != CFX_Path::Point::Type::kBezier) { + return false; + } + + // Consecutive cubic curves are stored as runs of three Bezier points, with + // the endpoint last in each group. Find the start of the run to determine + // whether `index` refers to an endpoint. + size_t run_start_index = index; + while (run_start_index > 0 && + points[run_start_index - 1].type_ == CFX_Path::Point::Type::kBezier) { + --run_start_index; + } + + if ((index - run_start_index) % 3 != 2u) { + return false; + } + + first_control_point->x = points[index - 2].point_.x; + first_control_point->y = points[index - 2].point_.y; + second_control_point->x = points[index - 1].point_.x; + second_control_point->y = points[index - 1].point_.y; + return true; +} + FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_Close(FPDF_PAGEOBJECT path) { auto* pPathObj = CPDFPathObjectFromFPDFPageObject(path); if (!pPathObj) { diff --git a/fpdfsdk/fpdf_editpath_embeddertest.cpp b/fpdfsdk/fpdf_editpath_embeddertest.cpp index 2d48064363..dbddb6ebb0 100644 --- a/fpdfsdk/fpdf_editpath_embeddertest.cpp +++ b/fpdfsdk/fpdf_editpath_embeddertest.cpp @@ -31,32 +31,33 @@ ScopedFPDFPageObject CreateBlackTriangle() { TEST_F(FPDFEditPathEmbedderTest, VerifyCorrectColoursReturned) { static constexpr int kObjectCount = 256; CreateEmptyDocument(); - FPDF_PAGE page = FPDFPage_New(document(), 0, 612, 792); - for (size_t i = 0; i < kObjectCount; ++i) { - FPDF_PAGEOBJECT path = FPDFPageObj_CreateNewPath(400, 100); - EXPECT_TRUE(FPDFPageObj_SetFillColor(path, i, i, i, i)); - EXPECT_TRUE(FPDFPageObj_SetStrokeColor(path, i, i, i, i)); - EXPECT_TRUE(FPDFPath_SetDrawMode(path, FPDF_FILLMODE_ALTERNATE, 0)); - EXPECT_TRUE(FPDFPath_LineTo(path, 400, 200)); - EXPECT_TRUE(FPDFPath_LineTo(path, 300, 100)); - EXPECT_TRUE(FPDFPath_Close(path)); - - FPDFPage_InsertObject(page, path); + { + ScopedFPDFPage page(FPDFPage_New(document(), 0, 612, 792)); + + for (size_t i = 0; i < kObjectCount; ++i) { + FPDF_PAGEOBJECT path = FPDFPageObj_CreateNewPath(400, 100); + EXPECT_TRUE(FPDFPageObj_SetFillColor(path, i, i, i, i)); + EXPECT_TRUE(FPDFPageObj_SetStrokeColor(path, i, i, i, i)); + EXPECT_TRUE(FPDFPath_SetDrawMode(path, FPDF_FILLMODE_ALTERNATE, 0)); + EXPECT_TRUE(FPDFPath_LineTo(path, 400, 200)); + EXPECT_TRUE(FPDFPath_LineTo(path, 300, 100)); + EXPECT_TRUE(FPDFPath_Close(path)); + + FPDFPage_InsertObject(page.get(), path); + } + + EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); } - EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); - FPDF_ClosePage(page); - page = nullptr; - ScopedSavedDoc saved_document = OpenScopedSavedDocument(); ASSERT_TRUE(saved_document); - page = LoadSavedPage(0); - ASSERT_TRUE(page); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); + ASSERT_TRUE(saved_page); for (size_t i = 0; i < kObjectCount; ++i) { - FPDF_PAGEOBJECT path = FPDFPage_GetObject(page, i); + FPDF_PAGEOBJECT path = FPDFPage_GetObject(saved_page.get(), i); ASSERT_TRUE(path); EXPECT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path)); @@ -77,8 +78,6 @@ TEST_F(FPDFEditPathEmbedderTest, VerifyCorrectColoursReturned) { EXPECT_EQ(i, b); EXPECT_EQ(i, a); } - - CloseSavedPage(page); } TEST_F(FPDFEditPathEmbedderTest, GetAndSetMatrixForPath) { diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp index 9214aeea3d..55ce265927 100644 --- a/fpdfsdk/fpdf_formfill_embeddertest.cpp +++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp @@ -253,14 +253,14 @@ class FPDFFormFillTextFormEmbedderTest } static CFX_PointF CharLimitFormAtX(float x) { - DCHECK(x >= kFormBeginX); - DCHECK(x <= kFormEndX); + DCHECK_GE(x, kFormBeginX); + DCHECK_LE(x, kFormEndX); return CFX_PointF(x, kCharLimitFormY); } static CFX_PointF RegularFormAtX(float x) { - DCHECK(x >= kFormBeginX); - DCHECK(x <= kFormEndX); + DCHECK_GE(x, kFormBeginX); + DCHECK_LE(x, kFormEndX); return CFX_PointF(x, kRegularFormY); } @@ -297,14 +297,14 @@ class FPDFFormFillComboBoxFormEmbedderTest } void SelectEditableFormOption(int item_index) { - DCHECK(item_index >= 0); - DCHECK(item_index < 3); + DCHECK_GE(item_index, 0); + DCHECK_LT(item_index, 3); SelectOption(item_index, EditableFormDropDown()); } void SelectNonEditableFormOption(int item_index) { - DCHECK(item_index >= 0); - DCHECK(item_index < 26); + DCHECK_GE(item_index, 0); + DCHECK_LT(item_index, 26); SelectOption(item_index, NonEditableFormDropDown()); } @@ -347,14 +347,14 @@ class FPDFFormFillComboBoxFormEmbedderTest } static CFX_PointF EditableFormAtX(float x) { - DCHECK(x >= kFormBeginX); - DCHECK(x <= kFormEndX); + DCHECK_GE(x, kFormBeginX); + DCHECK_LE(x, kFormEndX); return CFX_PointF(x, kEditableFormY); } static CFX_PointF NonEditableFormAtX(float x) { - DCHECK(x >= kFormBeginX); - DCHECK(x <= kFormEndX); + DCHECK_GE(x, kFormBeginX); + DCHECK_LE(x, kFormEndX); return CFX_PointF(x, kNonEditableFormY); } @@ -443,8 +443,8 @@ class FPDFFormFillListBoxFormEmbedderTest void ClickOnSingleSelectFormOption(int item_index) { // Only the first two indices are visible so can only click on those // without scrolling. - DCHECK(item_index >= 0); - DCHECK(item_index < 2); + DCHECK_GE(item_index, 0); + DCHECK_LT(item_index, 2); if (item_index == 0) { ClickOnFormFieldAtPoint(SingleSelectFirstVisibleOption()); } else { @@ -455,8 +455,8 @@ class FPDFFormFillListBoxFormEmbedderTest void ClickOnMultiSelectFormOption(int item_index) { // Only the first two indices are visible so can only click on those // without scrolling. - DCHECK(item_index >= 0); - DCHECK(item_index < 2); + DCHECK_GE(item_index, 0); + DCHECK_LT(item_index, 2); if (item_index == 0) { ClickOnFormFieldAtPoint(MultiSelectFirstVisibleOption()); } else { @@ -467,8 +467,8 @@ class FPDFFormFillListBoxFormEmbedderTest void ClickOnMultiSelectMultipleValuesFormOption(int item_index) { // Only two indices are visible so can only click on those // without scrolling. - DCHECK(item_index >= 0); - DCHECK(item_index < 2); + DCHECK_GE(item_index, 0); + DCHECK_LT(item_index, 2); if (item_index == 0) { ClickOnFormFieldAtPoint(MultiSelectMultipleValuesFirstVisibleOption()); } else { @@ -479,8 +479,8 @@ class FPDFFormFillListBoxFormEmbedderTest void ClickOnSingleSelectLastSelectedFormOption(int item_index) { // Only two indices are visible so can only click on those // without scrolling. - DCHECK(item_index >= 0); - DCHECK(item_index < 2); + DCHECK_GE(item_index, 0); + DCHECK_LT(item_index, 2); if (item_index == 0) { ClickOnFormFieldAtPoint(SingleSelectLastSelectedFirstVisibleOption()); } else { @@ -941,21 +941,14 @@ TEST_F(FPDFFormFillEmbedderTest, DoNotHandleShortcutsOnKeyDown) { EXPECT_TRUE(FORM_OnKeyDown(form_handle(), page.get(), FWL_VKEY_Up, 0)); EXPECT_TRUE(FORM_OnKeyDown(form_handle(), page.get(), FWL_VKEY_Right, 0)); - // Edit shortcuts should not be handled by PDFium so that embedders can handle - // these shortcuts on platforms like MacOS which don't send OnChar events for - // the shortcuts. - EXPECT_FALSE( - FORM_OnKeyDown(form_handle(), page.get(), FWL_VKEY_A, kModifier)); + // Clipboard shortcuts (C, V, X) should not be handled by PDFium so that + // embedders can handle these shortcuts via platform clipboard handlers. EXPECT_FALSE( FORM_OnKeyDown(form_handle(), page.get(), FWL_VKEY_C, kModifier)); EXPECT_FALSE( FORM_OnKeyDown(form_handle(), page.get(), FWL_VKEY_V, kModifier)); EXPECT_FALSE( FORM_OnKeyDown(form_handle(), page.get(), FWL_VKEY_X, kModifier)); - EXPECT_FALSE( - FORM_OnKeyDown(form_handle(), page.get(), FWL_VKEY_Z, kModifier)); - EXPECT_FALSE(FORM_OnKeyDown(form_handle(), page.get(), FWL_VKEY_Z, - kModifier | FWL_EVENTFLAG_ShiftKey)); } #ifdef PDF_ENABLE_XFA @@ -1674,9 +1667,10 @@ TEST_F(FPDFFormFillEmbedderTest, ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page.get(), 0)); ASSERT_TRUE(annot); - // Focus the field and type "ABC" + // Focus the field and type BiDi text. EXPECT_TRUE(FORM_OnFocus(form_handle(), page.get(), 0, 120.0, 120.0)); - constexpr auto kInput = std::to_array({'A', 'B', 'C'}); + constexpr auto kInput = + std::to_array({'A', ' ', 0x05E9, ' ', 0x05DE, ' ', 'B'}); for (uint32_t c : kInput) { EXPECT_TRUE(FORM_OnChar(form_handle(), page.get(), c, 0)); } @@ -1684,34 +1678,31 @@ TEST_F(FPDFFormFillEmbedderTest, EXPECT_EQ(FPDF_TEXTDIR_AUTO, FORM_GetTextDirection(form_handle(), annot.get())); - // Change text direction to Right-to-Left (RTL). + // Live rendering in AUTO direction (resolves to LTR for this input). + ScopedFPDFBitmap bitmap_auto = RenderLoadedPage(page.get()); + + // Change text direction to Right-to-Left (RTL) while remaining focused. EXPECT_TRUE( FORM_SetTextDirection(form_handle(), annot.get(), FPDF_TEXTDIR_RTL)); EXPECT_EQ(FPDF_TEXTDIR_RTL, FORM_GetTextDirection(form_handle(), annot.get())); - // Type 'D' and kill focus. This forces the form filler to flush its - // in-memory state and regenerate the PDF appearance stream with the new RTL - // layout. - EXPECT_TRUE(FORM_OnChar(form_handle(), page.get(), 'D', 0)); - FORM_ForceToKillFocus(form_handle()); + // Live rendering should immediately reflect RTL. ScopedFPDFBitmap bitmap_rtl = RenderLoadedPage(page.get()); - // Refocus the field and change text direction back to Left-to-Right (LTR). - EXPECT_TRUE(FORM_OnFocus(form_handle(), page.get(), 0, 120.0, 120.0)); + // Change text direction back to Left-to-Right (LTR) while remaining focused. EXPECT_TRUE( FORM_SetTextDirection(form_handle(), annot.get(), FPDF_TEXTDIR_LTR)); EXPECT_EQ(FPDF_TEXTDIR_LTR, FORM_GetTextDirection(form_handle(), annot.get())); - // Type 'E' and kill focus to flush the new LTR layout into the appearance - // stream. - EXPECT_TRUE(FORM_OnChar(form_handle(), page.get(), 'E', 0)); - FORM_ForceToKillFocus(form_handle()); + // Live rendering should immediately reflect LTR. ScopedFPDFBitmap bitmap_ltr = RenderLoadedPage(page.get()); // Rendering should be visually different when the text direction changes. + EXPECT_NE(HashBitmap(bitmap_auto.get()), HashBitmap(bitmap_rtl.get())); EXPECT_NE(HashBitmap(bitmap_rtl.get()), HashBitmap(bitmap_ltr.get())); + EXPECT_EQ(HashBitmap(bitmap_auto.get()), HashBitmap(bitmap_ltr.get())); } TEST_F(FPDFFormFillTextFormEmbedderTest, FormTextFieldBiDiLiveEdit) { @@ -3459,20 +3450,47 @@ TEST_F(FPDFFormFillTextFormEmbedderTest, RedoCutSelection) { EXPECT_EQ(Selection(), ""); } -TEST_F(FPDFFormFillTextFormEmbedderTest, SelectAllWithKeyboardShortcut) { +TEST_F(FPDFFormFillTextFormEmbedderTest, DoNotHandleSelectAllOnChar) { + // Start with a couple of letters in the text form. + TypeTextIntoTextField(2, RegularFormBegin()); + EXPECT_EQ(FocusedFieldText(), "AB"); + EXPECT_EQ(Selection(), ""); + + // FORM_OnChar should not handle Select All; this is now handled via + // OnKeyDown. +#if BUILDFLAG(IS_APPLE) + static constexpr int kCorrectModifier = FWL_EVENTFLAG_MetaKey; +#else + static constexpr int kCorrectModifier = FWL_EVENTFLAG_ControlKey; +#endif + EXPECT_FALSE(FORM_OnChar(form_handle(), page(), pdfium::ascii::kControlA, + kCorrectModifier)); + EXPECT_EQ(Selection(), ""); + + // Select all with the keyboard shortcut using the wrong modifier key. +#if BUILDFLAG(IS_APPLE) + static constexpr int kWrongModifier = FWL_EVENTFLAG_ControlKey; +#else + static constexpr int kWrongModifier = FWL_EVENTFLAG_MetaKey; +#endif + FORM_OnChar(form_handle(), page(), pdfium::ascii::kControlA, kWrongModifier); + EXPECT_EQ(Selection(), ""); +} + +TEST_F(FPDFFormFillTextFormEmbedderTest, SelectAllWithOnKeyDown) { // Start with a couple of letters in the text form. TypeTextIntoTextField(2, RegularFormBegin()); EXPECT_EQ(FocusedFieldText(), "AB"); EXPECT_EQ(Selection(), ""); - // Select all with the keyboard shortcut. + // Select all with the keyboard shortcut via OnKeyDown. #if BUILDFLAG(IS_APPLE) static constexpr int kCorrectModifier = FWL_EVENTFLAG_MetaKey; #else static constexpr int kCorrectModifier = FWL_EVENTFLAG_ControlKey; #endif - FORM_OnChar(form_handle(), page(), pdfium::ascii::kControlA, - kCorrectModifier); + EXPECT_TRUE( + FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_A, kCorrectModifier)); EXPECT_EQ(Selection(), "AB"); // Reset the selection again. @@ -3485,10 +3503,83 @@ TEST_F(FPDFFormFillTextFormEmbedderTest, SelectAllWithKeyboardShortcut) { #else static constexpr int kWrongModifier = FWL_EVENTFLAG_MetaKey; #endif - FORM_OnChar(form_handle(), page(), pdfium::ascii::kControlA, kWrongModifier); + EXPECT_FALSE( + FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_A, kWrongModifier)); + EXPECT_EQ(Selection(), ""); + + // Select all with Shift key pressed should not be handled. + EXPECT_FALSE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_A, + kCorrectModifier | FWL_EVENTFLAG_ShiftKey)); EXPECT_EQ(Selection(), ""); } +TEST_F(FPDFFormFillTextFormEmbedderTest, UndoWithOnKeyDown) { + // Start with a couple of letters in the text form. + TypeTextIntoTextField(2, RegularFormBegin()); + EXPECT_EQ(FocusedFieldText(), "AB"); + + // Undo with the keyboard shortcut via OnKeyDown. +#if BUILDFLAG(IS_APPLE) + static constexpr int kCorrectModifier = FWL_EVENTFLAG_MetaKey; +#else + static constexpr int kCorrectModifier = FWL_EVENTFLAG_ControlKey; +#endif + // First undo reverts 'B'. + EXPECT_TRUE( + FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Z, kCorrectModifier)); + EXPECT_EQ(FocusedFieldText(), "A"); + + // Second undo reverts 'A'. + EXPECT_TRUE( + FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Z, kCorrectModifier)); + EXPECT_EQ(FocusedFieldText(), ""); + + // First redo restores 'A'. + EXPECT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Z, + kCorrectModifier | FWL_EVENTFLAG_ShiftKey)); + EXPECT_EQ(FocusedFieldText(), "A"); + + // Second redo restores 'B'. + EXPECT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Z, + kCorrectModifier | FWL_EVENTFLAG_ShiftKey)); + EXPECT_EQ(FocusedFieldText(), "AB"); + + // Undo with the keyboard shortcut using the wrong modifier key. +#if BUILDFLAG(IS_APPLE) + static constexpr int kWrongModifier = FWL_EVENTFLAG_ControlKey; +#else + static constexpr int kWrongModifier = FWL_EVENTFLAG_MetaKey; +#endif + EXPECT_FALSE( + FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Z, kWrongModifier)); + EXPECT_EQ(FocusedFieldText(), "AB"); +} + +TEST_F(FPDFFormFillTextFormEmbedderTest, RedoWithCtrlYKeyboardShortcut) { + // Start with a letter in the text form. + TypeTextIntoTextField(1, RegularFormBegin()); + EXPECT_EQ(FocusedFieldText(), "A"); + + // Undo with the keyboard shortcut. + EXPECT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Z, kModifier)); + EXPECT_EQ(FocusedFieldText(), ""); + + // Shortcut with Shift key pressed should not be handled. + EXPECT_FALSE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Y, + kModifier | FWL_EVENTFLAG_ShiftKey)); + EXPECT_EQ(FocusedFieldText(), ""); + +#if BUILDFLAG(IS_APPLE) + // On Apple platforms, Cmd+Y is not Redo and should not be handled. + EXPECT_FALSE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Y, kModifier)); + EXPECT_EQ(FocusedFieldText(), ""); +#else + // On non-Apple platforms, Ctrl+Y is Redo. + EXPECT_TRUE(FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Y, kModifier)); + EXPECT_EQ(FocusedFieldText(), "A"); +#endif +} + class FPDFXFAFormBug1055869EmbedderTest : public FPDFFormFillInteractiveEmbedderTest { protected: diff --git a/fpdfsdk/fpdf_ppo_embeddertest.cpp b/fpdfsdk/fpdf_ppo_embeddertest.cpp index b21e56b471..1a0f60c44f 100644 --- a/fpdfsdk/fpdf_ppo_embeddertest.cpp +++ b/fpdfsdk/fpdf_ppo_embeddertest.cpp @@ -213,20 +213,20 @@ TEST_F(FPDFPPOEmbedderTest, ImportPageToXObject) { ScopedSavedDoc saved_document = OpenScopedSavedDocument(); ASSERT_TRUE(saved_document); - std::array saved_pages; + std::array saved_pages; std::array xobjects; for (int i = 0; i < kExpectedPageCount; ++i) { - saved_pages[i] = LoadSavedPage(i); + saved_pages[i] = LoadScopedSavedPage(i); ASSERT_TRUE(saved_pages[i]); - EXPECT_EQ(1, FPDFPage_CountObjects(saved_pages[i])); - xobjects[i] = FPDFPage_GetObject(saved_pages[i], 0); + EXPECT_EQ(1, FPDFPage_CountObjects(saved_pages[i].get())); + xobjects[i] = FPDFPage_GetObject(saved_pages[i].get(), 0); ASSERT_TRUE(xobjects[i]); ASSERT_EQ(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(xobjects[i])); EXPECT_EQ(8, FPDFFormObj_CountObjects(xobjects[i])); { - ScopedFPDFBitmap page_bitmap = RenderPage(saved_pages[i]); + ScopedFPDFBitmap page_bitmap = RenderPage(saved_pages[i].get()); CompareBitmapWithExpectationSuffix(page_bitmap.get(), "import_page_to_xobject"); } @@ -258,10 +258,6 @@ TEST_F(FPDFPPOEmbedderTest, ImportPageToXObject) { ASSERT_TRUE(obj2->AsForm()->form()->GetStream()); EXPECT_EQ(obj1->AsForm()->form()->GetStream(), obj2->AsForm()->form()->GetStream()); - - for (FPDF_PAGE saved_page : saved_pages) { - CloseSavedPage(saved_page); - } } TEST_F(FPDFPPOEmbedderTest, ImportPageToXObjectWithSameDoc) { diff --git a/fpdfsdk/fpdf_save_embeddertest.cpp b/fpdfsdk/fpdf_save_embeddertest.cpp index 69a4fa2e2e..f6c6993925 100644 --- a/fpdfsdk/fpdf_save_embeddertest.cpp +++ b/fpdfsdk/fpdf_save_embeddertest.cpp @@ -175,7 +175,7 @@ TEST_F(FPDFSaveEmbedderTest, SaveLinearizedDoc) { EXPECT_THAT(GetString(), HasSubstr("36 0 obj")); EXPECT_THAT(GetString(), Not(HasSubstr("37 0 obj"))); EXPECT_THAT(GetString(), Not(HasSubstr("38 0 obj"))); - EXPECT_EQ(7986u, GetString().size()); + EXPECT_EQ(7884u, GetString().size()); // Make sure new document renders the same as the old one. ScopedSavedDoc saved_document = OpenScopedSavedDocument(); diff --git a/fpdfsdk/fpdf_text_embeddertest.cpp b/fpdfsdk/fpdf_text_embeddertest.cpp index a561859033..c991d02e0b 100644 --- a/fpdfsdk/fpdf_text_embeddertest.cpp +++ b/fpdfsdk/fpdf_text_embeddertest.cpp @@ -2505,6 +2505,31 @@ TEST_F(FPDFTextEmbedderTest, Bug491516663) { ElementsAreArray(kHelloWorldText)); } +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. + static constexpr std::array kExpectedChars = { + 'A', 'B', 'C', 'U', 0x009b, 'X', 'A', 'B', 'p', 'q', 0}; + + ASSERT_TRUE(OpenDocument("bug_554790604.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + ScopedFPDFTextPage textpage(FPDFText_LoadPage(page.get())); + ASSERT_TRUE(textpage); + + std::array buffer = {}; + int num_chars = + FPDFText_GetText(textpage.get(), 0, buffer.size(), buffer.data()); + ASSERT_EQ(static_cast(kExpectedChars.size()), num_chars); + EXPECT_THAT(pdfium::span(buffer).first(), + ElementsAreArray(kExpectedChars)); +} + TEST_F(FPDFTextEmbedderTest, ActualTextRtl) { ASSERT_TRUE(OpenDocument("actual_text_rtl.pdf")); ScopedPage page = LoadScopedPage(0); diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c index 4e824de5ef..b51a810876 100644 --- a/fpdfsdk/fpdf_view_c_api_test.c +++ b/fpdfsdk/fpdf_view_c_api_test.c @@ -150,6 +150,7 @@ int CheckPDFiumCApi() { CHK(FPDFBookmark_GetDest); CHK(FPDFBookmark_GetFirstChild); CHK(FPDFBookmark_GetNextSibling); + CHK(FPDFBookmark_GetStyle); CHK(FPDFBookmark_GetTitle); CHK(FPDFDest_GetDestPageIndex); CHK(FPDFDest_GetLocationInPage); @@ -268,6 +269,7 @@ int CheckPDFiumCApi() { CHK(FPDFPath_BezierTo); CHK(FPDFPath_Close); CHK(FPDFPath_CountSegments); + CHK(FPDFPath_GetBezierControlPoints); CHK(FPDFPath_GetDrawMode); CHK(FPDFPath_GetPathSegment); CHK(FPDFPath_LineTo); diff --git a/fpdfsdk/fpdfxfa/cpdfxfa_page.cpp b/fpdfsdk/fpdfxfa/cpdfxfa_page.cpp index 19fb95d044..3beb2155f1 100644 --- a/fpdfsdk/fpdfxfa/cpdfxfa_page.cpp +++ b/fpdfsdk/fpdfxfa/cpdfxfa_page.cpp @@ -13,6 +13,7 @@ #include "core/fpdfapi/page/cpdf_pageimagecache.h" #include "core/fpdfapi/parser/cpdf_document.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "fpdfsdk/cpdfsdk_pageview.h" #include "fpdfsdk/fpdfxfa/cpdfxfa_context.h" #include "fpdfsdk/fpdfxfa/cpdfxfa_widget.h" @@ -78,7 +79,7 @@ CXFA_FFWidget::IteratorIface* GCedWidgetIteratorForAnnot( CPDFXFA_Page::CPDFXFA_Page(CPDF_Document* document, int page_index) : document_(document), page_index_(page_index) { DCHECK(document_->GetExtension()); - DCHECK(page_index_ >= 0); + DCHECK_GE(page_index_, 0); } CPDFXFA_Page::~CPDFXFA_Page() = default; diff --git a/fpdfsdk/pwl/cpwl_edit.cpp b/fpdfsdk/pwl/cpwl_edit.cpp index 4fb85ff4b9..d6fa576d5d 100644 --- a/fpdfsdk/pwl/cpwl_edit.cpp +++ b/fpdfsdk/pwl/cpwl_edit.cpp @@ -540,6 +540,33 @@ bool CPWL_Edit::OnKeyDownInternal(FWL_VKEYCODE nKeyCode, case FWL_VKEY_End: edit_impl_->OnVK_END(IsSHIFTKeyDown(nFlag), IsCTRLKeyDown(nFlag)); return true; + case FWL_VKEY_A: + if (IsPlatformShortcutKey(nFlag) && !IsSHIFTKeyDown(nFlag) && + !IsALTKeyDown(nFlag)) { + SelectAllText(); + return true; + } + return false; +#if !BUILDFLAG(IS_APPLE) + // Only non-Apple platforms use Ctrl+Y for Redo. + case FWL_VKEY_Y: + if (IsPlatformShortcutKey(nFlag) && !IsSHIFTKeyDown(nFlag) && + !IsALTKeyDown(nFlag)) { + Redo(); + return true; + } + return false; +#endif + case FWL_VKEY_Z: + if (IsPlatformShortcutKey(nFlag) && !IsALTKeyDown(nFlag)) { + if (IsSHIFTKeyDown(nFlag)) { + Redo(); + } else { + Undo(); + } + return true; + } + return false; case FWL_VKEY_Unknown: ClearSelection(); return true; @@ -565,36 +592,15 @@ bool CPWL_Edit::OnCharInternal(uint16_t nChar, Mask nFlag) { break; } - bool bCtrl = IsPlatformShortcutKey(nFlag); - bool bAlt = IsALTKeyDown(nFlag); - bool bShift = IsSHIFTKeyDown(nFlag); - - uint16_t word = nChar; - - if (bCtrl && !bAlt) { - switch (nChar) { - case pdfium::ascii::kControlA: - SelectAllText(); - return true; - case pdfium::ascii::kControlZ: - if (bShift) { - Redo(); - } else { - Undo(); - } - return true; - default: - if (nChar < 32) { - return false; - } - } + if (IsPlatformShortcutKey(nFlag) && !IsALTKeyDown(nFlag)) { + return false; } if (IsReadOnly()) { return true; } - edit_impl_->TypeChar(word, GetCharSet()); + edit_impl_->TypeChar(nChar, GetCharSet()); return true; } diff --git a/fpdfsdk/pwl/cpwl_edit_impl.cpp b/fpdfsdk/pwl/cpwl_edit_impl.cpp index f34657e4ae..ea7239377b 100644 --- a/fpdfsdk/pwl/cpwl_edit_impl.cpp +++ b/fpdfsdk/pwl/cpwl_edit_impl.cpp @@ -14,6 +14,7 @@ #include "core/fpdfapi/font/cpdf_font.h" #include "core/fpdfapi/render/cpdf_renderoptions.h" #include "core/fpdfapi/render/cpdf_textrenderer.h" +#include "core/fpdfdoc/cpvt_line.h" #include "core/fpdfdoc/cpvt_word.h" #include "core/fpdfdoc/ipvt_fontmap.h" #include "core/fxcrt/autorestorer.h" @@ -30,6 +31,12 @@ namespace { +CFX_FloatRect GetWordRect(const CPVT_Word& word, const CPVT_Line& line) { + return CFX_FloatRect(word.location().x, line.ptLine.y + line.fLineDescent, + word.location().x + word.width(), + line.ptLine.y + line.fLineAscent); +} + void DrawTextString(CFX_RenderDevice* pDevice, const CFX_PointF& pt, CPDF_Font* font, @@ -653,16 +660,14 @@ void CPWL_EditImpl::DrawEdit(CFX_RenderDevice* pDevice, if (bSelect) { CPVT_Line line; pIterator->GetLine(line); + CFX_FloatRect word_rect = GetWordRect(word, line); if (pFillerNotify->IsSelectionImplemented()) { - CFX_FloatRect rc(word.location().x, line.ptLine.y + line.fLineDescent, - word.CaretX(), line.ptLine.y + line.fLineAscent); - rc.Intersect(rcClip); - pFillerNotify->OutputSelectedRect(pSystemData, rc); + word_rect.Intersect(rcClip); + pFillerNotify->OutputSelectedRect(pSystemData, word_rect); } else { CFX_Path pathSelBK; - pathSelBK.AppendRect(word.location().x, - line.ptLine.y + line.fLineDescent, word.CaretX(), - line.ptLine.y + line.fLineAscent); + pathSelBK.AppendRect(word_rect.left, word_rect.bottom, + word_rect.right, word_rect.top); pDevice->DrawPath(pathSelBK, &mtUser2Device, nullptr, crSelBK, 0, CFX_FillRenderOptions::WindingOptions()); @@ -751,7 +756,11 @@ void CPWL_EditImpl::SetAlignmentV(int32_t nFormat) { void CPWL_EditImpl::SetTextDirection( CFX_BidiResolver::ParagraphDirection direction) { + if (vt_->GetTextDirection() == direction) { + return; + } vt_->SetTextDirection(direction); + Paint(); } CFX_BidiResolver::ParagraphDirection CPWL_EditImpl::GetTextDirection() const { @@ -1355,15 +1364,13 @@ void CPWL_EditImpl::RefreshWordRange(const CPVT_WordRange& wr) { pIterator->GetLine(lineinfo); if (place.LineCmp(wrTemp.BeginPos) == 0 || place.LineCmp(wrTemp.EndPos) == 0) { - CFX_FloatRect rcWord( - wordinfo.location().x, lineinfo.ptLine.y + lineinfo.fLineDescent, - wordinfo.CaretX(), lineinfo.ptLine.y + lineinfo.fLineAscent); + CFX_FloatRect word_rect = GetWordRect(wordinfo, lineinfo); if (notify_) { if (!notify_flag_) { AutoRestorer restorer(¬ify_flag_); notify_flag_ = true; - CFX_FloatRect rcRefresh = VTToEdit(rcWord); + CFX_FloatRect rcRefresh = VTToEdit(word_rect); if (!notify_->InvalidateRect(&rcRefresh)) { notify_ = nullptr; // Gone, dangling even. } diff --git a/fxbarcode/datamatrix/BC_C40Encoder.cpp b/fxbarcode/datamatrix/BC_C40Encoder.cpp index 35a485dced..def5872831 100644 --- a/fxbarcode/datamatrix/BC_C40Encoder.cpp +++ b/fxbarcode/datamatrix/BC_C40Encoder.cpp @@ -24,7 +24,7 @@ #include -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/fx_extension.h" #include "fxbarcode/datamatrix/BC_Encoder.h" @@ -201,7 +201,7 @@ int32_t CBC_C40Encoder::EncodeChar(wchar_t c, WideString* sb) { int32_t CBC_C40Encoder::BacktrackOneCharacter(CBC_EncoderContext* context, WideString* buffer, int32_t lastCharSize) { - DCHECK(lastCharSize >= 0); + DCHECK_GE(lastCharSize, 0); if (context->pos_ < 1) { return -1; diff --git a/fxbarcode/datamatrix/BC_ErrorCorrection.cpp b/fxbarcode/datamatrix/BC_ErrorCorrection.cpp index 3a0697c290..e5f2560be7 100644 --- a/fxbarcode/datamatrix/BC_ErrorCorrection.cpp +++ b/fxbarcode/datamatrix/BC_ErrorCorrection.cpp @@ -29,6 +29,7 @@ #include #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fixed_size_data_vector.h" #include "core/fxcrt/span.h" #include "fxbarcode/datamatrix/BC_Encoder.h" @@ -150,7 +151,7 @@ constexpr std::array ALOG = { 0}}; WideString CreateECCBlock(const WideString& codewords, size_t numECWords) { - DCHECK(numECWords > 0); + DCHECK_GT(numECWords, 0); const size_t len = codewords.GetLength(); static constexpr size_t kFactorTableNum = std::size(FACTORS); diff --git a/fxbarcode/qrcode/BC_QRCoderBitVector.cpp b/fxbarcode/qrcode/BC_QRCoderBitVector.cpp index 074c8f0476..76a386e37b 100644 --- a/fxbarcode/qrcode/BC_QRCoderBitVector.cpp +++ b/fxbarcode/qrcode/BC_QRCoderBitVector.cpp @@ -23,6 +23,7 @@ #include "fxbarcode/qrcode/BC_QRCoderBitVector.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_system.h" CBC_QRCoderBitVector::CBC_QRCoderBitVector() = default; @@ -55,8 +56,8 @@ void CBC_QRCoderBitVector::AppendBit(int32_t bit) { } void CBC_QRCoderBitVector::AppendBits(int32_t value, int32_t numBits) { - DCHECK(numBits > 0); - DCHECK(numBits <= 32); + DCHECK_GT(numBits, 0); + DCHECK_LE(numBits, 32); int32_t numBitsLeft = numBits; while (numBitsLeft > 0) { diff --git a/fxbarcode/qrcode/BC_QRCoderEncoder.cpp b/fxbarcode/qrcode/BC_QRCoderEncoder.cpp index 9590f0ec88..e9b3125fe7 100644 --- a/fxbarcode/qrcode/BC_QRCoderEncoder.cpp +++ b/fxbarcode/qrcode/BC_QRCoderEncoder.cpp @@ -31,7 +31,6 @@ #include #include -#include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/data_vector.h" #include "core/fxcrt/fx_extension.h" @@ -198,7 +197,7 @@ bool InitQRCode(int32_t numInputBytes, DataVector GenerateECBytes(pdfium::span dataBytes, size_t numEcBytesInBlock) { // If |numEcBytesInBlock| is 0, the encoder will fail anyway. - DCHECK(numEcBytesInBlock > 0); + DCHECK_GT(numEcBytesInBlock, 0); std::vector toEncode(dataBytes.size() + numEcBytesInBlock); std::copy(dataBytes.begin(), dataBytes.end(), toEncode.begin()); @@ -323,8 +322,8 @@ bool InterleaveWithECBytes(CBC_QRCoderBitVector* bits, int32_t numDataBytes, int32_t numRSBlocks, CBC_QRCoderBitVector* result) { - DCHECK(numTotalBytes >= 0); - DCHECK(numDataBytes >= 0); + DCHECK_GE(numTotalBytes, 0); + DCHECK_GE(numDataBytes, 0); if (bits->sizeInBytes() != static_cast(numDataBytes)) { return false; } diff --git a/fxbarcode/qrcode/BC_QRCoderMode.cpp b/fxbarcode/qrcode/BC_QRCoderMode.cpp index d60e6766e7..edcb7de535 100644 --- a/fxbarcode/qrcode/BC_QRCoderMode.cpp +++ b/fxbarcode/qrcode/BC_QRCoderMode.cpp @@ -24,7 +24,7 @@ #include -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_system.h" CBC_QRCoderMode* CBC_QRCoderMode::sBYTE = nullptr; @@ -72,6 +72,6 @@ int32_t CBC_QRCoderMode::GetCharacterCountBits(int32_t number) const { } int32_t result = character_count_bits_for_versions_[offset]; - DCHECK(result != 0); + DCHECK_NE(result, 0); return result; } diff --git a/fxjs/cjs_publicmethods.cpp b/fxjs/cjs_publicmethods.cpp index 38d7839c02..92ee10d1b2 100644 --- a/fxjs/cjs_publicmethods.cpp +++ b/fxjs/cjs_publicmethods.cpp @@ -24,6 +24,7 @@ #include "core/fpdfdoc/cpdf_formcontrol.h" #include "core/fpdfdoc/cpdf_interactiveform.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/fx_extension.h" #include "core/fxcrt/fx_string_wrappers.h" @@ -657,7 +658,7 @@ CJS_Result CJS_PublicMethods::AFNumber_Format( iDec2 = 1; } } - DCHECK(iDec2 >= 0); + DCHECK_GE(iDec2, 0); // Processing separator style if (static_cast(iDec2) < strValue.GetLength()) { diff --git a/fxjs/cjs_util.cpp b/fxjs/cjs_util.cpp index 2eb56b531f..439cc55dcd 100644 --- a/fxjs/cjs_util.cpp +++ b/fxjs/cjs_util.cpp @@ -224,8 +224,7 @@ CJS_Result CJS_Util::printd(CJS_Runtime* pRuntime, // Convert PDF-style format specifiers to wcsftime specifiers. Remove any // pre-existing %-directives before inserting our own. std::wstring cFormat = pRuntime->ToWideStringReentrant(params[0]).c_str(); - cFormat.erase(std::remove(cFormat.begin(), cFormat.end(), '%'), - cFormat.end()); + cFormat.erase(std::ranges::remove(cFormat, '%').begin(), cFormat.end()); for (const auto& conversion : kTbConvertTable) { size_t nFound = 0; diff --git a/fxjs/gc/heap.cpp b/fxjs/gc/heap.cpp index 4cbc860183..96b813c3df 100644 --- a/fxjs/gc/heap.cpp +++ b/fxjs/gc/heap.cpp @@ -7,6 +7,7 @@ #include #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_system.h" #include "v8/include/cppgc/heap.h" @@ -99,7 +100,7 @@ void FXGC_ForceGarbageCollection(cppgc::Heap* heap) { void FXGCHeapDeleter::operator()(cppgc::Heap* heap) { DCHECK(heap); - DCHECK(g_platform_ref_count > 0); + DCHECK_GT(g_platform_ref_count, 0); --g_platform_ref_count; FXGC_ForceGarbageCollection(heap); diff --git a/fxjs/xfa/cfxjse_formcalc_context.cpp b/fxjs/xfa/cfxjse_formcalc_context.cpp index 8fa105484d..503b6746b4 100644 --- a/fxjs/xfa/cfxjse_formcalc_context.cpp +++ b/fxjs/xfa/cfxjse_formcalc_context.cpp @@ -19,6 +19,7 @@ #include #include "core/fxcrt/cfx_datetime.h" +#include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/code_point_view.h" #include "core/fxcrt/compiler_specific.h" @@ -1084,7 +1085,7 @@ v8::Local GetExtractedValue(v8::Isolate* pIsolate, v8::Local GetSimpleValue( const v8::FunctionCallbackInfo& info, uint32_t index) { - DCHECK(index < static_cast(info.Length())); + DCHECK_LT(index, static_cast(info.Length())); return GetExtractedValue(info.GetIsolate(), info[index]); } diff --git a/fxjs/xfa/cfxjse_resolveprocessor.cpp b/fxjs/xfa/cfxjse_resolveprocessor.cpp index 4b7dc656ca..8af230bd2e 100644 --- a/fxjs/xfa/cfxjse_resolveprocessor.cpp +++ b/fxjs/xfa/cfxjse_resolveprocessor.cpp @@ -10,7 +10,6 @@ #include #include -#include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" #include "core/fxcrt/containers/contains.h" #include "core/fxcrt/fx_extension.h" @@ -530,7 +529,7 @@ bool CFXJSE_ResolveProcessor::ResolveAsterisk(NodeData& rnd) { int32_t CFXJSE_ResolveProcessor::GetFilter(WideStringView wsExpression, int32_t nStart, NodeData& rnd) { - DCHECK(nStart > -1); + DCHECK_GT(nStart, -1); int32_t iLength = wsExpression.GetLength(); if (nStart >= iLength) { 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 302c946fde..bbeda7f787 100644 --- a/pdfium.gni +++ b/pdfium.gni @@ -28,18 +28,6 @@ declare_args() { # Build PDFium either with or without XFA Forms support. pdf_enable_xfa = pdf_enable_xfa_override - # If XFA, also support bmp codec. Ignored if not XFA. - pdf_enable_xfa_bmp = true - - # If XFA, also support gif codec. Ignored if not XFA. - pdf_enable_xfa_gif = true - - # If XFA, also support png codec. Ignored if not XFA. - pdf_enable_xfa_png = true - - # If XFA, also support tiff codec. Ignored if not XFA. - pdf_enable_xfa_tiff = true - # Build PDFium against PartitionAlloc. When false, PDFium must build without # requiring any PartitionAlloc headers or code to be present. When true, # PDFium will use PartitionAlloc partitions to separate strings, scalars, @@ -78,9 +66,11 @@ declare_args() { # `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. + pdf_enable_rust_jpeg = pdf_enable_rust_jpeg_override + # Build PDFium with Rust-based PNG decoder and encoder. # If enabled, it ensures that PDFium doesn't depend on `libpng`. - # Note that `pdf_enable_rust_png` requires Skia and Rust support. pdf_enable_rust_png = pdf_enable_rust_png_override # Build PDFium standalone. Now only controls whether the test binaries @@ -128,9 +118,13 @@ assert( "pdf_enable_fontations=true requires enable_rust=true, enable_rust_cxx=true, and pdf_use_skia=true") assert( - !pdf_enable_rust_bmp || (enable_rust && enable_rust_cxx && pdf_use_skia), - "pdf_enable_rust_bmp=true requires enable_rust=true, enable_rust_cxx=true, and pdf_use_skia=true") + !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") assert( - !pdf_enable_rust_png || (enable_rust && enable_rust_cxx && pdf_use_skia), - "pdf_enable_rust_png=true requires enable_rust=true, enable_rust_cxx=true, and pdf_use_skia=true") + !pdf_enable_rust_png || (enable_rust && enable_rust_cxx), + "pdf_enable_rust_png=true requires enable_rust=true and enable_rust_cxx=true") diff --git a/public/fpdf_annot.h b/public/fpdf_annot.h index f939df7140..255efa4ddf 100644 --- a/public/fpdf_annot.h +++ b/public/fpdf_annot.h @@ -401,10 +401,7 @@ FPDFAnnot_GetAttachmentPoints(FPDF_ANNOTATION annot, FS_QUADPOINTSF* quad_points); // Experimental API. -// Set the annotation rectangle defining the location of the annotation. If the -// annotation's appearance stream is defined and this annotation is of a type -// without quadpoints, then update the bounding box too if the new rectangle -// defines a bigger one. +// Set the annotation rectangle defining the location of the annotation. // // annot - handle to an annotation. // rect - the annotation rectangle to be set. diff --git a/public/fpdf_doc.h b/public/fpdf_doc.h index a351b18bd0..6d551c1ce2 100644 --- a/public/fpdf_doc.h +++ b/public/fpdf_doc.h @@ -150,6 +150,18 @@ FPDFBookmark_GetAction(FPDF_BOOKMARK bookmark); FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFBookmark_GetColor(FPDF_BOOKMARK bookmark, float* R, float* G, float* B); +// Experimental API. +// Get the text style of a bookmark. +// +// bookmark - handle to a bookmark. +// +// Returns the style as an integer, in line with ISO 32000-1:2008 Table 154. +// The first bit is used to represent if the text is italic and the second +// bit is used to represent if the text is bold. Returns 0 if the bookmark +// doesn't have styling or if the handle is invalid. Parsing this value to +// apply styles remains the responsibility of the embedder. +FPDF_EXPORT int FPDF_CALLCONV FPDFBookmark_GetStyle(FPDF_BOOKMARK bookmark); + // Get the type of |action|. // // action - handle to the action. diff --git a/public/fpdf_edit.h b/public/fpdf_edit.h index 8fac863f6a..2dbd60db58 100644 --- a/public/fpdf_edit.h +++ b/public/fpdf_edit.h @@ -1281,6 +1281,24 @@ FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFPath_BezierTo(FPDF_PAGEOBJECT path, float x3, float y3); +// Experimental API. +// Get the two control points of a cubic Bezier segment in |path| at |index|. +// +// path - handle to a path. +// index - the index of the endpoint of a cubic Bezier segment. +// first_control_point - the first control point. +// second_control_point - the second control point. +// +// Returns TRUE on success. Returns FALSE when |path| is not a valid path, +// |index| is out of bounds, the indexed segment is not the endpoint of a cubic +// Bezier segment, or either output pointer is NULL. On failure, the output +// parameters are not modified. +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPath_GetBezierControlPoints(FPDF_PAGEOBJECT path, + size_t index, + FS_POINTF* first_control_point, + FS_POINTF* second_control_point); + // Close the current subpath of a given path. // // path - the handle to the path object. diff --git a/skia/BUILD.gn b/skia/BUILD.gn index bf2c9af45d..e465a3cc53 100644 --- a/skia/BUILD.gn +++ b/skia/BUILD.gn @@ -39,8 +39,8 @@ if (pdf_enable_fontations) { deps = [ ":path_bridge", "//third_party/rust/font_types/v0_12:lib", - "//third_party/rust/read_fonts/v0_40:lib", - "//third_party/rust/skrifa/v0_43:lib", + "//third_party/rust/read_fonts/v0_43:lib", + "//third_party/rust/skrifa/v0_46:lib", ] configs -= [ "//build/config/compiler:chromium_code" ] configs += [ @@ -202,6 +202,13 @@ 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", @@ -353,6 +360,7 @@ static_library("redaction_pathops") { "//third_party/skia/src/ports/SkMemory_malloc.cpp", "//third_party/skia/src/utils/SkDashPath.cpp", "//third_party/skia/src/utils/SkDashPathPriv.h", + "//third_party/skia/src/utils/SkLogHandler.cpp", "config/SkPdfiumUserConfig.h", "ext/google_logging.cc", ] @@ -573,6 +581,16 @@ 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 sources += skia_codec_rust_png_public @@ -588,6 +606,7 @@ 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 e1922a503a..f914cc888b 100644 --- a/skia/OWNERS +++ b/skia/OWNERS @@ -1,2 +1 @@ kjlubick@google.com -bungeman@google.com \ No newline at end of file diff --git a/testing/BUILD.gn b/testing/BUILD.gn index d7a74567d8..11331b1260 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -5,6 +5,7 @@ import("//build/config/sanitizers/sanitizers.gni") import("//build_overrides/build.gni") import("../pdfium.gni") +import("test.gni") source_set("test_support") { testonly = true @@ -37,8 +38,6 @@ source_set("test_support") { "utils/file_util.h", "utils/hash.cpp", "utils/hash.h", - "utils/png_encode.cpp", - "utils/png_encode.h", "utils/scoped_time_overrides.cpp", "utils/scoped_time_overrides.h", ] @@ -53,7 +52,7 @@ source_set("test_support") { "../core/fxcrt", "../core/fxge", "../fpdfsdk", - "image_diff", + "png_codec", ] configs += [ "../:pdfium_strict_config", @@ -108,6 +107,12 @@ source_set("pixel_diff_utils") { configs += [ "../:pdfium_strict_config" ] } +pdfium_unittest_source_set("unittests") { + sources = [ "utils/pixel_diff_util_unittest.cpp" ] + deps = [ ":pixel_diff_utils" ] + pdfium_root_dir = "../" +} + source_set("path_service") { testonly = true sources = [ @@ -228,7 +233,7 @@ source_set("embedder_test_support") { "../core/fdrm", "../core/fxge", "../fpdfsdk", - "image_diff", + "png_codec", "//testing/gmock", "//testing/gtest", "//third_party/simdutf", @@ -295,61 +300,17 @@ config("pdfium_test_config") { } } -executable("pdfium_test") { +executable("pdfium_diff") { testonly = true - sources = [ - "helpers/dump.cc", - "helpers/dump.h", - "helpers/event.cc", - "helpers/event.h", - "helpers/page_renderer.cc", - "helpers/page_renderer.h", - "helpers/write.cc", - "helpers/write.h", - "pdfium_test.cc", - ] - - # Note: One should write programs that depend on ../:pdfium. Whereas this - # test program does not set a good example, and depends on PDFium internals, - # as well as test support code, for convenience. + sources = [ "image_diff.cpp" ] deps = [ - ":test_support", - "../:pdfium_public_headers", - "../fpdfsdk", - "image_diff", + ":path_service", + ":pixel_diff_utils", + "../core/fxcrt", + "png_codec", "//build/win:default_exe_manifest", ] - configs += [ - ":pdfium_in_chromium_config", - ":pdfium_test_config", - "../:pdfium_common_config", - ] - - if (is_win) { - sources += [ - "helpers/win32/com_factory.cc", - "helpers/win32/com_factory.h", - ] - } - - if (pdf_enable_v8) { - deps += [ - "//v8:v8_headers", - "//v8:v8_libplatform", - ] - include_dirs = [ "//v8" ] - configs += [ "//v8:external_startup_data" ] - } - if (pdf_use_skia) { - deps += [ "//skia" ] - if (build_with_chromium) { - deps += [ ":discardable_memory_allocator" ] - } - } - - if (!build_with_chromium) { - assert_no_deps = [ "//testing/gtest/*" ] - } + configs += [ "../:pdfium_strict_config" ] } # Dummy group to keep satisfy references from //build. diff --git a/testing/DEPS b/testing/DEPS index c071352f7c..b49801418b 100644 --- a/testing/DEPS +++ b/testing/DEPS @@ -7,5 +7,5 @@ include_rules = [ '+third_party/simdutf/simdutf.h', '+third_party/skia/include', '+v8', - '+xfa' + '+xfa', ] diff --git a/testing/SUPPRESSIONS b/testing/SUPPRESSIONS index 7497bbbd84..d4bded8977 100644 --- a/testing/SUPPRESSIONS +++ b/testing/SUPPRESSIONS @@ -549,6 +549,8 @@ 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 diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp index 986ad8b3b4..2d2855ddc6 100644 --- a/testing/embedder_test.cpp +++ b/testing/embedder_test.cpp @@ -33,14 +33,13 @@ #include "testing/embedder_test_environment.h" #include "testing/embedpdf_layer_mode.h" #include "testing/gmock/include/gmock/gmock.h" -#include "testing/image_diff/image_diff_png.h" +#include "testing/png_codec/png_codec.h" #include "testing/test_loader.h" #include "testing/utils/bitmap_saver.h" #include "testing/utils/file_util.h" #include "testing/utils/hash.h" #include "testing/utils/path_service.h" #include "testing/utils/pixel_diff_util.h" -#include "testing/utils/png_encode.h" #include "third_party/simdutf/simdutf.h" namespace { @@ -292,14 +291,6 @@ std::string_view GetCpuArchSuffix() { #endif // BUILDFLAG(IS_APPLE) && !defined(ARCH_CPU_ARM64) } -int GetPlatformMaxPixelDelta() { -#if BUILDFLAG(IS_APPLE) - return 1; -#else - return 0; -#endif // BUILDFLAG(IS_APPLE) -} - std::string GetEmbedderTestExpectationPath( std::string_view expectation_png_name) { std::string path = PathService::GetTestFilePath("embedder_tests"); @@ -364,8 +355,8 @@ DecodedPng DecodePngData(pdfium::span png_data) { int width = -1; int height = -1; - std::vector decoded_png = image_diff_png::DecodePNG( - png_data, /*reverse_byte_order=*/true, &width, &height); + std::vector decoded_png = + png_codec::Decode(png_data, /*reverse_byte_order=*/true, &width, &height); if (width > 0 && height > 0 && !decoded_png.empty()) { results.width = width; results.height = height; @@ -377,11 +368,13 @@ 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) { + int max_pixel_per_channel_delta, + double max_mean_squared_error) { const size_t unsigned_width = static_cast(decoded_png.width); auto decoded_png_span32 = fxcrt::reinterpret_span( pdfium::span(decoded_png.pixel_data)); int pixels_different = 0; + 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); @@ -400,6 +393,14 @@ int CompareBGRxBitmapToPng(pdfium::span bitmap_span, 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; @@ -408,7 +409,8 @@ int CompareBGRxBitmapToPng(pdfium::span bitmap_span, int CompareGrayBitmapToPng(pdfium::span bitmap_span, size_t bitmap_stride, const DecodedPng& decoded_png, - int max_pixel_per_channel_delta) { + int max_pixel_per_channel_delta, + double max_mean_squared_error) { const int width = decoded_png.width; const size_t dest_row_width = static_cast(width); const int height = decoded_png.height; @@ -427,14 +429,16 @@ int CompareGrayBitmapToPng(pdfium::span bitmap_span, } } return CompareBGRxBitmapToPng(bgrx_buffer, bgrx_stride, decoded_png, - max_pixel_per_channel_delta); + max_pixel_per_channel_delta, + max_mean_squared_error); } #ifdef PDF_USE_SKIA int CompareBGRxPremultBitmapToPng(pdfium::span bitmap_span, size_t bitmap_stride, const DecodedPng& decoded_png, - int max_pixel_per_channel_delta) { + int max_pixel_per_channel_delta, + double max_mean_squared_error) { std::vector bitmap_data(bitmap_span.begin(), bitmap_span.end()); pdfium::span converted_bitmap_span{bitmap_data}; @@ -446,15 +450,17 @@ int CompareBGRxPremultBitmapToPng(pdfium::span bitmap_span, bitmap_row[w] = UnPreMultiplyColor(bitmap_row[w]); } } - return CompareBGRxBitmapToPng(bitmap_span, bitmap_stride, decoded_png, - max_pixel_per_channel_delta); + return CompareBGRxBitmapToPng(bitmap_data, bitmap_stride, decoded_png, + max_pixel_per_channel_delta, + max_mean_squared_error); } #endif // PDF_USE_SKIA int CompareBGRBitmapToPng(pdfium::span bitmap_span, size_t bitmap_stride, const DecodedPng& decoded_png, - int max_pixel_per_channel_delta) { + int max_pixel_per_channel_delta, + double max_mean_squared_error) { const int width = decoded_png.width; const size_t dest_row_width = static_cast(width); const int height = decoded_png.height; @@ -474,7 +480,8 @@ int CompareBGRBitmapToPng(pdfium::span bitmap_span, } } return CompareBGRxBitmapToPng(bgrx_buffer, bgrx_stride, decoded_png, - max_pixel_per_channel_delta); + max_pixel_per_channel_delta, + max_mean_squared_error); } std::string EncodeBase64(pdfium::span png) { @@ -485,7 +492,7 @@ std::string EncodeBase64(pdfium::span png) { } std::string EncodeBase64Png(FPDF_BITMAP bitmap) { - return EncodeBase64(EncodePng(bitmap)); + return EncodeBase64(BitmapSaver::EncodeBitmapToPng(bitmap)); } void ReportMissingExpectation(FPDF_BITMAP bitmap) { @@ -496,7 +503,8 @@ void ReportMissingExpectation(FPDF_BITMAP bitmap) { void CompareBitmapToPngFile(FPDF_BITMAP bitmap, const std::string& png_path, - int max_pixel_per_channel_delta) { + int max_pixel_per_channel_delta, + double max_mean_squared_error) { std::vector png_data = GetFileContents(png_path.c_str()); if (png_data.empty()) { ReportMissingExpectation(bitmap); @@ -524,22 +532,26 @@ void CompareBitmapToPngFile(FPDF_BITMAP bitmap, switch (FPDFBitmap_GetFormat(bitmap)) { case FPDFBitmap_Gray: pixels_different = CompareGrayBitmapToPng( - bitmap_span, stride, decoded_png, max_pixel_per_channel_delta); + bitmap_span, stride, decoded_png, max_pixel_per_channel_delta, + max_mean_squared_error); break; case FPDFBitmap_BGR: pixels_different = CompareBGRBitmapToPng(bitmap_span, stride, decoded_png, - max_pixel_per_channel_delta); + max_pixel_per_channel_delta, + max_mean_squared_error); break; case FPDFBitmap_BGRx: case FPDFBitmap_BGRA: { pixels_different = CompareBGRxBitmapToPng( - bitmap_span, stride, decoded_png, max_pixel_per_channel_delta); + bitmap_span, stride, decoded_png, max_pixel_per_channel_delta, + max_mean_squared_error); break; } #ifdef PDF_USE_SKIA case FPDFBitmap_BGRA_Premul: pixels_different = CompareBGRxPremultBitmapToPng( - bitmap_span, stride, decoded_png, max_pixel_per_channel_delta); + bitmap_span, stride, decoded_png, max_pixel_per_channel_delta, + max_mean_squared_error); break; #endif // PDF_USE_SKIA default: @@ -1191,7 +1203,8 @@ 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); + CompareBitmapToPngFile(bitmap, png_path, /*max_pixel_per_channel_delta=*/0, + /*max_mean_squared_error=*/0.0); if (EmbedderTestEnvironment::GetInstance()->write_pngs()) { WriteBitmapToPng(bitmap, png_path); } @@ -1201,7 +1214,8 @@ void EmbedderTest::CompareBitmap(FPDF_BITMAP bitmap, void EmbedderTest::CompareBitmapWithExpectationSuffix( FPDF_BITMAP bitmap, std::string_view expectation_png_name, - int max_pixel_per_channel_delta) { + int max_pixel_per_channel_delta, + double max_mean_squared_error) { std::vector candidate_png_path = GetEmbedderTestExpectationsWithSuffixPath(expectation_png_name); for (const std::string& png_path : candidate_png_path) { @@ -1211,7 +1225,8 @@ void EmbedderTest::CompareBitmapWithExpectationSuffix( SCOPED_TRACE(testing::Message() << "CompareBitmapWithExpectationSuffix() with " << png_path); - CompareBitmapToPngFile(bitmap, png_path, max_pixel_per_channel_delta); + CompareBitmapToPngFile(bitmap, png_path, max_pixel_per_channel_delta, + max_mean_squared_error); if (EmbedderTestEnvironment::GetInstance()->write_pngs()) { WriteBitmapToPng(bitmap, png_path); } @@ -1226,7 +1241,8 @@ void EmbedderTest::CompareBitmapWithFuzzyExpectationSuffix( FPDF_BITMAP bitmap, std::string_view expectation_png_name) { CompareBitmapWithExpectationSuffix(bitmap, expectation_png_name, - GetPlatformMaxPixelDelta()); + kMaxFuzzyPixelDelta, + kMaxFuzzyMeanSquaredError); } // static diff --git a/testing/embedder_test.h b/testing/embedder_test.h index 6daf245335..8571af5bd2 100644 --- a/testing/embedder_test.h +++ b/testing/embedder_test.h @@ -346,15 +346,17 @@ class EmbedderTest : public ::testing::Test, // // This is similar to the behavior in testing/tools/pngdiffer.py. // - // `max_pixel_per_channel_delta` can optionally be set to tolerate minor pixel - // discrepancies. The default is exact matching. + // `max_pixel_per_channel_delta` and `max_mean_squared_error` 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); + int max_pixel_per_channel_delta = 0, + double max_mean_squared_error = 0.0); // Same as `CompareBitmapWithExpectationSuffix()`, but automatically - // applies platform-specific tolerance. + // applies standard fuzzy tolerance. static void CompareBitmapWithFuzzyExpectationSuffix( FPDF_BITMAP bitmap, std::string_view expectation_png_name); @@ -372,8 +374,6 @@ class EmbedderTest : public ::testing::Test, ScopedSavedDoc OpenScopedSavedDocumentWithPassword( const ByteString& password); ScopedSavedPage LoadScopedSavedPage(int page_index); - FPDF_PAGE LoadSavedPage(int page_index); - void CloseSavedPage(FPDF_PAGE page); // See comments for CompareBitmap() and CompareBitmapWithExpectationSuffix() // above. @@ -428,6 +428,14 @@ class EmbedderTest : public ::testing::Test, // in the destructor of the scoped methods. void CloseSavedDocument(); + // Helper for loading saved pages. Callers should use the Scoped methods that + // manage lifetime automatically. + FPDF_PAGE LoadSavedPage(int page_index); + + // Closes a page loaded via LoadSavedPage(). This must only be invoked in + // pages loaded by the helper above. + void CloseSavedPage(FPDF_PAGE page); + void UnloadPageCommon(FPDF_PAGE page, bool do_events); FPDF_PAGE LoadPageCommon(int page_index, bool do_events); diff --git a/testing/embedpdf_layer_mode_writers.txt b/testing/embedpdf_layer_mode_writers.txt index ce03412ab5..14fa1a378d 100644 --- a/testing/embedpdf_layer_mode_writers.txt +++ b/testing/embedpdf_layer_mode_writers.txt @@ -158,7 +158,7 @@ FPDFAnnotEmbedderTest.GetSetStringValue 1 FPDFAnnotEmbedderTest.InkAnnotation 1 FPDFAnnotEmbedderTest.LineAnnotation 1 FPDFAnnotEmbedderTest.ModifyAnnotationFlags 1 -FPDFAnnotEmbedderTest.ModifyRectQuadpointsWithAP 4 +FPDFAnnotEmbedderTest.ModifyRectQuadpointsWithAP 3 FPDFAnnotEmbedderTest.PolygonAnnotation 1 FPDFAnnotEmbedderTest.RemoveAnnotation 2 FPDFAnnotEmbedderTest.RemoveOptionalAP 3 diff --git a/testing/fuzzers/BUILD.gn b/testing/fuzzers/BUILD.gn index da727fe786..d9a89721e1 100644 --- a/testing/fuzzers/BUILD.gn +++ b/testing/fuzzers/BUILD.gn @@ -42,27 +42,17 @@ if (pdf_enable_v8) { "pdf_cfx_barcode_fuzzer", "pdf_codec_jpeg_fuzzer", "pdf_css_fuzzer", + "pdf_codec_bmp_fuzzer", + "pdf_codec_gif_fuzzer", + "pdf_codec_png_fuzzer", + "pdf_codec_tiff_fuzzer", "pdf_formcalc_context_fuzzer", "pdf_formcalc_fuzzer", "pdf_formcalc_translate_fuzzer", + "pdf_lzw_fuzzer", "pdfium_xfa_fuzzer", "pdfium_xfa_lpm_fuzzer", ] - if (pdf_enable_xfa_bmp) { - fuzzer_list += [ "pdf_codec_bmp_fuzzer" ] - } - if (pdf_enable_xfa_gif) { - fuzzer_list += [ - "pdf_codec_gif_fuzzer", - "pdf_lzw_fuzzer", - ] - } - if (pdf_enable_xfa_png) { - fuzzer_list += [ "pdf_codec_png_fuzzer" ] - } - if (pdf_enable_xfa_tiff) { - fuzzer_list += [ "pdf_codec_tiff_fuzzer" ] - } } } if (is_clang) { @@ -309,37 +299,33 @@ if (pdf_enable_v8) { ] } - if (pdf_enable_xfa_bmp) { - pdfium_fuzzer("pdf_codec_bmp_fuzzer") { - sources = [ - "pdf_codec_bmp_fuzzer.cc", - "xfa_codec_fuzzer.h", - ] - deps = [ - "../../core/fxcodec", - "../../core/fxcrt", - "../../core/fxge", - ] - } + pdfium_fuzzer("pdf_codec_bmp_fuzzer") { + sources = [ + "pdf_codec_bmp_fuzzer.cc", + "xfa_codec_fuzzer.h", + ] + deps = [ + "../../core/fxcodec", + "../../core/fxcrt", + "../../core/fxge", + ] } - if (pdf_enable_xfa_gif) { - pdfium_fuzzer("pdf_codec_gif_fuzzer") { - sources = [ - "pdf_codec_gif_fuzzer.cc", - "xfa_codec_fuzzer.h", - ] - deps = [ - "../../core/fxcodec", - "../../core/fxcrt", - "../../core/fxge", - ] - } + pdfium_fuzzer("pdf_codec_gif_fuzzer") { + sources = [ + "pdf_codec_gif_fuzzer.cc", + "xfa_codec_fuzzer.h", + ] + deps = [ + "../../core/fxcodec", + "../../core/fxcrt", + "../../core/fxge", + ] + } - pdfium_fuzzer("pdf_lzw_fuzzer") { - sources = [ "pdf_lzw_fuzzer.cc" ] - deps = [ "../../core/fxcodec" ] - } + pdfium_fuzzer("pdf_lzw_fuzzer") { + sources = [ "pdf_lzw_fuzzer.cc" ] + deps = [ "../../core/fxcodec" ] } pdfium_fuzzer("pdf_codec_jpeg_fuzzer") { @@ -354,32 +340,28 @@ if (pdf_enable_v8) { ] } - if (pdf_enable_xfa_png) { - pdfium_fuzzer("pdf_codec_png_fuzzer") { - sources = [ - "pdf_codec_png_fuzzer.cc", - "xfa_codec_fuzzer.h", - ] - deps = [ - "../../core/fxcodec", - "../../core/fxcrt", - "../../core/fxge", - ] - } + pdfium_fuzzer("pdf_codec_png_fuzzer") { + sources = [ + "pdf_codec_png_fuzzer.cc", + "xfa_codec_fuzzer.h", + ] + deps = [ + "../../core/fxcodec", + "../../core/fxcrt", + "../../core/fxge", + ] } - if (pdf_enable_xfa_tiff) { - pdfium_fuzzer("pdf_codec_tiff_fuzzer") { - sources = [ - "pdf_codec_tiff_fuzzer.cc", - "xfa_codec_fuzzer.h", - ] - deps = [ - "../../core/fxcodec", - "../../core/fxcrt", - "../../core/fxge", - ] - } + pdfium_fuzzer("pdf_codec_tiff_fuzzer") { + sources = [ + "pdf_codec_tiff_fuzzer.cc", + "xfa_codec_fuzzer.h", + ] + deps = [ + "../../core/fxcodec", + "../../core/fxcrt", + "../../core/fxge", + ] } pdfium_fuzzer("pdf_css_fuzzer") { diff --git a/testing/image_diff/image_diff.cpp b/testing/image_diff.cpp similarity index 93% rename from testing/image_diff/image_diff.cpp rename to testing/image_diff.cpp index a2f3a834b1..0ecff329b8 100644 --- a/testing/image_diff/image_diff.cpp +++ b/testing/image_diff.cpp @@ -20,7 +20,7 @@ #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/numerics/safe_conversions.h" #include "core/fxcrt/span_io.h" -#include "testing/image_diff/image_diff_png.h" +#include "testing/png_codec/png_codec.h" #include "testing/utils/path_service.h" #include "testing/utils/pixel_diff_util.h" @@ -102,7 +102,7 @@ class Image { fclose(f); - data_ = image_diff_png::DecodePNG(compressed, reverse_byte_order, &w_, &h_); + data_ = png_codec::Decode(compressed, reverse_byte_order, &w_, &h_); if (data_.empty()) { Clear(); return false; @@ -152,12 +152,14 @@ void CountImageSizeMismatchAsPixelDifference(const Image& baseline, int PixelsDifferent(const Image& baseline, const Image& actual, - uint8_t max_pixel_per_channel_delta) { + uint8_t max_pixel_per_channel_delta, + double max_mean_squared_error) { int w = std::min(baseline.w(), actual.w()); int h = std::min(baseline.h(), actual.h()); // Compute pixels different in the overlap. int pixels_different = 0; + uint64_t total_squared_error = 0; for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const uint32_t baseline_pixel = baseline.pixel_at(x, y); @@ -170,6 +172,14 @@ int PixelsDifferent(const Image& baseline, max_pixel_per_channel_delta) { ++pixels_different; } + 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; } } @@ -240,7 +250,8 @@ 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) { + uint8_t max_pixel_per_channel_delta, + double max_mean_squared_error) { Image actual_image; Image baseline_image; @@ -270,8 +281,9 @@ 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); + int pixels_different = + PixelsDifferent(actual_image, baseline_image, max_pixel_per_channel_delta, + max_mean_squared_error); float percent = CalculateDifferencePercentage(actual_image, pixels_different); const char* const passed = percent > 0.0 ? "failed" : "passed"; UNSAFE_TODO(printf("%s: %01.2f%% %s (%d pixels differ)\n", diff_name, percent, @@ -383,7 +395,7 @@ int DiffImages(const std::string& binary_name, return kStatusSame; } - std::vector png_encoding = image_diff_png::EncodeRGBAPNG( + std::vector png_encoding = png_codec::EncodeRGBA( diff_image.span(), diff_image.w(), diff_image.h(), diff_image.w() * 4); if (png_encoding.empty()) { return kStatusError; @@ -407,6 +419,7 @@ int main(int argc, const char* argv[]) { 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; std::string filename1; std::string filename2; std::string diff_filename; @@ -431,7 +444,8 @@ int main(int argc, const char* argv[]) { } 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 = 1; + max_pixel_per_channel_delta = kMaxFuzzyPixelDelta; + max_mean_squared_error = kMaxFuzzyMeanSquaredError; } } if (i < argc) { @@ -451,7 +465,8 @@ 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); + reverse_byte_order, max_pixel_per_channel_delta, + max_mean_squared_error); } PrintHelp(binary_name); diff --git a/testing/image_diff/BUILD.gn b/testing/image_diff/BUILD.gn deleted file mode 100644 index 6706ba2ba7..0000000000 --- a/testing/image_diff/BUILD.gn +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2018 The PDFium Authors -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import("../../pdfium.gni") -import("../../testing/test.gni") - -source_set("image_diff") { - testonly = true - sources = [ "image_diff_png.h" ] - deps = [] - - if (pdf_use_skia) { - sources += [ "image_diff_png_skia.cpp" ] - deps += [ "//skia" ] - } else { - sources += [ "image_diff_png_libpng.cpp" ] - deps += [ - "../../third_party:png", - "../../third_party:zlib", - ] - } - - configs += [ - "../../:pdfium_strict_config", - "../../:pdfium_noshorten_config", - ] - public_deps = [ "../../core/fxcrt" ] -} - -pdfium_unittest_source_set("unittests") { - sources = [ "image_diff_png_unittests.cpp" ] - deps = [ ":image_diff" ] - pdfium_root_dir = "../../" -} diff --git a/testing/image_diff/image_diff_png.h b/testing/image_diff/image_diff_png.h deleted file mode 100644 index 74ecfdfe96..0000000000 --- a/testing/image_diff/image_diff_png.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2013 The PDFium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef TESTING_IMAGE_DIFF_IMAGE_DIFF_PNG_H_ -#define TESTING_IMAGE_DIFF_IMAGE_DIFF_PNG_H_ - -#include // for size_t. - -#include - -#include "core/fxcrt/span.h" - -namespace image_diff_png { - -// Decode a PNG into an RGBA pixel array, or BGRA pixel array if -// |reverse_byte_order| is set to true. -std::vector DecodePNG(pdfium::span input, - bool reverse_byte_order, - int* width, - int* height); - -// Encode a BGR pixel array into a PNG. -std::vector EncodeBGRPNG(pdfium::span input, - int width, - int height, - int row_byte_width); - -// Encode an RGBA pixel array into a PNG. -std::vector EncodeRGBAPNG(pdfium::span input, - int width, - int height, - int row_byte_width); - -// Encode an BGRA pixel array into a PNG. -std::vector EncodeBGRAPNG(pdfium::span input, - int width, - int height, - int row_byte_width, - bool discard_transparency); - -// Encode a grayscale pixel array into a PNG. -std::vector EncodeGrayPNG(pdfium::span input, - int width, - int height, - int row_byte_width); - -} // namespace image_diff_png - -#endif // TESTING_IMAGE_DIFF_IMAGE_DIFF_PNG_H_ diff --git a/testing/image_diff/image_diff_png_skia.cpp b/testing/image_diff/image_diff_png_skia.cpp deleted file mode 100644 index 64ce821cc6..0000000000 --- a/testing/image_diff/image_diff_png_skia.cpp +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2025 The PDFium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "testing/image_diff/image_diff_png.h" - -#include -#include -#include -#include - -#include "core/fxcrt/check_op.h" -#include "core/fxcrt/fx_safe_types.h" -#include "core/fxcrt/numerics/checked_math.h" -#include "core/fxcrt/span.h" -#include "core/fxcrt/span_util.h" -#include "third_party/skia/include/core/SkColorSpace.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" - -#ifdef PDF_ENABLE_RUST_PNG -#include "third_party/skia/include/codec/SkPngRustDecoder.h" -#include "third_party/skia/include/encode/SkPngRustEncoder.h" -#else -#include "third_party/skia/include/codec/SkPngDecoder.h" -#include "third_party/skia/include/encode/SkPngEncoder.h" -#endif - -namespace image_diff_png { - -namespace { - -std::vector EncodePNG(pdfium::span input, - SkColorType color, - SkAlphaType alpha, - int width, - int height, - size_t row_byte_width) { - SkImageInfo info = - SkImageInfo::Make(width, height, color, alpha, SkColorSpace::MakeSRGB()); - CHECK_NE(info.minRowBytes(), 0); // 0 means conversion problems. - CHECK_LE(info.minRowBytes(), row_byte_width); - CHECK_NE(info.computeMinByteSize(), 0); // 0 means conversion problems. - CHECK_LE(info.computeMinByteSize(), input.size()); - SkPixmap pixmap(info, input.data(), row_byte_width); - - SkDynamicMemoryWStream output; -#ifdef PDF_ENABLE_RUST_PNG - bool success = SkPngRustEncoder::Encode(&output, pixmap, {}); -#else - bool success = SkPngEncoder::Encode(&output, pixmap, {}); -#endif - if (!success) { - return {}; - } - return output.detachAsVector(); -} - -} // namespace - -std::vector DecodePNG(pdfium::span input, - bool reverse_byte_order, - int* width, - int* height) { - CHECK(width); - CHECK(height); - - auto stream = std::make_unique(input.data(), input.size(), - /*copyData=*/false); -#ifdef PDF_ENABLE_RUST_PNG - std::unique_ptr codec = - SkPngRustDecoder::Decode(std::move(stream), nullptr); -#else - std::unique_ptr codec = - SkPngDecoder::Decode(std::move(stream), nullptr); -#endif - if (!codec) { - return {}; - } - - SkColorType format = - reverse_byte_order ? kBGRA_8888_SkColorType : kRGBA_8888_SkColorType; - SkImageInfo info = codec->getInfo(); - info = info.makeColorType(format); - info = info.makeColorSpace(SkColorSpace::MakeSRGB()); - - std::vector output; - output.resize(info.computeMinByteSize()); - - SkCodec::Result result = - codec->getPixels(info, output.data(), info.minRowBytes()); - if (result != SkCodec::kSuccess) { - return {}; - } - - *width = info.width(); - *height = info.height(); - return output; -} - -std::vector EncodeBGRPNG(pdfium::span bgr_input, - int width, - int height, - int row_byte_width) { - // Check inputs. Expected values are manually calculated (instead of using - // `SkImageInfo`'s `computeMinByteSize` and/or `minRowBytes`), because - // `SkColorType` doesn't cover a format with 3 bytes per pixel (bpp) - e.g. - // `kRGB_565_SkColorType` is 2 bpp and `kRGB_888x_SkColorType` is 4 bpp. - size_t row_byte_width_as_size_t = - pdfium::checked_cast(row_byte_width); - FX_SAFE_SIZE_T expected_minimum_row_byte_width = 3; - expected_minimum_row_byte_width *= width; - CHECK_LE(expected_minimum_row_byte_width.ValueOrDie(), - row_byte_width_as_size_t); - - FX_SAFE_SIZE_T expected_minimum_input_size = row_byte_width_as_size_t; - expected_minimum_input_size *= height; - CHECK_LE(expected_minimum_input_size.ValueOrDie(), bgr_input.size()); - - // Convert `bgr_input` into `intermediate_bgra_buf` (because Skia doesn't - // allow encoding BGR pixels - see the comment at the top of the function - // that talks about limitations of `SkColorType`). - SkImageInfo intermediate_bgra_info = - SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, - kOpaque_SkAlphaType, SkColorSpace::MakeSRGB()); - size_t intermediate_bgra_row_byte_width = - intermediate_bgra_info.minRowBytes(); - CHECK_NE(0, - intermediate_bgra_row_byte_width); // 0 means conversion problems. - std::vector intermediate_bgra_buf; - intermediate_bgra_buf.resize(intermediate_bgra_info.computeMinByteSize()); - CHECK_NE(0, intermediate_bgra_buf.size()); // 0 means conversion problems. - { - pdfium::span src = bgr_input; - pdfium::span dst = intermediate_bgra_buf; - size_t height_as_size_t = pdfium::checked_cast(height); - size_t width_as_size_t = pdfium::checked_cast(width); - for (size_t y = 0; y < height_as_size_t; y++) { - for (size_t x = 0; x < width_as_size_t; x++) { - // If `computeMinByteSize` didn't report an error (`0`), then integer - // overflow won't happen in the `x * N` expressions below. - pdfium::span dst_pixel = dst.subspan(x * 4u).first(4u); - pdfium::span src_pixel = src.subspan(x * 3u).first(3u); - - fxcrt::spancpy(dst_pixel.first(3u), src_pixel); // Copy BGR channels. - dst_pixel[3] = 0xFF; // Set alpha channel to "opaque". - } - src = src.subspan(row_byte_width_as_size_t); - dst = dst.subspan(intermediate_bgra_row_byte_width); - } - } - - return EncodePNG(intermediate_bgra_buf, kBGRA_8888_SkColorType, - kOpaque_SkAlphaType, width, height, - intermediate_bgra_row_byte_width); -} - -std::vector EncodeRGBAPNG(pdfium::span input, - int width, - int height, - int row_byte_width) { - return EncodePNG(input, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, width, - height, pdfium::checked_cast(row_byte_width)); -} - -std::vector EncodeBGRAPNG(pdfium::span input, - int width, - int height, - int row_byte_width, - bool discard_transparency) { - return EncodePNG( - input, kBGRA_8888_SkColorType, - discard_transparency ? kOpaque_SkAlphaType : kUnpremul_SkAlphaType, width, - height, pdfium::checked_cast(row_byte_width)); -} - -std::vector EncodeGrayPNG(pdfium::span input, - int width, - int height, - int row_byte_width) { - return EncodePNG(input, kGray_8_SkColorType, kOpaque_SkAlphaType, width, - height, pdfium::checked_cast(row_byte_width)); -} - -} // namespace image_diff_png diff --git a/testing/pdfium_test/BUILD.gn b/testing/pdfium_test/BUILD.gn new file mode 100644 index 0000000000..424d28ea90 --- /dev/null +++ b/testing/pdfium_test/BUILD.gn @@ -0,0 +1,63 @@ +# 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. + +import("//build_overrides/build.gni") +import("../../pdfium.gni") + +executable("pdfium_test") { + testonly = true + sources = [ + "dump.cc", + "dump.h", + "event.cc", + "event.h", + "page_renderer.cc", + "page_renderer.h", + "pdfium_test.cc", + "write.cc", + "write.h", + ] + + # Note: One should write programs that depend on ../../:pdfium. Whereas this + # test program does not set a good example, and depends on PDFium internals, + # as well as test support code, for convenience. + deps = [ + "../:test_support", + "../../:pdfium_public_headers", + "../../fpdfsdk", + "../png_codec", + "//build/win:default_exe_manifest", + ] + configs += [ + "../:pdfium_in_chromium_config", + "../:pdfium_test_config", + "../../:pdfium_common_config", + ] + + if (is_win) { + sources += [ + "win32/com_factory.cc", + "win32/com_factory.h", + ] + } + + if (pdf_enable_v8) { + deps += [ + "//v8:v8_headers", + "//v8:v8_libplatform", + ] + include_dirs = [ "//v8" ] + configs += [ "//v8:external_startup_data" ] + } + if (pdf_use_skia) { + deps += [ "//skia" ] + if (build_with_chromium) { + deps += [ "../:discardable_memory_allocator" ] + } + } + + if (!build_with_chromium) { + assert_no_deps = [ "//testing/gtest/*" ] + } +} diff --git a/testing/helpers/dump.cc b/testing/pdfium_test/dump.cc similarity index 99% rename from testing/helpers/dump.cc rename to testing/pdfium_test/dump.cc index 22e36de25b..d4e5d6db3f 100644 --- a/testing/helpers/dump.cc +++ b/testing/pdfium_test/dump.cc @@ -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 "testing/helpers/dump.h" +#include "testing/pdfium_test/dump.h" #include #include diff --git a/testing/helpers/dump.h b/testing/pdfium_test/dump.h similarity index 77% rename from testing/helpers/dump.h rename to testing/pdfium_test/dump.h index 2e804e3504..64204b5a5c 100644 --- a/testing/helpers/dump.h +++ b/testing/pdfium_test/dump.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 TESTING_HELPERS_DUMP_H_ -#define TESTING_HELPERS_DUMP_H_ +#ifndef TESTING_PDFIUM_TEST_DUMP_H_ +#define TESTING_PDFIUM_TEST_DUMP_H_ #include "public/fpdfview.h" @@ -12,4 +12,4 @@ void DumpPageInfo(FPDF_PAGE page, int page_idx); void DumpPageStructure(FPDF_PAGE page, int page_idx); void DumpMetaData(FPDF_DOCUMENT doc); -#endif // TESTING_HELPERS_DUMP_H_ +#endif // TESTING_PDFIUM_TEST_DUMP_H_ diff --git a/testing/helpers/event.cc b/testing/pdfium_test/event.cc similarity index 99% rename from testing/helpers/event.cc rename to testing/pdfium_test/event.cc index abcf9d60d6..75906d8609 100644 --- a/testing/helpers/event.cc +++ b/testing/pdfium_test/event.cc @@ -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 "testing/helpers/event.h" +#include "testing/pdfium_test/event.h" #include #include diff --git a/testing/helpers/event.h b/testing/pdfium_test/event.h similarity index 79% rename from testing/helpers/event.h rename to testing/pdfium_test/event.h index f7af3a8582..9bb3d1dc07 100644 --- a/testing/helpers/event.h +++ b/testing/pdfium_test/event.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 TESTING_HELPERS_EVENT_H_ -#define TESTING_HELPERS_EVENT_H_ +#ifndef TESTING_PDFIUM_TEST_EVENT_H_ +#define TESTING_PDFIUM_TEST_EVENT_H_ #include #include @@ -16,4 +16,4 @@ void SendPageEvents(FPDF_FORMHANDLE form, const std::string& events, const std::function& idler); -#endif // TESTING_HELPERS_EVENT_H_ +#endif // TESTING_PDFIUM_TEST_EVENT_H_ diff --git a/testing/helpers/page_renderer.cc b/testing/pdfium_test/page_renderer.cc similarity index 89% rename from testing/helpers/page_renderer.cc rename to testing/pdfium_test/page_renderer.cc index c673be8643..4a37b57b15 100644 --- a/testing/helpers/page_renderer.cc +++ b/testing/pdfium_test/page_renderer.cc @@ -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 "testing/helpers/page_renderer.h" +#include "testing/pdfium_test/page_renderer.h" #include "public/fpdfview.h" diff --git a/testing/helpers/page_renderer.h b/testing/pdfium_test/page_renderer.h similarity index 89% rename from testing/helpers/page_renderer.h rename to testing/pdfium_test/page_renderer.h index 8710b3d26c..44b7e948fc 100644 --- a/testing/helpers/page_renderer.h +++ b/testing/pdfium_test/page_renderer.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 TESTING_HELPERS_PAGE_RENDERER_H_ -#define TESTING_HELPERS_PAGE_RENDERER_H_ +#ifndef TESTING_PDFIUM_TEST_PAGE_RENDERER_H_ +#define TESTING_PDFIUM_TEST_PAGE_RENDERER_H_ #include @@ -44,4 +44,4 @@ class PageRenderer { int flags_; }; -#endif // TESTING_HELPERS_PAGE_RENDERER_H_ +#endif // TESTING_PDFIUM_TEST_PAGE_RENDERER_H_ diff --git a/testing/pdfium_test.cc b/testing/pdfium_test/pdfium_test.cc similarity index 99% rename from testing/pdfium_test.cc rename to testing/pdfium_test/pdfium_test.cc index fbce83e9ad..6ace38dacb 100644 --- a/testing/pdfium_test.cc +++ b/testing/pdfium_test/pdfium_test.cc @@ -41,10 +41,10 @@ #include "testing/command_line_helpers.h" #include "testing/font_renamer.h" #include "testing/fx_string_testhelpers.h" -#include "testing/helpers/dump.h" -#include "testing/helpers/event.h" -#include "testing/helpers/page_renderer.h" -#include "testing/helpers/write.h" +#include "testing/pdfium_test/dump.h" +#include "testing/pdfium_test/event.h" +#include "testing/pdfium_test/page_renderer.h" +#include "testing/pdfium_test/write.h" #include "testing/test_loader.h" #include "testing/utils/file_util.h" #include "testing/utils/hash.h" @@ -56,7 +56,7 @@ #include #include -#include "testing/helpers/win32/com_factory.h" +#include "testing/pdfium_test/win32/com_factory.h" #else #include #endif // _WIN32 @@ -1859,6 +1859,15 @@ void ShowConfig() { #endif #ifdef _WIN32 append_config("GDI"); +#endif +#ifdef PDF_ENABLE_RUST_BMP + append_config("RUST_BMP"); +#endif +#ifdef PDF_ENABLE_RUST_JPEG + append_config("RUST_JPEG"); +#endif +#ifdef PDF_ENABLE_RUST_PNG + append_config("RUST_PNG"); #endif printf("%s\n", config.c_str()); } diff --git a/testing/helpers/win32/com_factory.cc b/testing/pdfium_test/win32/com_factory.cc similarity index 96% rename from testing/helpers/win32/com_factory.cc rename to testing/pdfium_test/win32/com_factory.cc index 6c37dc3f07..9820c9a795 100644 --- a/testing/helpers/win32/com_factory.cc +++ b/testing/pdfium_test/win32/com_factory.cc @@ -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 "testing/helpers/win32/com_factory.h" +#include "testing/pdfium_test/win32/com_factory.h" #include #include diff --git a/testing/helpers/win32/com_factory.h b/testing/pdfium_test/win32/com_factory.h similarity index 74% rename from testing/helpers/win32/com_factory.h rename to testing/pdfium_test/win32/com_factory.h index d634cefff9..c489c3e0d1 100644 --- a/testing/helpers/win32/com_factory.h +++ b/testing/pdfium_test/win32/com_factory.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 TESTING_HELPERS_WIN32_COM_FACTORY_H_ -#define TESTING_HELPERS_WIN32_COM_FACTORY_H_ +#ifndef TESTING_PDFIUM_TEST_WIN32_COM_FACTORY_H_ +#define TESTING_PDFIUM_TEST_WIN32_COM_FACTORY_H_ struct IXpsOMObjectFactory; @@ -22,4 +22,4 @@ class ComFactory final { IXpsOMObjectFactory* xps_om_object_factory_ = nullptr; }; -#endif // TESTING_HELPERS_WIN32_COM_FACTORY_H_ +#endif // TESTING_PDFIUM_TEST_WIN32_COM_FACTORY_H_ diff --git a/testing/helpers/write.cc b/testing/pdfium_test/write.cc similarity index 95% rename from testing/helpers/write.cc rename to testing/pdfium_test/write.cc index 04bc0717b3..821281e4d3 100644 --- a/testing/helpers/write.cc +++ b/testing/pdfium_test/write.cc @@ -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 "testing/helpers/write.h" +#include "testing/pdfium_test/write.h" #include @@ -21,8 +21,9 @@ #include "public/fpdf_edit.h" #include "public/fpdf_thumbnail.h" #include "testing/fx_string_testhelpers.h" +#include "testing/png_codec/png_codec.h" +#include "testing/utils/bitmap_saver.h" #include "testing/utils/file_util.h" -#include "testing/utils/png_encode.h" #ifdef PDF_ENABLE_SKIA #include "third_party/skia/include/core/SkData.h" // nogncheck @@ -249,16 +250,20 @@ std::string WritePng(const char* pdf_name, UNSAFE_TODO(pdfium::span(static_cast(buffer), static_cast(bitmap_attributes.stride) * bitmap_attributes.height)); - int format; - if (bitmap_attributes.has_alpha) { - format = buffer_has_premultiplied_alpha ? FPDFBitmap_BGRA_Premul - : FPDFBitmap_BGRA; - } else { - format = FPDFBitmap_BGRx; +#ifdef PDF_ENABLE_SKIA + std::vector straight_alpha_input; + if (bitmap_attributes.has_alpha && buffer_has_premultiplied_alpha) { + straight_alpha_input = BitmapSaver::ConvertToStraightAlpha( + input, bitmap_attributes.width, bitmap_attributes.height, + bitmap_attributes.stride); + input = straight_alpha_input; } - std::vector png_encoding = - EncodePng(input, bitmap_attributes.width, bitmap_attributes.height, - bitmap_attributes.stride, format); +#endif + + std::vector png_encoding = png_codec::EncodeBGRA( + input, bitmap_attributes.width, bitmap_attributes.height, + bitmap_attributes.stride, + /*discard_transparency=*/!bitmap_attributes.has_alpha); if (png_encoding.empty()) { fprintf(stderr, "Failed to convert bitmap to PNG\n"); return ""; @@ -714,28 +719,6 @@ void WriteBufferToFile(const void* buf, } } -std::vector EncodeBitmapToPng(ScopedFPDFBitmap bitmap) { - std::vector png_encoding; - int format = FPDFBitmap_GetFormat(bitmap.get()); - if (format == FPDFBitmap_Unknown) { - return png_encoding; - } - - int width = FPDFBitmap_GetWidth(bitmap.get()); - int height = FPDFBitmap_GetHeight(bitmap.get()); - int stride = FPDFBitmap_GetStride(bitmap.get()); - if (!CheckDimensions(stride, width, height)) { - return png_encoding; - } - - auto input = UNSAFE_TODO(pdfium::span( - static_cast(FPDFBitmap_GetBuffer(bitmap.get())), - static_cast(stride) * height)); - - png_encoding = EncodePng(input, width, height, stride, format); - return png_encoding; -} - void WriteAttachments(FPDF_DOCUMENT doc, const std::string& name) { for (int i = 0; i < FPDFDoc_GetAttachmentCount(doc); ++i) { FPDF_ATTACHMENT attachment = FPDFDoc_GetAttachment(doc, i); @@ -811,7 +794,8 @@ void WriteImages(FPDF_PAGE page, const char* pdf_name, int page_num) { continue; } - std::vector png_encoding = EncodeBitmapToPng(std::move(bitmap)); + std::vector png_encoding = + BitmapSaver::EncodeBitmapToPng(bitmap.get()); if (png_encoding.empty()) { fprintf(stderr, "Failed to convert image object #%d, on page #%d to png.\n", @@ -847,7 +831,8 @@ void WriteRenderedImages(FPDF_DOCUMENT doc, continue; } - std::vector png_encoding = EncodeBitmapToPng(std::move(bitmap)); + std::vector png_encoding = + BitmapSaver::EncodeBitmapToPng(bitmap.get()); if (png_encoding.empty()) { fprintf(stderr, "Failed to convert image object #%d, on page #%d to png.\n", @@ -935,7 +920,7 @@ void WriteThumbnail(FPDF_PAGE page, const char* pdf_name, int page_num) { } std::vector png_encoding = - EncodeBitmapToPng(std::move(thumb_bitmap)); + BitmapSaver::EncodeBitmapToPng(thumb_bitmap.get()); if (png_encoding.empty()) { fprintf(stderr, "Failed to convert thumbnail of page #%d to png.\n", page_num + 1); diff --git a/testing/helpers/write.h b/testing/pdfium_test/write.h similarity index 95% rename from testing/helpers/write.h rename to testing/pdfium_test/write.h index 190124539a..4e048d36ee 100644 --- a/testing/helpers/write.h +++ b/testing/pdfium_test/write.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 TESTING_HELPERS_WRITE_H_ -#define TESTING_HELPERS_WRITE_H_ +#ifndef TESTING_PDFIUM_TEST_WRITE_H_ +#define TESTING_PDFIUM_TEST_WRITE_H_ #include #include @@ -75,4 +75,4 @@ void WriteRawThumbnailStream(FPDF_PAGE page, int page_num); void WriteThumbnail(FPDF_PAGE page, const char* pdf_name, int page_num); -#endif // TESTING_HELPERS_WRITE_H_ +#endif // TESTING_PDFIUM_TEST_WRITE_H_ diff --git a/testing/png_codec/BUILD.gn b/testing/png_codec/BUILD.gn new file mode 100644 index 0000000000..5b1d7940ea --- /dev/null +++ b/testing/png_codec/BUILD.gn @@ -0,0 +1,56 @@ +# 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. + +import("../../pdfium.gni") +import("../../testing/test.gni") + +if (pdf_enable_rust_png) { + import("//build/rust/rust_static_library.gni") + + rust_static_library("png_codec_rust_ffi") { + testonly = true + crate_name = "png_codec_rust_ffi" + sources = [ "png_codec_rust_ffi.rs" ] + crate_root = "png_codec_rust_ffi.rs" + cxx_bindings = [ "png_codec_rust_ffi.rs" ] + allow_unsafe = true + deps = [ + "//build/rust:cxx_cppdeps", + "//third_party/rust/png/v0_18:lib", + ] + } +} + +source_set("png_codec") { + testonly = true + sources = [ "png_codec.h" ] + deps = [] + + if (pdf_enable_rust_png) { + sources += [ "png_codec_rust.cpp" ] + include_dirs = [ "$target_gen_dir/../.." ] + deps += [ + ":png_codec_rust_ffi", + "//build/rust:cxx_cppdeps", + ] + } else { + sources += [ "png_codec_libpng.cpp" ] + deps += [ + "../../third_party:png", + "../../third_party:zlib", + ] + } + + configs += [ + "../../:pdfium_strict_config", + "../../:pdfium_noshorten_config", + ] + public_deps = [ "../../core/fxcrt" ] +} + +pdfium_unittest_source_set("unittests") { + sources = [ "png_codec_unittest.cpp" ] + deps = [ ":png_codec" ] + pdfium_root_dir = "../../" +} diff --git a/testing/image_diff/DEPS b/testing/png_codec/DEPS similarity index 71% rename from testing/image_diff/DEPS rename to testing/png_codec/DEPS index fcac201c2d..17b552adce 100644 --- a/testing/image_diff/DEPS +++ b/testing/png_codec/DEPS @@ -1,5 +1,5 @@ include_rules = [ '+third_party/libpng', + '+third_party/rust/png', '+third_party/zlib', ] - diff --git a/testing/png_codec/png_codec.h b/testing/png_codec/png_codec.h new file mode 100644 index 0000000000..6714978afe --- /dev/null +++ b/testing/png_codec/png_codec.h @@ -0,0 +1,50 @@ +// Copyright 2013 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef TESTING_PNG_CODEC_PNG_CODEC_H_ +#define TESTING_PNG_CODEC_PNG_CODEC_H_ + +#include + +#include + +#include "core/fxcrt/span.h" + +namespace png_codec { + +// Decode a PNG into an RGBA pixel array, or BGRA pixel array if +// |reverse_byte_order| is set to true. +std::vector Decode(pdfium::span input, + bool reverse_byte_order, + int* width, + int* height); + +// Encode a BGR pixel array into a PNG. +std::vector EncodeBGR(pdfium::span input, + int width, + int height, + int row_byte_width); + +// Encode an RGBA pixel array into a PNG. +std::vector EncodeRGBA(pdfium::span input, + int width, + int height, + int row_byte_width); + +// Encode an BGRA pixel array into a PNG. +std::vector EncodeBGRA(pdfium::span input, + int width, + int height, + int row_byte_width, + bool discard_transparency); + +// Encode a grayscale pixel array into a PNG. +std::vector EncodeGray(pdfium::span input, + int width, + int height, + int row_byte_width); + +} // namespace png_codec + +#endif // TESTING_PNG_CODEC_PNG_CODEC_H_ diff --git a/testing/image_diff/image_diff_png_libpng.cpp b/testing/png_codec/png_codec_libpng.cpp similarity index 94% rename from testing/image_diff/image_diff_png_libpng.cpp rename to testing/png_codec/png_codec_libpng.cpp index f77bc06f8f..f6ed1d3707 100644 --- a/testing/image_diff/image_diff_png_libpng.cpp +++ b/testing/png_codec/png_codec_libpng.cpp @@ -9,8 +9,6 @@ // This is a duplicate of ui/gfx/codec/png_codec.cc, after removing code related // to Skia, that we can use when running layout tests with minimal dependencies. -#include "testing/image_diff/image_diff_png.h" - #include #include @@ -21,6 +19,7 @@ #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/fx_memcpy_wrappers.h" #include "core/fxcrt/notreached.h" +#include "testing/png_codec/png_codec.h" #ifdef USE_SYSTEM_ZLIB #include @@ -38,7 +37,7 @@ #error "If Rust PNG is enabled, then `libpng` should not be used." #endif -namespace image_diff_png { +namespace png_codec { namespace { @@ -680,45 +679,45 @@ std::vector Encode(pdfium::span input, } // namespace -std::vector DecodePNG(pdfium::span input, - bool reverse_byte_order, - int* width, - int* height) { +std::vector Decode(pdfium::span input, + bool reverse_byte_order, + int* width, + int* height) { ColorFormat format = reverse_byte_order ? FORMAT_BGRA : FORMAT_RGBA; return Decode(input, format, width, height); } -std::vector EncodeBGRPNG(pdfium::span input, - int width, - int height, - int row_byte_width) { +std::vector EncodeBGR(pdfium::span input, + int width, + int height, + int row_byte_width) { return Encode(input, FORMAT_BGR, width, height, row_byte_width, false, std::vector()); } -std::vector EncodeRGBAPNG(pdfium::span input, - int width, - int height, - int row_byte_width) { +std::vector EncodeRGBA(pdfium::span input, + int width, + int height, + int row_byte_width) { return Encode(input, FORMAT_RGBA, width, height, row_byte_width, false, std::vector()); } -std::vector EncodeBGRAPNG(pdfium::span input, - int width, - int height, - int row_byte_width, - bool discard_transparency) { +std::vector EncodeBGRA(pdfium::span input, + int width, + int height, + int row_byte_width, + bool discard_transparency) { return Encode(input, FORMAT_BGRA, width, height, row_byte_width, discard_transparency, std::vector()); } -std::vector EncodeGrayPNG(pdfium::span input, - int width, - int height, - int row_byte_width) { +std::vector EncodeGray(pdfium::span input, + int width, + int height, + int row_byte_width) { return Encode(input, FORMAT_GRAY, width, height, row_byte_width, false, std::vector()); } -} // namespace image_diff_png +} // namespace png_codec diff --git a/testing/png_codec/png_codec_rust.cpp b/testing/png_codec/png_codec_rust.cpp new file mode 100644 index 0000000000..8d5cf1f12d --- /dev/null +++ b/testing/png_codec/png_codec_rust.cpp @@ -0,0 +1,89 @@ +// 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 "testing/png_codec/png_codec.h" + +#include + +#include + +#include "core/fxcrt/numerics/safe_conversions.h" +#include "core/fxcrt/span.h" +#include "core/fxcrt/span_util.h" +#include "testing/png_codec/png_codec_rust_ffi.rs.h" + +namespace png_codec { + +std::vector Decode(pdfium::span input, + bool reverse_byte_order, + int* width, + int* height) { + rust::Slice src_slice(input); + int32_t w = 0; + int32_t h = 0; + rust::Vec decoded = + rust_png::decode_png(src_slice, reverse_byte_order, w, h); + if (w <= 0 || h <= 0 || decoded.empty()) { + return {}; + } + *width = w; + *height = h; + return {decoded.begin(), decoded.end()}; +} + +std::vector EncodeBGR(pdfium::span input, + int width, + int height, + int row_byte_width) { + rust::Slice src_slice(input); + rust::Vec encoded = rust_png::encode_bgr( + src_slice, width, height, pdfium::checked_cast(row_byte_width)); + if (encoded.empty()) { + return {}; + } + return {encoded.begin(), encoded.end()}; +} + +std::vector EncodeRGBA(pdfium::span input, + int width, + int height, + int row_byte_width) { + rust::Slice src_slice(input); + rust::Vec encoded = rust_png::encode_rgba( + src_slice, width, height, pdfium::checked_cast(row_byte_width)); + if (encoded.empty()) { + return {}; + } + return {encoded.begin(), encoded.end()}; +} + +std::vector EncodeBGRA(pdfium::span input, + int width, + int height, + int row_byte_width, + bool discard_transparency) { + rust::Slice src_slice(input); + rust::Vec encoded = rust_png::encode_bgra( + src_slice, width, height, pdfium::checked_cast(row_byte_width), + discard_transparency); + if (encoded.empty()) { + return {}; + } + return {encoded.begin(), encoded.end()}; +} + +std::vector EncodeGray(pdfium::span input, + int width, + int height, + int row_byte_width) { + rust::Slice src_slice(input); + rust::Vec encoded = rust_png::encode_gray( + src_slice, width, height, pdfium::checked_cast(row_byte_width)); + if (encoded.empty()) { + return {}; + } + return {encoded.begin(), encoded.end()}; +} + +} // namespace png_codec diff --git a/testing/png_codec/png_codec_rust_ffi.rs b/testing/png_codec/png_codec_rust_ffi.rs new file mode 100644 index 0000000000..0dfa81d16f --- /dev/null +++ b/testing/png_codec/png_codec_rust_ffi.rs @@ -0,0 +1,322 @@ +// 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 std::io::Cursor; + +#[cxx::bridge(namespace = "png_codec::rust_png")] +mod ffi { + extern "Rust" { + fn decode_png( + src: &[u8], + reverse_byte_order: bool, + width: &mut i32, + height: &mut i32, + ) -> Vec; + + fn encode_bgr(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec; + + fn encode_rgba(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec; + + fn encode_bgra( + input: &[u8], + width: i32, + height: i32, + row_byte_width: usize, + discard_transparency: bool, + ) -> Vec; + + fn encode_gray(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec; + } +} + +fn decode_png( + src: &[u8], + reverse_byte_order: bool, + width_out: &mut i32, + height_out: &mut i32, +) -> Vec { + *width_out = 0; + *height_out = 0; + + let cursor = Cursor::new(src); + let mut decoder = png::Decoder::new(cursor); + decoder.set_transformations(png::Transformations::EXPAND | png::Transformations::STRIP_16); + let mut reader = match decoder.read_info() { + Ok(r) => r, + Err(_) => return Vec::new(), + }; + let buf_size = match reader.output_buffer_size() { + Some(s) => s, + None => return Vec::new(), + }; + let mut buf = vec![0; buf_size]; + let output_info = match reader.next_frame(&mut buf) { + Ok(info) => info, + Err(_) => return Vec::new(), + }; + let width = output_info.width as usize; + let height = output_info.height as usize; + if width == 0 || height == 0 { + return Vec::new(); + } + let line_size = output_info.line_size; + if line_size < width { + return Vec::new(); + } + let bytes_per_pixel = line_size / width; + if bytes_per_pixel == 0 || bytes_per_pixel > 4 { + return Vec::new(); + } + if let Some(total_src_bytes) = line_size.checked_mul(height) { + if total_src_bytes > buf.len() { + return Vec::new(); + } + } else { + return Vec::new(); + } + + let out_size = match width.checked_mul(height).and_then(|wh| wh.checked_mul(4)) { + Some(s) => s, + None => return Vec::new(), + }; + let mut out = vec![0u8; out_size]; + + // Standard default file gamma for sRGB (1.0 / 2.2). + const DEFAULT_FILE_GAMMA: f64 = 0.45455; + let file_gamma: f64 = if reader.info().srgb.is_some() { + DEFAULT_FILE_GAMMA + } else if let Some(gama) = reader.info().gama_chunk { + let val = gama.into_value() as f64; + if val > 0.0 { + val + } else { + DEFAULT_FILE_GAMMA + } + } else { + DEFAULT_FILE_GAMMA + }; + + const DISPLAY_GAMMA: f64 = 2.2; + let mut gamma_lut = [0u8; 256]; + let exponent = 1.0 / (file_gamma * DISPLAY_GAMMA); + if (exponent - 1.0).abs() < 1e-4 { + for (i, item) in gamma_lut.iter_mut().enumerate() { + *item = i as u8; + } + } else { + for (i, item) in gamma_lut.iter_mut().enumerate() { + let v = (i as f64) / 255.0; + *item = (v.powf(exponent) * 255.0).round().clamp(0.0, 255.0) as u8; + } + } + + for y in 0..height { + let src_start = y * line_size; + let src_row = &buf[src_start..src_start + line_size]; + let dst_start = y * width * 4; + let dst_slice = &mut out[dst_start..dst_start + width * 4]; + + for x in 0..width { + let (r, g, b, a) = match bytes_per_pixel { + 1 => { + let gray = gamma_lut[src_row[x] as usize]; + (gray, gray, gray, 255) + } + 2 => { + let gray = gamma_lut[src_row[x * 2] as usize]; + let alpha = src_row[x * 2 + 1]; + (gray, gray, gray, alpha) + } + 3 => { + let red = gamma_lut[src_row[x * 3] as usize]; + let green = gamma_lut[src_row[x * 3 + 1] as usize]; + let blue = gamma_lut[src_row[x * 3 + 2] as usize]; + (red, green, blue, 255) + } + 4 => { + let red = gamma_lut[src_row[x * 4] as usize]; + let green = gamma_lut[src_row[x * 4 + 1] as usize]; + let blue = gamma_lut[src_row[x * 4 + 2] as usize]; + let alpha = src_row[x * 4 + 3]; + (red, green, blue, alpha) + } + _ => return Vec::new(), + }; + + if reverse_byte_order { + dst_slice[x * 4] = b; + dst_slice[x * 4 + 1] = g; + dst_slice[x * 4 + 2] = r; + dst_slice[x * 4 + 3] = a; + } else { + dst_slice[x * 4] = r; + dst_slice[x * 4 + 1] = g; + dst_slice[x * 4 + 2] = b; + dst_slice[x * 4 + 3] = a; + } + } + } + + *width_out = width as i32; + *height_out = height as i32; + out +} + +fn write_png(data: &[u8], width: u32, height: u32, color_type: png::ColorType) -> Vec { + let mut out = Vec::new(); + { + let mut encoder = png::Encoder::new(&mut out, width, height); + encoder.set_color(color_type); + encoder.set_depth(png::BitDepth::Eight); + let mut writer = match encoder.write_header() { + Ok(w) => w, + Err(_) => return Vec::new(), + }; + if writer.write_image_data(data).is_err() { + return Vec::new(); + } + } + out +} + +fn encode_bgr(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec { + if width <= 0 || height <= 0 { + return Vec::new(); + } + let w = width as usize; + let h = height as usize; + if row_byte_width < w * 3 { + return Vec::new(); + } + if let Some(total_input) = row_byte_width.checked_mul(h) { + if total_input > input.len() { + return Vec::new(); + } + } else { + return Vec::new(); + } + + let mut rgb = vec![0u8; w * h * 3]; + for y in 0..h { + let src_row = &input[y * row_byte_width..y * row_byte_width + w * 3]; + let dst_row = &mut rgb[y * w * 3..(y + 1) * w * 3]; + for x in 0..w { + dst_row[x * 3] = src_row[x * 3 + 2]; // R + dst_row[x * 3 + 1] = src_row[x * 3 + 1]; // G + dst_row[x * 3 + 2] = src_row[x * 3]; // B + } + } + write_png(&rgb, width as u32, height as u32, png::ColorType::Rgb) +} + +fn encode_rgba(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec { + if width <= 0 || height <= 0 { + return Vec::new(); + } + let w = width as usize; + let h = height as usize; + if row_byte_width < w * 4 { + return Vec::new(); + } + if let Some(total_input) = row_byte_width.checked_mul(h) { + if total_input > input.len() { + return Vec::new(); + } + } else { + return Vec::new(); + } + + if row_byte_width == w * 4 { + return write_png(&input[..w * h * 4], width as u32, height as u32, png::ColorType::Rgba); + } + + let mut rgba = vec![0u8; w * h * 4]; + for y in 0..h { + let src_row = &input[y * row_byte_width..y * row_byte_width + w * 4]; + let dst_row = &mut rgba[y * w * 4..(y + 1) * w * 4]; + dst_row.copy_from_slice(src_row); + } + write_png(&rgba, width as u32, height as u32, png::ColorType::Rgba) +} + +fn encode_bgra( + input: &[u8], + width: i32, + height: i32, + row_byte_width: usize, + discard_transparency: bool, +) -> Vec { + if width <= 0 || height <= 0 { + return Vec::new(); + } + let w = width as usize; + let h = height as usize; + if row_byte_width < w * 4 { + return Vec::new(); + } + if let Some(total_input) = row_byte_width.checked_mul(h) { + if total_input > input.len() { + return Vec::new(); + } + } else { + return Vec::new(); + } + + if discard_transparency { + let mut rgb = vec![0u8; w * h * 3]; + for y in 0..h { + let src_row = &input[y * row_byte_width..y * row_byte_width + w * 4]; + let dst_row = &mut rgb[y * w * 3..(y + 1) * w * 3]; + for x in 0..w { + dst_row[x * 3] = src_row[x * 4 + 2]; // R + dst_row[x * 3 + 1] = src_row[x * 4 + 1]; // G + dst_row[x * 3 + 2] = src_row[x * 4]; // B + } + } + write_png(&rgb, width as u32, height as u32, png::ColorType::Rgb) + } else { + let mut rgba = vec![0u8; w * h * 4]; + for y in 0..h { + let src_row = &input[y * row_byte_width..y * row_byte_width + w * 4]; + let dst_row = &mut rgba[y * w * 4..(y + 1) * w * 4]; + for x in 0..w { + dst_row[x * 4] = src_row[x * 4 + 2]; // R + dst_row[x * 4 + 1] = src_row[x * 4 + 1]; // G + dst_row[x * 4 + 2] = src_row[x * 4]; // B + dst_row[x * 4 + 3] = src_row[x * 4 + 3]; // A + } + } + write_png(&rgba, width as u32, height as u32, png::ColorType::Rgba) + } +} + +fn encode_gray(input: &[u8], width: i32, height: i32, row_byte_width: usize) -> Vec { + if width <= 0 || height <= 0 { + return Vec::new(); + } + let w = width as usize; + let h = height as usize; + if row_byte_width < w { + return Vec::new(); + } + if let Some(total_input) = row_byte_width.checked_mul(h) { + if total_input > input.len() { + return Vec::new(); + } + } else { + return Vec::new(); + } + + if row_byte_width == w { + return write_png(&input[..w * h], width as u32, height as u32, png::ColorType::Grayscale); + } + + let mut gray = vec![0u8; w * h]; + for y in 0..h { + let src_row = &input[y * row_byte_width..y * row_byte_width + w]; + let dst_row = &mut gray[y * w..(y + 1) * w]; + dst_row.copy_from_slice(src_row); + } + write_png(&gray, width as u32, height as u32, png::ColorType::Grayscale) +} diff --git a/testing/image_diff/image_diff_png_unittests.cpp b/testing/png_codec/png_codec_unittest.cpp similarity index 64% rename from testing/image_diff/image_diff_png_unittests.cpp rename to testing/png_codec/png_codec_unittest.cpp index b92c66b681..ee4c1edd44 100644 --- a/testing/image_diff/image_diff_png_unittests.cpp +++ b/testing/png_codec/png_codec_unittest.cpp @@ -2,15 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "testing/image_diff/image_diff_png.h" - #include #include #include "testing/gtest/include/gtest/gtest.h" +#include "testing/png_codec/png_codec.h" -namespace image_diff_png { +namespace png_codec { namespace { @@ -24,7 +23,7 @@ std::vector Decode1x1PNGToRGBA(pdfium::span input) { int output_width = -1; int output_height = -1; std::vector rgba_output = - DecodePNG(input, kReverseByteOrder, &output_width, &output_height); + Decode(input, kReverseByteOrder, &output_width, &output_height); EXPECT_EQ(output_height, 1); EXPECT_EQ(output_width, 1); return rgba_output; @@ -32,13 +31,13 @@ std::vector Decode1x1PNGToRGBA(pdfium::span input) { } // namespace -TEST(ImageDiffPng, EncodeBGRAPNGAndDiscardTransparency) { +TEST(PngCodec, EncodeBGRAAndDiscardTransparency) { const std::vector bgrx_input = {1, 2, 3, /*ignored_alpha=*/4}; - std::vector png = EncodeBGRAPNG(bgrx_input, - /*width=*/1, - /*height=*/1, - /*row_byte_width=*/4, - /*discard_transparency=*/true); + std::vector png = EncodeBGRA(bgrx_input, + /*width=*/1, + /*height=*/1, + /*row_byte_width=*/4, + /*discard_transparency=*/true); ASSERT_FALSE(png.empty()); std::vector rgba_output = Decode1x1PNGToRGBA(png); @@ -54,13 +53,13 @@ TEST(ImageDiffPng, EncodeBGRAPNGAndDiscardTransparency) { EXPECT_EQ(rgba_output[3], 255); } -TEST(ImageDiffPng, EncodeBGRAPNGAndPreserveTransparency) { +TEST(PngCodec, EncodeBGRAAndPreserveTransparency) { const std::vector bgra_input = {1, 2, 3, 4}; - std::vector png = EncodeBGRAPNG(bgra_input, - /*width=*/1, - /*height=*/1, - /*row_byte_width=*/4, - /*discard_transparency=*/false); + std::vector png = EncodeBGRA(bgra_input, + /*width=*/1, + /*height=*/1, + /*row_byte_width=*/4, + /*discard_transparency=*/false); ASSERT_FALSE(png.empty()); std::vector rgba_output = Decode1x1PNGToRGBA(png); @@ -73,12 +72,12 @@ TEST(ImageDiffPng, EncodeBGRAPNGAndPreserveTransparency) { EXPECT_EQ(rgba_output[3], bgra_input[3]); } -TEST(ImageDiffPng, EncodeBGRPNG) { +TEST(PngCodec, EncodeBGR) { std::vector bgr_input = {1, 2, 3}; - std::vector png = EncodeBGRPNG(bgr_input, - /*width=*/1, - /*height=*/1, - /*row_byte_width=*/3); + std::vector png = EncodeBGR(bgr_input, + /*width=*/1, + /*height=*/1, + /*row_byte_width=*/3); ASSERT_FALSE(png.empty()); std::vector rgba_output = Decode1x1PNGToRGBA(png); @@ -93,12 +92,12 @@ TEST(ImageDiffPng, EncodeBGRPNG) { EXPECT_EQ(rgba_output[3], 255); } -TEST(ImageDiffPng, EncodeRGBAPNG) { +TEST(PngCodec, EncodeRGBA) { std::vector rgba_input = {1, 2, 3, 4}; - std::vector png = EncodeRGBAPNG(rgba_input, - /*width=*/1, - /*height=*/1, - /*row_byte_width=*/4); + std::vector png = EncodeRGBA(rgba_input, + /*width=*/1, + /*height=*/1, + /*row_byte_width=*/4); ASSERT_FALSE(png.empty()); std::vector rgba_output = Decode1x1PNGToRGBA(png); @@ -111,12 +110,12 @@ TEST(ImageDiffPng, EncodeRGBAPNG) { EXPECT_EQ(rgba_output[3], rgba_input[3]); } -TEST(ImageDiffPng, EncodeGrayPNG) { +TEST(PngCodec, EncodeGray) { std::vector grayscale_input = {123}; - std::vector png = EncodeGrayPNG(grayscale_input, - /*width=*/1, - /*height=*/1, - /*row_byte_width=*/1); + std::vector png = EncodeGray(grayscale_input, + /*width=*/1, + /*height=*/1, + /*row_byte_width=*/1); ASSERT_FALSE(png.empty()); std::vector rgba_output = Decode1x1PNGToRGBA(png); @@ -129,4 +128,4 @@ TEST(ImageDiffPng, EncodeGrayPNG) { EXPECT_EQ(rgba_output[3], 255); // A=opaque } -} // namespace image_diff_png +} // namespace png_codec diff --git a/testing/resources/bookmarks_style.in b/testing/resources/bookmarks_style.in new file mode 100644 index 0000000000..1ba231a454 --- /dev/null +++ b/testing/resources/bookmarks_style.in @@ -0,0 +1,130 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R + /Outlines 8 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /Count 2 + /Kids [ + 3 0 R + 4 0 R + ] +>> +endobj +% Page number 0. +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Resources << + /Font << + /F1 5 0 R + >> + >> + /Contents [6 0 R] + /MediaBox [0 0 612 792] +>> +endobj +% Page number 1. +{{object 4 0}} << + /Type /Page + /Parent 2 0 R + /Resources << + /Font << + /F1 5 0 R + >> + >> + /Contents [7 0 R] + /MediaBox [0 0 612 792] +>> +endobj +% Font resource. +{{object 5 0}} << + /Type /Font + /Subtype /Type1 + /BaseFont /Arial +>> +endobj +% Content for page 0. +{{object 6 0}} << + {{streamlen}} +>> +stream +BT +/F1 20 Tf +100 600 TD +(Page1) Tj +ET +endstream +endobj +{{object 7 0}} << + {{streamlen}} +>> +stream +BT +/F1 20 Tf +100 600 TD +(Page2) Tj +ET +endstream +endobj +{{object 8 0}} << + /Type /Outlines + /Count 5 + /First 9 0 R + /Last 13 0 R +>> +endobj +{{object 9 0}} << + /Title (No Style) + /Parent 8 0 R + /Next 10 0 R + /Dest (foo) +>> +endobj +{{object 10 0}} << + /Title (Style Is Not An Int) + /Parent 8 0 R + /First 11 0 R + /Last 11 0 R + /Prev 9 0 R + /Next 12 0 R + /Count 1 + /F (Bold) + /A << + /Type /Action + /S /URI + /URI (https://theplay.test) + >> +>> +endobj +{{object 11 0}} << + /Title (Style Is 15) + /Parent 10 0 R + /Dest [3 0 R /XYZ 100 200 0] + /F 15 % 1111 in binary. +>> +endobj +{{object 12 0}} << + /Title (Style is Normal) + /Prev 10 0 R + /Parent 8 0 R + /Next 13 0 R + /Dest (bar) + /F 0 +>> +endobj +{{object 13 0}} << + /Title (Style is Bold and Italic) + /Parent 8 0 R + /Dest (bar) + /Prev 12 0 R + /F 3 +>> +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/bookmarks_style.pdf b/testing/resources/bookmarks_style.pdf new file mode 100644 index 0000000000..d840c4a9f6 Binary files /dev/null and b/testing/resources/bookmarks_style.pdf differ diff --git a/testing/resources/bug_554790604.in b/testing/resources/bug_554790604.in new file mode 100644 index 0000000000..839f3245d0 --- /dev/null +++ b/testing/resources/bug_554790604.in @@ -0,0 +1,80 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /Pages 2 0 R +>> +endobj +{{object 2 0}} << + /Type /Pages + /MediaBox [0 0 300 120] + /Count 1 + /Kids [3 0 R] +>> +endobj +{{object 3 0}} << + /Type /Page + /Parent 2 0 R + /Resources << + /Font << + /F1 4 0 R + >> + >> + /Contents 5 0 R +>> +endobj +{{object 4 0}} << + /Type /Font + /Subtype /Type1 + /BaseFont /Helvetica + /ToUnicode 6 0 R +>> +endobj +{{object 5 0}} << + {{streamlen}} +>> +stream +BT +20 50 Td +/F1 14 Tf +(\101\102\103\125\233\130\301\302\160\161) Tj +ET +endstream +endobj +{{object 6 0}} << + {{streamlen}} +>> +stream +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CMapName /Repro def +/CMapType 1 def +1 begincodespacerange +<00> +endcodespacerange +1 beginbfrange +<00> <0000> +endbfrange +8 beginbfchar +<41> <00F3> +<42> <00BA> +<43> <20AC> +<55> <00ED> +<9B> <00BA> +<58> <0058> + <0041> + <0042> +endbfchar +1 beginbfrange +<70> <71> <00E9> +endbfrange +endcmap +CMapName currentdict /CMap defineresource pop +end +end +endstream +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/bug_554790604.pdf b/testing/resources/bug_554790604.pdf new file mode 100644 index 0000000000..4549603850 Binary files /dev/null and b/testing/resources/bug_554790604.pdf differ diff --git a/testing/resources/pixel/bug_1966_expected_gdi_skia.pdf.0.png b/testing/resources/pixel/bug_1966_expected_skia_gdi.pdf.0.png similarity index 100% rename from testing/resources/pixel/bug_1966_expected_gdi_skia.pdf.0.png rename to testing/resources/pixel/bug_1966_expected_skia_gdi.pdf.0.png diff --git a/testing/resources/pixel/bug_2106_expected_gdi_skia.pdf.0.png b/testing/resources/pixel/bug_2106_expected_skia_gdi.pdf.0.png similarity index 100% rename from testing/resources/pixel/bug_2106_expected_gdi_skia.pdf.0.png rename to testing/resources/pixel/bug_2106_expected_skia_gdi.pdf.0.png diff --git a/testing/resources/pixel/bug_346598551_expected_gdi_skia.pdf.0.png b/testing/resources/pixel/bug_346598551_expected_skia_gdi.pdf.0.png similarity index 100% rename from testing/resources/pixel/bug_346598551_expected_gdi_skia.pdf.0.png rename to testing/resources/pixel/bug_346598551_expected_skia_gdi.pdf.0.png diff --git a/testing/resources/pixel/bug_440028542_expected_gdi_skia.pdf.0.png b/testing/resources/pixel/bug_440028542_expected_skia_gdi.pdf.0.png similarity index 100% rename from testing/resources/pixel/bug_440028542_expected_gdi_skia.pdf.0.png rename to testing/resources/pixel/bug_440028542_expected_skia_gdi.pdf.0.png diff --git a/testing/resources/pixel/form_textfield_selected_ltr.evt b/testing/resources/pixel/form_textfield_selected_ltr.evt new file mode 100644 index 0000000000..aca67c23c8 --- /dev/null +++ b/testing/resources/pixel/form_textfield_selected_ltr.evt @@ -0,0 +1,24 @@ +# This test explicitly tests the interactive UI rendering (CPWL_EditImpl). +# It simulates double-clicking an LTR text field during live editing to ensure +# the selection highlight is rendered with the correct dimensions. +# Must leave the text field focused at the end of the test. No clicking +# outside. If the field loses focus, it will fall back to generating a static +# Appearance Stream. +mousemove,100,50 +mousedown,left,100,50 +mouseup,left,100,50 +# Type Word 1 (a, b, c) +charcode,97 +charcode,98 +charcode,99 +# Type ... +charcode,46 +charcode,46 +charcode,46 +# Type Space +charcode,32 +# Type Word 2 (d, e, f) +charcode,100 +charcode,101 +charcode,102 +mousedoubleclick,left,100,50 diff --git a/testing/resources/pixel/form_textfield_selected_ltr.in b/testing/resources/pixel/form_textfield_selected_ltr.in new file mode 100644 index 0000000000..73e77e1f9f --- /dev/null +++ b/testing/resources/pixel/form_textfield_selected_ltr.in @@ -0,0 +1,57 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /AcroForm << + /Fields [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 + /Annots [4 0 R] + /Parent 2 0 R + /MediaBox [0 0 200 100] +>> +endobj +{{object 4 0}} << + /Type /Annot + /Subtype /Widget + /FT /Tx + /BS << + /S /S + /W 1 + >> + /DA (/Arial 12 Tf 0 0 0 rg) + /DR << + /Font << + /Arial 5 0 R + >> + >> + /DV () + /F 4 + /Ff 0 + /MK << + /BG [1 1 1] + >> + /P 3 0 R + /T (Default) + /Rect [50 40 150 70] +>> +endobj +{{object 5 0}} << + /Type /Font + /Subtype /TrueType + /BaseFont /Arial +>> +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/pixel/form_textfield_selected_ltr_expected.pdf.0.png b/testing/resources/pixel/form_textfield_selected_ltr_expected.pdf.0.png new file mode 100644 index 0000000000..a7ee22c7a8 Binary files /dev/null and b/testing/resources/pixel/form_textfield_selected_ltr_expected.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_selected_ltr_expected_mac.pdf.0.png b/testing/resources/pixel/form_textfield_selected_ltr_expected_mac.pdf.0.png new file mode 100644 index 0000000000..f422701f24 Binary files /dev/null and b/testing/resources/pixel/form_textfield_selected_ltr_expected_mac.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 new file mode 100644 index 0000000000..06580a48d6 Binary files /dev/null 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 new file mode 100644 index 0000000000..0985a6a833 Binary files /dev/null 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 new file mode 100644 index 0000000000..63c474dc86 Binary files /dev/null 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.evt b/testing/resources/pixel/form_textfield_selected_rtl.evt new file mode 100644 index 0000000000..3519b574e8 --- /dev/null +++ b/testing/resources/pixel/form_textfield_selected_rtl.evt @@ -0,0 +1,25 @@ +# This test explicitly tests the interactive UI rendering (CPWL_EditImpl). +# It simulates double-clicking an RTL text field during live editing to ensure +# the selection highlight is rendered with the correct dimensions. +# Must leave the text field focused at the end of the test. No clicking +# outside. If the field loses focus, it will fall back to generating a static +# Appearance Stream. +mousemove,100,50 +mousedown,left,100,50 +mouseup,left,100,50 +# Type Word 1 (Bet, Het, Resh) +charcode,1489 +charcode,1495 +charcode,1512 +# Type ... +charcode,46 +charcode,46 +charcode,46 +# Type Space +charcode,32 +# Type Word 2 (Alef, Gimel, Tav) +charcode,1488 +charcode,1490 +charcode,1514 +# Select the text +mousedoubleclick,left,100,50 diff --git a/testing/resources/pixel/form_textfield_selected_rtl.in b/testing/resources/pixel/form_textfield_selected_rtl.in new file mode 100644 index 0000000000..73e77e1f9f --- /dev/null +++ b/testing/resources/pixel/form_textfield_selected_rtl.in @@ -0,0 +1,57 @@ +{{header}} +{{object 1 0}} << + /Type /Catalog + /AcroForm << + /Fields [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 + /Annots [4 0 R] + /Parent 2 0 R + /MediaBox [0 0 200 100] +>> +endobj +{{object 4 0}} << + /Type /Annot + /Subtype /Widget + /FT /Tx + /BS << + /S /S + /W 1 + >> + /DA (/Arial 12 Tf 0 0 0 rg) + /DR << + /Font << + /Arial 5 0 R + >> + >> + /DV () + /F 4 + /Ff 0 + /MK << + /BG [1 1 1] + >> + /P 3 0 R + /T (Default) + /Rect [50 40 150 70] +>> +endobj +{{object 5 0}} << + /Type /Font + /Subtype /TrueType + /BaseFont /Arial +>> +endobj +{{xref}} +{{trailer}} +{{startxref}} +%%EOF diff --git a/testing/resources/pixel/form_textfield_selected_rtl_expected.pdf.0.png b/testing/resources/pixel/form_textfield_selected_rtl_expected.pdf.0.png new file mode 100644 index 0000000000..d35359829f Binary files /dev/null and b/testing/resources/pixel/form_textfield_selected_rtl_expected.pdf.0.png differ diff --git a/testing/resources/pixel/form_textfield_selected_rtl_expected_mac.pdf.0.png b/testing/resources/pixel/form_textfield_selected_rtl_expected_mac.pdf.0.png new file mode 100644 index 0000000000..9d51514b1d Binary files /dev/null and b/testing/resources/pixel/form_textfield_selected_rtl_expected_mac.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 new file mode 100644 index 0000000000..93b18d2a28 Binary files /dev/null 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 new file mode 100644 index 0000000000..351f50eee4 Binary files /dev/null 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 new file mode 100644 index 0000000000..67c3f7ec92 Binary files /dev/null and b/testing/resources/pixel/form_textfield_selected_rtl_expected_skia_win.pdf.0.png differ diff --git a/testing/resources/pixel/same_color_knockout_fill_expected_gdi_skia.pdf.0.png b/testing/resources/pixel/same_color_knockout_fill_expected_skia_gdi.pdf.0.png similarity index 100% rename from testing/resources/pixel/same_color_knockout_fill_expected_gdi_skia.pdf.0.png rename to testing/resources/pixel/same_color_knockout_fill_expected_skia_gdi.pdf.0.png diff --git a/testing/resources/pixel/shade-tensor_expected_gdi_skia.pdf.0.png b/testing/resources/pixel/shade-tensor_expected_skia_gdi.pdf.0.png similarity index 100% rename from testing/resources/pixel/shade-tensor_expected_gdi_skia.pdf.0.png rename to testing/resources/pixel/shade-tensor_expected_skia_gdi.pdf.0.png diff --git a/testing/resources/pixel/shade_expected_gdi_skia.pdf.0.png b/testing/resources/pixel/shade_expected_skia_gdi.pdf.0.png similarity index 100% rename from testing/resources/pixel/shade_expected_gdi_skia.pdf.0.png rename to testing/resources/pixel/shade_expected_skia_gdi.pdf.0.png diff --git a/testing/resources/pixel/single_point_paths_expected_gdi_agg.pdf.0.png b/testing/resources/pixel/single_point_paths_expected_gdi.pdf.0.png similarity index 100% rename from testing/resources/pixel/single_point_paths_expected_gdi_agg.pdf.0.png rename to testing/resources/pixel/single_point_paths_expected_gdi.pdf.0.png diff --git a/testing/resources/pixel/single_point_paths_expected_gdi_skia.pdf.0.png b/testing/resources/pixel/single_point_paths_expected_skia_gdi.pdf.0.png similarity index 100% rename from testing/resources/pixel/single_point_paths_expected_gdi_skia.pdf.0.png rename to testing/resources/pixel/single_point_paths_expected_skia_gdi.pdf.0.png diff --git a/testing/resources/pixel/transfer_function_expected_gdi_skia.pdf.0.png b/testing/resources/pixel/transfer_function_expected_skia_gdi.pdf.0.png similarity index 100% rename from testing/resources/pixel/transfer_function_expected_gdi_skia.pdf.0.png rename to testing/resources/pixel/transfer_function_expected_skia_gdi.pdf.0.png diff --git a/testing/resources/pixel/transfer_function_expected_gdi_skia.pdf.1.png b/testing/resources/pixel/transfer_function_expected_skia_gdi.pdf.1.png similarity index 100% rename from testing/resources/pixel/transfer_function_expected_gdi_skia.pdf.1.png rename to testing/resources/pixel/transfer_function_expected_skia_gdi.pdf.1.png diff --git a/testing/tools/BUILD.gn b/testing/tools/BUILD.gn index 7e3153c179..49f99e06d2 100644 --- a/testing/tools/BUILD.gn +++ b/testing/tools/BUILD.gn @@ -82,8 +82,8 @@ if (pdf_is_standalone) { # Built runtime dependencies. data_deps = [ - "../:pdfium_test", - "../../:pdfium_diff", + "../:pdfium_diff", + "../pdfium_test", ] # Force `data_deps` to be built before this target, rather than in parallel. diff --git a/testing/tools/pngdiffer.py b/testing/tools/pngdiffer.py index f88d644c42..2b904fe610 100755 --- a/testing/tools/pngdiffer.py +++ b/testing/tools/pngdiffer.py @@ -18,18 +18,8 @@ # most specific, and the root being the least specific. _COMMON_SUFFIX_ORDER = ('_{os}', '') _AGG_SUFFIX_ORDER = ('_agg_{os}', '_agg') + _COMMON_SUFFIX_ORDER -_GDI_AGG_SUFFIX_ORDER = ( - '_gdi_agg_{os}', - '_gdi_agg', - '_gdi_{os}', - '_gdi', -) + _COMMON_SUFFIX_ORDER -_GDI_SKIA_SUFFIX_ORDER = ( - '_gdi_skia_{os}', - '_gdi_skia', - '_gdi_{os}', - '_gdi', -) + _COMMON_SUFFIX_ORDER +_GDI_AGG_SUFFIX_ORDER = ('_gdi',) + _COMMON_SUFFIX_ORDER +_GDI_SKIA_SUFFIX_ORDER = ('_skia_gdi', '_gdi') + _COMMON_SUFFIX_ORDER _SKIA_SUFFIX_ORDER = ('_skia_{os}', '_skia') + _COMMON_SUFFIX_ORDER @@ -148,11 +138,10 @@ def ComputeDifferences(self, input_filename, source_dir, working_dir, print(f'WARNING: No diff for {page_diff.actual_path}') page_diff.diff_path = None else: - # Validate that no other paths match. + # Validate that no other paths match exactly. for unexpected_path in path_templates.GetExpectedPaths(page)[1:]: page_diff.expected_path = unexpected_path - if not self._RunImageCompareCommand(page_diff, - image_matching_algorithm): + if not self._RunImageCompareCommand(page_diff, EXACT_MATCHING): page_diff.reason = f'Also matches {unexpected_path}' break page_diff.expected_path = expected_path diff --git a/testing/tools/suppressor.py b/testing/tools/suppressor.py index 64cdf7c2bf..180536e322 100755 --- a/testing/tools/suppressor.py +++ b/testing/tools/suppressor.py @@ -15,6 +15,8 @@ def __init__(self, finder, features, js_disabled, xfa_disabled, rendering_option): 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( @@ -86,7 +88,8 @@ def IsImageDiffSuppressed(self, input_filename): return False def GetImageMatchingAlgorithm(self, input_filename): - if input_filename in self.exact_matching_suppression_set: + if (self.has_rust_decoders or + input_filename in self.exact_matching_suppression_set): print(f"{input_filename} image diff comparison is fuzzy") return pngdiffer.FUZZY_MATCHING return pngdiffer.EXACT_MATCHING diff --git a/testing/unit_test_main.cpp b/testing/unit_test_main.cpp index af254cbcf6..8253a7b844 100644 --- a/testing/unit_test_main.cpp +++ b/testing/unit_test_main.cpp @@ -2,10 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "build/build_config.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/pdf_test_environment.h" +#if BUILDFLAG(IS_WIN) +#include + +#include +#endif + #if defined(PDF_USE_PARTITION_ALLOC) #include "testing/allocator_shim_config.h" #endif @@ -17,6 +24,26 @@ #endif // PDF_ENABLE_XFA #endif // PDF_ENABLE_V8 +#if BUILDFLAG(IS_WIN) +namespace { + +// In SDK 10.0.28000+, dbghelp.dll dynamically loads msdia140.dll at runtime, +// and symbolization calls WinVerifyTrust() which creates threads in crypt32. +// If dbghelp is initialized during ASAN symbolization of a crash, this +// causes reentrancy or deadlocks inside AddressSanitizer's thread registry +// or malloc handlers (see crbug.com/548509159). Therefore, preload these +// DLLs and initialize DbgHelp before test execution starts. +void PreloadSymbols() { + ::LoadLibraryW(L"dbghelp.dll"); + ::LoadLibraryW(L"msdia140.dll"); + if (::SymInitialize(::GetCurrentProcess(), nullptr, TRUE)) { + ::SymCleanup(::GetCurrentProcess()); + } +} + +} // namespace +#endif // BUILDFLAG(IS_WIN) + // Can't use gtest-provided main since we need to initialize partition // alloc before invoking any test, and add test environments. int main(int argc, char** argv) { @@ -24,6 +51,10 @@ int main(int argc, char** argv) { pdfium::ConfigurePartitionAllocShimPartitionForTest(); #endif // defined(PDF_USE_PARTITION_ALLOC) +#if BUILDFLAG(IS_WIN) + PreloadSymbols(); +#endif // BUILDFLAG(IS_WIN) + // PDF test environment will be deleted by gtest. AddGlobalTestEnvironment(new PDFTestEnvironment()); diff --git a/testing/utils/bitmap_saver.cpp b/testing/utils/bitmap_saver.cpp index 9659e3971b..90689b31d0 100644 --- a/testing/utils/bitmap_saver.cpp +++ b/testing/utils/bitmap_saver.cpp @@ -4,18 +4,77 @@ #include "testing/utils/bitmap_saver.h" +#include + #include #include #include "core/fxcrt/check.h" -#include "testing/utils/png_encode.h" +#include "core/fxcrt/check_op.h" +#include "core/fxcrt/compiler_specific.h" +#include "core/fxcrt/fx_safe_types.h" +#include "core/fxcrt/notreached.h" +#include "testing/png_codec/png_codec.h" + +#ifdef PDF_ENABLE_SKIA +#include "third_party/skia/include/core/SkAlphaType.h" // nogncheck +#include "third_party/skia/include/core/SkColorType.h" // nogncheck +#include "third_party/skia/include/core/SkImageInfo.h" // nogncheck +#include "third_party/skia/include/core/SkPixmap.h" // nogncheck +#endif + +// static +std::vector BitmapSaver::EncodeBitmapToPng(FPDF_BITMAP bitmap) { + if (!bitmap) { + return {}; + } + const int stride = FPDFBitmap_GetStride(bitmap); + const int width = FPDFBitmap_GetWidth(bitmap); + const int height = FPDFBitmap_GetHeight(bitmap); + if (stride <= 0 || width <= 0 || height <= 0) { + return {}; + } + FX_SAFE_SIZE_T size = stride; + size *= height; + if (!size.IsValid()) { + return {}; + } + auto input = UNSAFE_TODO( + pdfium::span(static_cast(FPDFBitmap_GetBuffer(bitmap)), + size.ValueOrDie())); + + switch (FPDFBitmap_GetFormat(bitmap)) { + case FPDFBitmap_Gray: + return png_codec::EncodeGray(input, width, height, stride); + case FPDFBitmap_BGR: + return png_codec::EncodeBGR(input, width, height, stride); + case FPDFBitmap_BGRx: + return png_codec::EncodeBGRA(input, width, height, stride, + /*discard_transparency=*/true); + case FPDFBitmap_BGRA: + return png_codec::EncodeBGRA(input, width, height, stride, + /*discard_transparency=*/false); +#ifdef PDF_ENABLE_SKIA + case FPDFBitmap_BGRA_Premul: { + std::vector straight_alpha_input = + ConvertToStraightAlpha(input, width, height, stride); + return png_codec::EncodeBGRA(straight_alpha_input, width, height, stride, + /*discard_transparency=*/false); + } +#endif + case FPDFBitmap_Unknown: + return {}; + default: + NOTREACHED(); + } +} // static void BitmapSaver::WriteBitmapToPng(FPDF_BITMAP bitmap, const std::string& filename) { - std::vector png = EncodePng(bitmap); + std::vector png = EncodeBitmapToPng(bitmap); DCHECK(!png.empty()); - DCHECK(filename.size() < 256u); + DCHECK_LT(filename.size(), 256u); std::ofstream png_file; png_file.open(filename, std::ios_base::out | std::ios_base::binary); @@ -29,3 +88,24 @@ void BitmapSaver::WriteBitmapToPng(CFX_DIBitmap* bitmap, const std::string& filename) { WriteBitmapToPng(reinterpret_cast(bitmap), filename); } + +#ifdef PDF_ENABLE_SKIA +// static +std::vector BitmapSaver::ConvertToStraightAlpha( + pdfium::span input, + int width, + int height, + int row_byte_width) { + SkImageInfo premul_alpha_image_info = SkImageInfo::Make( + width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType); + SkPixmap src_pixmap(premul_alpha_image_info, input.data(), row_byte_width); + + std::vector result(input.size()); + SkImageInfo straight_alpha_image_info = SkImageInfo::Make( + width, height, kBGRA_8888_SkColorType, kUnpremul_SkAlphaType); + SkPixmap dst_pixmap(straight_alpha_image_info, result.data(), row_byte_width); + + CHECK(src_pixmap.readPixels(dst_pixmap)); + return result; +} +#endif diff --git a/testing/utils/bitmap_saver.h b/testing/utils/bitmap_saver.h index 8a9b2c6bed..1111b154f8 100644 --- a/testing/utils/bitmap_saver.h +++ b/testing/utils/bitmap_saver.h @@ -5,17 +5,29 @@ #ifndef TESTING_UTILS_BITMAP_SAVER_H_ #define TESTING_UTILS_BITMAP_SAVER_H_ +#include + #include +#include +#include "core/fxcrt/span.h" #include "public/fpdfview.h" class CFX_DIBitmap; class BitmapSaver { public: + static std::vector EncodeBitmapToPng(FPDF_BITMAP bitmap); static void WriteBitmapToPng(FPDF_BITMAP bitmap, const std::string& filename); static void WriteBitmapToPng(CFX_DIBitmap* bitmap, const std::string& filename); +#ifdef PDF_ENABLE_SKIA + static std::vector ConvertToStraightAlpha( + pdfium::span input, + int width, + int height, + int row_byte_width); +#endif }; #endif // TESTING_UTILS_BITMAP_SAVER_H_ diff --git a/testing/utils/pixel_diff_util.cpp b/testing/utils/pixel_diff_util.cpp index f22b6084e6..433a53b164 100644 --- a/testing/utils/pixel_diff_util.cpp +++ b/testing/utils/pixel_diff_util.cpp @@ -41,3 +41,12 @@ uint8_t MaxPixelPerChannelDelta(uint32_t baseline_pixel, ChannelDelta(baseline_unpacked.blue, actual_unpacked.blue), ChannelDelta(baseline_unpacked.alpha, actual_unpacked.alpha)}); } + +uint32_t PixelSquaredError(uint32_t baseline_pixel, uint32_t actual_pixel) { + UnpackedPixel baseline_unpacked(baseline_pixel); + UnpackedPixel actual_unpacked(actual_pixel); + int dr = baseline_unpacked.red - actual_unpacked.red; + int dg = baseline_unpacked.green - actual_unpacked.green; + int db = baseline_unpacked.blue - actual_unpacked.blue; + return static_cast(dr * dr + dg * dg + db * db); +} diff --git a/testing/utils/pixel_diff_util.h b/testing/utils/pixel_diff_util.h index fb1a77e3c6..9a67500e00 100644 --- a/testing/utils/pixel_diff_util.h +++ b/testing/utils/pixel_diff_util.h @@ -7,8 +7,16 @@ #include +// Standard fuzzy matching limits. +inline constexpr uint8_t kMaxFuzzyPixelDelta = 3; +inline constexpr double kMaxFuzzyMeanSquaredError = 0.05; + // Returns the largest difference in pixel channels between `baseline_pixel` and -// `actual_pixel`. Pixels are expected to be in 32-bit ARGB format. +// `actual_pixel`. Pixels are expected to be in 32-bit ARGB or BGRA format. uint8_t MaxPixelPerChannelDelta(uint32_t baseline_pixel, uint32_t actual_pixel); +// Returns the sum of squared differences across color channels (R, G, B) +// between `baseline_pixel` and `actual_pixel`. +uint32_t PixelSquaredError(uint32_t baseline_pixel, uint32_t actual_pixel); + #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 new file mode 100644 index 0000000000..5890a08904 --- /dev/null +++ b/testing/utils/pixel_diff_util_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 "testing/utils/pixel_diff_util.h" + +#include + +#include "testing/gtest/include/gtest/gtest.h" + +TEST(PixelDiffUtilTest, MaxPixelPerChannelDeltaExactMatch) { + EXPECT_EQ(0, MaxPixelPerChannelDelta(0x00000000, 0x00000000)); + EXPECT_EQ(0, MaxPixelPerChannelDelta(0xffffffff, 0xffffffff)); + EXPECT_EQ(0, MaxPixelPerChannelDelta(0x12345678, 0x12345678)); +} + +TEST(PixelDiffUtilTest, MaxPixelPerChannelDeltaSingleChannel) { + EXPECT_EQ(5, MaxPixelPerChannelDelta(0x00000005, 0x00000000)); + EXPECT_EQ(10, MaxPixelPerChannelDelta(0x00000a00, 0x00000000)); + EXPECT_EQ(15, MaxPixelPerChannelDelta(0x000f0000, 0x00000000)); + EXPECT_EQ(20, MaxPixelPerChannelDelta(0x14000000, 0x00000000)); + EXPECT_EQ(255, MaxPixelPerChannelDelta(0x000000ff, 0x00000000)); +} + +TEST(PixelDiffUtilTest, MaxPixelPerChannelDeltaMultipleChannels) { + EXPECT_EQ(8, MaxPixelPerChannelDelta(0x01020304, 0x09020304)); + EXPECT_EQ(7, MaxPixelPerChannelDelta(0x01020304, 0x01090304)); + EXPECT_EQ(6, MaxPixelPerChannelDelta(0x01020304, 0x01020904)); + EXPECT_EQ(5, MaxPixelPerChannelDelta(0x01020304, 0x01020309)); +} + +TEST(PixelDiffUtilTest, PixelSquaredError) { + EXPECT_EQ(0u, PixelSquaredError(0x00000000, 0x00000000)); + EXPECT_EQ(0u, PixelSquaredError(0xffffffff, 0xffffffff)); + + // Differences in color channels (bytes 0, 1, 2). + EXPECT_EQ(4u, PixelSquaredError(0x00000002, 0x00000000)); + EXPECT_EQ(9u, PixelSquaredError(0x00000300, 0x00000000)); + EXPECT_EQ(16u, PixelSquaredError(0x00040000, 0x00000000)); + EXPECT_EQ(29u, PixelSquaredError(0x00040302, 0x00000000)); + + // Alpha (byte 3) is ignored for color MSE calculation. + EXPECT_EQ(0u, PixelSquaredError(0xff000000, 0x00000000)); +} + +TEST(PixelDiffUtilTest, FuzzyThresholds) { + EXPECT_EQ(3, kMaxFuzzyPixelDelta); + EXPECT_DOUBLE_EQ(0.05, kMaxFuzzyMeanSquaredError); +} diff --git a/testing/utils/png_encode.cpp b/testing/utils/png_encode.cpp deleted file mode 100644 index 58d93502bb..0000000000 --- a/testing/utils/png_encode.cpp +++ /dev/null @@ -1,97 +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 "testing/utils/png_encode.h" - -#include "core/fxcrt/check.h" -#include "core/fxcrt/compiler_specific.h" -#include "core/fxcrt/fx_safe_types.h" -#include "core/fxcrt/notreached.h" -#include "public/fpdfview.h" -#include "testing/image_diff/image_diff_png.h" - -#ifdef PDF_ENABLE_SKIA -#include "third_party/skia/include/core/SkAlphaType.h" // nogncheck -#include "third_party/skia/include/core/SkColorType.h" // nogncheck -#include "third_party/skia/include/core/SkImageInfo.h" // nogncheck -#include "third_party/skia/include/core/SkPixmap.h" // nogncheck -#endif - -namespace { - -#ifdef PDF_ENABLE_SKIA -std::vector ConvertToStraightAlpha(pdfium::span input, - int width, - int height, - int row_byte_width) { - SkImageInfo premul_alpha_image_info = SkImageInfo::Make( - width, height, kBGRA_8888_SkColorType, kPremul_SkAlphaType); - SkPixmap src_pixmap(premul_alpha_image_info, input.data(), row_byte_width); - - std::vector result(input.size()); - SkImageInfo straight_alpha_image_info = SkImageInfo::Make( - width, height, kBGRA_8888_SkColorType, kUnpremul_SkAlphaType); - SkPixmap dst_pixmap(straight_alpha_image_info, result.data(), row_byte_width); - - CHECK(src_pixmap.readPixels(dst_pixmap)); - return result; -} -#endif - -} // namespace - -std::vector EncodePng(pdfium::span input, - int width, - int height, - int stride, - int format) { - std::vector png; - switch (format) { - case FPDFBitmap_Unknown: - break; - case FPDFBitmap_Gray: - png = image_diff_png::EncodeGrayPNG(input, width, height, stride); - break; - case FPDFBitmap_BGR: - png = image_diff_png::EncodeBGRPNG(input, width, height, stride); - break; - case FPDFBitmap_BGRx: - png = image_diff_png::EncodeBGRAPNG(input, width, height, stride, - /*discard_transparency=*/true); - break; - case FPDFBitmap_BGRA: - png = image_diff_png::EncodeBGRAPNG(input, width, height, stride, - /*discard_transparency=*/false); - break; -#ifdef PDF_ENABLE_SKIA - case FPDFBitmap_BGRA_Premul: { - std::vector input_with_straight_alpha = - ConvertToStraightAlpha(input, width, height, stride); - png = image_diff_png::EncodeBGRAPNG(input_with_straight_alpha, width, - height, stride, - /*discard_transparency=*/false); - break; - } -#endif - default: - NOTREACHED(); - } - return png; -} - -std::vector EncodePng(FPDF_BITMAP bitmap) { - const int stride = FPDFBitmap_GetStride(bitmap); - const int width = FPDFBitmap_GetWidth(bitmap); - const int height = FPDFBitmap_GetHeight(bitmap); - CHECK(stride >= 0); - CHECK(width >= 0); - CHECK(height >= 0); - FX_SAFE_FILESIZE size = stride; - size *= height; - auto input = UNSAFE_TODO( - pdfium::span(static_cast(FPDFBitmap_GetBuffer(bitmap)), - pdfium::ValueOrDieForType(size))); - - return EncodePng(input, width, height, stride, FPDFBitmap_GetFormat(bitmap)); -} diff --git a/testing/utils/png_encode.h b/testing/utils/png_encode.h deleted file mode 100644 index 410d086e62..0000000000 --- a/testing/utils/png_encode.h +++ /dev/null @@ -1,22 +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 TESTING_UTILS_PNG_ENCODE_H_ -#define TESTING_UTILS_PNG_ENCODE_H_ - -#include - -#include - -#include "core/fxcrt/span.h" -#include "public/fpdfview.h" - -std::vector EncodePng(pdfium::span input, - int width, - int height, - int stride, - int format); -std::vector EncodePng(FPDF_BITMAP bitmap); - -#endif // TESTING_UTILS_PNG_ENCODE_H_ diff --git a/third_party/BUILD.gn b/third_party/BUILD.gn index 2f5181473e..d8da88e425 100644 --- a/third_party/BUILD.gn +++ b/third_party/BUILD.gn @@ -3,6 +3,7 @@ # found in the LICENSE file. import("//build/config/arm.gni") +import("//build/config/gclient_args.gni") import("//build/config/linux/pkg_config.gni") import("//build/config/mips.gni") import("//build_overrides/build.gni") @@ -223,7 +224,8 @@ if (pdf_use_agg) { "//build/config/compiler:no_chromium_code", ":pdfium_third_party_config", - # Must be after no_chromium_code for warning flags to be ordered correctly. + # Must be after no_chromium_code for warning flags to be ordered + # correctly. ":fx_agg_warnings", ] sources = [ @@ -339,8 +341,8 @@ if (!build_with_chromium) { } # This is a meta target that forwards to the system's libjpeg, - # third_party/libjpeg, or third_party/libjpeg_turbo depending on the build args - # declared in this file. + # third_party/libjpeg, or third_party/libjpeg_turbo depending on the build + # args declared in this file. group("jpeg") { if (use_system_libjpeg) { public_configs = [ ":system_libjpeg_config" ] @@ -446,6 +448,10 @@ source_set("fx_libopenjpeg") { } if (!pdf_enable_rust_png) { + assert( + use_system_libpng || checkout_libpng, + "Cannot use bundled libpng without checkout_libpng. Enable pdf_enable_rust_png or use_system_libpng.") + config("system_libpng_config") { libs = [ "png" ] defines = [ "USE_SYSTEM_LIBPNG" ] @@ -460,7 +466,7 @@ if (!pdf_enable_rust_png) { } } -if (pdf_enable_xfa_tiff) { +if (pdf_enable_xfa) { if (use_system_libtiff) { config("system_tiff_config") { libs = [ "tiff" ] diff --git a/third_party/freetype/OWNERS b/third_party/freetype/OWNERS index 57d31f7ad3..d3d5e9583f 100644 --- a/third_party/freetype/OWNERS +++ b/third_party/freetype/OWNERS @@ -1,3 +1,2 @@ -bungeman@chromium.org drott@chromium.org thestig@chromium.org diff --git a/third_party/freetype/README.pdfium b/third_party/freetype/README.pdfium index ec07084cad..43e7bf72c9 100644 --- a/third_party/freetype/README.pdfium +++ b/third_party/freetype/README.pdfium @@ -1,7 +1,7 @@ Name: FreeType URL: https://gitlab.freedesktop.org/freetype/freetype -Version: VER-2-14-3-75 -Revision: 656cb777798fa420a13faba3758779e9ed6c4798 +Version: VER-2-14-3-85 +Revision: 4800589f76dc62fbe523a74a59e0d4850a5dd7df Update Mechanism: Manual CPEPrefix: cpe:/a:freetype:freetype:2.14.3 Security Critical: yes diff --git a/third_party/libtiff/0034-shrink-TIFFFaxTabEnt.patch b/third_party/libtiff/0034-shrink-TIFFFaxTabEnt.patch new file mode 100644 index 0000000000..1da4e90bd2 --- /dev/null +++ b/third_party/libtiff/0034-shrink-TIFFFaxTabEnt.patch @@ -0,0 +1,14 @@ +diff --git a/third_party/libtiff/tif_fax3.h b/third_party/libtiff/tif_fax3.h +index 60d709c7e5..494914f9f3 100644 +--- a/third_party/libtiff/tif_fax3.h ++++ b/third_party/libtiff/tif_fax3.h +@@ -89,8 +89,7 @@ typedef struct + { /* state table entry */ + unsigned char State; /* see above */ + unsigned char Width; /* width of code in bits */ +- uint32_t Param; /* unsigned 32-bit run length in bits (holds on 16 bit +- actually, but cannot be changed. See above warning) */ ++ uint16_t Param; /* unsigned 16-bit run length in bits */ + } TIFFFaxTabEnt; + + extern const TIFFFaxTabEnt TIFFFaxMainTable[]; diff --git a/third_party/libtiff/README.pdfium b/third_party/libtiff/README.pdfium index 17d27a7eae..bd61bce497 100644 --- a/third_party/libtiff/README.pdfium +++ b/third_party/libtiff/README.pdfium @@ -19,3 +19,4 @@ Local Modifications: 0028-nstrips-OOM.patch: return error for excess number of tiles/strips. 0031-safe_size_ingtStripContig.patch: return error if the size to read overflow from int32. 0033-avail-out-overflow.patch: signed comparison in PixarLogDecode(). +0034-shrink-TIFFFaxTabEnt.patch: make Param member of TIFFFaxTabEnt uint16_t. diff --git a/third_party/libtiff/tif_fax3.h b/third_party/libtiff/tif_fax3.h index 60d709c7e5..494914f9f3 100644 --- a/third_party/libtiff/tif_fax3.h +++ b/third_party/libtiff/tif_fax3.h @@ -89,8 +89,7 @@ typedef struct { /* state table entry */ unsigned char State; /* see above */ unsigned char Width; /* width of code in bits */ - uint32_t Param; /* unsigned 32-bit run length in bits (holds on 16 bit - actually, but cannot be changed. See above warning) */ + uint16_t Param; /* unsigned 16-bit run length in bits */ } TIFFFaxTabEnt; extern const TIFFFaxTabEnt TIFFFaxMainTable[]; diff --git a/tools/roll_pdfium_deps.py b/tools/roll_pdfium_deps.py index 8d72be6b79..f72b1a4eb8 100755 --- a/tools/roll_pdfium_deps.py +++ b/tools/roll_pdfium_deps.py @@ -17,6 +17,7 @@ 'clang_format_revision', 'clang_revision', 'cpu_features_revision', + 'cpython3_version', 'dragonbox_revision', 'fast_float_revision', 'fp16_revision', diff --git a/tools/roll_pdfium_deps_test.py b/tools/roll_pdfium_deps_test.py index c7a467003c..b771f52cd6 100644 --- a/tools/roll_pdfium_deps_test.py +++ b/tools/roll_pdfium_deps_test.py @@ -205,6 +205,9 @@ # siso CIPD package version. 'siso_version': 'git_revision:49dcca5d2be985d8ac6d512e59ee59e315264fb8', + # CPython 3 CIPD package version for Siso hermetic toolchain. + 'cpython3_version': 'version:3@3.11.9.chromium.38', + # reclient options. # download reclient binaries, required for 'use_reclient` gn arg. # TODO(crbug.com/448517720): make it false by default. @@ -1155,6 +1158,30 @@ 'dep_type': 'cipd', }, + # Always download Linux x64 package regardless of host OS for RBE workers. + 'src/third_party/cpython3/linux-amd64': { + 'packages': [ + { + 'package': 'infra/3pp/tools/cpython3/linux-amd64', + 'version': Var('cpython3_version'), + }, + ], + 'condition': 'non_git_source', + 'dep_type': 'cipd', + }, + + # Host platform package. + 'src/third_party/cpython3/host': { + 'packages': [ + { + 'package': 'infra/3pp/tools/cpython3/${{platform}}', + 'version': Var('cpython3_version'), + }, + ], + 'condition': 'non_git_source', + 'dep_type': 'cipd', + }, + 'src/third_party/pdfium': Var('pdfium_git') + '/pdfium.git' + '@' + Var('pdfium_revision'), @@ -1495,7 +1522,7 @@ ] ''' -PDFIUM_DEPS = ''' +PDFIUM_DEPS = r''' use_relative_paths = True gclient_gn_args_file = 'build/config/gclient_args.gni' @@ -1503,6 +1530,7 @@ 'android_ndk_version', 'build_with_chromium', 'checkout_android', + 'checkout_libpng', 'checkout_skia', ] @@ -1534,6 +1562,8 @@ 'checkout_instrumented_libraries': 'checkout_linux and checkout_configuration != "small" and checkout_configuration != "minimal"', + 'checkout_libpng': True, + # Fetch the rust toolchain. # # Use a custom_vars section to enable it: @@ -1608,6 +1638,10 @@ # and whatever else without interference from each other. 'cpu_features_revision': '936b9ab5515dead115606559502e3864958f7f6e', # Three lines of non-changing comments so that + # the commit queue can handle CLs rolling cpython3_version + # and whatever else without interference from each other. + 'cpython3_version': 'version:3@3.11.9.chromium.38', + # 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': 'f8cc59a94be950b1f174d5f3373fdc0843c90036', @@ -1870,6 +1904,30 @@ 'condition': 'checkout_android', }, + # Always download Linux x64 package regardless of host OS for RBE workers. + 'third_party/cpython3/linux-amd64': { + 'packages': [ + { + 'package': 'infra/3pp/tools/cpython3/linux-amd64', + 'version': Var('cpython3_version'), + }, + ], + 'condition': 'non_git_source', + 'dep_type': 'cipd', + }, + + # Host platform package. + 'third_party/cpython3/host': { + 'packages': [ + { + 'package': 'infra/3pp/tools/cpython3/${{platform}}', + 'version': Var('cpython3_version'), + }, + ], + 'condition': 'non_git_source', + 'dep_type': 'cipd', + }, + 'third_party/depot_tools': Var('chromium_git') + '/chromium/tools/depot_tools.git@' + Var('depot_tools_revision'), @@ -1939,9 +1997,11 @@ Var('chromium_git') + '/chromium/deps/libjpeg_turbo.git@' + Var('jpeg_turbo_revision'), - 'third_party/libpng': - Var('chromium_git') + '/chromium/src/third_party/libpng.git@' + + 'third_party/libpng': { + 'url': Var('chromium_git') + '/chromium/src/third_party/libpng.git@' + Var('libpng_revision'), + 'condition': 'checkout_libpng', + }, 'third_party/llvm-build/Release+Asserts': { 'dep_type': 'gcs', @@ -2242,7 +2302,7 @@ specific_include_rules = { # Allow embedder tests to use public APIs. - '(.*embeddertest\.cpp)': [ + r'(.*embeddertest\.cpp)': [ '+public', ] } @@ -2410,6 +2470,10 @@ def testCipdDepsRollsNothingToRoll(self): self.assertTrue(success) self.assertEqual('Revisions are the same.', message) + success, message = roll_dep(CHROMIUM_DEPS, PDFIUM_DEPS, 'cpython3_version') + self.assertTrue(success) + self.assertEqual('Revisions are the same.', message) + success, message = roll_dep(CHROMIUM_DEPS, PDFIUM_DEPS, 'ninja_version') self.assertTrue(success) self.assertEqual('Revisions are the same.', message) diff --git a/xfa/fde/cfde_texteditengine.cpp b/xfa/fde/cfde_texteditengine.cpp index 7b65cabd4d..4d115fc617 100644 --- a/xfa/fde/cfde_texteditengine.cpp +++ b/xfa/fde/cfde_texteditengine.cpp @@ -1205,8 +1205,8 @@ void CFDE_TextEditEngine::RebuildPieces() { std::pair CFDE_TextEditEngine::GetCharacterInfo( int32_t start_idx) { - DCHECK(start_idx >= 0); - DCHECK(static_cast(start_idx) <= text_length_); + DCHECK_GE(start_idx, 0); + DCHECK_LE(static_cast(start_idx), text_length_); // Make sure the current available data is fresh. Layout(); diff --git a/xfa/fde/cfde_textout.cpp b/xfa/fde/cfde_textout.cpp index 738826436e..9e52be3748 100644 --- a/xfa/fde/cfde_textout.cpp +++ b/xfa/fde/cfde_textout.cpp @@ -138,7 +138,7 @@ void CFDE_TextOut::SetFont(RetainPtr pFont) { } void CFDE_TextOut::SetFontSize(float fFontSize) { - DCHECK(fFontSize > 0); + DCHECK_GT(fFontSize, 0); font_size_ = fFontSize; txt_break_->SetFontSize(fFontSize); } @@ -171,7 +171,7 @@ void CFDE_TextOut::SetAlignment(FDE_TextAlignment iAlignment) { } void CFDE_TextOut::SetLineSpace(float fLineSpace) { - DCHECK(fLineSpace > 1.0f); + DCHECK_GT(fLineSpace, 1.0f); line_space_ = fLineSpace; } @@ -194,7 +194,7 @@ void CFDE_TextOut::CalcLogicSize(WideStringView str, CFX_RectF* pRect) { } DCHECK(font_); - DCHECK(font_size_ >= 1.0f); + DCHECK_GE(font_size_, 1.0f); if (!styles_.single_line_) { if (pRect->Width() < 1.0f) { @@ -278,7 +278,7 @@ void CFDE_TextOut::DrawLogicText(CFX_RenderDevice* device, const WideString& str, const CFX_RectF& rect) { DCHECK(font_); - DCHECK(font_size_ >= 1.0f); + DCHECK_GE(font_size_, 1.0f); if (str.IsEmpty()) { return; diff --git a/xfa/fde/cfde_wordbreak_data.cpp b/xfa/fde/cfde_wordbreak_data.cpp index 9c4c7a95dc..eb68f409b0 100644 --- a/xfa/fde/cfde_wordbreak_data.cpp +++ b/xfa/fde/cfde_wordbreak_data.cpp @@ -9,7 +9,7 @@ #include #include -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_system.h" #include "core/fxcrt/span.h" @@ -2826,7 +2826,7 @@ const std::array kCodePointProperties = {{ bool FX_CheckStateChangeForWordBreak(WordBreakProperty from, WordBreakProperty to) { - DCHECK(static_cast(from) < 13); + DCHECK_LT(static_cast(from), 13); return !!(kWordBreakTable[static_cast(from)] & static_cast(1 << static_cast(to))); } diff --git a/xfa/fgas/crt/cfgas_decimal.cpp b/xfa/fgas/crt/cfgas_decimal.cpp index 859481a3f7..309aad762e 100644 --- a/xfa/fgas/crt/cfgas_decimal.cpp +++ b/xfa/fgas/crt/cfgas_decimal.cpp @@ -12,7 +12,7 @@ #include #include -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/fx_extension.h" @@ -143,7 +143,7 @@ inline void decimal_helper_raw_mul(uint64_t a[], uint8_t bl, uint64_t c[], uint8_t cl) { - DCHECK(al + bl <= cl); + DCHECK_LE(al + bl, cl); UNSAFE_TODO({ for (int i = 0; i < cl; i++) { c[i] = 0; diff --git a/xfa/fgas/font/cfgas_fontmgr.cpp b/xfa/fgas/font/cfgas_fontmgr.cpp index 890a15d5de..ac63335129 100644 --- a/xfa/fgas/font/cfgas_fontmgr.cpp +++ b/xfa/fgas/font/cfgas_fontmgr.cpp @@ -19,6 +19,7 @@ #include "core/fxcrt/byteorder.h" #include "core/fxcrt/cfx_read_only_container_stream.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/containers/contains.h" #include "core/fxcrt/data_vector.h" @@ -491,7 +492,7 @@ int32_t CalcPenalty(CFGAS_FontDescriptor* pInstalled, ? static_cast(-1) : FX_GetCodePageBit(wCodePage); if (wBit != static_cast(-1)) { - DCHECK(wBit < 64); + DCHECK_LT(wBit, 64); if ((pInstalled->csb_[wBit / 32] & (1 << (wBit % 32))) == 0) { nPenalty += 0xFFFF; } else { @@ -501,7 +502,7 @@ int32_t CalcPenalty(CFGAS_FontDescriptor* pInstalled, wBit = (wcUnicode == 0 || wcUnicode == 0xFFFE) ? FGAS_FONTUSB::kNoBitField : FX_GetUnicodeBit(wcUnicode); if (wBit != FGAS_FONTUSB::kNoBitField) { - DCHECK(wBit < 128); + DCHECK_LT(wBit, 128); if ((pInstalled->usb_[wBit / 32] & (1 << (wBit % 32))) == 0) { nPenalty += 0xFFFF; } else { diff --git a/xfa/fgas/layout/cfgas_breakline.cpp b/xfa/fgas/layout/cfgas_breakline.cpp index fbeb0775f5..2ee4e6de7a 100644 --- a/xfa/fgas/layout/cfgas_breakline.cpp +++ b/xfa/fgas/layout/cfgas_breakline.cpp @@ -6,7 +6,7 @@ #include "xfa/fgas/layout/cfgas_breakline.h" -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" CFGAS_BreakLine::CFGAS_BreakLine() = default; @@ -36,6 +36,6 @@ void CFGAS_BreakLine::IncrementArabicCharCount() { } void CFGAS_BreakLine::DecrementArabicCharCount() { - DCHECK(arabic_chars_ > 0); + DCHECK_GT(arabic_chars_, 0); --arabic_chars_; } diff --git a/xfa/fgas/layout/cfgas_char.cpp b/xfa/fgas/layout/cfgas_char.cpp index ee2ddf18b2..4d5d0feaad 100644 --- a/xfa/fgas/layout/cfgas_char.cpp +++ b/xfa/fgas/layout/cfgas_char.cpp @@ -11,6 +11,7 @@ #include #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_extension.h" #include "core/fxcrt/stl_util.h" @@ -272,7 +273,7 @@ void ReverseString(std::vector* chars, size_t iStart, size_t iCount) { DCHECK(fxcrt::IndexInBounds(*chars, iStart)); - DCHECK(iStart + iCount <= chars->size()); + DCHECK_LE(iStart + iCount, chars->size()); std::reverse(chars->begin() + iStart, chars->begin() + iStart + iCount); } @@ -281,8 +282,8 @@ void SetDeferredRunClass(std::vector* chars, size_t iStart, size_t iCount, BidiClass eValue) { - DCHECK(iStart <= chars->size()); - DCHECK(iStart >= iCount); + DCHECK_LE(iStart, chars->size()); + DCHECK_GE(iStart, iCount); size_t iLast = iStart - iCount; for (size_t i = iStart; i > iLast; --i) { @@ -294,8 +295,8 @@ void SetDeferredRunLevel(std::vector* chars, size_t iStart, size_t iCount, int32_t iValue) { - DCHECK(iStart <= chars->size()); - DCHECK(iStart >= iCount); + DCHECK_LE(iStart, chars->size()); + DCHECK_GE(iStart, iCount); size_t iLast = iStart - iCount; for (size_t i = iStart; i > iLast; --i) { @@ -515,9 +516,9 @@ size_t ReorderLevel(std::vector* chars, int32_t iBaseLevel, size_t iStart, bool bReverse) { - DCHECK(iBaseLevel >= 0); - DCHECK(iBaseLevel <= kBidiMaxLevel); - DCHECK(iStart < iCount); + DCHECK_GE(iBaseLevel, 0); + DCHECK_LE(iBaseLevel, kBidiMaxLevel); + DCHECK_LT(iStart, iCount); if (iCount < 1) { return 0; @@ -565,7 +566,7 @@ void Position(std::vector* chars, size_t iCount) { // static void CFGAS_Char::BidiLine(std::vector* chars, size_t iCount) { - DCHECK(iCount <= chars->size()); + DCHECK_LE(iCount, chars->size()); if (iCount < 2) { return; } diff --git a/xfa/fgas/layout/cfgas_rtfbreak.cpp b/xfa/fgas/layout/cfgas_rtfbreak.cpp index 6053c43998..5472b38d20 100644 --- a/xfa/fgas/layout/cfgas_rtfbreak.cpp +++ b/xfa/fgas/layout/cfgas_rtfbreak.cpp @@ -10,6 +10,7 @@ #include "build/build_config.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/containers/adapters.h" #include "core/fxcrt/fx_extension.h" @@ -163,7 +164,7 @@ void CFGAS_RTFBreak::AppendChar_Tab(CFGAS_Char* pCurChar) { iSafeCharWidth = iCharWidth; } else { // Tab width is >= 160000, so this part does not need to be checked. - DCHECK(tab_width_ >= kMinimumTabWidth); + DCHECK_GE(tab_width_, kMinimumTabWidth); iSafeCharWidth = iLineWidth / tab_width_ + 1; iSafeCharWidth *= tab_width_; } diff --git a/xfa/fgas/layout/cfgas_txtbreak.cpp b/xfa/fgas/layout/cfgas_txtbreak.cpp index 61bbbc185d..4b54192aad 100644 --- a/xfa/fgas/layout/cfgas_txtbreak.cpp +++ b/xfa/fgas/layout/cfgas_txtbreak.cpp @@ -11,6 +11,7 @@ #include "build/build_config.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/containers/adapters.h" #include "core/fxcrt/fx_codepage.h" @@ -45,12 +46,12 @@ CFGAS_TxtBreak::~CFGAS_TxtBreak() = default; void CFGAS_TxtBreak::SetLineWidth(float fLineWidth) { line_width_ = FXSYS_roundf(fLineWidth * kConversionFactor); - DCHECK(line_width_ >= 20000); + DCHECK_GE(line_width_, 20000); } void CFGAS_TxtBreak::SetAlignment(int32_t iAlignment) { - DCHECK(iAlignment >= CFX_TxtLineAlignment_Left); - DCHECK(iAlignment <= CFX_TxtLineAlignment_Justified); + DCHECK_GE(iAlignment, CFX_TxtLineAlignment_Left); + DCHECK_LE(iAlignment, CFX_TxtLineAlignment_Justified); alignment_ = iAlignment; } diff --git a/xfa/fwl/cfwl_monthcalendar.cpp b/xfa/fwl/cfwl_monthcalendar.cpp index 52f81b802b..a00ab5a901 100644 --- a/xfa/fwl/cfwl_monthcalendar.cpp +++ b/xfa/fwl/cfwl_monthcalendar.cpp @@ -12,7 +12,7 @@ #include #include "core/fxcrt/cfx_datetime.h" -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/containers/contains.h" #include "core/fxcrt/notreached.h" #include "core/fxcrt/stl_util.h" @@ -542,7 +542,7 @@ void CFWL_MonthCalendar::RemoveSelDay() { } void CFWL_MonthCalendar::AddSelDay(int32_t iDay) { - DCHECK(iDay > 0); + DCHECK_GT(iDay, 0); if (!Contains(sel_day_array_, iDay)) { return; } diff --git a/xfa/fxfa/cxfa_ffpageview.cpp b/xfa/fxfa/cxfa_ffpageview.cpp index f4bbf3cdfb..8e2ee54757 100644 --- a/xfa/fxfa/cxfa_ffpageview.cpp +++ b/xfa/fxfa/cxfa_ffpageview.cpp @@ -9,7 +9,7 @@ #include #include -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/containers/contains.h" #include "core/fxcrt/stl_util.h" #include "fxjs/gc/container_trace.h" @@ -31,8 +31,8 @@ namespace { CFX_Matrix GetPageMatrix(const CFX_RectF& docPageRect, const FX_RECT& devicePageRect, int32_t rotation) { - DCHECK(rotation >= 0); - DCHECK(rotation <= 3); + DCHECK_GE(rotation, 0); + DCHECK_LE(rotation, 3); CFX_Matrix m; if (rotation == 0 || rotation == 2) { diff --git a/xfa/fxfa/cxfa_textlayout.cpp b/xfa/fxfa/cxfa_textlayout.cpp index b6bf7998ba..e2bee081d2 100644 --- a/xfa/fxfa/cxfa_textlayout.cpp +++ b/xfa/fxfa/cxfa_textlayout.cpp @@ -12,6 +12,7 @@ #include #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/css/cfx_csscomputedstyle.h" #include "core/fxcrt/css/cfx_cssstyleselector.h" @@ -517,7 +518,7 @@ float CXFA_TextLayout::Layout(const CFX_SizeF& size) { } bool CXFA_TextLayout::LayoutInternal(size_t szBlockIndex) { - DCHECK(szBlockIndex < CountBlocks()); + DCHECK_LT(szBlockIndex, CountBlocks()); if (!loader_ || loader_->fWidth < 1) { return false; diff --git a/xfa/fxfa/parser/cxfa_localevalue.cpp b/xfa/fxfa/parser/cxfa_localevalue.cpp index e9ed66e02a..09e47e1a4b 100644 --- a/xfa/fxfa/parser/cxfa_localevalue.cpp +++ b/xfa/fxfa/parser/cxfa_localevalue.cpp @@ -13,6 +13,7 @@ #include "core/fxcrt/cfx_datetime.h" #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_extension.h" #include "core/fxcrt/fx_string.h" #include "core/fxcrt/span.h" @@ -693,8 +694,8 @@ void CXFA_LocaleValue::GetNumericFormat(WideString& wsFormat, int32_t nIntLen, int32_t nDecLen) { DCHECK(wsFormat.IsEmpty()); - DCHECK(nIntLen >= -1); - DCHECK(nDecLen >= -1); + DCHECK_GE(nIntLen, -1); + DCHECK_GE(nDecLen, -1); int32_t nTotalLen = (nIntLen >= 0 ? nIntLen : 2) + 1 + (nDecLen >= 0 ? nDecLen : 2) + (nDecLen == 0 ? 0 : 1); diff --git a/xfa/fxfa/parser/cxfa_node.cpp b/xfa/fxfa/parser/cxfa_node.cpp index c942c4279b..8e746d5500 100644 --- a/xfa/fxfa/parser/cxfa_node.cpp +++ b/xfa/fxfa/parser/cxfa_node.cpp @@ -450,31 +450,18 @@ FXCODEC_IMAGE_TYPE XFA_GetImageType(const WideString& wsType) { if (wsContentType.EqualsASCIINoCase("image/jpg")) { return FXCODEC_IMAGE_JPG; } - -#ifdef PDF_ENABLE_XFA_BMP if (wsContentType.EqualsASCIINoCase("image/bmp")) { return FXCODEC_IMAGE_BMP; } -#endif // PDF_ENABLE_XFA_BMP - -#ifdef PDF_ENABLE_XFA_GIF if (wsContentType.EqualsASCIINoCase("image/gif")) { return FXCODEC_IMAGE_GIF; } -#endif // PDF_ENABLE_XFA_GIF - -#ifdef PDF_ENABLE_XFA_PNG if (wsContentType.EqualsASCIINoCase("image/png")) { return FXCODEC_IMAGE_PNG; } -#endif // PDF_ENABLE_XFA_PNG - -#ifdef PDF_ENABLE_XFA_TIFF if (wsContentType.EqualsASCII("image/tif")) { return FXCODEC_IMAGE_TIFF; } -#endif // PDF_ENABLE_XFA_TIFF - return FXCODEC_IMAGE_UNKNOWN; } diff --git a/xfa/fxfa/parser/cxfa_rectangle.cpp b/xfa/fxfa/parser/cxfa_rectangle.cpp index fbb745af82..2e7c54789a 100644 --- a/xfa/fxfa/parser/cxfa_rectangle.cpp +++ b/xfa/fxfa/parser/cxfa_rectangle.cpp @@ -10,7 +10,7 @@ #include -#include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/notreached.h" #include "fxjs/xfa/cjx_node.h" #include "xfa/fgas/graphics/cfgas_gegraphics.h" @@ -477,8 +477,8 @@ void CXFA_Rectangle::GetPath(const std::vector& strokes, int32_t nIndex, bool bStart, bool bCorner) { - DCHECK(nIndex >= 0); - DCHECK(nIndex < 8); + DCHECK_GE(nIndex, 0); + DCHECK_LT(nIndex, 8); int32_t n = (nIndex & 1) ? nIndex - 1 : nIndex; CXFA_Stroke* corner1 = strokes[n];