From d1c7b62ceae3c3c663df47391615ef9584dcfb44 Mon Sep 17 00:00:00 2001 From: "pdfium-autoroll@skia-public.iam.gserviceaccount.com" Date: Sun, 2 Aug 2026 22:04:03 -0700 Subject: [PATCH 001/123] Roll Instrumented Libraries from e8cb570a9a2e to d15c278eed5d (1 revision) https://chromium.googlesource.com/chromium/third_party/instrumented_libraries.git/+log/e8cb570a9a2e..d15c278eed5d If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/instrumented-libraries-pdfium Please CC dhoss@chromium.org,thestig@chromium.org,thestig@google.com on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Bug: None Tbr: thestig@google.com Change-Id: If631d53d2f96766e781841955e5408bf99967a7b Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154090 Bot-Commit: pdfium-autoroll@skia-public.iam.gserviceaccount.com Commit-Queue: pdfium-autoroll@skia-public.iam.gserviceaccount.com --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index b7000efb83..48bb4e31ec 100644 --- a/DEPS +++ b/DEPS @@ -167,7 +167,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling instrumented_lib # and whatever else without interference from each other. - 'instrumented_lib_revision': '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. From 0d5ebf0d5f41aa4b2f3bf61ee7cfed611bce3eb6 Mon Sep 17 00:00:00 2001 From: "pdfium-autoroll@skia-public.iam.gserviceaccount.com" Date: Sun, 2 Aug 2026 22:04:24 -0700 Subject: [PATCH 002/123] Roll Code Coverage from 88b5286372e2 to f2c64929fbd5 (9 revisions) https://chromium.googlesource.com/chromium/src/tools/code_coverage.git/+log/88b5286372e2..f2c64929fbd5 If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/code-coverage-pdfium Please CC dhoss@chromium.org,thestig@chromium.org,thestig@google.com on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Bug: None Tbr: thestig@google.com Change-Id: Ied7e5a30f612a7720d5d6f3c190a95ff6649b1a6 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154110 Bot-Commit: pdfium-autoroll@skia-public.iam.gserviceaccount.com Commit-Queue: pdfium-autoroll@skia-public.iam.gserviceaccount.com --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 48bb4e31ec..150882910b 100644 --- a/DEPS +++ b/DEPS @@ -115,7 +115,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling code_coverage # and whatever else without interference from each other. - 'code_coverage_revision': '88b5286372e2cb7295f962fe23efa58bf0ff59f8', + 'code_coverage_revision': 'f2c64929fbd5ee89eb7e5c06d73bc9cbda7796ae', # 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. From 2870fa9244b0f0f69fb743fab1e08deefcb07b2b Mon Sep 17 00:00:00 2001 From: "pdfium-autoroll@skia-public.iam.gserviceaccount.com" Date: Sun, 2 Aug 2026 22:26:25 -0700 Subject: [PATCH 003/123] Roll Catapult from 2cdd33999350 to e4d1a01c96de (23 revisions) https://chromium.googlesource.com/catapult.git/+log/2cdd33999350..e4d1a01c96de If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/catapult-pdfium Please CC dhoss@chromium.org,thestig@chromium.org,thestig@google.com on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Bug: None Tbr: thestig@google.com Change-Id: I83f8fa78cb3d7d49caede5c5a83bdd8567706cd3 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154111 Bot-Commit: pdfium-autoroll@skia-public.iam.gserviceaccount.com Commit-Queue: pdfium-autoroll@skia-public.iam.gserviceaccount.com --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 150882910b..cc3b3c6c4b 100644 --- a/DEPS +++ b/DEPS @@ -103,7 +103,7 @@ vars = { # 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': 'e4d1a01c96deb68b0aa26a66dca45fc290885cbf', # 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. From 2cbb87156e5b5efcccaf1f475345bb1f37a1f57d Mon Sep 17 00:00:00 2001 From: Aryan Date: Tue, 4 Aug 2026 13:06:10 -0700 Subject: [PATCH 004/123] Add CheckNoIOStreamInHeaders() to PDFium's PRESUBMIT.py This CL adds the CheckNoIOStreamInHeaders() presubmit rule, which has been ported over from chromium. Bug: 42270537 Change-Id: Ib07ad396ada8bc54c3587e9cee57c2eb25eb183f Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154070 Reviewed-by: Andy Phan Auto-Submit: Aryan Krishnan Reviewed-by: Tom Sepez Commit-Queue: Andy Phan --- PRESUBMIT.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/PRESUBMIT.py b/PRESUBMIT.py index dc07e5ac2e..0dba76b14d 100644 --- a/PRESUBMIT.py +++ b/PRESUBMIT.py @@ -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)) From eccf092832ecefe5a84c8e218a837d14b07857e2 Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Wed, 5 Aug 2026 17:37:00 -0700 Subject: [PATCH 005/123] Refactor CPVT_Section layout tests to parameterized tests Migrate TestTextLayout helper function to Google Test parameterized test suite. Removes the need for custom test body helpers and resolves anti-pattern where ASSERT_EQ macros were used inside sub-routines, which fail to correctly abort the calling test case on failure. Change-Id: Ie144028bd2d1499df32b61c9a10979ab5b03cc4b Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/153650 Reviewed-by: Lei Zhang Commit-Queue: Seung Hyun Jin --- core/fpdfdoc/cpvt_section_unittest.cpp | 166 +++++++++++-------------- 1 file changed, 72 insertions(+), 94 deletions(-) diff --git a/core/fpdfdoc/cpvt_section_unittest.cpp b/core/fpdfdoc/cpvt_section_unittest.cpp index 0b849ac8bd..7b45768672 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()); From ce16e247b3ad74a7fb2fd6129466aa6b09a5a4a4 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Thu, 6 Aug 2026 13:28:38 -0700 Subject: [PATCH 006/123] Roll v8/ 7ed96d92b..caa723ab9 (635 commits) https://chromium.googlesource.com/v8/v8.git/+log/7ed96d92b72b..caa723ab9c62 Version 15.2.1 To avoid pulling in the fadec library, set v8_enable_generated_code_validator to false. Created with: roll-dep v8 Change-Id: Iafbd3c08c7747f95beb7f90bfe8985fb63203d8c Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/153950 Reviewed-by: Tom Sepez Commit-Queue: Lei Zhang --- .gn | 3 +++ DEPS | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gn b/.gn index e34e6e71a6..f5d6cc8216 100644 --- a/.gn +++ b/.gn @@ -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/DEPS b/DEPS index cc3b3c6c4b..73a51f62dd 100644 --- a/DEPS +++ b/DEPS @@ -261,7 +261,7 @@ vars = { # 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': 'caa723ab9c621fcec3534de035b08d35e2947760', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling zlib # and whatever else without interference from each other. From c9918d04d7950721c1e380fa7d01060dfaaa2d69 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Thu, 6 Aug 2026 14:50:19 -0700 Subject: [PATCH 007/123] Roll third_party/llvm-libc/src/ 9a821f4e5..6622df0b6 (140 commits) https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libc.git/+log/9a821f4e58bb..6622df0b6261 Created with: roll-dep third_party/llvm-libc/src Change-Id: I1f2af56046e621679c0313f52e704a88ad9fe9ad Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154290 Auto-Submit: Lei Zhang Reviewed-by: Andy Phan Commit-Queue: Andy Phan --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 73a51f62dd..86fbbbdfbd 100644 --- a/DEPS +++ b/DEPS @@ -197,7 +197,7 @@ vars = { # 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': '6622df0b6261ceaccdbfe199040eb9d9e7166fe7', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling markupsafe # and whatever else without interference from each other. From 5471608ddb68c88f8c00ce72fa6519def7b41254 Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Thu, 6 Aug 2026 15:30:11 -0700 Subject: [PATCH 008/123] Handle bidi_resolver failures in CPVT_Section Update CPVT_Section::OutputLines() to handle failures when CFX_BidiResolver::Create() or GetVisualRunsForLine() returns empty. Previously, a null bidi_resolver check was used as a proxy for empty lines, but incorrectly skipped word positioning entirely if ICU failed to initialize. Now, word positioning is skipped for empty lines instead. If bidi_resolver fails to initialize or process a line, the layout logic falls back to a default LTR run. Change-Id: Ib5356ebb45463ee6e5545a58c01ef940361d2ade Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/153690 Reviewed-by: Lei Zhang Commit-Queue: Seung Hyun Jin --- core/fpdfdoc/cpvt_section.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/fpdfdoc/cpvt_section.cpp b/core/fpdfdoc/cpvt_section.cpp index 02ec128060..0feb0d66f4 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,10 @@ 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); + CHECK(!visual_runs.empty()); for (const CFX_BidiResolver::ResolvedRun& run : visual_runs) { pdfium::span> run_words = From f36d5ea2cf8c5c3e78630def1279fcc3709a4e77 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Thu, 6 Aug 2026 15:45:14 -0700 Subject: [PATCH 009/123] Update gn_version to 3fd3b0624d8cba16927853600130b2c33d4e7928 Change-Id: I4de8e7de29efd03b37517622c1835e8d62cdd176 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154330 Auto-Submit: Lei Zhang Commit-Queue: Andy Phan Reviewed-by: Andy Phan --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 86fbbbdfbd..ed38faf580 100644 --- a/DEPS +++ b/DEPS @@ -143,7 +143,7 @@ vars = { # 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:3fd3b0624d8cba16927853600130b2c33d4e7928', # 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. From 07c180eaed3fd7585681d706d62ee5146cf2a949 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Thu, 6 Aug 2026 15:45:44 -0700 Subject: [PATCH 010/123] Update siso_version to 7bc9a0bfe050ef97e1712ff61c6f11952799e951 Change-Id: I25a4ac5eb99b7e471e529d6a794a3a7aac0ccfb7 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154350 Auto-Submit: Lei Zhang Reviewed-by: Andy Phan Commit-Queue: Andy Phan --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index ed38faf580..39bd1f6c3a 100644 --- a/DEPS +++ b/DEPS @@ -233,7 +233,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling siso # and whatever else without interference from each other. - 'siso_version': 'git_revision:24b80aff540b202a88417f9f93b0dc86229eca7a', + 'siso_version': 'git_revision:7bc9a0bfe050ef97e1712ff61c6f11952799e951', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling skia # and whatever else without interference from each other. From 7e9f284d5a45a9498609bd210dce712320685df8 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Thu, 6 Aug 2026 16:05:49 -0700 Subject: [PATCH 011/123] Roll third_party/rust/ 9bcbc5767..7c4e7d9b0 (62 commits) https://chromium.googlesource.com/chromium/src/third_party/rust/+log/9bcbc576725c..7c4e7d9b0e7c Then update GN paths to match the new Rust crate versions. Created with: roll-dep third_party/rust Change-Id: I6374c9936d7c467809fd181e28ee38cd57a16105 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154370 Auto-Submit: Lei Zhang Reviewed-by: Andy Phan Commit-Queue: Andy Phan --- DEPS | 2 +- core/fxge/BUILD.gn | 4 ++-- skia/BUILD.gn | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DEPS b/DEPS index 39bd1f6c3a..c1447aa4fb 100644 --- a/DEPS +++ b/DEPS @@ -225,7 +225,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': '7c4e7d9b0e7c65639a375c3f0d338280ca0f28a4', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling simdutf # and whatever else without interference from each other. diff --git a/core/fxge/BUILD.gn b/core/fxge/BUILD.gn index 5617425a14..c21a5f0807 100644 --- a/core/fxge/BUILD.gn +++ b/core/fxge/BUILD.gn @@ -232,8 +232,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_41:lib", + "//third_party/rust/skrifa/v0_44:lib", ] configs += [ ":skrifa_cxx_config" ] } diff --git a/skia/BUILD.gn b/skia/BUILD.gn index 3145ef2ad4..8343813357 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_41:lib", + "//third_party/rust/skrifa/v0_44:lib", ] configs -= [ "//build/config/compiler:chromium_code" ] configs += [ From 9a8834cd996d5ae43231d0447f17b3aaa2a6efae Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Thu, 6 Aug 2026 16:54:30 -0700 Subject: [PATCH 012/123] Introduce ProgressiveDecoderContextDelegate header and Format enum This standalone delegate header allows individual codec engines to report scanline formats without including ProgressiveDecoder. Add an explanatory comment noting that FXDIB_Format is not used here because image decoders can return pixel formats not supported by it. Update ProgressiveDecoder to inherit from PDCD, alias Format, and replace unscoped enum FXCodec_Format across progressive_decoder.h and progressive_decoder.cpp. TAG=agy CONV=bbac8eab-d30e-43b6-895a-e5b8381bbaf9 Change-Id: Ib7e8875e43498f57d9602f26c02bb1cb31114d61 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154170 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- core/fxcodec/BUILD.gn | 1 + core/fxcodec/progressive_decoder.cpp | 62 ++++++++++--------- core/fxcodec/progressive_decoder.h | 21 ++----- .../progressive_decoder_context_delegate.h | 31 ++++++++++ 4 files changed, 70 insertions(+), 45 deletions(-) create mode 100644 core/fxcodec/progressive_decoder_context_delegate.h diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index 0ee37d3f4a..c046b918c3 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -99,6 +99,7 @@ source_set("fxcodec") { "progressive_decoder.h", "progressive_decoder_context.cpp", "progressive_decoder_context.h", + "progressive_decoder_context_delegate.h", ] if (pdf_enable_xfa_bmp) { sources += [ "bmp/bmp_decoder_delegate.h" ] diff --git a/core/fxcodec/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index 997eb9eeb5..ce2b902e3e 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -26,6 +26,7 @@ #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" @@ -146,7 +147,7 @@ bool ProgressiveDecoder::PngReadHeader(int width, int height, double* gamma) { // PNG decoder always decodes into BGRA. src_bits_per_component_ = 8; src_components_count_ = 4; - src_format_ = FXCodec_Argb; + src_format_ = Format::kArgb; return false; } @@ -162,13 +163,13 @@ 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); + CHECK_EQ(src_format_, Format::kArgb); return device_bitmap_->GetWritableScanline(line); } pdfium::span ProgressiveDecoder::PngAskImageBuf() { CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); - CHECK_EQ(src_format_, FXCodec_Argb); + CHECK_EQ(src_format_, Format::kArgb); return device_bitmap_->GetWritableBuffer(); } @@ -347,15 +348,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: @@ -486,7 +487,7 @@ bool ProgressiveDecoder::GifDetectImageTypeInBuffer() { } FXCODEC_STATUS ProgressiveDecoder::GifStartDecode() { - src_format_ = FXCodec_8bppRgb; + src_format_ = Format::k8bppRgb; SetTransMethod(); decode_buf_.resize(GetScanlineSize()); FXDIB_ResampleOptions options; @@ -591,13 +592,13 @@ FXCODEC_STATUS ProgressiveDecoder::JpegStartDecode() { src_width_, options); switch (src_components_count_) { case 1: - src_format_ = FXCodec_8bppGray; + src_format_ = Format::k8bppGray; break; case 3: - src_format_ = FXCodec_Rgb; + src_format_ = Format::kRgb; break; case 4: - src_format_ = FXCodec_Cmyk; + src_format_ = Format::kCmyk; break; } SetTransMethod(); @@ -625,7 +626,7 @@ FXCODEC_STATUS ProgressiveDecoder::JpegContinueDecode() { status_ = error_status; return status_; } - if (src_format_ == FXCodec_Rgb) { + if (src_format_ == Format::kRgb) { RGB2BGR(UNSAFE_TODO(decode_buf_.data()), src_width_); } if (src_row_ >= src_height_) { @@ -693,11 +694,11 @@ FXCODEC_STATUS ProgressiveDecoder::PngStartDecode() { // 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 + // `Format::kArgb` (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); + CHECK_EQ(src_format_, Format::kArgb); // Discard old/stale data from `codec_memory_` and restart reading the `file_` // from `offset_` 0. @@ -910,21 +911,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 +934,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 +973,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 +1119,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; } diff --git a/core/fxcodec/progressive_decoder.h b/core/fxcodec/progressive_decoder.h index 672ed7d54e..291d3f0ba4 100644 --- a/core/fxcodec/progressive_decoder.h +++ b/core/fxcodec/progressive_decoder.h @@ -15,6 +15,7 @@ #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" @@ -42,8 +43,6 @@ 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, @@ -54,17 +53,9 @@ class ProgressiveDecoder final : #ifdef PDF_ENABLE_XFA_PNG public PngDecoderDelegate, #endif // PDF_ENABLE_XFA_PNG - public Dummy { + 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; @@ -177,11 +168,11 @@ class ProgressiveDecoder final : 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). // @@ -208,7 +199,7 @@ class ProgressiveDecoder final : 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 diff --git a/core/fxcodec/progressive_decoder_context_delegate.h b/core/fxcodec/progressive_decoder_context_delegate.h new file mode 100644 index 0000000000..dd689cdf7b --- /dev/null +++ b/core/fxcodec/progressive_decoder_context_delegate.h @@ -0,0 +1,31 @@ +// Copyright 2026 The PDFium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CORE_FXCODEC_PROGRESSIVE_DECODER_CONTEXT_DELEGATE_H_ +#define CORE_FXCODEC_PROGRESSIVE_DECODER_CONTEXT_DELEGATE_H_ + +#include + +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; +}; + +} // namespace fxcodec + +#endif // CORE_FXCODEC_PROGRESSIVE_DECODER_CONTEXT_DELEGATE_H_ From 900371dc6b43947ce0d4882b23849d6ffb42e6d6 Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Thu, 6 Aug 2026 16:55:52 -0700 Subject: [PATCH 013/123] Handle NextWord() failure in GetLineCaretX() Update CPVT_VariableText::Iterator::GetLineCaretX() to explicitly check the boolean return value of NextWord() before attempting to retrieve the first word of the line. Prevent calling GetWord() when the iterator is already at the end of the text. Resolve implicit reliance on the subsequent bounds check within GetWord() and ensure `is_rtl` flag defaults to false when no word is found. Change-Id: I3885438d6182af00e7ae905b2363e4cabcb5f443 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/153110 Reviewed-by: Lei Zhang Commit-Queue: Seung Hyun Jin --- core/fpdfdoc/cpvt_variabletext.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/core/fpdfdoc/cpvt_variabletext.cpp b/core/fpdfdoc/cpvt_variabletext.cpp index b326e120a8..bd3c254e13 100644 --- a/core/fpdfdoc/cpvt_variabletext.cpp +++ b/core/fpdfdoc/cpvt_variabletext.cpp @@ -105,12 +105,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() { From 024e42f4281f0a58233cc7d658063ec90e6b6ba6 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Thu, 6 Aug 2026 17:56:14 -0700 Subject: [PATCH 014/123] Roll third_party/skia/ 3ba832f86..7ef86a5b0 (378 commits; 68 trivial rolls) https://skia.googlesource.com/skia.git/+log/3ba832f8688f..7ef86a5b0eb9 Created with: roll-dep third_party/skia Then fix Rust-enabled builds by updating the location of SkBmpRustDecoder.h. Same as crrev.com/1667412. Change-Id: I8ce3acd92b351cfdf8e6bc9dac9c880acef239c5 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154310 Reviewed-by: Tom Sepez Commit-Queue: Tom Sepez Commit-Queue: Lei Zhang --- DEPS | 2 +- core/fxcodec/bmp/DEPS | 1 - core/fxcodec/bmp/skia_bmp_context.cpp | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index c1447aa4fb..034e314d6e 100644 --- a/DEPS +++ b/DEPS @@ -237,7 +237,7 @@ vars = { # 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': '7ef86a5b0eb91db85580bd71ebed3c4ba489d5ba', # 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. 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/skia_bmp_context.cpp b/core/fxcodec/bmp/skia_bmp_context.cpp index da4d9113a0..4873c42ca6 100644 --- a/core/fxcodec/bmp/skia_bmp_context.cpp +++ b/core/fxcodec/bmp/skia_bmp_context.cpp @@ -17,7 +17,7 @@ #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/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" From 40ac5b5dc2bb33e96aceb95bf9a1adaf346329a2 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Thu, 6 Aug 2026 19:57:33 -0700 Subject: [PATCH 015/123] Migrate BMP decoder to use ProgressiveDecoderContextDelegate Expand the existing ProgressiveDecoderContextDelegate interface by adding I/O and scanline resampling callback methods. Update CFX_BmpContext and SkiaBmpContext to use PDCD instead of BmpDecoderDelegate. Update ProgressiveDecoder to implement the newly added delegate methods for BMP decoding, replacing the previous BMP-specific callbacks. Delete core/fxcodec/bmp/bmp_decoder_delegate.h and remove it from BUILD.gn. TAG=agy CONV=bbac8eab-d30e-43b6-895a-e5b8381bbaf9 Change-Id: Iee7fd5f694b781afe4dc8f806448275862180eef Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154150 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- core/fxcodec/BUILD.gn | 1 - core/fxcodec/bmp/bmp_decoder_delegate.h | 27 ----- core/fxcodec/bmp/cfx_bmpcontext.cpp | 5 +- core/fxcodec/bmp/cfx_bmpcontext.h | 7 +- core/fxcodec/bmp/cfx_bmpdecompressor.cpp | 8 +- core/fxcodec/bmp/cfx_bmpdecompressor.h | 1 - core/fxcodec/bmp/skia_bmp_context.cpp | 26 ++--- core/fxcodec/bmp/skia_bmp_context.h | 14 +-- core/fxcodec/progressive_decoder.cpp | 107 ++++++++++++++---- core/fxcodec/progressive_decoder.h | 32 +++--- .../progressive_decoder_context_delegate.h | 50 ++++++++ 11 files changed, 173 insertions(+), 105 deletions(-) delete mode 100644 core/fxcodec/bmp/bmp_decoder_delegate.h diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index c046b918c3..7810e02711 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -102,7 +102,6 @@ source_set("fxcodec") { "progressive_decoder_context_delegate.h", ] if (pdf_enable_xfa_bmp) { - sources += [ "bmp/bmp_decoder_delegate.h" ] if (pdf_enable_rust_bmp) { sources += [ "bmp/skia_bmp_context.cpp", 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..543d059efc 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; @@ -37,7 +36,6 @@ ProgressiveDecoderContext::Status CFX_BmpContext::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 +48,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..1cde84a108 100644 --- a/core/fxcodec/bmp/cfx_bmpcontext.h +++ b/core/fxcodec/bmp/cfx_bmpcontext.h @@ -16,12 +16,12 @@ 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: @@ -31,13 +31,12 @@ class CFX_BmpContext final : public ProgressiveDecoderContext { 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/skia_bmp_context.cpp b/core/fxcodec/bmp/skia_bmp_context.cpp index 4873c42ca6..35d5c69189 100644 --- a/core/fxcodec/bmp/skia_bmp_context.cpp +++ b/core/fxcodec/bmp/skia_bmp_context.cpp @@ -8,10 +8,10 @@ #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/fxcodec/progressive_decoder_context_delegate.h" #include "core/fxcrt/byteorder.h" #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" @@ -52,7 +52,7 @@ void ClampBmpHeaderOffset(pdfium::span header, } // namespace -SkiaBmpContext::SkiaBmpContext(BmpDecoderDelegate* delegate) +SkiaBmpContext::SkiaBmpContext(ProgressiveDecoderContextDelegate* delegate) : delegate_(delegate) {} SkiaBmpContext::~SkiaBmpContext() = default; @@ -75,15 +75,13 @@ void SkiaBmpContext::Input(RetainPtr codec_memory) { ProgressiveDecoderContext::Status SkiaBmpContext::ReadHeader( int32_t* width, int32_t* height, - bool* tb_flag, int32_t* components, pdfium::span* palette, - CFX_DIBAttribute* pAttribute) { + CFX_DIBAttribute* attribute) { if (header_read_) { SkImageInfo info = decoder_->getInfo(); *width = info.width(); *height = info.height(); - *tb_flag = top_down_; *components = components_; *palette = {}; return ProgressiveDecoderContext::Status::kSuccess; @@ -158,14 +156,13 @@ ProgressiveDecoderContext::Status SkiaBmpContext::ReadHeader( 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; + if (attribute) { + attribute->dpi_unit_ = CFX_DIBAttribute::kResUnitMeter; + attribute->x_dpi_ = 0; + attribute->y_dpi_ = 0; } header_read_ = true; return ProgressiveDecoderContext::Status::kSuccess; @@ -223,11 +220,10 @@ ProgressiveDecoderContext::Status SkiaBmpContext::DecodeImage( } 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); + delegate_->ResampleScanline(r, src_row); } else { - Forward3ComponentRow(dest_row, src_row, row_buffer, width); + Forward3ComponentRow(r, src_row, row_buffer, width); } } rows_forwarded_ = rows_decoded; @@ -347,7 +343,7 @@ ProgressiveDecoderContext::Status SkiaBmpContext::StartIncrementalDecode() { return ProgressiveDecoderContext::Status::kSuccess; } -void SkiaBmpContext::Forward3ComponentRow(uint32_t dest_row, +void SkiaBmpContext::Forward3ComponentRow(int dest_row, pdfium::span src_row, pdfium::span row_buffer, int width) { @@ -357,7 +353,7 @@ void SkiaBmpContext::Forward3ComponentRow(uint32_t dest_row, 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); + delegate_->ResampleScanline(dest_row, row_buffer); } } // namespace fxcodec diff --git a/core/fxcodec/bmp/skia_bmp_context.h b/core/fxcodec/bmp/skia_bmp_context.h index ef70f68b60..236b95cf90 100644 --- a/core/fxcodec/bmp/skia_bmp_context.h +++ b/core/fxcodec/bmp/skia_bmp_context.h @@ -21,12 +21,12 @@ class SkCodec; namespace fxcodec { -class BmpDecoderDelegate; class CFX_DIBAttribute; +class ProgressiveDecoderContextDelegate; class SkiaBmpContext final : public ProgressiveDecoderContext { public: - explicit SkiaBmpContext(BmpDecoderDelegate* delegate); + explicit SkiaBmpContext(ProgressiveDecoderContextDelegate* delegate); ~SkiaBmpContext() override; void SetCodecMemory(RetainPtr codec_memory); @@ -34,26 +34,24 @@ class SkiaBmpContext final : public ProgressiveDecoderContext { // ProgressiveDecoderContext: FX_FILESIZE GetAvailInput() const override; void Input(RetainPtr codec_memory) override; + ProgressiveDecoderContext::Status DecodeImage(size_t frame_index) 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; + CFX_DIBAttribute* attribute); private: bool ValidatePaletteIndices(); ProgressiveDecoderContext::Status StartIncrementalDecode(); - void Forward3ComponentRow(uint32_t dest_row, + void Forward3ComponentRow(int dest_row, pdfium::span src_row, pdfium::span row_buffer, int width); - UnownedPtr const delegate_; + UnownedPtr const delegate_; std::unique_ptr decoder_; RetainPtr codec_memory_; std::vector decoded_image_buf_; diff --git a/core/fxcodec/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index ce2b902e3e..7f9e87bfe5 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -291,28 +291,87 @@ void ProgressiveDecoder::GifReadScanline(int32_t row_num, } #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::ReadMoreData(std::optional rcd_pos, + FXCODEC_STATUS* err_status) { + if (rcd_pos.has_value()) { + offset_ = rcd_pos.value(); + } + size_t unconsumed_bytes = codec_memory_->GetUnconsumedSpan().size(); + if (!ReadMoreDataInternal(unconsumed_bytes, err_status)) { + return false; + } + if (context_) { + context_->Input(codec_memory_); + } + return true; } -void ProgressiveDecoder::BmpReadScanline(uint32_t row_num, - pdfium::span row_buf) { - RetainPtr pDIBitmap = device_bitmap_; - DCHECK(pDIBitmap); +uint32_t ProgressiveDecoder::GetCurrentInputPosition() const { + return offset_; +} - int scanline_size = GetScanlineSize(); - fxcrt::Copy(row_buf.first(static_cast(scanline_size)), decode_buf_); +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_width_ = src_width; + src_height_ = src_height; + src_format_ = src_format; + // For formats other than kArgb, these values are already reliably set + // during the header reading phase. + if (src_format_ == Format::kArgb) { + src_bits_per_component_ = 8; + src_components_count_ = 4; + } + 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); + } + if (fill_argb.has_value()) { + device_bitmap_->Clear(fill_argb.value()); + } + return true; +} - if (row_num >= static_cast(src_height_)) { +void ProgressiveDecoder::ResampleScanline( + int line, + pdfium::span src_span) { + CHECK(device_bitmap_); + if (line < 0 || line >= src_height_) { return; } + int scanline_size = GetScanlineSize(); + fxcrt::Copy(src_span.first(static_cast(scanline_size)), decode_buf_); + ResampleScanline(device_bitmap_, line, decode_buf_, src_format_); +} + +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(); } +#ifdef PDF_ENABLE_XFA_BMP + bool ProgressiveDecoder::BmpDetectImageTypeInBuffer( CFX_DIBAttribute* pAttribute) { std::unique_ptr pBmcontext = @@ -326,18 +385,16 @@ bool ProgressiveDecoder::BmpDetectImageTypeInBuffer( #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) { @@ -402,7 +459,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_); @@ -452,7 +509,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; } @@ -531,7 +588,7 @@ bool ProgressiveDecoder::JpegReadMoreData(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; } context_->Input(codec_memory_); @@ -643,7 +700,7 @@ FXCODEC_STATUS ProgressiveDecoder::JpegContinueDecode() { #ifdef PDF_ENABLE_XFA_PNG bool ProgressiveDecoder::PngReadMoreData() { size_t unconsumed_bytes = codec_memory_->GetUnconsumedSpan().size(); - if (!ReadMoreData(unconsumed_bytes, &status_)) { + if (!ReadMoreDataInternal(unconsumed_bytes, &status_)) { return false; } @@ -804,8 +861,8 @@ bool ProgressiveDecoder::DetectImageType(FXCODEC_IMAGE_TYPE imageType, 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; diff --git a/core/fxcodec/progressive_decoder.h b/core/fxcodec/progressive_decoder.h index 291d3f0ba4..84505159d8 100644 --- a/core/fxcodec/progressive_decoder.h +++ b/core/fxcodec/progressive_decoder.h @@ -23,10 +23,6 @@ #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 @@ -44,9 +40,6 @@ class CFX_DIBAttribute; class ProgressiveDecoderContext; 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 @@ -93,12 +86,19 @@ class ProgressiveDecoder final : 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; + pdfium::span AskScanlineBuf(int line) override; + pdfium::span AskImageBuf() override; private: using WeightTable = CStretchEngine::WeightTable; @@ -161,7 +161,8 @@ 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(); @@ -208,9 +209,6 @@ class ProgressiveDecoder final : 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_delegate.h b/core/fxcodec/progressive_decoder_context_delegate.h index dd689cdf7b..1cd60e4869 100644 --- a/core/fxcodec/progressive_decoder_context_delegate.h +++ b/core/fxcodec/progressive_decoder_context_delegate.h @@ -7,6 +7,12 @@ #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 { @@ -24,6 +30,50 @@ class ProgressiveDecoderContextDelegate { }; 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; + + // Codec decoders use one of two mutually exclusive output paths: + // + // A. Scanline Resampling (Methods 1 and 2; e.g., BMP, GIF, JPEG): + // Used by streaming line-by-line decoders or whenever scaling, + // vertical orientation flipping, or color conversion is required. + // Accepts arbitrary source pixel formats (k8bppGray, kRgb, kCmyk, + // etc.) and resamples each scanline into the destination bitmap. + // + // B. Direct In-Place Output (Methods 3 and 4; e.g., PNG): + // Used by decoders that decompress directly into destination + // memory without intermediate buffers. Strictly requires 1:1 + // scaling and an ARGB/BGRA destination format so decompressed + // bytes can be written safely in-place. + + // 1. Scaling: Prepares resampler weights and scanline buffer. 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. during image + // type detection). + virtual bool PrepareScanlineResampling( + int src_width, + int src_height, + Format src_format, + pdfium::span palette = {}, + std::optional fill_argb = std::nullopt) = 0; + + // 2. Scaling: Resamples a single decoded scanline into the device bitmap. + virtual void ResampleScanline(int line, + pdfium::span src_span) = 0; + + // 3. Direct Output: Returns a writable span for a scanline in the device + // bitmap. + virtual pdfium::span AskScanlineBuf(int line) = 0; + + // 4. Direct Output: Returns a writable span for the entire device bitmap. + virtual pdfium::span AskImageBuf() = 0; }; } // namespace fxcodec From fa60d89a399983eb060b0640748d8531aa97bd71 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Fri, 7 Aug 2026 09:44:41 -0700 Subject: [PATCH 016/123] Roll android_ndk_version to 2@30.0.15729638 Change-Id: I6498b1334412925b1c45d7ee26ff3e20128eed00 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154410 Auto-Submit: Lei Zhang Reviewed-by: Andy Phan Commit-Queue: Andy Phan --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 034e314d6e..8b3fe48aef 100644 --- a/DEPS +++ b/DEPS @@ -87,7 +87,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling android_ndk # and whatever else without interference from each other. - 'android_ndk_version': Str('2@30.0.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. From 68fccd35447c802525fff0c45023bda85694f3aa Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Fri, 7 Aug 2026 09:45:17 -0700 Subject: [PATCH 017/123] Roll third_party/libc++abi/src/ 8f11bb1d4..14cefa839 (41 commits) https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxxabi.git/+log/8f11bb1d4438..14cefa83943c Created with: roll-dep third_party/libc++abi/src Change-Id: Iad79e8051f20681f8244bfe30a0dabeb4057c509 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154450 Auto-Submit: Lei Zhang Commit-Queue: Andy Phan Reviewed-by: Andy Phan --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 8b3fe48aef..8e17191528 100644 --- a/DEPS +++ b/DEPS @@ -185,7 +185,7 @@ vars = { # 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': '14cefa83943c62cf7c42f20bdf8c939c855a8b34', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libpng # and whatever else without interference from each other. From e3af2d1ea1c41d13a69cbe8c26e57f7574aae5fa Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Fri, 7 Aug 2026 13:09:09 -0700 Subject: [PATCH 018/123] Remove display gamma of 1.7 for Apple devices. Historical artifact. All modern devices use standard gamma of 2.2. Fixed: 542675553 Change-Id: I6ad3ad00024647fcf1dd550af3a63bd2050c6395 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154510 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- core/fxcodec/progressive_decoder.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/core/fxcodec/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index 7f9e87bfe5..7dd20d6590 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -66,6 +66,10 @@ namespace { constexpr size_t kBlockSize = 4096; +#ifdef PDF_ENABLE_XFA_PNG +constexpr double kPngGamma = 2.2; +#endif // PDF_ENABLE_XFA_PNG + std::unique_ptr CreateDecoderContext( FXCODEC_IMAGE_TYPE type, ProgressiveDecoder* delegate) { @@ -108,14 +112,6 @@ std::unique_ptr CreateDecoderContext( } } -#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; From 7e7198b48ae1cc34c0712e90d0c3dfd6d39f0441 Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Fri, 7 Aug 2026 13:09:42 -0700 Subject: [PATCH 019/123] Add pixel tests documenting CPWL_EditImpl selection behavior Introduce baseline pixel tests for CPWL_EditImpl that assert buggy visual behaviors of RTL text selection alongside a working LTR baseline. Document the exact failure mode where the blue selection highlight is invisible when selecting Hebrew text due to the highlight bounding box computing a width of zero. Visual rendering will be fixed in https://pdfium-review.googlesource.com/c/154231 by substituting the logical caret evaluation with explicit geometric widths. Bug: 543033201 Change-Id: I5497eec7bad8a9fe5966d688968739acbb626d04 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154250 Reviewed-by: Lei Zhang Commit-Queue: Seung Hyun Jin --- testing/SUPPRESSIONS | 2 + .../pixel/form_textfield_selected_ltr.evt | 24 ++++++++ .../pixel/form_textfield_selected_ltr.in | 57 ++++++++++++++++++ ..._textfield_selected_ltr_expected.pdf.0.png | Bin 0 -> 1347 bytes ...tfield_selected_ltr_expected_mac.pdf.0.png | Bin 0 -> 951 bytes ...field_selected_ltr_expected_skia.pdf.0.png | Bin 0 -> 904 bytes ...d_selected_ltr_expected_skia_mac.pdf.0.png | Bin 0 -> 971 bytes ...d_selected_ltr_expected_skia_win.pdf.0.png | Bin 0 -> 783 bytes .../pixel/form_textfield_selected_rtl.evt | 27 +++++++++ .../pixel/form_textfield_selected_rtl.in | 57 ++++++++++++++++++ ..._textfield_selected_rtl_expected.pdf.0.png | Bin 0 -> 389 bytes ...tfield_selected_rtl_expected_mac.pdf.0.png | Bin 0 -> 362 bytes ...field_selected_rtl_expected_skia.pdf.0.png | Bin 0 -> 217 bytes ...d_selected_rtl_expected_skia_mac.pdf.0.png | Bin 0 -> 236 bytes ...d_selected_rtl_expected_skia_win.pdf.0.png | Bin 0 -> 352 bytes 15 files changed, 167 insertions(+) create mode 100644 testing/resources/pixel/form_textfield_selected_ltr.evt create mode 100644 testing/resources/pixel/form_textfield_selected_ltr.in create mode 100644 testing/resources/pixel/form_textfield_selected_ltr_expected.pdf.0.png create mode 100644 testing/resources/pixel/form_textfield_selected_ltr_expected_mac.pdf.0.png create mode 100644 testing/resources/pixel/form_textfield_selected_ltr_expected_skia.pdf.0.png create mode 100644 testing/resources/pixel/form_textfield_selected_ltr_expected_skia_mac.pdf.0.png create mode 100644 testing/resources/pixel/form_textfield_selected_ltr_expected_skia_win.pdf.0.png create mode 100644 testing/resources/pixel/form_textfield_selected_rtl.evt create mode 100644 testing/resources/pixel/form_textfield_selected_rtl.in create mode 100644 testing/resources/pixel/form_textfield_selected_rtl_expected.pdf.0.png create mode 100644 testing/resources/pixel/form_textfield_selected_rtl_expected_mac.pdf.0.png create mode 100644 testing/resources/pixel/form_textfield_selected_rtl_expected_skia.pdf.0.png create mode 100644 testing/resources/pixel/form_textfield_selected_rtl_expected_skia_mac.pdf.0.png create mode 100644 testing/resources/pixel/form_textfield_selected_rtl_expected_skia_win.pdf.0.png 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/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 0000000000000000000000000000000000000000..a7ee22c7a8dd6b1e38cd60e2f837480166339fb0 GIT binary patch literal 1347 zcmai!dsLEl7{`ABD-X@=YSvEHn$0awS$o0DkaVO;p&4qY>|(Pzv&@s{eZdfQtb*Ccv=( zo(b?KCjk6S;1CBKZUcuj;Ft)UvVl_`a4rJQC4j&L1P*ZH0=Ehj1fyKyP_DO7q%t%F zqls+vkqk7Y5bYyEQ|mAgjDhGF*Bh8qk1+wF)eyYqAHvNb*z7{1S!g@frw|)hho#G~ z(Y+QBY~hu%o`7sjP;4T_Y{9{nICv|2&Tgb|#-mwJVt1=LGMgMDGho z+)t+2JHiK|>hMPpJW_&Zh#Wl;$Czi%G4;;O76Mm8;7${`S{GNuCBB|`HT{qia;Qp6 zs?;8KV;$zHJ^324aE@H7r})t+VO3rg8gGBP&v}*)l0o%Gs0@*BTAM!s_W!m2WTWBK zoyP%1FV4EafluCCh^o8<-wCF~2DdF;DUgJ;7(z2jkjs$>t%&B0&;sb;y@p6C63xnD z_#q5tW(+HfnUKr6C5}_PVYlcKhK)&;%_%t*DLhGv^22SOIGvfq>6_<_&t+AKb6@Im zr{?qO+KaByi#7U^u;?+mP|W=pU&!=Gj6mh zUbY%uNc&XMzHzC_D1F`EHl=AZ%(VBZ+6}WE`Kg^x`n&RzWi9NJN+6ohX2o{HF1L8F3WYIS{~t^C^BgVc#3tv( z)l=Gh8V#ApYd$J&?KSV;*0be@#ObtGxm`hXY&oCAm!Bp!mdTgzMh<>Fb5Us@5|p!u zEf(5YrX8T@t-JHz%0^~qR6(~%$F4i$V%2X%mAz-%^@o0emPc~;)P_`rUrBdOOef@V zn0?CXoWOND+S~-$V)#jXPl)AL|yRbtJa)B>F9EEP|p#jdWx;$Rt#S091s^L zUoMV%;+xgcihAInHLlL2TKRsKrMIN&LoFDO?3TQ5Di-yfF)@4BF>Oz(D9z@x9`0j4 z<3EbtRVyaCQn(g`@4n1JN@uR&N2jILHJQmy4cCI4&q>cZZrt$w7@xb=7Q6oQH~&+~ fzik=pja$vukSkNRIC~0e}u}w>B-Tb$`&Z|6y^Uw5E?q5Ci&6^Z^@#?jzzx|aI92yuH znOHaka4}qwj(#O?3yLmO)zA7|-uM1&5y*JPK&#npcm%o17^<3-8 zmm4(;eg9pl_<6D{_1K|@Il4)oJt}LgE}V0J_<1t#WwCihBAZUHlb@q|V*Bpr+J|S& zunC+M8OW>|cJAI52mg4PovAkumldvFETF$v*<9#UeAcB0NvA&DD$6M4OYHx_pPnl&eT+f_CE3VxIV3D+TXv`N7njRiIvQnd6TzYaqs)+QtL_o)t3D` zm3Mme*3+vehH!Q5=f0(@Z+bfM#MSU9wdTpJ&itIdYV!Bf6I(M&_ikOuEG2dI<5Sbu`{brB@kzOOhxw_~ z?+f>LT>Q4n`eyW!XKtF)dOW*S|4S%+O!1ktWnpJ*x2YV?fJF=QJ7>(Zg%UhXcdvK@ P%#aM8u6{1-oD!M<8XoSA literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..06580a48d6e46cf7c0700785c7d699fa59820b3b GIT binary patch literal 904 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGgBeJEiRiavU|{SF@Ck8cFfL>?E@Uz(Vm2*e zF)L;@FJ`wW;jk>_v?}AWF5|W?=dmg0wXNW@tKhe<6tJ%pa;z41sup#w5qGVXaI2H| zZc^}RQu1q7@o!ZPXjKbrQx9s>2yWL5Zr2Lw(2wXgi0n3u>M@G$F^TCjkMFmLpJ16V z!8&Q8ZSrKhl*#s~QykK!IHpf^%bwwpGt)D7rg#1wRm=Z04-jHp}`S+yv- zdT~t6lDN91@%77+8keUuugGXyo!P!Rt9?y&$C{kZwYgnu^Sjp<^sF!L-&oSWv3%0z z>Zx1nrf+YUxubE`&gR*>+UD)vNpU*3H1>dxcWcOSpG_vG#Ur*9uV zfA{#s`zJ3yJbV4|#hXuW-hX-b@#}}r-@knO`SttHZ{L6Y{PpMm|Nm0kXJ-Q=WtXRm zV@SoVx7Tm_2Rq6f{doDcu3os#wBX(7?dR#KIwfgYl{Qygd5}`$ik79dF*G zU%qzIZL{LKa>*w-yQDP@M2j|SUsKqbT%N4x`Te)ExM=7_vFnqi7OkCj=stT>o70=K z6HipRyj%9YWPAAGl{cP?KbQT=e*RJRyhq%%@9(Yttx|V9;KbLmmE~dYR$bW~QZQ%z zCiUlviw_4KI&H5vC4aY@`_g06YCjx4R2B8Er6hlUnf-S8V!_N4tA9^CnX+jvo3oW! z-pj(McT=u>S^CI8+(>w8T=n+NeUB}+9h`75^XXEtr*(UmP51o2-EHgA(BO5_i7yi; zs_dOPW$&Y#)rvO@k0*E(=zra4YyG9h%yfV7+7IWJubnVyeMqI2tj&Za2VR}}RN-`1 z`g3-EYLWXo7M-8EC$&C$eiDtgoWAgTe~DWM4f<*~W` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0985a6a833b3e21239ea03a22f3ad9895d37f52b GIT binary patch literal 971 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGgBeJEiRiZkQjEnx?oJHr&dIz4veyRqgt#&o z7cv?bGMN-Hn-;N{6|3x2OfSsRy-b1h;F2bm)Y2>V|je zM|2xS_n5@=na1{+#r0byPP9&%Xq!CQE@iTP>J*2xDURt=ozkbeWzX=)ndy^1+plm= zVDY@r@&yrD%gOY;T*lyK}+b?nV3hmK>P0{Lr-3N2aenI%oZfxf@Q*-*j@p z=2MHeo>{W(%+l>=mu)}0a@YA)yU(xQePR9nOB?oI-gNNFmcv)K9=^8y==B}PuJ1a2 zW6#N3`%d59fBN=;Gj|T2y>sOJy`vZIAG>(}#N`JkFF!nW<>Be8kIr0sbng0-^EaMc zxcT(rt*4i6Kf8MO`StrRZ#{f<`_b#WkKf#T^7g^AcMqSxd-VMM;}`Fry!`O=)rV)V zKR$o`>BXB*uikxr{r=1QPv1U&{qgnt&u`y<{rvUk_wT=d|Ni^`|9|-Ev`@f@JLBo% z7*cWT?e&}f!HyEg9>!;HbUmV^(JdAdAm+HVVbOx3SH~VP$t>8S*AU&`#v~({;6Bj zOKZ_Kr%#ige|B?@cxm?kbe)*&y;^eikFx*iw)KDg^QUfH`qZlUzv(qD z7W--WyFN@!b2{{0A!mB7X!YLJ*MGz&=4}3czVB#q#QBGpzTe9K{X6-S*q0kyZ}&a6 zSj*`^k6p7E zemd@W#r{I>ymrwW<&$MK=WE!U{}gWy7QZfU*P&*&hBd=a_qe{m%cVu^bta4=`n7SD zR!*0vmwU&m&F2tlxXmlVR8Uhtea;QpKaqu-pPz}={;l|{pF^obp@~>R#_m5u#%$qK UkuIyRz}(5;>FVdQ&MBb@0CxxERsaA1 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..63c474dc86e7e53db9b215c8c5cde34e808efe77 GIT binary patch literal 783 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGgBeJEiRiZkQjEnx?oJHr&dIz4vK0b+LR=Y) z3wdoTWIP+x0^77gI*g)wOk?{T(xy12Pj$_j?wLE&JAamM!5sghxq-#=f=lOzmM@5? zTohHkIJS04LjAI&#^uRPD^i+Pq&Bb2>0F!Fy{@QlL)pYlm6NyBPTy8HeS7_k?F}<` zG|k@GJbPF7qWwLK_xCQ|-?!wz#N`JkEk87Q#o;L{4^LfnWcuo(GuIxQv;M^VO(z#@ zKDB7e>80DxF57-~#m;l9cAsCp`@-727uWBjRI)w|EH-+y`Q;j4R3-rj%u_R;hAk6*lh^76ye zS0A3e{`lg}rD!0T-#>l%@%ihIFW-KC{r>a&k6*uk|NZ~} z{~xtYslc$B>*?YcQgQ3;^>F{g1|n?_A19REtQO-+ZT4Px#_8scDS{fh0@wck7dL5` z!PLn;q4i$zC&pSUy_TBkESxCX5fq{{Ug+l-by73Q z%#Pf6ub_XoBVQ|~HN;PK@hX$7a-(;hU24hq*j>|==51J1HEHoK9l?|+*;bRM+n@P` zAHDNt$+_!Ss`RGb4tp=tEdKJ#%+S~B#nJ7ue9k5Nr$uV3$xqy2|EAn?w`M*2%^M;o zZ(OUHKSlP;7LlZNZL!O{e=e%^aa^+@CgbN6{@I+yLc90w+qLVo1<%ayubo;?t`bUl z$?b8> + /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 0000000000000000000000000000000000000000..8d478688c9cfc9ac888fa08dc1cf11166216555d GIT binary patch literal 389 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGgBeJEiRiZkQfUD`A+8L@g$%|;493L_#-$9V zbquC$3}$T%mR$^%Ga0NWGuY2(a9GOVu$;-bkjb`}$#xc#eLs`^L?+kyOm54V+*UGq ztYkJWWC>o&60(-fxRA}Jn%%gNJ^he?NugNO0{_h)%g+D)|NnpL_a~A-8*h2KIEGZ* zdVBqP*I@$z)_|qk?|zx*`|-^to>h1=24o+h^&e0R}QBk?E0S^edw!~}p zyovYJ8yjk*Q{*?E+S>Uor0tDdf7HjcnDi4SNfWX~-tOd?IobP0l+XkKBZrX% literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..fc3fedaa70a6fc48a090f55bb9ee847fc44f43ef GIT binary patch literal 362 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGgBeJEiRiZkQXv68A+8L@g^b39oK|H#HsySF z75w&*Bem&wc&( ze&&XW>#C$PYMBr37e9C4xKdKZKMz&`JYYhKn#%DHofm32Sf1Qh=$4qgX}Ro#(AtkH z&QB=riM{E{{v;%R&wJw(Ipym6Uc#H!=+FBqIVpVc%?0--=9)(FNNrl_w!XPdCOPI* zoce@9=}F%ui~2um{GR3@mV4qLyWo-k97KQ<=C4_g&NZ0xlAX^8=s5;YS3j3^P6h_d literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b471f2d267ab81522e29bd685fde9ce71bc639df GIT binary patch literal 217 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGg&9a1u1K-~QgQ)4A+8L@h0LZ!tmeh+7A4%) z>k!B^$O0MDL>Da zhQ3T$+n6(PpGlF+G8?-IN0W0per!SL0&BkS?03yq^F?8T0t?Uu44$rjF6*2UngIG} BOT_>H literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6099258b1935ce3324720fd8aa99efeacfe9f253 GIT binary patch literal 236 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGg&9a1u1K-~QjEnx?oJHr&dIz4a#R9*LR=Y) z3mJ_InN5pW&5PO0OV}++IIYSA9jc_<>zilqnziou|NsBfB~to;>dQP`978hhy}i1Z zv%x@s?ZTJ7XBvGEH1r59WLsS{F}^WDCknDCov=H7MUg7d`?dO%vA)SqCCH1~hy T+TeWy=mG{$S3j3^P6cz3OOUfo}s+qQ}Zu<77 z**iBLxP12diC;6HTL|898ih2I>|Nc;sTu1R2>rU+S z{4Dh%S|&q*_qx3&10x>r@=MD(lO;m;#2XuWc#CpQ{jq#?#4P3aju6xGZ>r8=^7$!u z+kE1;Rhy|gi`~yzYtgWF>*=+V?L15cJl-@PnWT97j+OU$Tjy2V62gt1T$)mB%hx$g s(XjUNt2uw%9o#qy;M1& literal 0 HcmV?d00001 From 38f15eb835927465c826e0cb9fb240c0a5b6d0c3 Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Fri, 7 Aug 2026 13:19:38 -0700 Subject: [PATCH 020/123] Refactor CPWL_EditImpl to use GetWordRect helper Introduce a GetWordRect() helper in CPWL_EditImpl to consolidate the calculation of character bounding boxes across DrawEdit() and RefreshWordRange(). Bug: 543033201 Change-Id: I4f42c9febaac1f5f67f0b78c27db558846c10556 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154530 Commit-Queue: Seung Hyun Jin Reviewed-by: Lei Zhang --- fpdfsdk/pwl/cpwl_edit_impl.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/fpdfsdk/pwl/cpwl_edit_impl.cpp b/fpdfsdk/pwl/cpwl_edit_impl.cpp index f34657e4ae..a3ab89eb51 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,11 @@ namespace { +CFX_FloatRect GetWordRect(const CPVT_Word& word, const CPVT_Line& line) { + return CFX_FloatRect(word.location().x, line.ptLine.y + line.fLineDescent, + word.CaretX(), line.ptLine.y + line.fLineAscent); +} + void DrawTextString(CFX_RenderDevice* pDevice, const CFX_PointF& pt, CPDF_Font* font, @@ -653,16 +659,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()); @@ -1355,15 +1359,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. } From 45f7ebce0e5ecdd1edf7b82a1a8379499a7f3916 Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Fri, 7 Aug 2026 13:33:01 -0700 Subject: [PATCH 021/123] Calculate absolute right edge for CPWL_EditImpl selection bounds Substitute word.CaretX() with word.location().x + word.width() in GetWordRect(). In RTL text, word.CaretX() evaluates to the character's left edge for logical cursor placement. This caused the resulting selection rectangle to span from the left edge to the left edge, collapsing the width to zero and preventing the selection highlight from rendering. Using explicit geometric width ensures the bounding box encompasses the full character across all text directions. Update the form_textfield_selected_rtl pixel test expectations to reflect the corrected selection highlight. Bug: 543033201 Change-Id: I1c2b1c93bd7c845a42e811e6ee6140b7dd543971 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154231 Reviewed-by: Lei Zhang Commit-Queue: Seung Hyun Jin --- fpdfsdk/pwl/cpwl_edit_impl.cpp | 3 ++- .../pixel/form_textfield_selected_rtl.evt | 2 -- ...m_textfield_selected_rtl_expected.pdf.0.png | Bin 389 -> 1062 bytes ...xtfield_selected_rtl_expected_mac.pdf.0.png | Bin 362 -> 883 bytes ...tfield_selected_rtl_expected_skia.pdf.0.png | Bin 217 -> 786 bytes ...ld_selected_rtl_expected_skia_mac.pdf.0.png | Bin 236 -> 810 bytes ...ld_selected_rtl_expected_skia_win.pdf.0.png | Bin 352 -> 720 bytes 7 files changed, 2 insertions(+), 3 deletions(-) diff --git a/fpdfsdk/pwl/cpwl_edit_impl.cpp b/fpdfsdk/pwl/cpwl_edit_impl.cpp index a3ab89eb51..500678d9e1 100644 --- a/fpdfsdk/pwl/cpwl_edit_impl.cpp +++ b/fpdfsdk/pwl/cpwl_edit_impl.cpp @@ -33,7 +33,8 @@ namespace { CFX_FloatRect GetWordRect(const CPVT_Word& word, const CPVT_Line& line) { return CFX_FloatRect(word.location().x, line.ptLine.y + line.fLineDescent, - word.CaretX(), line.ptLine.y + line.fLineAscent); + word.location().x + word.width(), + line.ptLine.y + line.fLineAscent); } void DrawTextString(CFX_RenderDevice* pDevice, diff --git a/testing/resources/pixel/form_textfield_selected_rtl.evt b/testing/resources/pixel/form_textfield_selected_rtl.evt index abe799e021..3519b574e8 100644 --- a/testing/resources/pixel/form_textfield_selected_rtl.evt +++ b/testing/resources/pixel/form_textfield_selected_rtl.evt @@ -1,8 +1,6 @@ # 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. -# TODO(crbug.com/543033201): The selection highlight bounding box currently -# computes to a width of zero for RTL text, making it invisible. # 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. 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 index 8d478688c9cfc9ac888fa08dc1cf11166216555d..d35359829f4c07ca6af2802be461ba8d81e8334e 100644 GIT binary patch delta 1042 zcmaLTZ7`d6008j6$VC=!p+>L0+iLBdy6I^>$(;zTnkZe-ZE=cWv{tmG{|8OHL@HiF zQfVadk~dGfM5B$@>bxbos#MC%Ot)IQS!+8LXBb1z82h{r+qd86U%BmVqWB;eckJvL zCIcW4ASgg`0g?wOEI=s&6b_)20BSLyRsfnD&=i2C1T;0EO95R6f}23F9*00&P(CiG z8W*I%ov6W`RNxpGF02_Bb{7|Z#}e;$u7U=kDX|u@k|ocfLb)vp&EK-9_efxS(qKgYR8qrr>iSd^FP08sh??Wn3)^?>v`YS zyk&dt$?ooMgG+`C0Gp18(34E|i^VBMwk*Z1Vy5uw`+oio4)L1aPfTpcVr^;CVUGuk z#S+wS=1u25J-XlhQ1Ywd4KG?OD&czZ$4Qpj>{ z1#$ZrKOOh8&TKYYY4+M9UN6mVALnp?U|aKjAGzb? zn>0ni8=L-5tG#t28+{X-?98>-3Dv&Y;WxJW37U0W<{50bVRKfTw2Y4QaR(prc%Ag~ zq&Q}PegE>z)bHzBr{bts#cF_ub)_^ucKq0%^x(z3wLo&60(-fxRA}Jn%%gNJ^he?NugNO0{_h)%g+D)|NnpL_a~A-8*h2KIEGZ* zdVBqP*I@$z)_|qk?|zx*`|-^to>h1=24o+h^&e0R}QBk?E0S^edw!~}p zyovYJ8yjk*Q{*?E+S>Uor0tDdf7HjcnDi4SNfWX~-tOd?IobP0l+XkKBZrX% 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 index fc3fedaa70a6fc48a090f55bb9ee847fc44f43ef..9d51514b1d115e9461be599e5f13fa5f98682f8b 100644 GIT binary patch delta 861 zcmaFG^qFmfay?^hfKP}kgK;6FaUqjQ5wmF#i&-(Nc`=)L3A;rJhh-_JRT+;>Ij?O6 zpIrsNeWieXrJzHVkYly5Q?-b5jhIWVxNEJ1Tb-nPowP@ToOh#wPm{8Li;91%YCx-I zaJx=er&(OTd3?V``~>TyiMGj;>{F*Wq)l;5pX!=5-Mv10hDXj!&)k__d9!>AW(O3{ z4J@7)R5CBPbbeU*f{4mRkyVSLs~5-AEJgHHrDoc;`Wf4sX76m7v#WLP?zVZm+vo4;T(Gxm;oj~= z`zEhAG8p>b4`-b{@a6>-demr*7>#ee2MoxKYRW0x$BQF+>S`A7$RWcFr`Mq zb%JAnB+C@bCL;@j)(iXZ@=9yv-MM?I_*-o0{B`@T_Wo6N6mVn_biq$d_&jTWze3ZW z`tS)6hb+YpG&gj$3;45$$JgGrWcR&3r}*d=tMDhlgEzjMJJUp;ww z_O-5OnV<81!Qv18CMhrHH&0V-SZ+MeNhrQSee#C->aDfA-#$s-A`mqH=~WYxT!xbK zRa`p@LzrSOPm#6ts$2i!%zBr9EGN$#I2+I48Snmj!+WtT<5gy+`7XTygz*xpZENEVwRx7)cr!%uOEMwa`>wytbAcZ za7tQP%&U)1Jz` K=d#Wzp$P!4-k_8K literal 362 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGgBeJEiRiZkQXv68A+8L@g^b39oK|H#HsySF z75w&*Bem&wc&( ze&&XW>#C$PYMBr37e9C4xKdKZKMz&`JYYhKn#%DHofm32Sf1Qh=$4qgX}Ro#(AtkH z&QB=riM{E{{v;%R&wJw(Ipym6Uc#H!=+FBqIVpVc%?0--=9)(FNNrl_w!XPdCOPI* zoce@9=}F%ui~2um{GR3@mV4qLyWo-k97KQ<=C4_g&NZ0xlAX^8=s5;YS3j3^P6h_d diff --git a/testing/resources/pixel/form_textfield_selected_rtl_expected_skia.pdf.0.png b/testing/resources/pixel/form_textfield_selected_rtl_expected_skia.pdf.0.png index b471f2d267ab81522e29bd685fde9ce71bc639df..93b18d2a28f8f720fea3f432bab3a94f0d2ea305 100644 GIT binary patch literal 786 zcmeAS@N?(olHy`uVBq!ia0vp^CxAGGgBeJEiRiavU|{qL@Ck8cFfL>?E@Uz(Vm2*e zH7{l}FJZSR;j}8_wl3$fDd)4R;J2?7u&)$ytQK~v7ICf-b*_mg3hB@e?bHkJ(vRpij_x&y=`)M#w}_u$oix!Vd6Heq zWY?_eZrL+Da%Otv&GIjr8(Fm|s(NvB_2Rg?rHKv8Q<_(#wXDo&Tb{PUqUZ z?sfUy>x=p}lup=GJ#}mC^lf$1x7W|u-Y|1Vd5rfM`x}*Hf!DS+3SzbS$|^wrjv`eo>{W(%+l>= zSL{5ua@YB_doQlrcWKkXD_aj=+jivIj$_yNp1QT~^zHqpZyz{w=g_&khtJSf`c*V?#`MPj-5Oz%ulp@=Kc&#}INf*{$g$cubL};i3*ULv zHeIRL-~VEL!20bbdzhXvM2TPirL?~BTi}cno5L)AxVd_~SJ}XK;fLDN34hcIj1OFi z?b}>>{AkyNca;y69X_9);uuL$ZT4~YF^B4QNnFqF63A(=iS&ed*`fm z$N&HTU)?Q!38=En)5S3)_BHvN3& z4F``?qHawN3IZG~OpPGw$=nk%FBc^VpEkYA?qQu$@1)?&ney{&Y3R$8wT(Fw_n8#A qEVHqja5Oom!jT4r9B#Cy&C1co0R=qRQy}j0^77hJ9WZ3^}@UKBf5>EdyJ!dO=9}Y z;`%M(Cs-#4Z%+)3()4-&Qw$d;N^<4KsH%&)(HCXIJao-5vAybS~K2 zvv_~+;{AO~4op~faMJQaQ&t|Hy6VWZ)kkKmIXY|I@j2^H%-eW!{-%=)H=kO(^~{oO zXO?Y0yK49O4f`)|JaBo_!7G~&UDdxcW_ny3c`s%~;*PmX!`~2?X*Z=?j-~B(U02r{{o-U3d z6}R4A5A|nu6lr_7-XVHT$wV>DcOVeAV8`G8-f0UIr+i`3^Duj2d-BZ8v*%QvpG+y< zRP|R~QNf{sfsu)YLjVUu$o}(h2Bs(O>3`&}rp}m{>-KB=D*JhlXaCV_Yk8Nv@rGwW z^>jto*Gn!RcIl|KUDFpmd0k2FGPmvbPB<)`Y<^wue%89EdEPu-r~DUbW`rCweO~}Ej@AZZdK@{{-i+HV70S1N)N8_R=md=9J|C{iYwcDwt1$f z;#Os~+#99m=6Ea4W9EK2{a94#FVdQ&MBb@0D+=>umAu6 delta 220 zcmZ3*_J(nSL_G^L0|SHMiX;ml#aJBV?!>U}oXkrgM>4E_~^GrqTC6 zL+>Gf(~PG!8@&_LLn=7$KMk_AVDj8~$V-5Og{jd&Arws42umvazR7rGak{i|PkRpQ zCCTfuo31!4lR6=lIp6Kui3z`%X6{`lE;wHdtO=@Dfa6L13C2is|5vUJ-Zy|wVDNPH Kb6Mw<&;$T*>P}Js diff --git a/testing/resources/pixel/form_textfield_selected_rtl_expected_skia_win.pdf.0.png b/testing/resources/pixel/form_textfield_selected_rtl_expected_skia_win.pdf.0.png index 09f386b1a58ddc899b771c0ea2fd262aad4e70a1..67c3f7ec92046e735d38f2846e919958494a1e38 100644 GIT binary patch delta 660 zcmaFBbb)n(0Y4*0fKP}kgK;6RZH0_y!$ceDdhh&M{zY>Gi{}NG&JQhL5LLZ6wsuKU zdM2@S0A0d{`mZjCl_u$y?E>CrQ6T0-hF=U-izz^U*3B7>h`1851zey^!)wf7w@0E z{P5zN^XHFDpeP|kdtNz!j zD;Dz0G?w-h8|sE$`JlP9^v(bO>Uk<1_jXM1skr{v@7v@zDXFV}yDKR;G%zqSv2Y0B zV0^Os{F#CA(|Uzs-RxZ5#ji6=Bz-k%V~yjt?b~|ooz+q6?6)r;EZsW&=L{#dRqc)! zv@)M9TTs7Ya_Q;cGxaAtRk*Q9E&M*GnUpiPV?l{nut%1@qG(r8o!8B>>gun*HD~Uc zxyEamj^{m3AH{piRQBsUT`u%+##^2*Ek9CE%2;e{liBa=#ae7QCv*PyuPrW4A+Pd&~-uB-{LXYJN=k_~$eObkKeWJ{CCzeC@c!B~| cYX2`haPI0S|+HfKP}kgK;6RZN)@;>H5g3#ZlFZV{4a`P1sa3ZCl;+?M<_HZai@L z?Dfb0|Nmd_?i0(vz;NEv#WAGf*4yhR`I-zwST8(^dH(YM{!o!zNAVZyPVDsjEcGH< zCPRVuy1gd@BOdVbOUpTvB|`Va8yk9fi*io=v8;b}#4P3aju6xGZ>r8=^7$!u+kE1; zRhy|gi`~yzYtgWF>*=+V?L15cJl-@PnWT97j+OU$Tjy2V62gt1T$)mB%hx$g(XjUN pt2uw%9o#qybWJ5N_Xmvv4FO#mW{XAuAZ From 9b6f23989c5da39bff4ce36df96d62f9cabeebbf Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Fri, 7 Aug 2026 14:36:14 -0700 Subject: [PATCH 022/123] fxcrt: fix building against system ICU in cfx_bidi_resolver This was the only location which forgot to check for this define, core/fxcrt/fx_extension.h:18 already did the same. Change-Id: I6efdd8c5a3322b146f9ccabcabd2ac2506912d85 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154490 Commit-Queue: Andy Phan Reviewed-by: Lei Zhang Reviewed-by: Andy Phan --- core/fxcrt/cfx_bidi_resolver.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/fxcrt/cfx_bidi_resolver.cpp b/core/fxcrt/cfx_bidi_resolver.cpp index 50e302e358..efa585f387 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); From 6aee90ba4e96b615a2210cf4ef1f9fe72732a9ed Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Fri, 7 Aug 2026 15:26:45 -0700 Subject: [PATCH 023/123] Optimize CFX_BidiResolver initialization and restrict inheritance Append the final keyword to CFX_BidiResolver to restrict class inheritance. Document that CFX_BidiResolver::Create() returns nullptr on failure. Improve code clarity by adding inline parameter comments to ubidi_openSized() and ubidi_setPara(). Change-Id: I0f997317eeb3759ae9368347f53bfdec4a1f4e49 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/153570 Commit-Queue: Seung Hyun Jin Reviewed-by: Lei Zhang --- core/fxcrt/cfx_bidi_resolver.cpp | 11 ++++++----- core/fxcrt/cfx_bidi_resolver.h | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/core/fxcrt/cfx_bidi_resolver.cpp b/core/fxcrt/cfx_bidi_resolver.cpp index efa585f387..75ececc0ae 100644 --- a/core/fxcrt/cfx_bidi_resolver.cpp +++ b/core/fxcrt/cfx_bidi_resolver.cpp @@ -49,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; } @@ -63,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; } @@ -82,7 +82,8 @@ CFX_BidiResolver::GetVisualRunsForLine(int line_start, int line_length) const { } 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; } 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); From 0d5aa7a6536781ea4353118fd6efc0486bb9e0d6 Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Fri, 7 Aug 2026 15:26:53 -0700 Subject: [PATCH 024/123] Validate input boundaries for CFX_BidiResolver line extraction Catch negative line_start indices and verify the substring slice fits entirely within utf16_text_ before delegating work to ICU. Introduce new unit tests to verify empty returns for invalid line boundaries and ensure graceful handling of malformed UTF-16 input during resolver creation. Change-Id: If4c70c8a2664109d1516a6c79f44a48b1c09e6f0 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/153590 Commit-Queue: Seung Hyun Jin Reviewed-by: Lei Zhang --- core/fxcrt/cfx_bidi_resolver.cpp | 5 +-- core/fxcrt/cfx_bidi_resolver_unittest.cpp | 38 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/core/fxcrt/cfx_bidi_resolver.cpp b/core/fxcrt/cfx_bidi_resolver.cpp index 75ececc0ae..59f13a3b1c 100644 --- a/core/fxcrt/cfx_bidi_resolver.cpp +++ b/core/fxcrt/cfx_bidi_resolver.cpp @@ -77,7 +77,7 @@ 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; } @@ -89,7 +89,8 @@ CFX_BidiResolver::GetVisualRunsForLine(int line_start, int line_length) const { } 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_unittest.cpp b/core/fxcrt/cfx_bidi_resolver_unittest.cpp index 3469969aa7..ebfd5d54f3 100644 --- a/core/fxcrt/cfx_bidi_resolver_unittest.cpp +++ b/core/fxcrt/cfx_bidi_resolver_unittest.cpp @@ -4,6 +4,7 @@ #include "core/fxcrt/cfx_bidi_resolver.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" TEST(fxcrt, BidiResolverPureLTR) { @@ -69,3 +70,40 @@ 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); + auto runs = resolver->GetVisualRunsForLine(0, 2); + // 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. + ASSERT_EQ(1u, runs.size()); + EXPECT_EQ(0, runs[0].start); + EXPECT_EQ(2, runs[0].length); + EXPECT_FALSE(runs[0].is_rtl); +} From 7029ca54e4a94beb2d7dc7b890ac9abdcc3146f0 Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Fri, 7 Aug 2026 15:53:34 -0700 Subject: [PATCH 025/123] Modernize CFX_BidiResolver unit tests with ElementsAre Migrate test assertions to utilize testing::ElementsAre() for more concise verification. Implement inline operator==() and PrintTo() methods for CFX_BidiResolver::ResolvedRun to support new matcher assertions. Change-Id: I7ca1f5a3770d43c41912fdb1b47e098070f2cdf2 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/153610 Reviewed-by: Lei Zhang Commit-Queue: Seung Hyun Jin --- core/fxcrt/cfx_bidi_resolver_unittest.cpp | 60 ++++++++++------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/core/fxcrt/cfx_bidi_resolver_unittest.cpp b/core/fxcrt/cfx_bidi_resolver_unittest.cpp index ebfd5d54f3..7e6726e301 100644 --- a/core/fxcrt/cfx_bidi_resolver_unittest.cpp +++ b/core/fxcrt/cfx_bidi_resolver_unittest.cpp @@ -4,18 +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) { @@ -24,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) { @@ -36,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) { @@ -57,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) { @@ -98,12 +95,9 @@ TEST(fxcrt, BidiResolverInvalidUTF16) { u"\xDC00\xDC00", CFX_BidiResolver::ParagraphDirection::kLeftToRight); ASSERT_TRUE(resolver); - auto runs = resolver->GetVisualRunsForLine(0, 2); // 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. - ASSERT_EQ(1u, runs.size()); - EXPECT_EQ(0, runs[0].start); - EXPECT_EQ(2, runs[0].length); - EXPECT_FALSE(runs[0].is_rtl); + EXPECT_THAT(resolver->GetVisualRunsForLine(0, 2), + ElementsAre(CFX_BidiResolver::ResolvedRun{0, 2, false})); } From f13ead9925f52eb6151a3860ec678f51f23b0380 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Fri, 7 Aug 2026 16:57:08 -0700 Subject: [PATCH 026/123] Remove unused frame parameter from ProgressiveDecoderContext ProgressiveDecoder contained a `frame_cur_` member that was initialized to 0 and reset to 0 in StartDecode() and GifStartDecode(), but was never incremented or advanced. The progressive decoding flow finishes its single-image decode upon completing the first frame, resetting decoder bindings without supporting multi-frame stepping. Remove the unused `frame_cur_` member and remove the unused frame_index argument from ProgressiveDecoderContext::DecodeImage() and its overrides, hardcoding GIF decoding to frame 0. TAG=agy CONV=0ddacdfb-13f6-47af-a8e8-cc5336eb23a5 Change-Id: I745f7e68abac7a7e0fd8d2b2e0d7ff4947023356 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154570 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- core/fxcodec/bmp/cfx_bmpcontext.cpp | 3 +-- core/fxcodec/bmp/cfx_bmpcontext.h | 2 +- core/fxcodec/bmp/skia_bmp_context.cpp | 3 +-- core/fxcodec/bmp/skia_bmp_context.h | 2 +- core/fxcodec/gif/cfx_gifcontext.cpp | 8 ++++---- core/fxcodec/gif/cfx_gifcontext.h | 2 +- core/fxcodec/progressive_decoder.cpp | 10 ++++------ core/fxcodec/progressive_decoder.h | 1 - core/fxcodec/progressive_decoder_context.cpp | 3 +-- core/fxcodec/progressive_decoder_context.h | 4 +--- 10 files changed, 15 insertions(+), 23 deletions(-) diff --git a/core/fxcodec/bmp/cfx_bmpcontext.cpp b/core/fxcodec/bmp/cfx_bmpcontext.cpp index 543d059efc..51022a7cb2 100644 --- a/core/fxcodec/bmp/cfx_bmpcontext.cpp +++ b/core/fxcodec/bmp/cfx_bmpcontext.cpp @@ -28,8 +28,7 @@ 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(); } diff --git a/core/fxcodec/bmp/cfx_bmpcontext.h b/core/fxcodec/bmp/cfx_bmpcontext.h index 1cde84a108..3e3b350e84 100644 --- a/core/fxcodec/bmp/cfx_bmpcontext.h +++ b/core/fxcodec/bmp/cfx_bmpcontext.h @@ -27,7 +27,7 @@ class CFX_BmpContext final : public ProgressiveDecoderContext { // 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, diff --git a/core/fxcodec/bmp/skia_bmp_context.cpp b/core/fxcodec/bmp/skia_bmp_context.cpp index 35d5c69189..b39def255f 100644 --- a/core/fxcodec/bmp/skia_bmp_context.cpp +++ b/core/fxcodec/bmp/skia_bmp_context.cpp @@ -168,8 +168,7 @@ ProgressiveDecoderContext::Status SkiaBmpContext::ReadHeader( return ProgressiveDecoderContext::Status::kSuccess; } -ProgressiveDecoderContext::Status SkiaBmpContext::DecodeImage( - size_t frame_index) { +ProgressiveDecoderContext::Status SkiaBmpContext::DecodeImage() { if (!header_read_ || !decoder_) { return ProgressiveDecoderContext::Status::kError; } diff --git a/core/fxcodec/bmp/skia_bmp_context.h b/core/fxcodec/bmp/skia_bmp_context.h index 236b95cf90..844f5bb1b4 100644 --- a/core/fxcodec/bmp/skia_bmp_context.h +++ b/core/fxcodec/bmp/skia_bmp_context.h @@ -34,7 +34,7 @@ class SkiaBmpContext final : public ProgressiveDecoderContext { // ProgressiveDecoderContext: FX_FILESIZE GetAvailInput() const override; void Input(RetainPtr codec_memory) override; - ProgressiveDecoderContext::Status DecodeImage(size_t frame_index) override; + ProgressiveDecoderContext::Status DecodeImage() override; ProgressiveDecoderContext::Status ReadHeader( int32_t* width, diff --git a/core/fxcodec/gif/cfx_gifcontext.cpp b/core/fxcodec/gif/cfx_gifcontext.cpp index dca7fb1756..c783fa8bf4 100644 --- a/core/fxcodec/gif/cfx_gifcontext.cpp +++ b/core/fxcodec/gif/cfx_gifcontext.cpp @@ -191,13 +191,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; } diff --git a/core/fxcodec/gif/cfx_gifcontext.h b/core/fxcodec/gif/cfx_gifcontext.h index b35ba339a3..9a62100aae 100644 --- a/core/fxcodec/gif/cfx_gifcontext.h +++ b/core/fxcodec/gif/cfx_gifcontext.h @@ -61,7 +61,7 @@ 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; void SetInputBuffer(RetainPtr codec_memory); size_t GetFrameNum() const { return images_.size(); } diff --git a/core/fxcodec/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index 7dd20d6590..a760049637 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -474,7 +474,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)) { @@ -483,7 +483,7 @@ FXCODEC_STATUS ProgressiveDecoder::BmpContinueDecode() { status_ = error_status; return status_; } - read_res = context_->DecodeImage(0); + read_res = context_->DecodeImage(); } device_bitmap_ = nullptr; @@ -547,13 +547,12 @@ FXCODEC_STATUS ProgressiveDecoder::GifStartDecode() { options.bInterpolateBilinear = true; weight_horz_.CalculateWeights(src_width_, 0, src_width_, src_width_, 0, src_width_, options); - frame_cur_ = 0; status_ = FXCODEC_STATUS::kDecodeToBeContinued; return status_; } FXCODEC_STATUS ProgressiveDecoder::GifContinueDecode() { - ProgressiveDecoderContext::Status readRes = context_->DecodeImage(frame_cur_); + ProgressiveDecoderContext::Status readRes = context_->DecodeImage(); while (readRes == ProgressiveDecoderContext::Status::kContinue) { FXCODEC_STATUS error_status = FXCODEC_STATUS::kDecodeFinished; if (!GifReadMoreData(&error_status)) { @@ -562,7 +561,7 @@ FXCODEC_STATUS ProgressiveDecoder::GifContinueDecode() { status_ = error_status; return status_; } - readRes = context_->DecodeImage(frame_cur_); + readRes = context_->DecodeImage(); } if (readRes == ProgressiveDecoderContext::Status::kSuccess) { @@ -1279,7 +1278,6 @@ 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 diff --git a/core/fxcodec/progressive_decoder.h b/core/fxcodec/progressive_decoder.h index 84505159d8..9313da986a 100644 --- a/core/fxcodec/progressive_decoder.h +++ b/core/fxcodec/progressive_decoder.h @@ -202,7 +202,6 @@ class ProgressiveDecoder final : int src_row_ = 0; 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_; 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 From e9fc01804a0c5224ea780ad782abb8cfede628ef Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Fri, 7 Aug 2026 17:32:38 -0700 Subject: [PATCH 027/123] Migrate GIF decoder to ProgressiveDecoderDelegate and evacuate state Update CFX_GifContext to depend on ProgressiveDecoderDelegate instead of the nested CFX_GifContext::Delegate. Remove the nested Delegate class declaration. Evacuate GIF palette state, frame bounds, and scanline buffer from ProgressiveDecoder into CFX_GifContext so that record position parsing and scanline resampling are self-contained within CFX_GifContext. Update ProgressiveDecoder so it no longer inherits from CFX_GifContext::Delegate. TAG=agy CONV=bbac8eab-d30e-43b6-895a-e5b8381bbaf9 Change-Id: I0834b3480ccf102015bd7cb28b62f1b2ad3ddd64 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154151 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- core/fxcodec/gif/cfx_gifcontext.cpp | 122 ++++++++++++++++---- core/fxcodec/gif/cfx_gifcontext.h | 44 ++++---- core/fxcodec/progressive_decoder.cpp | 161 ++++----------------------- core/fxcodec/progressive_decoder.h | 24 +--- 4 files changed, 139 insertions(+), 212 deletions(-) diff --git a/core/fxcodec/gif/cfx_gifcontext.cpp b/core/fxcodec/gif/cfx_gifcontext.cpp index c783fa8bf4..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() { @@ -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 9a62100aae..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); @@ -63,10 +50,14 @@ class CFX_GifContext : public ProgressiveDecoderContext { void Input(RetainPtr codec_memory) 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/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index a760049637..d7a862a278 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -66,10 +66,6 @@ namespace { constexpr size_t kBlockSize = 4096; -#ifdef PDF_ENABLE_XFA_PNG -constexpr double kPngGamma = 2.2; -#endif // PDF_ENABLE_XFA_PNG - std::unique_ptr CreateDecoderContext( FXCODEC_IMAGE_TYPE type, ProgressiveDecoder* delegate) { @@ -112,6 +108,14 @@ std::unique_ptr CreateDecoderContext( } } +#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; @@ -174,123 +178,11 @@ void ProgressiveDecoder::PngFinishedDecoding() { } #endif // PDF_ENABLE_XFA_PNG -#ifdef PDF_ENABLE_XFA_GIF -uint32_t ProgressiveDecoder::GifCurrentPosition() const { - uint32_t remain_size = - pdfium::checked_cast(context_->GetAvailInput()); - 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()) { - 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; - } - } - 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; - } - } - 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; - if (line < 0 || line >= src_height_) { - return; - } - ResampleScanline(pDIBitmap, line, decode_span, src_format_); -} -#endif // PDF_ENABLE_XFA_GIF - 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)) { @@ -303,7 +195,9 @@ bool ProgressiveDecoder::ReadMoreData(std::optional rcd_pos, } uint32_t ProgressiveDecoder::GetCurrentInputPosition() const { - return offset_; + uint32_t remain_size = + context_ ? pdfium::checked_cast(context_->GetAvailInput()) : 0; + return offset_ - remain_size; } bool ProgressiveDecoder::PrepareScanlineResampling( @@ -333,6 +227,7 @@ bool ProgressiveDecoder::PrepareScanlineResampling( if (!palette.empty()) { device_bitmap_->SetPalette(palette); + src_palette_ = DataVector(palette.begin(), palette.end()); } if (fill_argb.has_value()) { device_bitmap_->Clear(fill_argb.value()); @@ -519,7 +414,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)) { @@ -527,8 +422,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; @@ -547,33 +441,16 @@ FXCODEC_STATUS ProgressiveDecoder::GifStartDecode() { options.bInterpolateBilinear = true; weight_horz_.CalculateWeights(src_width_, 0, src_width_, src_width_, 0, src_width_, options); - 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(); - 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(); - } - - 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 diff --git a/core/fxcodec/progressive_decoder.h b/core/fxcodec/progressive_decoder.h index 9313da986a..28dc969dee 100644 --- a/core/fxcodec/progressive_decoder.h +++ b/core/fxcodec/progressive_decoder.h @@ -23,14 +23,11 @@ #include "core/fxge/dib/cstretchengine.h" #include "core/fxge/dib/fx_dib.h" -#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 +class CFX_CodecMemory; class CFX_DIBitmap; class IFX_SeekableReadStream; @@ -40,9 +37,6 @@ class CFX_DIBAttribute; class ProgressiveDecoderContext; class ProgressiveDecoder final : -#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 @@ -76,16 +70,6 @@ class ProgressiveDecoder final : 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 - // ProgressiveDecoderContextDelegate: bool ReadMoreData(std::optional rcd_pos, FXCODEC_STATUS* err_status) override; @@ -202,12 +186,6 @@ class ProgressiveDecoder final : int src_row_ = 0; Format src_format_ = Format::kInvalid; size_t frame_number_ = 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 }; } // namespace fxcodec From 2ba254b6ac9f1ee3d91544e299e8a06b9d0f8c18 Mon Sep 17 00:00:00 2001 From: "pdfium-autoroll@skia-public.iam.gserviceaccount.com" Date: Sun, 9 Aug 2026 21:34:13 -0700 Subject: [PATCH 028/123] Roll Zlib from 8b3aa8a1cd75 to c5cc9edf8992 (2 revisions) https://chromium.googlesource.com/chromium/src/third_party/zlib.git/+log/8b3aa8a1cd75..c5cc9edf8992 If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/zlib-pdfium Please CC thestig@chromium.org,thestig@google.com on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Bug: None Tbr: thestig@google.com Change-Id: I37c65ce53f009ad9dc2c2b9bb3675f3181b55873 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154670 Commit-Queue: pdfium-autoroll@skia-public.iam.gserviceaccount.com Bot-Commit: pdfium-autoroll@skia-public.iam.gserviceaccount.com --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 8e17191528..e2b800d841 100644 --- a/DEPS +++ b/DEPS @@ -265,7 +265,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling zlib # and whatever else without interference from each other. - 'zlib_revision': '8b3aa8a1cd7585c0c4c67351481227b046a662a0', + 'zlib_revision': 'c5cc9edf8992ff36dfca3c2c4f6c8327a66b6782', } # Only these hosts are allowed for dependencies in this DEPS file. From c36f3d81f70166c090360ceac5c1a25c6735e8be Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Mon, 10 Aug 2026 10:40:13 -0700 Subject: [PATCH 029/123] Migrate JPEG decoder call into ProgressiveDecoderContextDelegate Update ProgressiveDecoder so that JPEG start and continue decoding delegate to LibjpegJpegContext. TAG=agy CONV=bbac8eab-d30e-43b6-895a-e5b8381bbaf9 Change-Id: I2a8ee8ff2ced31a238ab8272e52f96bd4f8a932a Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154152 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- core/fxcodec/jpeg/libjpeg_jpeg_context.cpp | 107 ++++++++++++++++++--- core/fxcodec/jpeg/libjpeg_jpeg_context.h | 20 +++- core/fxcodec/progressive_decoder.cpp | 88 +++-------------- core/fxcodec/progressive_decoder.h | 2 - 4 files changed, 126 insertions(+), 91 deletions(-) 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/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index d7a862a278..ecda3024be 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -83,7 +83,7 @@ std::unique_ptr CreateDecoderContext( return std::make_unique(delegate); #endif // PDF_ENABLE_XFA_GIF case FXCODEC_IMAGE_JPG: { - auto context = std::make_unique(); + auto context = std::make_unique(delegate); if (!context->create_ok_) { return nullptr; } @@ -116,21 +116,6 @@ 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; @@ -503,70 +488,25 @@ bool ProgressiveDecoder::JpegDetectImageTypeInBuffer( } 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_; - } - } - 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_ = Format::k8bppGray; - break; - case 3: - src_format_ = Format::kRgb; - break; - case 4: - src_format_ = Format::kCmyk; - break; + 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_; } 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_ == Format::kRgb) { - 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_++; + 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_; } #ifdef PDF_ENABLE_XFA_PNG diff --git a/core/fxcodec/progressive_decoder.h b/core/fxcodec/progressive_decoder.h index 28dc969dee..a921b5b8bf 100644 --- a/core/fxcodec/progressive_decoder.h +++ b/core/fxcodec/progressive_decoder.h @@ -14,7 +14,6 @@ #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" @@ -183,7 +182,6 @@ 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; Format src_format_ = Format::kInvalid; size_t frame_number_ = 0; }; From 17c6f449c95bb56032affba13d833644b6aadfa2 Mon Sep 17 00:00:00 2001 From: Aryan Date: Mon, 10 Aug 2026 11:43:16 -0700 Subject: [PATCH 030/123] Use LoadScopedSavedPage() in embedder tests This CL replaces calls to LoadSavedPage() and CloseSavedPage() with LoadScopedSavedPage() in fpdf_editpath_embeddertest.cpp and fpdf_ppo_embeddertest.cpp to automatically manage page lifetimes. This CL also makes LoadSavedPage() and CloseSavedPage() private to enforce the use of scoped types across tests. Bug: 407147676 Change-Id: I70dcd36ca37ccc119e2cfac46df01ad2a90fb528 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154650 Reviewed-by: Lei Zhang Reviewed-by: Andy Phan Commit-Queue: Lei Zhang --- fpdfsdk/fpdf_editpath_embeddertest.cpp | 41 +++++++++++++------------- fpdfsdk/fpdf_ppo_embeddertest.cpp | 14 ++++----- testing/embedder_test.h | 10 +++++-- 3 files changed, 33 insertions(+), 32 deletions(-) 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_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/testing/embedder_test.h b/testing/embedder_test.h index 6daf245335..505cd83484 100644 --- a/testing/embedder_test.h +++ b/testing/embedder_test.h @@ -372,8 +372,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 +426,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); From 3abda657c073f89dc6b39bbad8f7dfe2fbf17fd4 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Mon, 10 Aug 2026 11:59:46 -0700 Subject: [PATCH 031/123] fxge: fix missing windows include in cfx_renderdevice.h This resulted in a build failure here: In file included from .../pdfium/core/fxge/cfx_drawutils.cpp:13: .../core/fxge/cfx_renderdevice.h(74,7): error: unknown type name 'HDC' 74 | HDC hdc, | ^ It seems this started after commit fc790866bf26d977b2ea3a0c3938bb52895309ea (Consolidate CFX_RenderDevice and remove CPDF_WindowsRenderDevice, 2026-06-29): the Windows-DC code was moved from cpdf_windowsrenderdevice.h to cfx_renderdevice.h but the windows.h include wasn't moved. Given that it looks like every other pdfium header that uses HDC includes directly (core/fxge/win32/cpsoutput.h etc.), just add the missing include here as well, conditionally. Change-Id: Ie9920b2031148f7781862502905d999ce045b014 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154690 Reviewed-by: Tom Sepez Reviewed-by: Lei Zhang Commit-Queue: Lei Zhang --- core/fxge/cfx_renderdevice.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/fxge/cfx_renderdevice.h b/core/fxge/cfx_renderdevice.h index 8f427ce01c..4114b98840 100644 --- a/core/fxge/cfx_renderdevice.h +++ b/core/fxge/cfx_renderdevice.h @@ -19,6 +19,10 @@ #include "core/fxge/dib/fx_dib.h" #include "core/fxge/renderdevicedriver_iface.h" +#if BUILDFLAG(IS_WIN) +#include +#endif + class CFX_DIBBase; class CFX_DIBitmap; class CFX_Font; From 78ba1465f0fe387be3c9845a0037f558246cdd8d Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Mon, 10 Aug 2026 15:27:20 -0700 Subject: [PATCH 032/123] Migrate PNG decoder to ProgressiveDecoderDelegate and evacuate state Update LibpngPngContext and SkiaPngContext to depend on ProgressiveDecoderDelegate instead of PngDecoderDelegate. Remove core/fxcodec/png/png_decoder_delegate.h and remove it from BUILD.gn. Evacuate PNG metadata flag and stream position resetting from ProgressiveDecoder into the PNG decoder contexts so start/continue decoding are self-contained. Update ProgressiveDecoder so it no longer inherits from PngDecoderDelegate. TAG=agy CONV=bbac8eab-d30e-43b6-895a-e5b8381bbaf9 Change-Id: Iccd271f0d5a1d9dd7e46fa86e02ce2fbd5c7fcb2 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154153 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- core/fxcodec/BUILD.gn | 2 - core/fxcodec/png/libpng_png_context.cpp | 72 +++++++-- core/fxcodec/png/libpng_png_context.h | 23 ++- core/fxcodec/png/png_decoder_delegate.h | 48 ------ core/fxcodec/png/skia_png_context.cpp | 150 ++++++++++++------ core/fxcodec/png/skia_png_context.h | 68 ++++---- core/fxcodec/progressive_decoder.cpp | 146 +++++------------ core/fxcodec/progressive_decoder.h | 25 +-- .../progressive_decoder_context_delegate.h | 41 +++-- 9 files changed, 275 insertions(+), 300 deletions(-) delete mode 100644 core/fxcodec/png/png_decoder_delegate.h diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index 7810e02711..771010e239 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -129,8 +129,6 @@ source_set("fxcodec") { ] } 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 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/skia_png_context.cpp b/core/fxcodec/png/skia_png_context.cpp index 144c1b3c61..66ba3cdbdd 100644 --- a/core/fxcodec/png/skia_png_context.cpp +++ b/core/fxcodec/png/skia_png_context.cpp @@ -7,11 +7,13 @@ #include #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/core/SkColorSpace.h" #include "third_party/skia/include/core/SkColorType.h" @@ -29,9 +31,11 @@ namespace fxcodec { namespace { -sk_sp GetTargetColorSpace(double gamma) { +constexpr double kPngGamma = 2.2; + +sk_sp GetTargetColorSpace() { const skcms_TransferFunction fn = { - .g = static_cast(1.0 / gamma), + .g = static_cast(1.0 / kPngGamma), .a = 1.0f, }; return SkColorSpace::MakeRGB(fn, SkNamedGamut::kSRGB); @@ -39,72 +43,83 @@ sk_sp GetTargetColorSpace(double gamma) { } // namespace -SkiaPngContext::SkiaPngContext(PngDecoderDelegate* delegate) +SkiaPngContext::SkiaPngContext(ProgressiveDecoderContextDelegate* 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. +bool SkiaPngContext::ReadHeader(RetainPtr codec_memory) { + if (state_ == State::kGotDecoder || state_ == State::kStartedDecode || + state_ == State::kFinishedDecoding) { + return true; + } + if (state_ == State::kError) { + return false; + } + + CHECK_EQ(state_, State::kNoDecoder); + CHECK(!decoder_); + + // `ProgressiveDecoder` guarantees that all calls to + // `ReadHeader`/`ProcessData` 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; + 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); + decoder_ = SkPngRustDecoder::Decode(std::move(stream), &result); #else - decoder_ = SkPngDecoder::Decode(std::move(stream), &result); + 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 + switch (result) { + case SkCodec::kSuccess: { + SkImageInfo info = decoder_->getInfo(); + // Notifies the delegate of image dimensions and metadata. + if (!delegate_->PrepareDirectOutput( + info.width(), info.height(), + ProgressiveDecoderContextDelegate::Format::kArgb)) { + decoder_.reset(); + state_ = State::kError; + return false; } - break; + state_ = State::kGotDecoder; + return true; + } + 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 + } +} + +bool SkiaPngContext::ProcessData(RetainPtr codec_memory) { + if (state_ == State::kNoDecoder) { + if (!ReadHeader(std::move(codec_memory))) { + return false; + } + if (state_ == State::kNoDecoder) { + return true; // Needs more input to finish reading header. } - 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); + CHECK(bitmap_); + SkImageInfo dst_info = decoder_->getInfo() + .makeColorSpace(GetTargetColorSpace()) + .makeColorType(kBGRA_8888_SkColorType); - pdfium::span dst_buffer = delegate_->PngAskImageBuf(); + pdfium::span dst_buffer = delegate_->AskImageBuf(); FX_SAFE_SIZE_T row_bytes = dst_buffer.size(); row_bytes /= dst_info.height(); @@ -128,7 +143,6 @@ bool SkiaPngContext::ContinueDecode(RetainPtr codec_memory) { switch (result) { case SkCodec::kSuccess: decoder_.reset(); - delegate_->PngFinishedDecoding(); state_ = State::kFinishedDecoding; return true; // finished decoding case SkCodec::kIncompleteInput: @@ -140,4 +154,38 @@ bool SkiaPngContext::ContinueDecode(RetainPtr codec_memory) { } } +void SkiaPngContext::Input(RetainPtr codec_memory) { + codec_memory_ = std::move(codec_memory); +} + +FXCODEC_STATUS SkiaPngContext::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 SkiaPngContext::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/png/skia_png_context.h b/core/fxcodec/png/skia_png_context.h index f010bce38d..16e0593fac 100644 --- a/core/fxcodec/png/skia_png_context.h +++ b/core/fxcodec/png/skia_png_context.h @@ -8,33 +8,32 @@ #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/fxge/dib/cfx_dibitmap.h" class SkCodec; namespace fxcodec { +class ProgressiveDecoderContextDelegate; + class SkiaPngContext final : public ProgressiveDecoderContext { public: // Caller needs to guarantee that `delegate` lives longer than // `SkiaPngContext`. - explicit SkiaPngContext(PngDecoderDelegate* delegate); + explicit SkiaPngContext(ProgressiveDecoderContextDelegate* 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); + // ProgressiveDecoderContext: + void Input(RetainPtr codec_memory) override; + + FXCODEC_STATUS StartDecode(RetainPtr bitmap); + FXCODEC_STATUS ContinueDecode(); + + bool ReadHeader(RetainPtr codec_memory); private: enum class State { @@ -42,36 +41,39 @@ class SkiaPngContext final : public ProgressiveDecoderContext { // // 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. + // `decoder_` is ready to accept incremental decoding inputs. + // + // This state is entered via: + // * `StartDecode` - initial attempt to configure `decoder_` + // * `kError` when `ContinueDecode` retries after receiving more input + // data from `delegate_->ReadMoreData(...)` // - // `incrementalDecode` didn't run yet, or returned `kIncompleteInput`. + // From this state `ContinueDecode` transitions to: + // * `kFinishedDecoding` - when all pixels have been decoded + // * `kError` - when `decoder_` needs more input or encounters a fatal error kStartedDecode, - - // `decoder_` is null. All pixels have been decoded to - // `delegate_->PngAskImageBuf`. + // `decoder_` is null. // - // This is a terminal state. + // This state is entered when `ContinueDecode` successfully decodes all + // pixels. kFinishedDecoding, - - // `decoder_` is null. A non-recoverable (i.e. non-`kIncompleteInput`-kind) - // error has been encountered. + // `decoder_` is null. // - // This is a terminal state. + // This state is entered when `ContinueDecode` encounters an error (either a + // fatal decoding error, or a recoverable error when `decoder_` runs out of + // input data). From this state `ContinueDecode` attempts to read more data + // from `delegate_->ReadMoreData(...)` and transitions to `kStartedDecode` + // on success. kError, }; - State state_ = State::kNoDecoder; - UnownedPtr const delegate_; + bool ProcessData(RetainPtr codec_memory); + + State state_ = State::kNoDecoder; + UnownedPtr const delegate_; + RetainPtr bitmap_; 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 diff --git a/core/fxcodec/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index ecda3024be..f5625a3817 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -44,7 +44,6 @@ #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 @@ -108,61 +107,12 @@ std::unique_ptr CreateDecoderContext( } } -#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 - } // 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_ = Format::kArgb; - - return false; - } - - CHECK_EQ(width, src_width_); - CHECK_EQ(height, src_height_); - CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); - *gamma = kPngGamma; - 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_, Format::kArgb); - return device_bitmap_->GetWritableScanline(line); -} - -pdfium::span ProgressiveDecoder::PngAskImageBuf() { - CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra); - CHECK_EQ(src_format_, Format::kArgb); - return device_bitmap_->GetWritableBuffer(); -} - -void ProgressiveDecoder::PngFinishedDecoding() { - status_ = FXCODEC_STATUS::kDecodeFinished; -} -#endif // PDF_ENABLE_XFA_PNG - bool ProgressiveDecoder::ReadMoreData(std::optional rcd_pos, FXCODEC_STATUS* err_status) { if (rcd_pos.has_value()) { @@ -197,19 +147,12 @@ bool ProgressiveDecoder::PrepareScanlineResampling( src_width_ = src_width; src_height_ = src_height; src_format_ = src_format; - // For formats other than kArgb, these values are already reliably set - // during the header reading phase. - if (src_format_ == Format::kArgb) { - src_bits_per_component_ = 8; - src_components_count_ = 4; - } 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()); @@ -232,6 +175,20 @@ void ProgressiveDecoder::ResampleScanline( ResampleScanline(device_bitmap_, line, decode_buf_, src_format_); } +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; +} + pdfium::span ProgressiveDecoder::AskScanlineBuf(int line) { CHECK_GE(line, 0); CHECK_LT(line, src_height_); @@ -510,20 +467,6 @@ FXCODEC_STATUS ProgressiveDecoder::JpegContinueDecode() { } #ifdef PDF_ENABLE_XFA_PNG -bool ProgressiveDecoder::PngReadMoreData() { - size_t unconsumed_bytes = codec_memory_->GetUnconsumedSpan().size(); - if (!ReadMoreDataInternal(unconsumed_bytes, &status_)) { - return false; - } - -#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) - auto* ctx = static_cast(context_.get()); -#else - auto* ctx = static_cast(context_.get()); -#endif - return ctx->ContinueDecode(codec_memory_); -} - bool ProgressiveDecoder::PngDetectImageTypeInBuffer() { context_ = CreateDecoderContext(FXCODEC_IMAGE_PNG, this); if (!context_) { @@ -537,19 +480,20 @@ bool ProgressiveDecoder::PngDetectImageTypeInBuffer() { 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()) { + 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; + } } } - // 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() { @@ -560,37 +504,27 @@ 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 - // `Format::kArgb` (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_, Format::kArgb); - - // 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_USE_SKIA) && 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_USE_SKIA) && 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 diff --git a/core/fxcodec/progressive_decoder.h b/core/fxcodec/progressive_decoder.h index a921b5b8bf..e02bda3c6f 100644 --- a/core/fxcodec/progressive_decoder.h +++ b/core/fxcodec/progressive_decoder.h @@ -18,14 +18,9 @@ #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_PNG -#include "core/fxcodec/png/png_decoder_delegate.h" -#endif // PDF_ENABLE_XFA_PNG - class CFX_CodecMemory; class CFX_DIBitmap; class IFX_SeekableReadStream; @@ -35,11 +30,7 @@ namespace fxcodec { class CFX_DIBAttribute; class ProgressiveDecoderContext; -class ProgressiveDecoder final : -#ifdef PDF_ENABLE_XFA_PNG - public PngDecoderDelegate, -#endif // PDF_ENABLE_XFA_PNG - public ProgressiveDecoderContextDelegate { +class ProgressiveDecoder final : public ProgressiveDecoderContextDelegate { public: using Format = ProgressiveDecoderContextDelegate::Format; @@ -61,14 +52,6 @@ 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 - // ProgressiveDecoderContextDelegate: bool ReadMoreData(std::optional rcd_pos, FXCODEC_STATUS* err_status) override; @@ -80,6 +63,9 @@ class ProgressiveDecoder final : 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; @@ -172,9 +158,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; diff --git a/core/fxcodec/progressive_decoder_context_delegate.h b/core/fxcodec/progressive_decoder_context_delegate.h index 1cd60e4869..c3bcae8efa 100644 --- a/core/fxcodec/progressive_decoder_context_delegate.h +++ b/core/fxcodec/progressive_decoder_context_delegate.h @@ -39,24 +39,12 @@ class ProgressiveDecoderContextDelegate { // Returns the current reading offset in the underlying input stream. virtual uint32_t GetCurrentInputPosition() const = 0; - // Codec decoders use one of two mutually exclusive output paths: - // - // A. Scanline Resampling (Methods 1 and 2; e.g., BMP, GIF, JPEG): - // Used by streaming line-by-line decoders or whenever scaling, - // vertical orientation flipping, or color conversion is required. - // Accepts arbitrary source pixel formats (k8bppGray, kRgb, kCmyk, - // etc.) and resamples each scanline into the destination bitmap. - // - // B. Direct In-Place Output (Methods 3 and 4; e.g., PNG): - // Used by decoders that decompress directly into destination - // memory without intermediate buffers. Strictly requires 1:1 - // scaling and an ARGB/BGRA destination format so decompressed - // bytes can be written safely in-place. - - // 1. Scaling: Prepares resampler weights and scanline buffer. 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. during image - // type detection). + // 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, @@ -64,15 +52,22 @@ class ProgressiveDecoderContextDelegate { pdfium::span palette = {}, std::optional fill_argb = std::nullopt) = 0; - // 2. Scaling: Resamples a single decoded scanline into the device bitmap. + // Resamples a single decoded scanline into the device bitmap (e.g., BMP, + // GIF, JPEG). virtual void ResampleScanline(int line, pdfium::span src_span) = 0; - // 3. Direct Output: Returns a writable span for a scanline in the device - // bitmap. - virtual pdfium::span AskScanlineBuf(int line) = 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; - // 4. Direct Output: Returns a writable span for the entire device bitmap. + // 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; }; From 6f8882cc23989a000c4164adbf84204e59c5d2b6 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Mon, 10 Aug 2026 15:55:32 -0700 Subject: [PATCH 033/123] Remove per-codec pdf_enable_xfa_ GN arguments and macros Consolidate individual XFA image codec support (BMP, GIF, PNG, TIFF) under the single pdf_enable_xfa build flag and PDF_ENABLE_XFA macro. The historic requirement to selectively omit individual image codecs to restrict library size is obsolete. Any build with XFA forms enabled expects support for all standard XFA image formats. Remove pdf_enable_xfa_bmp, pdf_enable_xfa_gif, pdf_enable_xfa_png, and pdf_enable_xfa_tiff from pdfium.gni, BUILD.gn, and testing/fuzzers. Replace the corresponding PDF_ENABLE_XFA_ preprocessor defines with PDF_ENABLE_XFA throughout core/fxcodec, xfa, and tests. TAG=agy CONV=0ddacdfb-13f6-47af-a8e8-cc5336eb23a5 Bug: 543059197 Change-Id: Id2ef13d5339529225525d5d2fe4f9ab0cc47ad9d Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154591 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- BUILD.gn | 12 -- core/fxcodec/BUILD.gn | 95 +++++++------- core/fxcodec/fx_codec_def.h | 8 -- core/fxcodec/progressive_decoder.cpp | 80 ++---------- core/fxcodec/progressive_decoder.h | 13 +- core/fxcodec/progressive_decoder_unittest.cpp | 6 - core/fxcodec/tiff/libtiff_tiff_context.h | 4 +- pdfium.gni | 12 -- testing/fuzzers/BUILD.gn | 116 ++++++++---------- third_party/BUILD.gn | 9 +- xfa/fxfa/parser/cxfa_node.cpp | 13 -- 11 files changed, 110 insertions(+), 258 deletions(-) diff --git a/BUILD.gn b/BUILD.gn index 57a42b0b1b..a62e4d6410 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -149,18 +149,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" ] - } } } diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index 771010e239..3133782b15 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -93,6 +93,12 @@ source_set("fxcodec") { sources += [ "cfx_codec_memory.cpp", "cfx_codec_memory.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", "jpeg/libjpeg_jpeg_context.cpp", "jpeg/libjpeg_jpeg_context.h", "progressive_decoder.cpp", @@ -100,59 +106,42 @@ source_set("fxcodec") { "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) { - 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", - ] - } - } - if (pdf_enable_xfa_gif) { + deps += [ "../../third_party:tiff" ] + if (pdf_enable_rust_bmp) { 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", + "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", ] } - if (pdf_enable_xfa_png) { - # 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) { + + # 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 += [ - "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 += [ @@ -181,14 +170,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/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/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index f5625a3817..223683df63 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -12,8 +12,10 @@ #include "build/build_config.h" #include "core/fxcodec/cfx_codec_memory.h" +#include "core/fxcodec/gif/cfx_gifcontext.h" #include "core/fxcodec/jpeg/libjpeg_jpeg_context.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" @@ -31,19 +33,15 @@ #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" #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 // 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 @@ -53,11 +51,6 @@ #else #include "core/fxcodec/png/libpng_png_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 namespace fxcodec { @@ -69,18 +62,14 @@ 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); #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(delegate); if (!context->create_ok_) { @@ -88,7 +77,6 @@ std::unique_ptr CreateDecoderContext( } return context; } -#ifdef PDF_ENABLE_XFA_PNG case FXCODEC_IMAGE_PNG: #if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) return std::make_unique(delegate); @@ -101,7 +89,6 @@ std::unique_ptr CreateDecoderContext( return context; } #endif -#endif // PDF_ENABLE_XFA_PNG default: return nullptr; } @@ -203,8 +190,6 @@ pdfium::span ProgressiveDecoder::AskImageBuf() { return device_bitmap_->GetWritableBuffer(); } -#ifdef PDF_ENABLE_XFA_BMP - bool ProgressiveDecoder::BmpDetectImageTypeInBuffer( CFX_DIBAttribute* pAttribute) { std::unique_ptr pBmcontext = @@ -330,9 +315,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 @@ -395,7 +378,6 @@ FXCODEC_STATUS ProgressiveDecoder::GifContinueDecode() { file_ = nullptr; return status_; } -#endif // PDF_ENABLE_XFA_GIF bool ProgressiveDecoder::JpegReadMoreData(FXCODEC_STATUS* err_status) { FX_SAFE_SIZE_T avail_input = context_->GetAvailInput(); @@ -466,7 +448,6 @@ FXCODEC_STATUS ProgressiveDecoder::JpegContinueDecode() { return status_; } -#ifdef PDF_ENABLE_XFA_PNG bool ProgressiveDecoder::PngDetectImageTypeInBuffer() { context_ = CreateDecoderContext(FXCODEC_IMAGE_PNG, this); if (!context_) { @@ -527,9 +508,7 @@ FXCODEC_STATUS ProgressiveDecoder::PngContinueDecode() { } return status_; } -#endif // PDF_ENABLE_XFA_PNG -#ifdef PDF_ENABLE_XFA_TIFF bool ProgressiveDecoder::TiffDetectImageTypeFromFile( CFX_DIBAttribute* pAttribute) { auto ctx = std::make_unique(); @@ -560,15 +539,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)); @@ -585,23 +561,15 @@ 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; @@ -933,16 +901,10 @@ void ProgressiveDecoder::Resample(const RetainPtr& pDeviceBitmap, FXDIB_Format ProgressiveDecoder::GetBitmapFormat() const { switch (image_type_) { case FXCODEC_IMAGE_JPG: -#ifdef PDF_ENABLE_XFA_BMP 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` @@ -957,20 +919,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) { @@ -1000,7 +955,6 @@ std::pair ProgressiveDecoder::GetFrames() { return {status_, 0}; } } -#endif // PDF_ENABLE_XFA_GIF default: return {FXCODEC_STATUS::kError, 0}; } @@ -1031,25 +985,17 @@ FXCODEC_STATUS ProgressiveDecoder::StartDecode(RetainPtr bitmap) { 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; } @@ -1063,22 +1009,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 e02bda3c6f..42733c12c0 100644 --- a/core/fxcodec/progressive_decoder.h +++ b/core/fxcodec/progressive_decoder.h @@ -21,6 +21,10 @@ #include "core/fxge/dib/cstretchengine.h" #include "core/fxge/dib/fx_dib.h" +#ifndef PDF_ENABLE_XFA +#error "XFA only" +#endif + class CFX_CodecMemory; class CFX_DIBitmap; class IFX_SeekableReadStream; @@ -82,32 +86,23 @@ class ProgressiveDecoder final : public ProgressiveDecoderContextDelegate { 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); diff --git a/core/fxcodec/progressive_decoder_unittest.cpp b/core/fxcodec/progressive_decoder_unittest.cpp index 173c292763..3b8f396059 100644 --- a/core/fxcodec/progressive_decoder_unittest.cpp +++ b/core/fxcodec/progressive_decoder_unittest.cpp @@ -52,7 +52,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, @@ -311,9 +310,7 @@ TEST_F(ProgressiveDecoderTest, LargeBmp) { EXPECT_EQ(0, scanline[kScanlineSize - 1]); } } -#endif // PDF_ENABLE_XFA_BMP -#ifdef PDF_ENABLE_XFA_GIF TEST_F(ProgressiveDecoderTest, Gif87a) { static constexpr uint8_t kInput[] = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x01, @@ -492,9 +489,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 +623,5 @@ TEST_F(ProgressiveDecoderTest, BigPng) { EXPECT_THAT(bitmap->GetScanline(0).first(4u), ElementsAre(0x00, 0xFF, 0x00, 0xFF)); } -#endif } // 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/pdfium.gni b/pdfium.gni index fcf2cb8244..cbd606b6c3 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, 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/third_party/BUILD.gn b/third_party/BUILD.gn index 548d365616..e4e4e8e7db 100644 --- a/third_party/BUILD.gn +++ b/third_party/BUILD.gn @@ -221,7 +221,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 = [ @@ -331,8 +332,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" ] @@ -452,7 +453,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/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; } From d6eec1265c46584f5b3569d046055a8a62ff589a Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Mon, 10 Aug 2026 17:10:06 -0700 Subject: [PATCH 034/123] Add unit test for CPVT_Section::OutputLines() on an empty section Add CPVT_SectionTest.OutputLines_EmptySection to test CPVT_Section::OutputLines() behavior when layout formatting runs on an empty section. Verify that CPVT_Section::Rearrange() successfully creates a single default empty line in `line_array_` even when `word_array_` has size 0, and correctly calculates line height metrics based on the font. TAG=agy CONV=52a116c6-afa2-4b48-9c03-c7750f7e9a30 Change-Id: Ieaec1c1bb179da199fce72a10525b76361243174 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154270 Reviewed-by: Andy Phan Commit-Queue: Lei Zhang --- core/fpdfdoc/cpvt_section_unittest.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/fpdfdoc/cpvt_section_unittest.cpp b/core/fpdfdoc/cpvt_section_unittest.cpp index 7b45768672..0b0bc21389 100644 --- a/core/fpdfdoc/cpvt_section_unittest.cpp +++ b/core/fpdfdoc/cpvt_section_unittest.cpp @@ -278,3 +278,29 @@ 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); +} From 694c027e9dd32529134128ddbf712a6674965395 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Tue, 11 Aug 2026 12:04:05 -0700 Subject: [PATCH 035/123] Add unit tests for JpegModule JpegModule previously lacked dedicated unit test coverage for its static decoding APIs. This ensures backend parity and prevents regressions when introducing alternate decoder implementations, such as a Skia/Rust-based JPEG scanline decoder. Also handle empty spans safely in JpegScanSOI() and CreateDecoder() to prevent debug assertion failures on invalid inputs. TAG=agy CONV=e5afc866-aadd-4927-8820-ff947090473b Change-Id: I92075744c8a50a184b1eb6379f952dc79a1591e3 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154870 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- core/fxcodec/BUILD.gn | 1 + core/fxcodec/jpeg/jpegmodule.cpp | 6 +- core/fxcodec/jpeg/jpegmodule_unittest.cpp | 128 ++++++++++++++++++++++ 3 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 core/fxcodec/jpeg/jpegmodule_unittest.cpp diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index 3133782b15..a09fe3de24 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -160,6 +160,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 = [ diff --git a/core/fxcodec/jpeg/jpegmodule.cpp b/core/fxcodec/jpeg/jpegmodule.cpp index 031c5bab32..c6db82bd9e 100644 --- a/core/fxcodec/jpeg/jpegmodule.cpp +++ b/core/fxcodec/jpeg/jpegmodule.cpp @@ -27,7 +27,9 @@ static pdfium::span JpegScanSOI( pdfium::span src_span) { - DCHECK(!src_span.empty()); + 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) { @@ -413,8 +415,6 @@ 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)) { diff --git a/core/fxcodec/jpeg/jpegmodule_unittest.cpp b/core/fxcodec/jpeg/jpegmodule_unittest.cpp new file mode 100644 index 0000000000..20da9a6a15 --- /dev/null +++ b/core/fxcodec/jpeg/jpegmodule_unittest.cpp @@ -0,0 +1,128 @@ +// 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); +} + +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 From 6d6af7512c9f69268efb67cb81a0fc3bd3aa4712 Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Tue, 11 Aug 2026 12:47:34 -0700 Subject: [PATCH 036/123] Handle CFX_BidiResolver failure in CPVT_Section Check for empty visual runs in OutputLines() and push a default LTR ResolvedRun spanning the line length. Prevent fatal CHECK assertion crashes when GetVisualRunsForLine() returns an empty vector for a line containing a Unicode paragraph separator (such as 0x001D). Add testing to verify safe fallback layout handling on non-empty sections that produce zero visual runs. Bug: 544246006 Change-Id: Ib80efb5149737ea06c54b731718219e991219bb6 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154710 Reviewed-by: Lei Zhang Reviewed-by: Andy Phan Commit-Queue: Seung Hyun Jin --- core/fpdfdoc/cpvt_section.cpp | 4 +++- core/fpdfdoc/cpvt_section_unittest.cpp | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/core/fpdfdoc/cpvt_section.cpp b/core/fpdfdoc/cpvt_section.cpp index 0feb0d66f4..b03c3e1c53 100644 --- a/core/fpdfdoc/cpvt_section.cpp +++ b/core/fpdfdoc/cpvt_section.cpp @@ -744,7 +744,9 @@ CPVT_FloatRect CPVT_Section::OutputLines(const CPVT_FloatRect& rect) const { CHECK(bidi_resolver); std::vector visual_runs = bidi_resolver->GetVisualRunsForLine(line_start, line_length); - CHECK(!visual_runs.empty()); + 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 0b0bc21389..50856339c5 100644 --- a/core/fpdfdoc/cpvt_section_unittest.cpp +++ b/core/fpdfdoc/cpvt_section_unittest.cpp @@ -304,3 +304,22 @@ TEST_F(CPVT_SectionTest, OutputLines_EmptySection) { 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); +} From a178a0ebe897abfdfd08978f9e9622ee36d764e3 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Tue, 11 Aug 2026 12:55:11 -0700 Subject: [PATCH 037/123] Move jpegmodule.cpp to libjpeg_scanline_decoder.cpp Move core/fxcodec/jpeg/jpegmodule.cpp to core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp in preparation for extracting LibjpegScanlineDecoder and re-introducing JpegModule as a facade. Bypass-Check-License: Moved file TAG=agy CONV=e5afc866-aadd-4927-8820-ff947090473b Change-Id: I4282e3394796f1029831f8670ec803daaae9f74a Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154850 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- core/fxcodec/BUILD.gn | 2 +- .../jpeg/{jpegmodule.cpp => libjpeg_scanline_decoder.cpp} | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) rename core/fxcodec/jpeg/{jpegmodule.cpp => libjpeg_scanline_decoder.cpp} (99%) diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index a09fe3de24..5eb57fcc15 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -60,8 +60,8 @@ source_set("fxcodec") { "jbig2/jbig2_trd_proc.h", "jpeg/jpeg_common.c", "jpeg/jpeg_common.h", - "jpeg/jpegmodule.cpp", "jpeg/jpegmodule.h", + "jpeg/libjpeg_scanline_decoder.cpp", "jpx/cjpx_decoder.cpp", "jpx/cjpx_decoder.h", "jpx/jpx_decode_utils.cpp", diff --git a/core/fxcodec/jpeg/jpegmodule.cpp b/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp similarity index 99% rename from core/fxcodec/jpeg/jpegmodule.cpp rename to core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp index c6db82bd9e..b26fe945ab 100644 --- a/core/fxcodec/jpeg/jpegmodule.cpp +++ b/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp @@ -4,8 +4,6 @@ // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com -#include "core/fxcodec/jpeg/jpegmodule.h" - #include #include #include @@ -15,6 +13,7 @@ #include "build/build_config.h" #include "core/fxcodec/cfx_codec_memory.h" #include "core/fxcodec/jpeg/jpeg_common.h" +#include "core/fxcodec/jpeg/jpegmodule.h" #include "core/fxcodec/scanlinedecoder.h" #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" From 3efbe60cf2a07c2817de3014790ea7c35c71ecaf Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Tue, 11 Aug 2026 13:37:06 -0700 Subject: [PATCH 038/123] Extract LibjpegScanlineDecoder from JpegModule Move JpegDecoder and JpegLoadInfo out of jpegmodule.cpp into dedicated LibjpegScanlineDecoder and LibjpegLoadInfo in libjpeg_scanline_decoder.h and libjpeg_scanline_decoder.cpp. Keep JpegModule as the high-level static facade calling through to LibjpegScanlineDecoder and LibjpegLoadInfo. TAG=agy CONV=f1b1440b-3e75-459c-a95f-15b694c7fa1f Change-Id: Iebe5cc07ac3e97d00e94074556c3c4762706733c Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154730 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- core/fxcodec/BUILD.gn | 2 + core/fxcodec/jpeg/jpegmodule.cpp | 163 +++++++++ .../fxcodec/jpeg/libjpeg_scanline_decoder.cpp | 345 +++++------------- core/fxcodec/jpeg/libjpeg_scanline_decoder.h | 77 ++++ 4 files changed, 325 insertions(+), 262 deletions(-) create mode 100644 core/fxcodec/jpeg/jpegmodule.cpp create mode 100644 core/fxcodec/jpeg/libjpeg_scanline_decoder.h diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index 5eb57fcc15..49a1744a44 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -60,8 +60,10 @@ source_set("fxcodec") { "jbig2/jbig2_trd_proc.h", "jpeg/jpeg_common.c", "jpeg/jpeg_common.h", + "jpeg/jpegmodule.cpp", "jpeg/jpegmodule.h", "jpeg/libjpeg_scanline_decoder.cpp", + "jpeg/libjpeg_scanline_decoder.h", "jpx/cjpx_decoder.cpp", "jpx/cjpx_decoder.h", "jpx/jpx_decode_utils.cpp", diff --git a/core/fxcodec/jpeg/jpegmodule.cpp b/core/fxcodec/jpeg/jpegmodule.cpp new file mode 100644 index 0000000000..8f271314eb --- /dev/null +++ b/core/fxcodec/jpeg/jpegmodule.cpp @@ -0,0 +1,163 @@ +// 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/jpegmodule.h" + +#include +#include + +#include "build/build_config.h" +#include "core/fxcodec/jpeg/libjpeg_scanline_decoder.h" +#include "core/fxcodec/scanlinedecoder.h" +#include "core/fxcrt/span.h" + +#if BUILDFLAG(IS_WIN) +#include + +#include "core/fxcodec/jpeg/jpeg_common.h" +#include "core/fxcrt/compiler_specific.h" +#include "core/fxcrt/fx_memory.h" +#include "core/fxcrt/fx_safe_types.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" +#endif // BUILDFLAG(IS_WIN) + +namespace fxcodec { + +// static +std::unique_ptr JpegModule::CreateDecoder( + pdfium::span src_span, + uint32_t width, + uint32_t height, + int nComps, + bool ColorTransform, + uint32_t scale_denom) { + return LibjpegScanlineDecoder::Create(src_span, width, height, nComps, + ColorTransform, scale_denom); +} + +// static +std::optional JpegModule::LoadInfo( + pdfium::span src_span) { + return LibjpegScanlineDecoder::LoadInfo(src_span); +} + +#if BUILDFLAG(IS_WIN) +bool JpegModule::JpegEncode(const RetainPtr& pSource, + uint8_t** dest_buf, + size_t* dest_size) { + jpeg_error_mgr jerr; + jerr.error_exit = jpeg_common_error_do_nothing; + jerr.emit_message = jpeg_common_error_do_nothing_int; + jerr.output_message = jpeg_common_error_do_nothing; + jerr.format_message = jpeg_common_error_do_nothing_char; + jerr.reset_error_mgr = jpeg_common_error_do_nothing; + + jpeg_compress_struct cinfo = {}; // Aggregate initialization. + static_assert(std::is_aggregate_v); + cinfo.err = &jerr; + jpeg_create_compress(&cinfo); + const int bytes_per_pixel = pSource->GetBPP() / 8; + uint32_t nComponents = bytes_per_pixel >= 3 ? 3 : 1; + uint32_t pitch = pSource->GetPitch(); + uint32_t width = pdfium::checked_cast(pSource->GetWidth()); + uint32_t height = pdfium::checked_cast(pSource->GetHeight()); + FX_SAFE_UINT32 safe_buf_len = width; + safe_buf_len *= height; + safe_buf_len *= nComponents; + safe_buf_len += 1024; + if (!safe_buf_len.IsValid()) { + return false; + } + + uint32_t dest_buf_length = safe_buf_len.ValueOrDie(); + *dest_buf = FX_TryAlloc(uint8_t, dest_buf_length); + const int MIN_TRY_BUF_LEN = 1024; + while (!(*dest_buf) && dest_buf_length > MIN_TRY_BUF_LEN) { + dest_buf_length >>= 1; + *dest_buf = FX_TryAlloc(uint8_t, dest_buf_length); + } + if (!(*dest_buf)) { + return false; + } + + jpeg_destination_mgr dest; + dest.init_destination = jpeg_common_dest_do_nothing; + dest.term_destination = jpeg_common_dest_do_nothing; + dest.empty_output_buffer = jpeg_common_dest_empty; + dest.next_output_byte = *dest_buf; + dest.free_in_buffer = dest_buf_length; + cinfo.dest = &dest; + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.input_components = nComponents; + if (nComponents == 1) { + cinfo.in_color_space = JCS_GRAYSCALE; + } else if (nComponents == 3) { + cinfo.in_color_space = JCS_RGB; + } else { + cinfo.in_color_space = JCS_CMYK; + } + uint8_t* line_buf = nullptr; + if (nComponents > 1) { + line_buf = FX_Alloc2D(uint8_t, width, nComponents); + } + + jpeg_set_defaults(&cinfo); + jpeg_start_compress(&cinfo, TRUE); + JSAMPROW row_pointer[1]; + JDIMENSION row; + while (cinfo.next_scanline < cinfo.image_height) { + pdfium::span src_scan = + pSource->GetScanline(cinfo.next_scanline); + if (nComponents > 1) { + uint8_t* dest_scan = line_buf; + if (nComponents == 3) { + UNSAFE_TODO({ + for (uint32_t i = 0; i < width; i++) { + ReverseCopy3Bytes(dest_scan, src_scan.data()); + dest_scan += 3; + src_scan = src_scan.subspan(static_cast(bytes_per_pixel)); + } + }); + } else { + UNSAFE_TODO({ + for (uint32_t i = 0; i < pitch; i++) { + *dest_scan++ = ~src_scan.front(); + src_scan = src_scan.subspan<1u>(); + } + }); + } + row_pointer[0] = line_buf; + } else { + row_pointer[0] = const_cast(src_scan.data()); + } + row = cinfo.next_scanline; + jpeg_write_scanlines(&cinfo, row_pointer, 1); + UNSAFE_TODO({ + if (cinfo.next_scanline == row) { + static constexpr size_t kJpegBlockSize = 1048576; + *dest_buf = + FX_Realloc(uint8_t, *dest_buf, dest_buf_length + kJpegBlockSize); + dest.next_output_byte = + *dest_buf + dest_buf_length - dest.free_in_buffer; + dest_buf_length += kJpegBlockSize; + dest.free_in_buffer += kJpegBlockSize; + } + }); + } + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + FX_Free(line_buf); + *dest_size = dest_buf_length - static_cast(dest.free_in_buffer); + + return true; +} +#endif // BUILDFLAG(IS_WIN) + +} // namespace fxcodec diff --git a/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp b/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp index b26fe945ab..0ecd1ffd47 100644 --- a/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp +++ b/core/fxcodec/jpeg/libjpeg_scanline_decoder.cpp @@ -1,31 +1,31 @@ -// 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. // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com +#include "core/fxcodec/jpeg/libjpeg_scanline_decoder.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/jpegmodule.h" -#include "core/fxcodec/scanlinedecoder.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" -#include "core/fxge/dib/cfx_dibbase.h" -#include "core/fxge/dib/fx_dib.h" -static pdfium::span JpegScanSOI( - pdfium::span src_span) { +namespace fxcodec { + +namespace { + +constexpr size_t kKnownBadHeaderWithInvalidHeightByteOffsetStarts[] = {94, 163}; + +pdfium::span JpegScanSOI(pdfium::span src_span) { if (src_span.size() < 2) { return src_span; } @@ -38,102 +38,6 @@ static pdfium::span JpegScanSOI( 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; -} - -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 @@ -142,9 +46,27 @@ uint32_t ScaledJpegSize(uint32_t dim, uint32_t scale_denom) { return (dim + scale_denom - 1) / scale_denom; } -JpegDecoder::JpegDecoder() = default; +} // 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; -JpegDecoder::~JpegDecoder() { +LibjpegScanlineDecoder::~LibjpegScanlineDecoder() { if (decompress_created_) { jpeg_common_destroy_decompress(&common_); } @@ -153,7 +75,7 @@ JpegDecoder::~JpegDecoder() { last_scanline_ = pdfium::span(); } -bool JpegDecoder::InitDecode(bool bAcceptKnownBadHeader) { +bool LibjpegScanlineDecoder::InitDecode(bool bAcceptKnownBadHeader) { common_.cinfo.err = &common_.error_mgr; common_.cinfo.client_data = &common_; if (!jpeg_common_create_decompress(&common_)) { @@ -239,12 +161,12 @@ bool JpegDecoder::InitDecode(bool bAcceptKnownBadHeader) { return true; } -bool JpegDecoder::Create(pdfium::span src_span, - uint32_t width, - uint32_t height, - int nComps, - bool ColorTransform, - uint32_t scale_denom) { +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 @@ -292,15 +214,15 @@ bool JpegDecoder::Create(pdfium::span src_span, return true; } -bool JpegDecoder::Rewind() { +bool LibjpegScanlineDecoder::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 + // `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_); @@ -316,7 +238,7 @@ bool JpegDecoder::Rewind() { return true; } -pdfium::span JpegDecoder::GetNextLine() { +pdfium::span LibjpegScanlineDecoder::GetNextLine() { uint8_t* row_array[] = {scanline_buf_.data()}; int nlines = jpeg_common_read_scanlines(&common_, row_array, 1u); if (nlines <= 0) { @@ -325,14 +247,14 @@ pdfium::span JpegDecoder::GetNextLine() { return scanline_buf_; } -uint32_t JpegDecoder::GetSrcOffset() { +uint32_t LibjpegScanlineDecoder::GetSrcOffset() { return static_cast(src_span_.size() - common_.source_mgr.bytes_in_buffer); } -void JpegDecoder::CalcPitch() { +void LibjpegScanlineDecoder::CalcPitch() { // `output_width_` must be finalized (post scale/alignment decision in - // `InitDecode()`) before this runs; the sole caller is `Create()`, after + // `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; @@ -341,13 +263,13 @@ void JpegDecoder::CalcPitch() { pitch_ *= 4; } -void JpegDecoder::InitDecompressSrc() { +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 JpegDecoder::HasKnownBadHeaderWithInvalidHeight( +bool LibjpegScanlineDecoder::HasKnownBadHeaderWithInvalidHeight( size_t dimension_offset) const { // Perform lots of possibly redundant checks to make sure this has no false // positives. @@ -378,13 +300,13 @@ bool JpegDecoder::HasKnownBadHeaderWithInvalidHeight( pHeaderDimensions[3] == nExpectedWidthByte2; } -bool JpegDecoder::IsSofSegment(size_t marker_offset) const { +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 JpegDecoder::PatchUpKnownBadHeaderWithInvalidHeight( +void LibjpegScanlineDecoder::PatchUpKnownBadHeaderWithInvalidHeight( size_t dimension_offset) { DCHECK(src_span_.size() > dimension_offset + 1u); auto pData = GetWritableSrcData().subspan(dimension_offset); @@ -392,159 +314,58 @@ void JpegDecoder::PatchUpKnownBadHeaderWithInvalidHeight( pData[1] = orig_height_ & 0xff; } -void JpegDecoder::PatchUpTrailer() { +void LibjpegScanlineDecoder::PatchUpTrailer() { auto pData = GetWritableSrcData(); pData[src_span_.size() - 2] = 0xff; pData[src_span_.size() - 1] = 0xd9; } -pdfium::span JpegDecoder::GetWritableSrcData() { +pdfium::span LibjpegScanlineDecoder::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, - uint32_t width, - uint32_t height, - int nComps, - bool ColorTransform, - uint32_t scale_denom) { - auto pDecoder = std::make_unique(); - if (!pDecoder->Create(src_span, width, height, nComps, ColorTransform, - scale_denom)) { - return nullptr; - } - - return pDecoder; -} - // static -std::optional JpegModule::LoadInfo( +std::optional LibjpegScanlineDecoder::LoadInfo( pdfium::span src_span) { - ImageInfo info; - if (!JpegLoadInfo(src_span, &info)) { - return std::nullopt; - } - - return info; -} - -#if BUILDFLAG(IS_WIN) -bool JpegModule::JpegEncode(const RetainPtr& pSource, - uint8_t** dest_buf, - size_t* dest_size) { - jpeg_error_mgr jerr; - jerr.error_exit = jpeg_common_error_do_nothing; - jerr.emit_message = jpeg_common_error_do_nothing_int; - jerr.output_message = jpeg_common_error_do_nothing; - jerr.format_message = jpeg_common_error_do_nothing_char; - jerr.reset_error_mgr = jpeg_common_error_do_nothing; - - jpeg_compress_struct cinfo = {}; // Aggregate initialization. - static_assert(std::is_aggregate_v); - cinfo.err = &jerr; - jpeg_create_compress(&cinfo); - const int bytes_per_pixel = pSource->GetBPP() / 8; - uint32_t nComponents = bytes_per_pixel >= 3 ? 3 : 1; - uint32_t pitch = pSource->GetPitch(); - uint32_t width = pdfium::checked_cast(pSource->GetWidth()); - uint32_t height = pdfium::checked_cast(pSource->GetHeight()); - FX_SAFE_UINT32 safe_buf_len = width; - safe_buf_len *= height; - safe_buf_len *= nComponents; - safe_buf_len += 1024; - if (!safe_buf_len.IsValid()) { - return false; - } - - uint32_t dest_buf_length = safe_buf_len.ValueOrDie(); - *dest_buf = FX_TryAlloc(uint8_t, dest_buf_length); - const int MIN_TRY_BUF_LEN = 1024; - while (!(*dest_buf) && dest_buf_length > MIN_TRY_BUF_LEN) { - dest_buf_length >>= 1; - *dest_buf = FX_TryAlloc(uint8_t, dest_buf_length); - } - if (!(*dest_buf)) { - return false; - } + src_span = JpegScanSOI(src_span); - jpeg_destination_mgr dest; - dest.init_destination = jpeg_common_dest_do_nothing; - dest.term_destination = jpeg_common_dest_do_nothing; - dest.empty_output_buffer = jpeg_common_dest_empty; - dest.next_output_byte = *dest_buf; - dest.free_in_buffer = dest_buf_length; - cinfo.dest = &dest; - cinfo.image_width = width; - cinfo.image_height = height; - cinfo.input_components = nComponents; - if (nComponents == 1) { - cinfo.in_color_space = JCS_GRAYSCALE; - } else if (nComponents == 3) { - cinfo.in_color_space = JCS_RGB; - } else { - cinfo.in_color_space = JCS_CMYK; - } - uint8_t* line_buf = nullptr; - if (nComponents > 1) { - line_buf = FX_Alloc2D(uint8_t, width, nComponents); + 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_set_defaults(&cinfo); - jpeg_start_compress(&cinfo, TRUE); - JSAMPROW row_pointer[1]; - JDIMENSION row; - while (cinfo.next_scanline < cinfo.image_height) { - pdfium::span src_scan = - pSource->GetScanline(cinfo.next_scanline); - if (nComponents > 1) { - uint8_t* dest_scan = line_buf; - if (nComponents == 3) { - UNSAFE_TODO({ - for (uint32_t i = 0; i < width; i++) { - ReverseCopy3Bytes(dest_scan, src_scan.data()); - dest_scan += 3; - src_scan = src_scan.subspan(static_cast(bytes_per_pixel)); - } - }); - } else { - UNSAFE_TODO({ - for (uint32_t i = 0; i < pitch; i++) { - *dest_scan++ = ~src_scan.front(); - src_scan = src_scan.subspan<1u>(); - } - }); - } - row_pointer[0] = line_buf; - } else { - row_pointer[0] = const_cast(src_scan.data()); - } - row = cinfo.next_scanline; - jpeg_write_scanlines(&cinfo, row_pointer, 1); - UNSAFE_TODO({ - if (cinfo.next_scanline == row) { - static constexpr size_t kJpegBlockSize = 1048576; - *dest_buf = - FX_Realloc(uint8_t, *dest_buf, dest_buf_length + kJpegBlockSize); - dest.next_output_byte = - *dest_buf + dest_buf_length - dest.free_in_buffer; - dest_buf_length += kJpegBlockSize; - dest.free_in_buffer += kJpegBlockSize; - } - }); + 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; } - jpeg_finish_compress(&cinfo); - jpeg_destroy_compress(&cinfo); - FX_Free(line_buf); - *dest_size = dest_buf_length - static_cast(dest.free_in_buffer); - return true; + 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; } -#endif // BUILDFLAG(IS_WIN) } // 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..1e417847d6 --- /dev/null +++ b/core/fxcodec/jpeg/libjpeg_scanline_decoder.h @@ -0,0 +1,77 @@ +// 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(); + 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; +}; + +} // namespace fxcodec + +#endif // CORE_FXCODEC_JPEG_LIBJPEG_SCANLINE_DECODER_H_ From bf68edf7bf78a879c730a606b47f680656416c6b Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Tue, 11 Aug 2026 17:51:46 -0700 Subject: [PATCH 039/123] Add baseline embedder tests for OnKeyDown shortcuts Document the baseline behavior in fpdf_formfill_embeddertest.cpp where FORM_OnKeyDown() fails to execute in-field editing shortcuts. Assert that passing FWL_VKEY_A and FWL_VKEY_Z with platform modifier flags does not trigger selection or undo in focused form fields. Include TODO comments noting the intended behavior for subsequent fix CL https://pdfium-review.googlesource.com/c/154610. Bug: 543962942 Change-Id: Ife16f02945548b05a46c7b0ac1a88de551e58cb3 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154590 Commit-Queue: Seung Hyun Jin Reviewed-by: Lei Zhang --- fpdfsdk/fpdf_formfill_embeddertest.cpp | 59 ++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp index 9214aeea3d..d8a5ebffee 100644 --- a/fpdfsdk/fpdf_formfill_embeddertest.cpp +++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp @@ -3459,6 +3459,8 @@ TEST_F(FPDFFormFillTextFormEmbedderTest, RedoCutSelection) { EXPECT_EQ(Selection(), ""); } +// TODO(crbug.com/543962942): FORM_OnChar handling for keyboard shortcuts is +// legacy behavior and will be replaced by FORM_OnKeyDown. TEST_F(FPDFFormFillTextFormEmbedderTest, SelectAllWithKeyboardShortcut) { // Start with a couple of letters in the text form. TypeTextIntoTextField(2, RegularFormBegin()); @@ -3489,6 +3491,63 @@ TEST_F(FPDFFormFillTextFormEmbedderTest, SelectAllWithKeyboardShortcut) { 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 via OnKeyDown. +#if BUILDFLAG(IS_APPLE) + static constexpr int kCorrectModifier = FWL_EVENTFLAG_MetaKey; +#else + static constexpr int kCorrectModifier = FWL_EVENTFLAG_ControlKey; +#endif + // TODO(crbug.com/543962942): FORM_OnKeyDown with FWL_VKEY_A should select all + // text in the field. + EXPECT_FALSE( + FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_A, 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 + EXPECT_FALSE( + FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_A, kWrongModifier)); + 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 + // TODO(crbug.com/543962942): FORM_OnKeyDown with FWL_VKEY_Z should undo + // field text. + EXPECT_FALSE( + FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_Z, kCorrectModifier)); + 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"); +} + class FPDFXFAFormBug1055869EmbedderTest : public FPDFFormFillInteractiveEmbedderTest { protected: From fb291235a48882758f8180868700fd150145572e Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Tue, 11 Aug 2026 17:51:54 -0700 Subject: [PATCH 040/123] Handle SelectAll and Undo/Redo in CPWL_Edit::OnKeyDownInternal() Move handling of Ctrl+A (SelectAll) and Ctrl+Z / Ctrl+Shift+Z (Undo/Redo) from OnCharInternal() to OnKeyDownInternal() using virtual key codes FWL_VKEY_A and FWL_VKEY_Z. In OnCharInternal(), return false whenever a platform shortcut key is pressed without Alt. This prevents localized characters from being typed into the text field on non-Latin keyboard layouts and allows external edit commands (such as Cut, Copy, Paste) to be handled by the embedder. Update embedder tests to verify keyboard shortcut execution via FORM_OnKeyDown(). Bug: 543962942 Change-Id: If01c09bf2e0c00ce0f3ce63d2cf3d8201639b0d1 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154610 Commit-Queue: Seung Hyun Jin Reviewed-by: Lei Zhang --- fpdfsdk/fpdf_formfill_embeddertest.cpp | 59 +++++++++++++++----------- fpdfsdk/pwl/cpwl_edit.cpp | 44 +++++++++---------- 2 files changed, 54 insertions(+), 49 deletions(-) diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp index d8a5ebffee..156035fdff 100644 --- a/fpdfsdk/fpdf_formfill_embeddertest.cpp +++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp @@ -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 @@ -3459,26 +3452,21 @@ TEST_F(FPDFFormFillTextFormEmbedderTest, RedoCutSelection) { EXPECT_EQ(Selection(), ""); } -// TODO(crbug.com/543962942): FORM_OnChar handling for keyboard shortcuts is -// legacy behavior and will be replaced by FORM_OnKeyDown. -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(), ""); - // Select all with the keyboard shortcut. + // 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 - FORM_OnChar(form_handle(), page(), pdfium::ascii::kControlA, - kCorrectModifier); - EXPECT_EQ(Selection(), "AB"); - - // Reset the selection again. - ClickOnFormFieldAtPoint(RegularFormBegin()); + 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. @@ -3503,10 +3491,12 @@ TEST_F(FPDFFormFillTextFormEmbedderTest, SelectAllWithOnKeyDown) { #else static constexpr int kCorrectModifier = FWL_EVENTFLAG_ControlKey; #endif - // TODO(crbug.com/543962942): FORM_OnKeyDown with FWL_VKEY_A should select all - // text in the field. - EXPECT_FALSE( + EXPECT_TRUE( FORM_OnKeyDown(form_handle(), page(), FWL_VKEY_A, kCorrectModifier)); + EXPECT_EQ(Selection(), "AB"); + + // Reset the selection again. + ClickOnFormFieldAtPoint(RegularFormBegin()); EXPECT_EQ(Selection(), ""); // Select all with the keyboard shortcut using the wrong modifier key. @@ -3518,6 +3508,11 @@ TEST_F(FPDFFormFillTextFormEmbedderTest, SelectAllWithOnKeyDown) { 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) { @@ -3531,10 +3526,24 @@ TEST_F(FPDFFormFillTextFormEmbedderTest, UndoWithOnKeyDown) { #else static constexpr int kCorrectModifier = FWL_EVENTFLAG_ControlKey; #endif - // TODO(crbug.com/543962942): FORM_OnKeyDown with FWL_VKEY_Z should undo - // field text. - EXPECT_FALSE( + // 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. diff --git a/fpdfsdk/pwl/cpwl_edit.cpp b/fpdfsdk/pwl/cpwl_edit.cpp index 4fb85ff4b9..3ccd6d1f19 100644 --- a/fpdfsdk/pwl/cpwl_edit.cpp +++ b/fpdfsdk/pwl/cpwl_edit.cpp @@ -540,6 +540,23 @@ 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; + 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 +582,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; } From 3a53053016baafbd31f35af445b3963a9fe441eb Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Tue, 11 Aug 2026 17:52:01 -0700 Subject: [PATCH 041/123] Support Ctrl+Y for Redo in CPWL_Edit Add FWL_VKEY_Y handling to CPWL_Edit::OnKeyDownInternal when building for non-Apple platforms, enabling the standard Ctrl+Y Redo shortcut. On Apple platforms, Cmd+Y is left unhandled to adhere to platform conventions where Cmd+Shift+Z is the exclusive Redo shortcut. Include embedder test coverage verifying Ctrl+Y performs Redo on non-Apple platforms and is ignored on Apple platforms. Bug: 543962942 Change-Id: Idc29c0b10d0b7225c7f8d02723ffd3afc046e486 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154630 Reviewed-by: Lei Zhang Commit-Queue: Seung Hyun Jin --- fpdfsdk/fpdf_formfill_embeddertest.cpp | 25 +++++++++++++++++++++++++ fpdfsdk/pwl/cpwl_edit.cpp | 10 ++++++++++ 2 files changed, 35 insertions(+) diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp index 156035fdff..ad11adaba0 100644 --- a/fpdfsdk/fpdf_formfill_embeddertest.cpp +++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp @@ -3557,6 +3557,31 @@ TEST_F(FPDFFormFillTextFormEmbedderTest, UndoWithOnKeyDown) { 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/pwl/cpwl_edit.cpp b/fpdfsdk/pwl/cpwl_edit.cpp index 3ccd6d1f19..d6fa576d5d 100644 --- a/fpdfsdk/pwl/cpwl_edit.cpp +++ b/fpdfsdk/pwl/cpwl_edit.cpp @@ -547,6 +547,16 @@ bool CPWL_Edit::OnKeyDownInternal(FWL_VKEYCODE nKeyCode, 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)) { From fd771362e49ffaa8fe3bfcbbf3872f09abcb0434 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Wed, 12 Aug 2026 10:13:17 -0700 Subject: [PATCH 042/123] Implement SkiaScanlineDecoder for JPEG decoding via Skia Introduce SkiaScanlineDecoder inheriting from ScanlineDecoder and implementing scanline-by-scanline JPEG decoding using Skia's SkCodec and SkJpegDecoder APIs. Support single-scanline decoding, RGB/Grayscale formats, power-of-two downscaling via scale_denom, and stream rewinding. TAG=agy CONV=f1b1440b-3e75-459c-a95f-15b694c7fa1f Change-Id: Ia72631d16a2136fec16b5e7bda74fd883ff480e3 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154750 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- core/fxcodec/BUILD.gn | 7 + core/fxcodec/jpeg/DEPS | 6 + core/fxcodec/jpeg/skia_scanline_decoder.cpp | 224 ++++++++++++++++++++ core/fxcodec/jpeg/skia_scanline_decoder.h | 60 ++++++ skia/BUILD.gn | 11 + 5 files changed, 308 insertions(+) create mode 100644 core/fxcodec/jpeg/skia_scanline_decoder.cpp create mode 100644 core/fxcodec/jpeg/skia_scanline_decoder.h diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index 49a1744a44..235159fd2e 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -152,6 +152,13 @@ source_set("fxcodec") { ] } } + if (pdf_use_skia) { + sources += [ + "jpeg/skia_scanline_decoder.cpp", + "jpeg/skia_scanline_decoder.h", + ] + deps += [ "//skia" ] + } visibility = [ "../../*" ] } diff --git a/core/fxcodec/jpeg/DEPS b/core/fxcodec/jpeg/DEPS index 88af2417fc..eb5cbbe40a 100644 --- a/core/fxcodec/jpeg/DEPS +++ b/core/fxcodec/jpeg/DEPS @@ -4,3 +4,9 @@ include_rules = [ '+third_party/libjpeg_turbo/jerror.h', '+third_party/libjpeg_turbo/jpeglib.h', ] + +specific_include_rules = { + "skia_scanline_decoder\.(cpp|h)": [ + '+third_party/skia/include', + ], +} diff --git a/core/fxcodec/jpeg/skia_scanline_decoder.cpp b/core/fxcodec/jpeg/skia_scanline_decoder.cpp new file mode 100644 index 0000000000..1a5e5967b0 --- /dev/null +++ b/core/fxcodec/jpeg/skia_scanline_decoder.cpp @@ -0,0 +1,224 @@ +// 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 "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; +} + +} // 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; + } + + 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 = kRGB_888x_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/skia/BUILD.gn b/skia/BUILD.gn index 8343813357..d3de517f69 100644 --- a/skia/BUILD.gn +++ b/skia/BUILD.gn @@ -472,6 +472,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 @@ -487,6 +497,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 From acf52a0b01420c97ed1005ae171edd63bd4701bd Mon Sep 17 00:00:00 2001 From: Seung Hyun Jin Date: Wed, 12 Aug 2026 12:48:13 -0700 Subject: [PATCH 043/123] Relayout and repaint text immediately in CPWL_EditImpl::SetTextDirection Call Paint() inside CPWL_EditImpl::SetTextDirection() to immediately recalculate text layout and trigger a repaint when the writing direction changes. Return early if the requested direction matches the current direction to avoid redundant relayout passes. Update SetTextDirectionUpdatesRenderingOfFocusedTextField in fpdf_formfill_embeddertest.cpp to test live rendering directly on the focused widget. Eliminate the prior workarounds that required typing additional characters and forcing focus kills to flush state into the appearance stream, and use mixed bidirectional text so that changing the paragraph direction produces distinct visual run ordering. Bug: 537870008 Change-Id: I92445baec48668f8edd824fd49e8bbfc5b37926d Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154550 Reviewed-by: Lei Zhang Commit-Queue: Seung Hyun Jin --- fpdfsdk/fpdf_formfill_embeddertest.cpp | 26 ++++++++++++-------------- fpdfsdk/pwl/cpwl_edit_impl.cpp | 4 ++++ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/fpdfsdk/fpdf_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp index ad11adaba0..3dd6b43d93 100644 --- a/fpdfsdk/fpdf_formfill_embeddertest.cpp +++ b/fpdfsdk/fpdf_formfill_embeddertest.cpp @@ -1667,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)); } @@ -1677,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) { diff --git a/fpdfsdk/pwl/cpwl_edit_impl.cpp b/fpdfsdk/pwl/cpwl_edit_impl.cpp index 500678d9e1..ea7239377b 100644 --- a/fpdfsdk/pwl/cpwl_edit_impl.cpp +++ b/fpdfsdk/pwl/cpwl_edit_impl.cpp @@ -756,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 { From 04a05a5b8c4efab10b26774ce209fdfb2656a77f Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Wed, 12 Aug 2026 13:03:02 -0700 Subject: [PATCH 044/123] Support Skia/Rust-based JPEG decoding in JpegModule Add build configuration flag pdf_enable_rust_jpeg and route JpegModule::CreateDecoder() and JpegModule::LoadInfo() to SkiaScanlineDecoder and SkiaLoadInfo when pdf_use_skia and pdf_enable_rust_jpeg are enabled. Configure Skia to enable Rust-based JPEG decoding via SK_CODEC_DECODES_JPEG_WITH_RUST when the flag is set. TAG=agy CONV=f1b1440b-3e75-459c-a95f-15b694c7fa1f Change-Id: I04623f5097c50c7297af8a19e42c4c95dd6cc222 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154770 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- BUILD.gn | 4 ++++ build_overrides/pdfium.gni | 3 +++ core/fxcodec/BUILD.gn | 15 +++++++++------ core/fxcodec/jpeg/jpegmodule.cpp | 16 +++++++++++++++- pdfium.gni | 7 +++++++ skia/BUILD.gn | 7 +++++++ 6 files changed, 45 insertions(+), 7 deletions(-) diff --git a/BUILD.gn b/BUILD.gn index a62e4d6410..e3d7feeca1 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -51,6 +51,10 @@ config("pdfium_common_config") { defines += [ "PDF_ENABLE_RUST_BMP" ] } + if (pdf_use_skia && pdf_enable_rust_jpeg) { + defines += [ "PDF_ENABLE_RUST_JPEG" ] + } + if (pdf_use_skia && pdf_enable_rust_png) { defines += [ "PDF_ENABLE_RUST_PNG" ] } 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/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index 235159fd2e..4ef7d96481 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -58,12 +58,8 @@ 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/jpegmodule.cpp", "jpeg/jpegmodule.h", - "jpeg/libjpeg_scanline_decoder.cpp", - "jpeg/libjpeg_scanline_decoder.h", "jpx/cjpx_decoder.cpp", "jpx/cjpx_decoder.h", "jpx/jpx_decode_utils.cpp", @@ -81,7 +77,6 @@ source_set("fxcodec") { "../../third_party:libopenjpeg2", "../../third_party:zlib", "../fxge", - "//third_party:jpeg", ] defines = [] if (pdf_enable_brotli) { @@ -152,12 +147,20 @@ source_set("fxcodec") { ] } } - if (pdf_use_skia) { + if (pdf_enable_rust_jpeg) { sources += [ "jpeg/skia_scanline_decoder.cpp", "jpeg/skia_scanline_decoder.h", ] deps += [ "//skia" ] + } else { + sources += [ + "jpeg/jpeg_common.c", + "jpeg/jpeg_common.h", + "jpeg/libjpeg_scanline_decoder.cpp", + "jpeg/libjpeg_scanline_decoder.h", + ] + deps += [ "//third_party:jpeg" ] } visibility = [ "../../*" ] } diff --git a/core/fxcodec/jpeg/jpegmodule.cpp b/core/fxcodec/jpeg/jpegmodule.cpp index 8f271314eb..7b8e6be545 100644 --- a/core/fxcodec/jpeg/jpegmodule.cpp +++ b/core/fxcodec/jpeg/jpegmodule.cpp @@ -10,10 +10,15 @@ #include #include "build/build_config.h" -#include "core/fxcodec/jpeg/libjpeg_scanline_decoder.h" #include "core/fxcodec/scanlinedecoder.h" #include "core/fxcrt/span.h" +#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_JPEG) +#include "core/fxcodec/jpeg/skia_scanline_decoder.h" +#else +#include "core/fxcodec/jpeg/libjpeg_scanline_decoder.h" +#endif + #if BUILDFLAG(IS_WIN) #include @@ -37,14 +42,23 @@ std::unique_ptr JpegModule::CreateDecoder( int nComps, bool ColorTransform, uint32_t scale_denom) { +#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_JPEG) + return SkiaScanlineDecoder::Create(src_span, width, height, nComps, + ColorTransform, scale_denom); +#else return LibjpegScanlineDecoder::Create(src_span, width, height, nComps, ColorTransform, scale_denom); +#endif } // static std::optional JpegModule::LoadInfo( pdfium::span src_span) { +#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_JPEG) + return SkiaScanlineDecoder::LoadInfo(src_span); +#else return LibjpegScanlineDecoder::LoadInfo(src_span); +#endif } #if BUILDFLAG(IS_WIN) diff --git a/pdfium.gni b/pdfium.gni index cbd606b6c3..c67b305571 100644 --- a/pdfium.gni +++ b/pdfium.gni @@ -66,6 +66,9 @@ 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. @@ -97,6 +100,10 @@ declare_args() { use_system_zlib = false } +# Temporarily force false to allow recipe rollout without breaking tests. +# TODO(tsepez): remove this once XFA side supports rust jpeg as well. +pdf_enable_rust_jpeg = false + assert(!pdf_is_complete_lib || !is_component_build, "pdf_is_complete_lib=true requires is_component_build=false") diff --git a/skia/BUILD.gn b/skia/BUILD.gn index d3de517f69..b50401e1a7 100644 --- a/skia/BUILD.gn +++ b/skia/BUILD.gn @@ -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", From 34cb5701b131d68c955eee38c829f9da6a8cd9e3 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Wed, 12 Aug 2026 14:43:17 -0700 Subject: [PATCH 045/123] Enforce GN build constraints for pdf_enable_rust_jpeg in pdfium.gni Add an assertion in pdfium.gni ensuring pdf_enable_rust_jpeg=true requires enable_rust=true, enable_rust_cxx=true, and pdf_use_skia=true, matching the build precondition patterns established for Fontations, Rust BMP, and Rust PNG decoders. Change-Id: Id2a0577d54cacdc2dfc3a6ec20366d4d085772f5 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154890 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- pdfium.gni | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pdfium.gni b/pdfium.gni index c67b305571..a70dda7d13 100644 --- a/pdfium.gni +++ b/pdfium.gni @@ -118,6 +118,10 @@ 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") +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") From 875172eae557a308d0c5b2be43822814c8a885bb Mon Sep 17 00:00:00 2001 From: Aryan Date: Thu, 13 Aug 2026 11:49:05 -0700 Subject: [PATCH 046/123] Replace generic DCHECK checks with specific operator variants This CL replaces the remaining occurrences of DCHECK() statements involving binary comparison operators with their specialised variants (DCHECK_LE, DCHECK_GE, DCHECK_LT, and DCHECK_GT). This improves failure diagnostics by logging both operands on assertion failure rather than just the boolean result. Additionally, this CL replaces include directives for check.h with check_op.h for files where CHECK() or DCHECK() isn't used. Bug: 42270641 Change-Id: I56550de50f843cf036cb79d5f5d34327397cebcf Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154815 Reviewed-by: Andy Phan Commit-Queue: Lei Zhang Reviewed-by: Lei Zhang Reviewed-by: Tom Sepez Commit-Queue: Aryan Krishnan --- core/fpdfapi/edit/cpdf_creator.cpp | 3 +- core/fpdfapi/edit/cpdf_npagetooneexporter.cpp | 10 ++--- core/fpdfapi/page/cpdf_image.cpp | 3 +- .../fpdfapi/page/cpdf_streamcontentparser.cpp | 7 ++-- core/fpdfapi/page/cpdf_textobject.cpp | 5 ++- core/fpdfapi/parser/cpdf_document.cpp | 2 +- core/fpdfapi/parser/cpdf_security_handler.cpp | 2 +- core/fpdfapi/parser/cpdf_syntax_parser.cpp | 2 +- core/fpdfapi/parser/fpdf_parser_utility.cpp | 3 +- core/fpdfdoc/cpdf_annot.cpp | 3 +- core/fpdftext/cpdf_textpage.cpp | 6 +-- core/fxcodec/basic/basicmodule.cpp | 6 +-- core/fxcodec/fax/faxmodule.cpp | 3 +- core/fxcodec/jbig2/jbig2_context.cpp | 6 +-- core/fxcodec/jbig2/jbig2_huffman_table.cpp | 3 +- core/fxcrt/cfx_bitstream.cpp | 4 +- core/fxcrt/cfx_datetime.cpp | 9 +++-- core/fxcrt/css/cfx_cssstyleselector.cpp | 3 +- core/fxcrt/fx_unicode.cpp | 6 +-- core/fxcrt/string_data_template.cpp | 3 +- core/fxge/dib/cstretchengine.cpp | 3 +- fpdfsdk/cpdfsdk_widget.cpp | 5 ++- fpdfsdk/fpdf_doc.cpp | 4 +- fpdfsdk/fpdf_formfill_embeddertest.cpp | 40 +++++++++---------- fpdfsdk/fpdfxfa/cpdfxfa_page.cpp | 3 +- fxbarcode/datamatrix/BC_C40Encoder.cpp | 4 +- fxbarcode/datamatrix/BC_ErrorCorrection.cpp | 3 +- fxbarcode/qrcode/BC_QRCoderBitVector.cpp | 5 ++- fxbarcode/qrcode/BC_QRCoderEncoder.cpp | 7 ++-- fxjs/cjs_publicmethods.cpp | 3 +- fxjs/gc/heap.cpp | 3 +- fxjs/xfa/cfxjse_formcalc_context.cpp | 2 +- fxjs/xfa/cfxjse_resolveprocessor.cpp | 3 +- testing/utils/bitmap_saver.cpp | 3 +- xfa/fde/cfde_texteditengine.cpp | 4 +- xfa/fde/cfde_textout.cpp | 8 ++-- xfa/fde/cfde_wordbreak_data.cpp | 4 +- xfa/fgas/crt/cfgas_decimal.cpp | 4 +- xfa/fgas/font/cfgas_fontmgr.cpp | 5 ++- xfa/fgas/layout/cfgas_breakline.cpp | 4 +- xfa/fgas/layout/cfgas_char.cpp | 19 ++++----- xfa/fgas/layout/cfgas_rtfbreak.cpp | 3 +- xfa/fgas/layout/cfgas_txtbreak.cpp | 7 ++-- xfa/fwl/cfwl_monthcalendar.cpp | 4 +- xfa/fxfa/cxfa_ffpageview.cpp | 6 +-- xfa/fxfa/cxfa_textlayout.cpp | 3 +- xfa/fxfa/parser/cxfa_localevalue.cpp | 5 ++- xfa/fxfa/parser/cxfa_rectangle.cpp | 6 +-- 48 files changed, 139 insertions(+), 120 deletions(-) diff --git a/core/fpdfapi/edit/cpdf_creator.cpp b/core/fpdfapi/edit/cpdf_creator.cpp index 44ce74f7f7..31e004f9be 100644 --- a/core/fpdfapi/edit/cpdf_creator.cpp +++ b/core/fpdfapi/edit/cpdf_creator.cpp @@ -28,6 +28,7 @@ #include "core/fpdfapi/parser/fpdf_parser_utility.h" #include "core/fpdfapi/parser/object_tree_traversal_util.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" @@ -453,7 +454,7 @@ CPDF_Creator::Stage CPDF_Creator::WriteDoc_Stage3() { } CPDF_Creator::Stage CPDF_Creator::WriteDoc_Stage4() { - DCHECK(stage_ >= Stage::kWriteTrailerAndFinish90); + DCHECK_GE(stage_, Stage::kWriteTrailerAndFinish90); bool bXRefStream = is_incremental_ && parser_->IsXRefStream(); if (!bXRefStream) { 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/page/cpdf_image.cpp b/core/fpdfapi/page/cpdf_image.cpp index 5067b09965..c13ae2b356 100644 --- a/core/fpdfapi/page/cpdf_image.cpp +++ b/core/fpdfapi/page/cpdf_image.cpp @@ -28,6 +28,7 @@ #include "core/fpdfapi/parser/cpdf_string.h" #include "core/fxcodec/jpeg/jpegmodule.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" @@ -222,7 +223,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 14c5b0ef9e..a0d239f6a3 100644 --- a/core/fpdfapi/page/cpdf_streamcontentparser.cpp +++ b/core/fpdfapi/page/cpdf_streamcontentparser.cpp @@ -39,6 +39,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/fx_safe_types.h" @@ -891,12 +892,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) { @@ -1643,7 +1644,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_textobject.cpp b/core/fpdfapi/page/cpdf_textobject.cpp index bbea3441b7..d6336c9a05 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_document.cpp b/core/fpdfapi/parser/cpdf_document.cpp index 1ca95b1657..b70120bbb5 100644 --- a/core/fpdfapi/parser/cpdf_document.cpp +++ b/core/fpdfapi/parser/cpdf_document.cpp @@ -251,7 +251,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); page_list_.resize(page_count); page_list_[first_page_num] = objnum; } diff --git a/core/fpdfapi/parser/cpdf_security_handler.cpp b/core/fpdfapi/parser/cpdf_security_handler.cpp index b0651ebc9a..25b0abcf9c 100644 --- a/core/fpdfapi/parser/cpdf_security_handler.cpp +++ b/core/fpdfapi/parser/cpdf_security_handler.cpp @@ -338,7 +338,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 a7e8abc9a0..ef02ae67c9 100644 --- a/core/fpdfapi/parser/cpdf_syntax_parser.cpp +++ b/core/fpdfapi/parser/cpdf_syntax_parser.cpp @@ -99,7 +99,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; 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 97d3277176..91782646f3 100644 --- a/core/fpdfdoc/cpdf_annot.cpp +++ b/core/fpdfdoc/cpdf_annot.cpp @@ -26,6 +26,7 @@ #include "core/fpdfapi/render/cpdf_renderoptions.h" #include "core/fpdfdoc/cpdf_generateap.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" @@ -252,7 +253,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); // QuadPoints are defined with 4 pairs of numbers // ([ pair0, pair1, pair2, pair3 ]), where diff --git a/core/fpdftext/cpdf_textpage.cpp b/core/fpdftext/cpdf_textpage.cpp index 80122aa0da..13ff030379 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; } @@ -445,7 +445,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/basic/basicmodule.cpp b/core/fxcodec/basic/basicmodule.cpp index 2033e4f89b..448e18b7c6 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" @@ -187,7 +187,7 @@ void RLScanlineDecoder::UpdateOperator(uint8_t used_bytes) { return; } if (operator_ < 128) { - DCHECK((uint32_t)operator_ + 1 >= used_bytes); + DCHECK_GE((uint32_t)operator_ + 1, used_bytes); if (used_bytes == operator_ + 1) { src_offset_ += used_bytes; GetNextOperator(); @@ -201,7 +201,7 @@ void RLScanlineDecoder::UpdateOperator(uint8_t used_bytes) { return; } uint8_t count = 257 - operator_; - DCHECK((uint32_t)count >= used_bytes); + DCHECK_GE((uint32_t)count, used_bytes); if (used_bytes == count) { src_offset_++; GetNextOperator(); 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/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/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..d1b4572267 100644 --- a/core/fxcrt/cfx_datetime.cpp +++ b/core/fxcrt/cfx_datetime.cpp @@ -8,6 +8,7 @@ #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" @@ -44,10 +45,10 @@ int64_t DateToDays(int32_t iYear, uint8_t iDay, bool bIncludeThisDay) { DCHECK(iYear != 0); - DCHECK(iMonth >= 1); - DCHECK(iMonth <= 12); - DCHECK(iDay >= 1); - DCHECK(iDay <= FX_DaysInMonth(iYear, iMonth)); + 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; 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 15b2aa2404..847d1b924c 100644 --- a/core/fxcrt/fx_unicode.cpp +++ b/core/fxcrt/fx_unicode.cpp @@ -156,7 +156,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); } @@ -164,14 +164,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); } #endif // PDF_ENABLE_XFA 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/dib/cstretchengine.cpp b/core/fxge/dib/cstretchengine.cpp index df5920142b..10622154f9 100644 --- a/core/fxge/dib/cstretchengine.cpp +++ b/core/fxge/dib/cstretchengine.cpp @@ -13,6 +13,7 @@ #include #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/fx_system.h" #include "core/fxcrt/pauseindicator_iface.h" @@ -175,7 +176,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_]); } 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/fpdf_doc.cpp b/fpdfsdk/fpdf_doc.cpp index bf956435a5..2fc473ccd9 100644 --- a/fpdfsdk/fpdf_doc.cpp +++ b/fpdfsdk/fpdf_doc.cpp @@ -26,7 +26,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" @@ -313,7 +313,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_formfill_embeddertest.cpp b/fpdfsdk/fpdf_formfill_embeddertest.cpp index 3dd6b43d93..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 { 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/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/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/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..09149e581e 100644 --- a/fxjs/xfa/cfxjse_formcalc_context.cpp +++ b/fxjs/xfa/cfxjse_formcalc_context.cpp @@ -1084,7 +1084,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/testing/utils/bitmap_saver.cpp b/testing/utils/bitmap_saver.cpp index 9659e3971b..39b652f9cd 100644 --- a/testing/utils/bitmap_saver.cpp +++ b/testing/utils/bitmap_saver.cpp @@ -8,6 +8,7 @@ #include #include "core/fxcrt/check.h" +#include "core/fxcrt/check_op.h" #include "testing/utils/png_encode.h" // static @@ -15,7 +16,7 @@ void BitmapSaver::WriteBitmapToPng(FPDF_BITMAP bitmap, const std::string& filename) { std::vector png = EncodePng(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); 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_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]; From 09c945609526b06b4ff87b4a8d7ab78f2fab950e Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Fri, 14 Aug 2026 16:37:21 -0700 Subject: [PATCH 047/123] Roll build/ 73c0fa98c..0d8b711d9 (122 commits) https://chromium.googlesource.com/chromium/src/build.git/+log/73c0fa98c5cf..0d8b711d97d1 Created with: roll-dep build Change-Id: Id737c3336396fc8c827e1738878c4d68715496bc Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154390 Commit-Queue: Andy Phan Reviewed-by: Andy Phan Auto-Submit: Lei Zhang --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index e2b800d841..5b354ad1b7 100644 --- a/DEPS +++ b/DEPS @@ -95,7 +95,7 @@ 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': '0d8b711d97d1ec64909af024363817e480640fb7', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling buildtools # and whatever else without interference from each other. From 1348385bb7c8dbc0d667d4b00f038a1d4684a196 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Fri, 14 Aug 2026 16:38:39 -0700 Subject: [PATCH 048/123] Roll clang and rust Roll tools/clang/ 66934666a..e4816757c (38 commits) https://chromium.googlesource.com/chromium/src/tools/clang/+log/66934666a7c7..e4816757c45a Roll clang+rust llvmorg-23-init-19482-g53d18800-1:llvmorg-23-init-19482-g53d18800-2 Created with: roll-dep tools/clang Roll tools/rust/ 1607679eb..63b87afd7 (5 commits) https://chromium.googlesource.com/chromium/src/tools/rust/+log/1607679eb7fa..63b87afd7580 Created with: roll-dep tools/rust Change-Id: I7fbd000f1f0371ed6908ec8a5d704f0275e9d04f Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154430 Auto-Submit: Lei Zhang Commit-Queue: Lei Zhang Reviewed-by: Andy Phan --- DEPS | 210 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 105 insertions(+), 105 deletions(-) diff --git a/DEPS b/DEPS index 5b354ad1b7..a9de5bbe53 100644 --- a/DEPS +++ b/DEPS @@ -111,7 +111,7 @@ vars = { # 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': 'e4816757c45a072f08279139108a0a937bbeb239', # 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. @@ -253,7 +253,7 @@ vars = { # 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': '63b87afd75805d610ff67f540318664369932f0b', # 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. @@ -462,157 +462,157 @@ deps = { '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, + 'object_name': 'Linux_x64/clang-llvmorg-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '94940b8da1c3e7fec9020a75cfc4e852922a4d163dab8957e691a36ae5d49f0b', + 'size_bytes': 59100444, + 'generation': 1785366626567993, 'condition': '(host_os == "linux" or 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-tidy-llvmorg-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '355a938fd3ab277dee7b2df50264416a0ebbdfd101e4b6beb82cc176f0ae300d', + 'size_bytes': 14809560, + 'generation': 1785366626548898, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '7eab6663df18976874c774eb2dee38f79287ac2dbdd336fcda2d955438fe131a', + 'size_bytes': 14988320, + 'generation': 1785366626796669, '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', + 'object_name': 'Linux_x64/llvm-code-coverage-llvmorg-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '2cb5220dceb4be408c306537e83b323219ed0129cf3512fb4bbf49aa0c334b7f', 'size_bytes': 2332356, - 'generation': 1781627382888550, + 'generation': 1785366626986503, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '61ac1326d1fd10fe3527f1ca0ce78aa357923157442056eaa53a027931db986e', + 'size_bytes': 5873792, + 'generation': 1785366626833938, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '51350c8f6da5c92ab19b0b17a3cbc7762b960b29fd68530e9feb52eb5079ae19', + 'size_bytes': 56088216, + 'generation': 1785366628543558, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '0105a7a2e8e6f4b88b6414756a5bfaa04e3cb1e678d29e3df367752a203edb6a', + 'size_bytes': 1010844, + 'generation': 1785366639043429, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '66d4a4b66e5ad9bee6b6235e9a8fee213afa4c321232e897881c8e4dcf752d61', + 'size_bytes': 14767916, + 'generation': 1785366628634961, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '9866482056c17d3f5b4c13a693c6e7e8417c54e943cf00e58a82a51b65dfd183', + 'size_bytes': 16714728, + 'generation': 1785366628706685, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': 'baec5b697375914d0ec1a377f66a6ac3826aad0103de2d6258b8a88cf7dd5c02', + 'size_bytes': 2372468, + 'generation': 1785366629000854, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': 'ab7600041b0db44338b42c55b7ed1224edc10c96283d71e53a373820be9e62f9', + 'size_bytes': 5790116, + 'generation': 1785366628828288, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': 'a9c5ab02bfdc07a6ad6c6711d884dccab1fdf52e05e71aa9328039cddaff3664', + 'size_bytes': 47045224, + 'generation': 1785366640567652, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '51eea196d67444cc33613791193acba52803e0b57cdcbecd8135b4578674145e', + 'size_bytes': 12837040, + 'generation': 1785366640711645, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '2e9c4180eaf40a5ffd4f26bfcb2c8b7172de1ad06bb81e98cfbec466cd5eabf9', + 'size_bytes': 13199252, + 'generation': 1785366640801569, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '9a2a764f9f2dbb46eba03ed29a87bf55b580a1f7e562ba4002056cb28e2c5902', + 'size_bytes': 1999328, + 'generation': 1785366641042033, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '39fad69229bd7f8b440809f74a711175f5f0ed3cc92c4d6f3652c0ca57e39c2e', + 'size_bytes': 5546692, + 'generation': 1785366640876543, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '0d42e6825a7b5cafb8b6d250e8aae3b9b1920f4cbc2fe4f5a8dd46154d95d25d', + 'size_bytes': 51315536, + 'generation': 1785366653133084, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': 'fbd7f3a0aa321c144542a3d97f702ae676306b837febfd7663a036929f8eb0d5', + 'size_bytes': 14855356, + 'generation': 1785366653263211, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': 'da5135a86ba5a585abc6dfb38c38803a14e9a83cd39ceaac85eedef50f193a1d', + 'size_bytes': 2622056, + 'generation': 1785366663332782, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '1e6d5c7e036b4740b7dabaca71e2e3e97a68477515ffc9faa10ba324781faa56', + 'size_bytes': 15260276, + 'generation': 1785366653344899, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': '6d21c3aebf0238eb31dc8fe41c01f079dde57a06bf628cedca7ef94f4e9cbdcb', + 'size_bytes': 2494100, + 'generation': 1785366653543218, '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-23-init-19482-g53d18800-2.tar.xz', + 'sha256sum': 'ec5ac24513c7b650b27b638bc46c207b228072fd5cd7c45a86b988820c01ff95', + 'size_bytes': 5940460, + 'generation': 1785366653406139, 'condition': '(checkout_linux or checkout_mac or checkout_android) and host_os == "win"', }, ] @@ -652,31 +652,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-b998449636a48e2c4a362809085b600a0174e1f2-6-llvmorg-23-init-19482-g53d18800.tar.xz', + 'sha256sum': 'fa341a74b1567fe57af21aa7bccbdf3c0ec3b704fbb01829763faa5292eb4ddd', + 'size_bytes': 274829308, + 'generation': 1785459300476581, '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-b998449636a48e2c4a362809085b600a0174e1f2-6-llvmorg-23-init-19482-g53d18800.tar.xz', + 'sha256sum': 'd256ca6d43eb8131d78cd908dbc8b07e59acb67902497de324c60b85a6d94635', + 'size_bytes': 262354296, + 'generation': 1785459303542909, '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-b998449636a48e2c4a362809085b600a0174e1f2-6-llvmorg-23-init-19482-g53d18800.tar.xz', + 'sha256sum': 'a23256540cd7c7c8668abdf4044f2ecc905881089a6b0ca6346a94a97887cc77', + 'size_bytes': 246808776, + 'generation': 1785459306444750, '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-b998449636a48e2c4a362809085b600a0174e1f2-6-llvmorg-23-init-19482-g53d18800.tar.xz', + 'sha256sum': '57272886008b441fa229d2e9997b185fa5670bca7cf1f338a77a07d9bcc8eb49', + 'size_bytes': 416157244, + 'generation': 1785459309430367, 'condition': 'host_os == "win"', }, ], From 4b1d0e0204eaf2d8481559cbf951b1e22d837017 Mon Sep 17 00:00:00 2001 From: "pdfium-autoroll@skia-public.iam.gserviceaccount.com" Date: Sun, 16 Aug 2026 21:33:29 -0700 Subject: [PATCH 049/123] Roll Depot Tools from 1709e20d5ca2 to 732902ab86f8 (81 revisions) https://chromium.googlesource.com/chromium/tools/depot_tools.git/+log/1709e20d5ca2..732902ab86f8 If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/depot-tools-pdfium Please CC thestig@chromium.org,thestig@google.com on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Bug: None Tbr: thestig@google.com Change-Id: I2abe3f01e79bb69a739ebd8e4b6b460357074629 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155150 Bot-Commit: pdfium-autoroll@skia-public.iam.gserviceaccount.com Commit-Queue: pdfium-autoroll@skia-public.iam.gserviceaccount.com --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index a9de5bbe53..9c7f914a7f 100644 --- a/DEPS +++ b/DEPS @@ -123,7 +123,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling depot_tools # and whatever else without interference from each other. - 'depot_tools_revision': '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. From ec4f56e9732e0a245707a7eb5601353ac91c1c7d Mon Sep 17 00:00:00 2001 From: "pdfium-autoroll@skia-public.iam.gserviceaccount.com" Date: Sun, 16 Aug 2026 22:09:24 -0700 Subject: [PATCH 050/123] Roll Memory Tools from a7e928b8bb8d to 8d3182bb3c86 (1 revision) https://chromium.googlesource.com/chromium/src/tools/memory.git/+log/a7e928b8bb8d..8d3182bb3c86 If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/memory-tools-pdfium Please CC thestig@chromium.org,thestig@google.com on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Bug: None Tbr: thestig@google.com Change-Id: I8f48428dad7c753b66c5f2bdc8c8700ce587883e Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155170 Commit-Queue: pdfium-autoroll@skia-public.iam.gserviceaccount.com Bot-Commit: pdfium-autoroll@skia-public.iam.gserviceaccount.com --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 9c7f914a7f..1538f71f11 100644 --- a/DEPS +++ b/DEPS @@ -249,7 +249,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling tools_memory # and whatever else without interference from each other. - 'tools_memory_revision': '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. From 4dfa7d73c0749a553dbc8592e3a91b0c3a268297 Mon Sep 17 00:00:00 2001 From: Aryan Date: Mon, 17 Aug 2026 09:07:51 -0700 Subject: [PATCH 051/123] Replace CHECKs() in png_encode.cpp with CHECK_GE() This CL replaces CHECKs of the form CHECK(a >= b) with CHECK_GE() to ensure greater clarity, to fix cpplint errors and to provide clear messages when these checks fail. This CL also updates the file to include check_op.h for the same. Bug: 42270641 Change-Id: I5ae466496149da382b2678531433e1df45492d9b Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155190 Reviewed-by: Lei Zhang Reviewed-by: Andy Phan Auto-Submit: Aryan Krishnan Commit-Queue: Lei Zhang --- testing/utils/png_encode.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/testing/utils/png_encode.cpp b/testing/utils/png_encode.cpp index 58d93502bb..1d0035b997 100644 --- a/testing/utils/png_encode.cpp +++ b/testing/utils/png_encode.cpp @@ -5,6 +5,7 @@ #include "testing/utils/png_encode.h" #include "core/fxcrt/check.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" @@ -84,9 +85,9 @@ 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); + CHECK_GE(stride, 0); + CHECK_GE(width, 0); + CHECK_GE(height, 0); FX_SAFE_FILESIZE size = stride; size *= height; auto input = UNSAFE_TODO( From 28d2c7aeee06c32ace5f7a518afa9261804e6dd1 Mon Sep 17 00:00:00 2001 From: Aryan Date: Mon, 17 Aug 2026 09:08:26 -0700 Subject: [PATCH 052/123] Add missing include in cfxjse_formcalc_context.cpp This CL adds the missing include for check.h in cfxjse_formcalc_context.cpp. While this is imported transitively via check_op.h, explicitly importing it in this file makes the include clearer. Change-Id: I16265a09f274152c706867feb5e058389280a61b Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155210 Reviewed-by: Andy Phan Commit-Queue: Lei Zhang Reviewed-by: Lei Zhang Auto-Submit: Aryan Krishnan --- fxjs/xfa/cfxjse_formcalc_context.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/fxjs/xfa/cfxjse_formcalc_context.cpp b/fxjs/xfa/cfxjse_formcalc_context.cpp index 09149e581e..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" From 5da3e72544ce1624048a3a934f5d3e059f28fb0a Mon Sep 17 00:00:00 2001 From: Aryan Date: Mon, 17 Aug 2026 09:43:02 -0700 Subject: [PATCH 053/123] Replace DCHECK() checks with DCHECK_NE() This CL replaces the remaining DCHECK()s of the form DCHECK(a != b) with DCHECK_NE for greater clarity, to fix cpplint errors and to provide clear messages when these checks fail. This CL also updates includes wherever needed. Bug: 42270641 Change-Id: Iad1ad8744919f0af4e9313d82aff39ce27a698c9 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155130 Reviewed-by: Lei Zhang Commit-Queue: Lei Zhang Auto-Submit: Aryan Krishnan Reviewed-by: Andy Phan --- core/fpdfapi/page/cpdf_streamparser.cpp | 16 ++++++++-------- core/fxcrt/cfx_datetime.cpp | 11 +++++------ fxbarcode/qrcode/BC_QRCoderMode.cpp | 4 ++-- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/core/fpdfapi/page/cpdf_streamparser.cpp b/core/fpdfapi/page/cpdf_streamparser.cpp index 97e663c6bf..fcd8aecbc7 100644 --- a/core/fpdfapi/page/cpdf_streamparser.cpp +++ b/core/fpdfapi/page/cpdf_streamparser.cpp @@ -26,7 +26,7 @@ #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" @@ -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(), diff --git a/core/fxcrt/cfx_datetime.cpp b/core/fxcrt/cfx_datetime.cpp index d1b4572267..f7cc965bc4 100644 --- a/core/fxcrt/cfx_datetime.cpp +++ b/core/fxcrt/cfx_datetime.cpp @@ -7,7 +7,6 @@ #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" @@ -27,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); @@ -36,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; } @@ -44,7 +43,7 @@ int64_t DateToDays(int32_t iYear, uint8_t iMonth, uint8_t iDay, bool bIncludeThisDay) { - DCHECK(iYear != 0); + DCHECK_NE(iYear, 0); DCHECK_GE(iMonth, 1); DCHECK_LE(iMonth, 12); DCHECK_GE(iDay, 1); @@ -69,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); @@ -78,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/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; } From 2beaa88f191c443972a276a90f96ab640aefab89 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Mon, 17 Aug 2026 10:13:18 -0700 Subject: [PATCH 054/123] Roll abseil, buildtools and libc++ Roll third_party/abseil-cpp/ d8e483edd..df548c50b (24 commits) https://chromium.googlesource.com/chromium/src/third_party/abseil-cpp.git/+log/d8e483edd8b4..df548c50b2cd Created with: roll-dep third_party/abseil-cpp Roll buildtools/ 0d39be5a3..9e7655f4e (8 commits) https://chromium.googlesource.com/chromium/src/buildtools.git/+log/0d39be5a3f12..9e7655f4ee43 Created with: roll-dep buildtools Roll third_party/libc++/src/ 5abc7f839..b16984ce9 (84 commits) https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxx.git/+log/5abc7f839700..b16984ce99c7 Created with: roll-dep third_party/libc++/src Change-Id: Ibf3137855ededd51bc152845b5f639e310969819 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155110 Auto-Submit: Lei Zhang Reviewed-by: Andy Phan Commit-Queue: Andy Phan --- DEPS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index 1538f71f11..cd97ee1ec3 100644 --- a/DEPS +++ b/DEPS @@ -83,7 +83,7 @@ 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': 'df548c50b2cda67158364d3d23c63043881b391d', # 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. @@ -99,7 +99,7 @@ vars = { # 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': '9e7655f4ee433ef4c6efcffd57e379db8f8c0432', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling catapult # and whatever else without interference from each other. @@ -181,7 +181,7 @@ 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': 'b16984ce99c702355a5b2b4c52574e82cec41fb9', # 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. From 365a1414e1b6775a95dc1449723be696148daa85 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Mon, 17 Aug 2026 11:46:51 -0700 Subject: [PATCH 055/123] Make jinja2 and markupsafe DEPS entries conditional on checkout_v8 Make it clearer these 3P projects are transitive dependencies for V8. Change-Id: Ibedd27d0cff1d62d5370d584c7e96b78a4563887 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155250 Auto-Submit: Lei Zhang Reviewed-by: Andy Phan Commit-Queue: Andy Phan --- DEPS | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/DEPS b/DEPS index cd97ee1ec3..7734dfdf40 100644 --- a/DEPS +++ b/DEPS @@ -427,9 +427,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') + @@ -622,9 +624,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@' + From e186963a592eb050a546265dc0e9bc81d0ecfd2c Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Mon, 17 Aug 2026 12:20:59 -0700 Subject: [PATCH 056/123] Roll base/allocator/partition_allocator/ 2723da2d1..29013c81d (35 commits) https://chromium.googlesource.com/chromium/src/base/allocator/partition_allocator.git/+log/2723da2d1b2c..29013c81d1fa Created with: roll-dep base/allocator/partition_allocator Change-Id: I6584aa67920377e2d745d3cd05271068f4bd4600 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155090 Commit-Queue: Andy Phan Auto-Submit: Lei Zhang Reviewed-by: Andy Phan --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 7734dfdf40..7803d461c7 100644 --- a/DEPS +++ b/DEPS @@ -213,7 +213,7 @@ 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': '29013c81d1fa46475bdf6608d71a10b5ebab3f48', # 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. From ae277bf214480d77ac74852969146655bf728a11 Mon Sep 17 00:00:00 2001 From: Adam DeMuri Date: Mon, 17 Aug 2026 16:03:07 -0700 Subject: [PATCH 057/123] Fix crash on invalid object numbers in xref table When parsing a PDF with object numbers exceeding CPDF_Parser::kMaxObjectNumber, PDFium would previously crash due to CHECK failures in CPDF_CrossRefTable. This CL changes CPDF_CrossRefTable's AddNormal, AddCompressed, and SetFree methods to return a boolean indicating success instead of crashing on invalid object numbers. CPDF_Parser is updated to propagate these failures and fail loading the document gracefully. Bug: 457855936 Change-Id: I589c42165a745e371594e737fc225e2b0bc98c2f Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/152630 Reviewed-by: Lei Zhang Commit-Queue: Lei Zhang --- core/fpdfapi/parser/cpdf_cross_ref_table.cpp | 30 ++++++++++------ core/fpdfapi/parser/cpdf_cross_ref_table.h | 6 ++-- core/fpdfapi/parser/cpdf_parser.cpp | 35 ++++++++++--------- core/fpdfapi/parser/cpdf_parser.h | 2 +- .../parser/cpdf_parser_embeddertest.cpp | 4 +-- 5 files changed, 43 insertions(+), 34 deletions(-) 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_parser.cpp b/core/fpdfapi/parser/cpdf_parser.cpp index 30dcb11085..6f89380920 100644 --- a/core/fpdfapi/parser/cpdf_parser.cpp +++ b/core/fpdfapi/parser/cpdf_parser.cpp @@ -672,30 +672,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( @@ -808,18 +814,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 613a1d750d..76cfa70806 100644 --- a/core/fpdfapi/parser/cpdf_parser.h +++ b/core/fpdfapi/parser/cpdf_parser.h @@ -182,7 +182,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")); } From e7961d6242bedb6ce1b0ff3ff0d367df27bd0dff Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Mon, 17 Aug 2026 17:45:29 -0700 Subject: [PATCH 058/123] Add dual max delta and MSE criteria for image fuzzy matching Update pixel diffing utilities and image diffing tools to evaluate both a maximum per-channel delta (<= 3) and an overall image mean squared error (MSE <= 0.05) when performing fuzzy image comparisons. This aligns image comparison with IEEE 1180 / ISO 10918-2 IDCT accuracy and chroma transformation tolerances, accommodating compliant decoder variances across platforms and libraries. TAG=agy CONV=8b22303a-be4a-409b-80e8-f2f4beae92a7 Change-Id: Ibd3e60eb7b414d29c5c901bcef4dcd7f60488e24 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154970 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- BUILD.gn | 3 +- testing/BUILD.gn | 7 +++ testing/embedder_test.cpp | 65 ++++++++++++++-------- testing/embedder_test.h | 10 ++-- testing/image_diff/image_diff.cpp | 27 +++++++-- testing/tools/pngdiffer.py | 5 +- testing/utils/pixel_diff_util.cpp | 9 +++ testing/utils/pixel_diff_util.h | 10 +++- testing/utils/pixel_diff_util_unittest.cpp | 49 ++++++++++++++++ 9 files changed, 146 insertions(+), 39 deletions(-) create mode 100644 testing/utils/pixel_diff_util_unittest.cpp diff --git a/BUILD.gn b/BUILD.gn index e3d7feeca1..6c2ca42598 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -301,7 +301,7 @@ group("pdfium_unittest_deps") { "fpdfsdk/*", "fxbarcode/*", "fxjs/*", - "testing/image_diff/*", + "testing/*", "xfa/*", ] } @@ -324,6 +324,7 @@ test("pdfium_unittests") { "core/fxge:unittests", "fpdfsdk:unittests", "testing:unit_test_support", + "testing:unittests", "testing/image_diff:unittests", "//testing/gmock", "//testing/gtest", diff --git a/testing/BUILD.gn b/testing/BUILD.gn index b987be59cf..898adcf090 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 @@ -104,6 +105,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 = [ diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp index c7ee35c4cb..1d79d945dd 100644 --- a/testing/embedder_test.cpp +++ b/testing/embedder_test.cpp @@ -291,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"); @@ -376,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); @@ -399,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; @@ -407,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; @@ -426,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,14 +451,16 @@ int CompareBGRxPremultBitmapToPng(pdfium::span bitmap_span, } } return CompareBGRxBitmapToPng(bitmap_span, bitmap_stride, decoded_png, - max_pixel_per_channel_delta); + 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; @@ -473,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) { @@ -495,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); @@ -523,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: @@ -1168,7 +1181,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); } @@ -1178,7 +1192,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) { @@ -1188,7 +1203,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); } @@ -1203,7 +1219,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 505cd83484..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); diff --git a/testing/image_diff/image_diff.cpp b/testing/image_diff/image_diff.cpp index a2f3a834b1..410af30296 100644 --- a/testing/image_diff/image_diff.cpp +++ b/testing/image_diff/image_diff.cpp @@ -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, @@ -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/tools/pngdiffer.py b/testing/tools/pngdiffer.py index f88d644c42..7c099fb324 100755 --- a/testing/tools/pngdiffer.py +++ b/testing/tools/pngdiffer.py @@ -148,11 +148,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/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); +} From 5e7f267e069a74e02fe904a184381ee3a04b48c7 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Mon, 17 Aug 2026 18:38:24 -0700 Subject: [PATCH 059/123] Expose Rust decoder features and enable fuzzy matching in test runner Expose RUST_BMP, RUST_JPEG, and RUST_PNG features via `pdfium_test --show-config`. Update `suppressor.py` to automatically enable fuzzy matching when any Rust decoder feature is active in the test build. Also update `pngdiffer.py` to ensure fallback expectation uniqueness checks use exact matching so valid overrides are not falsely flagged under fuzzy comparison. TAG=agy CONV=8b22303a-be4a-409b-80e8-f2f4beae92a7 Change-Id: Ibf244723e77ccd99fae21138b188c756cad2320f Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154990 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- testing/pdfium_test.cc | 9 +++++++++ testing/tools/suppressor.py | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/testing/pdfium_test.cc b/testing/pdfium_test.cc index fbce83e9ad..4d56e580de 100644 --- a/testing/pdfium_test.cc +++ b/testing/pdfium_test.cc @@ -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/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 From 29fe6d617d69c329a89fca4378a132472bd650d4 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Mon, 17 Aug 2026 19:00:33 -0700 Subject: [PATCH 060/123] Add unit tests for CPDFContentstreamWriteUtils Add unit tests for WriteFloat(), WriteMatrix(), WritePoint(), and WriteRect() in cpdf_contentstream_write_utils_unittest.cpp. Exercise WriteFloat() with various floating-point values including zero, positive and negative values, fractional numbers, minimum and maximum finite floats, infinities, and NaN. TAG=agy CONV=a28a9ffe-77bc-49af-a422-c6ece40c06b3 Change-Id: Ia0a3243da0842eff222ab5d4e420e71779935c6c Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155290 Commit-Queue: Lei Zhang Reviewed-by: Tom Sepez --- core/fpdfapi/edit/BUILD.gn | 2 + ...pdf_contentstream_write_utils_unittest.cpp | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 core/fpdfapi/edit/cpdf_contentstream_write_utils_unittest.cpp diff --git a/core/fpdfapi/edit/BUILD.gn b/core/fpdfapi/edit/BUILD.gn index a677182031..3b9bf3e781 100644 --- a/core/fpdfapi/edit/BUILD.gn +++ b/core/fpdfapi/edit/BUILD.gn @@ -56,10 +56,12 @@ source_set("contentstream_write_utils") { pdfium_unittest_source_set("unittests") { sources = [ + "cpdf_contentstream_write_utils_unittest.cpp", "cpdf_npagetooneexporter_unittest.cpp", "cpdf_pagecontentgenerator_unittest.cpp", ] deps = [ + ":contentstream_write_utils", ":edit", "../../fxge", "../font", 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..41bf592307 --- /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(".00124999997", WriteFloatToString(0.00125f)); + EXPECT_EQ("123.449997", WriteFloatToString(123.45f)); + EXPECT_EQ("-7.5", WriteFloatToString(-7.5f)); + EXPECT_EQ("38.895287", WriteFloatToString(38.895285f)); + EXPECT_EQ("-77.037231", WriteFloatToString(-77.037232f)); + EXPECT_EQ("340282350000000000000000000000000000000", + WriteFloatToString(std::numeric_limits::max())); + EXPECT_EQ("-340282350000000000000000000000000000000", + WriteFloatToString(-std::numeric_limits::max())); + EXPECT_EQ(".0000000000000000000000000000000000000117549435", + WriteFloatToString(std::numeric_limits::min())); + EXPECT_EQ("-.0000000000000000000000000000000000000117549435", + 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)); +} From 9ce000987b5118ebebaff5ab2618aaad46dd4759 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Mon, 17 Aug 2026 19:22:44 -0700 Subject: [PATCH 061/123] Use Dragonbox to format floating-point numbers in content streams Replace the SkFloatToDecimal() implementation and pow10() helper in cpdf_contentstream_write_utils.cpp with FloatToDecimal(), utilizing jkj::dragonbox::to_decimal() from //third_party/dragonbox. Dragonbox generates the shortest decimal representation that uniquely round-trips to the original IEEE 754 floating-point value. This avoids fixed-precision rounding artifacts (such as ".69999999" for 0.7f) and produces cleaner and more compact PDF content streams while adhering to the PDF requirement that numbers must not use the exponential format. Update existing test expectations across parser, edit, and embedder tests to reflect the shorter, exact decimal output. TAG=agy CONV=a28a9ffe-77bc-49af-a422-c6ece40c06b3 Change-Id: I494a7a17dd1f1c166a895430551fe73e3661f106 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155291 Reviewed-by: Tom Sepez Commit-Queue: Lei Zhang --- DEPS | 6 +- core/fpdfapi/edit/BUILD.gn | 5 +- core/fpdfapi/edit/DEPS | 5 + .../edit/cpdf_contentstream_write_utils.cpp | 258 +++++------------- ...pdf_contentstream_write_utils_unittest.cpp | 10 +- .../cpdf_pagecontentgenerator_unittest.cpp | 40 ++- core/fpdfapi/parser/cpdf_number_unittest.cpp | 4 +- core/fpdfapi/parser/cpdf_object_unittest.cpp | 13 +- fpdfsdk/fpdf_save_embeddertest.cpp | 2 +- 9 files changed, 116 insertions(+), 227 deletions(-) create mode 100644 core/fpdfapi/edit/DEPS diff --git a/DEPS b/DEPS index 7803d461c7..5e84c3d34b 100644 --- a/DEPS +++ b/DEPS @@ -387,11 +387,9 @@ deps = { 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@' + diff --git a/core/fpdfapi/edit/BUILD.gn b/core/fpdfapi/edit/BUILD.gn index 3b9bf3e781..a6ecda2908 100644 --- a/core/fpdfapi/edit/BUILD.gn +++ b/core/fpdfapi/edit/BUILD.gn @@ -51,7 +51,10 @@ source_set("contentstream_write_utils") { "../../../:pdfium_strict_config", "../../../:pdfium_noshorten_config", ] - deps = [ "../../fxcrt" ] + deps = [ + "../../fxcrt", + "//third_party/dragonbox", + ] } pdfium_unittest_source_set("unittests") { 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 index 41bf592307..a28964783f 100644 --- a/core/fpdfapi/edit/cpdf_contentstream_write_utils_unittest.cpp +++ b/core/fpdfapi/edit/cpdf_contentstream_write_utils_unittest.cpp @@ -30,18 +30,18 @@ TEST(CPDFContentstreamWriteUtils, WriteFloat) { EXPECT_EQ("-1", WriteFloatToString(-1.0f)); EXPECT_EQ(".5", WriteFloatToString(0.5f)); EXPECT_EQ("-.5", WriteFloatToString(-0.5f)); - EXPECT_EQ(".00124999997", WriteFloatToString(0.00125f)); - EXPECT_EQ("123.449997", WriteFloatToString(123.45f)); + 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.037231", WriteFloatToString(-77.037232f)); + 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(".0000000000000000000000000000000000000117549435", + EXPECT_EQ(".000000000000000000000000000000000000011754944", WriteFloatToString(std::numeric_limits::min())); - EXPECT_EQ("-.0000000000000000000000000000000000000117549435", + EXPECT_EQ("-.000000000000000000000000000000000000011754944", WriteFloatToString(-std::numeric_limits::min())); EXPECT_EQ("340282350000000000000000000000000000000", WriteFloatToString(INFINITY)); diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp index 6f4abd586c..9ccb871895 100644 --- a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp +++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp @@ -75,7 +75,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) { @@ -102,11 +102,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)); } @@ -138,11 +138,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)); } @@ -178,9 +178,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)); } @@ -215,8 +215,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(); @@ -239,7 +238,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())); @@ -298,8 +297,7 @@ TEST_F(CPDFPageContentGeneratorTest, ProcessStandardText) { second_resource_at.value() - first_resource_at.value()); ByteString last_string = text_string.Substr(second_resource_at.value()); // q and Q must be outside the BT .. ET operations - const ByteString kCompareString1 = - "q .5 .69999999 .34999999 rg 1 .89999998 0 RG /"; + const ByteString kCompareString1 = "q .5 .7 .35 rg 1 .9 0 RG /"; // Color RGB values used are integers divided by 255. const ByteString kCompareString2 = " gs BT 1 0 0 1 100 100 Tm /"; const ByteString kCompareString3 = @@ -444,8 +442,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/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/fpdfsdk/fpdf_save_embeddertest.cpp b/fpdfsdk/fpdf_save_embeddertest.cpp index 678b6a12c8..715e2b8da2 100644 --- a/fpdfsdk/fpdf_save_embeddertest.cpp +++ b/fpdfsdk/fpdf_save_embeddertest.cpp @@ -169,7 +169,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(); From 4f3ac49cea109b28b6f2ab8ccd12af4d9cba91dc Mon Sep 17 00:00:00 2001 From: Aryan Date: Tue, 18 Aug 2026 08:21:53 -0700 Subject: [PATCH 062/123] Replace or remove some C-style casts in basicmodule.cpp This CL replaces and removes some of the C-style uint32_t casts with static casts. Change-Id: I5d9980666b56597db2a5af778ca67c1f3e91114f Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155230 Commit-Queue: Andy Phan Reviewed-by: Lei Zhang Reviewed-by: Andy Phan --- core/fxcodec/basic/basicmodule.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/fxcodec/basic/basicmodule.cpp b/core/fxcodec/basic/basicmodule.cpp index 448e18b7c6..2572c46180 100644 --- a/core/fxcodec/basic/basicmodule.cpp +++ b/core/fxcodec/basic/basicmodule.cpp @@ -187,7 +187,7 @@ void RLScanlineDecoder::UpdateOperator(uint8_t used_bytes) { return; } if (operator_ < 128) { - DCHECK_GE((uint32_t)operator_ + 1, used_bytes); + DCHECK_GE(operator_ + 1, used_bytes); if (used_bytes == operator_ + 1) { src_offset_ += used_bytes; GetNextOperator(); @@ -201,7 +201,7 @@ void RLScanlineDecoder::UpdateOperator(uint8_t used_bytes) { return; } uint8_t count = 257 - operator_; - DCHECK_GE((uint32_t)count, used_bytes); + DCHECK_GE(count, used_bytes); if (used_bytes == count) { src_offset_++; GetNextOperator(); @@ -361,7 +361,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++; } From d62a2d01f17f8bea9bda7f6fd3f8a8023c21efdb Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Tue, 18 Aug 2026 10:14:14 -0700 Subject: [PATCH 063/123] Harmonize test image suffix order and rename local pixel expectations Update `pngdiffer.py` to use canonical GDI expectation suffix ordering: - `_GDI_SKIA_SUFFIX_ORDER`: `_skia_gdi` -> `_skia` -> `_gdi` -> baseline - `_GDI_AGG_SUFFIX_ORDER`: `_gdi` -> baseline Rename local pixel expectation images in `testing/resources/pixel/` to match the canonical `_{renderer}_{platform}` format (`_skia_gdi` and `_gdi`). TAG=agy CONV=8b22303a-be4a-409b-80e8-f2f4beae92a7 Cq-Depend: pdfium/pdfium_tests:151570 Change-Id: I0e526d9156038c6a16d7394281c70a5e887b6dae Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155030 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- DEPS | 2 +- PRESUBMIT.py | 18 +++++++++--------- PRESUBMIT_test.py | 14 +++++++------- ...g => bug_1966_expected_skia_gdi.pdf.0.png} | Bin ...g => bug_2106_expected_skia_gdi.pdf.0.png} | Bin ...bug_346598551_expected_skia_gdi.pdf.0.png} | Bin ...bug_440028542_expected_skia_gdi.pdf.0.png} | Bin ...knockout_fill_expected_skia_gdi.pdf.0.png} | Bin ... shade-tensor_expected_skia_gdi.pdf.0.png} | Bin ....png => shade_expected_skia_gdi.pdf.0.png} | Bin ...single_point_paths_expected_gdi.pdf.0.png} | Bin ...e_point_paths_expected_skia_gdi.pdf.0.png} | Bin ...sfer_function_expected_skia_gdi.pdf.0.png} | Bin ...sfer_function_expected_skia_gdi.pdf.1.png} | Bin testing/tools/pngdiffer.py | 14 ++------------ 15 files changed, 19 insertions(+), 29 deletions(-) rename testing/resources/pixel/{bug_1966_expected_gdi_skia.pdf.0.png => bug_1966_expected_skia_gdi.pdf.0.png} (100%) rename testing/resources/pixel/{bug_2106_expected_gdi_skia.pdf.0.png => bug_2106_expected_skia_gdi.pdf.0.png} (100%) rename testing/resources/pixel/{bug_346598551_expected_gdi_skia.pdf.0.png => bug_346598551_expected_skia_gdi.pdf.0.png} (100%) rename testing/resources/pixel/{bug_440028542_expected_gdi_skia.pdf.0.png => bug_440028542_expected_skia_gdi.pdf.0.png} (100%) rename testing/resources/pixel/{same_color_knockout_fill_expected_gdi_skia.pdf.0.png => same_color_knockout_fill_expected_skia_gdi.pdf.0.png} (100%) rename testing/resources/pixel/{shade-tensor_expected_gdi_skia.pdf.0.png => shade-tensor_expected_skia_gdi.pdf.0.png} (100%) rename testing/resources/pixel/{shade_expected_gdi_skia.pdf.0.png => shade_expected_skia_gdi.pdf.0.png} (100%) rename testing/resources/pixel/{single_point_paths_expected_gdi_agg.pdf.0.png => single_point_paths_expected_gdi.pdf.0.png} (100%) rename testing/resources/pixel/{single_point_paths_expected_gdi_skia.pdf.0.png => single_point_paths_expected_skia_gdi.pdf.0.png} (100%) rename testing/resources/pixel/{transfer_function_expected_gdi_skia.pdf.0.png => transfer_function_expected_skia_gdi.pdf.0.png} (100%) rename testing/resources/pixel/{transfer_function_expected_gdi_skia.pdf.1.png => transfer_function_expected_skia_gdi.pdf.1.png} (100%) diff --git a/DEPS b/DEPS index 5e84c3d34b..7ee176c15a 100644 --- a/DEPS +++ b/DEPS @@ -217,7 +217,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling pdfium_tests # and whatever else without interference from each other. - 'pdfium_tests_revision': '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. diff --git a/PRESUBMIT.py b/PRESUBMIT.py index 0dba76b14d..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'): @@ -583,9 +583,9 @@ def CheckNoIOStreamInHeaders(input_api, output_api): 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) + '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 [] 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/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/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/pngdiffer.py b/testing/tools/pngdiffer.py index 7c099fb324..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 From 7aac65de9a631542d5e65bd6c96b27bca2c86cd7 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Tue, 18 Aug 2026 14:47:21 -0700 Subject: [PATCH 064/123] Support Skia/Rust-based JPEG decoding in ProgressiveDecoder Introduce SkiaJpegContext implementing ProgressiveDecoderContext for JPEG image decoding via Skia's SkJpegDecoder when pdf_enable_rust_jpeg is enabled. Also fix SkiaScanlineDecoder to use kRGBA_8888_SkColorType and enforce MCU alignment checks for downscaled decoding, and update ProgressiveDecoder::GetBitmapFormat() for JPEG decoding via Skia. BUG=none TAG=agy CONV=7f2ceaaa-c519-4a0b-bedd-0122a389d3d4 Change-Id: I1e010937eb770123aa283e58fe2ecc0bbf174fe6 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154830 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- core/fxcodec/BUILD.gn | 34 +++-- core/fxcodec/jpeg/DEPS | 3 + core/fxcodec/jpeg/jpegmodule.cpp | 27 ++-- core/fxcodec/jpeg/skia_jpeg_context.cpp | 149 ++++++++++++++++++++ core/fxcodec/jpeg/skia_jpeg_context.h | 57 ++++++++ core/fxcodec/jpeg/skia_scanline_decoder.cpp | 68 ++++++++- core/fxcodec/progressive_decoder.cpp | 68 ++++++++- testing/embedder_test.cpp | 2 +- 8 files changed, 377 insertions(+), 31 deletions(-) create mode 100644 core/fxcodec/jpeg/skia_jpeg_context.cpp create mode 100644 core/fxcodec/jpeg/skia_jpeg_context.h diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index 4ef7d96481..190c32c636 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -96,8 +96,6 @@ source_set("fxcodec") { "gif/cfx_gifcontext.h", "gif/lzw_decompressor.cpp", "gif/lzw_decompressor.h", - "jpeg/libjpeg_jpeg_context.cpp", - "jpeg/libjpeg_jpeg_context.h", "progressive_decoder.cpp", "progressive_decoder.h", "progressive_decoder_context.cpp", @@ -122,6 +120,18 @@ source_set("fxcodec") { "bmp/fx_bmp.h", ] } + if (pdf_enable_rust_jpeg) { + sources += [ + "jpeg/skia_jpeg_context.cpp", + "jpeg/skia_jpeg_context.h", + ] + deps += [ "//skia" ] + } else { + sources += [ + "jpeg/libjpeg_jpeg_context.cpp", + "jpeg/libjpeg_jpeg_context.h", + ] + } # TODO(https://crbug.com/444045690): Remove `pdf_enable_rust_png` from the # condition below once this build mode has been tested and stabilized. @@ -147,20 +157,24 @@ 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" ] - } else { - sources += [ - "jpeg/jpeg_common.c", - "jpeg/jpeg_common.h", - "jpeg/libjpeg_scanline_decoder.cpp", - "jpeg/libjpeg_scanline_decoder.h", - ] - deps += [ "//third_party:jpeg" ] } visibility = [ "../../*" ] } diff --git a/core/fxcodec/jpeg/DEPS b/core/fxcodec/jpeg/DEPS index eb5cbbe40a..a78ca5aada 100644 --- a/core/fxcodec/jpeg/DEPS +++ b/core/fxcodec/jpeg/DEPS @@ -6,6 +6,9 @@ include_rules = [ ] 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 7b8e6be545..3cf8818a96 100644 --- a/core/fxcodec/jpeg/jpegmodule.cpp +++ b/core/fxcodec/jpeg/jpegmodule.cpp @@ -10,13 +10,12 @@ #include #include "build/build_config.h" +#include "core/fxcodec/jpeg/libjpeg_scanline_decoder.h" #include "core/fxcodec/scanlinedecoder.h" #include "core/fxcrt/span.h" -#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_JPEG) +#if defined(PDF_ENABLE_RUST_JPEG) #include "core/fxcodec/jpeg/skia_scanline_decoder.h" -#else -#include "core/fxcodec/jpeg/libjpeg_scanline_decoder.h" #endif #if BUILDFLAG(IS_WIN) @@ -42,23 +41,27 @@ std::unique_ptr JpegModule::CreateDecoder( int nComps, bool ColorTransform, uint32_t scale_denom) { -#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_JPEG) - return SkiaScanlineDecoder::Create(src_span, width, height, nComps, - ColorTransform, scale_denom); -#else +#if defined(PDF_ENABLE_RUST_JPEG) + auto decoder = SkiaScanlineDecoder::Create(src_span, width, height, nComps, + ColorTransform, scale_denom); + if (decoder) { + return decoder; + } +#endif return LibjpegScanlineDecoder::Create(src_span, width, height, nComps, ColorTransform, scale_denom); -#endif } // static std::optional JpegModule::LoadInfo( pdfium::span src_span) { -#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_JPEG) - return SkiaScanlineDecoder::LoadInfo(src_span); -#else - return LibjpegScanlineDecoder::LoadInfo(src_span); +#if defined(PDF_ENABLE_RUST_JPEG) + auto info = SkiaScanlineDecoder::LoadInfo(src_span); + if (info.has_value()) { + return info; + } #endif + return LibjpegScanlineDecoder::LoadInfo(src_span); } #if BUILDFLAG(IS_WIN) 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 index 1a5e5967b0..875601cc26 100644 --- a/core/fxcodec/jpeg/skia_scanline_decoder.cpp +++ b/core/fxcodec/jpeg/skia_scanline_decoder.cpp @@ -4,6 +4,7 @@ #include "core/fxcodec/jpeg/skia_scanline_decoder.h" +#include #include #include #include @@ -42,6 +43,63 @@ 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 @@ -99,6 +157,14 @@ bool SkiaScanlineDecoder::CreateImpl(pdfium::span src_span, 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_); @@ -127,7 +193,7 @@ bool SkiaScanlineDecoder::Rewind() { color_type = kGray_8_SkColorType; break; case 3: - color_type = kRGB_888x_SkColorType; + color_type = kRGBA_8888_SkColorType; break; default: NOTREACHED(); diff --git a/core/fxcodec/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index 223683df63..18936a2ea6 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -11,9 +11,7 @@ #include #include "build/build_config.h" -#include "core/fxcodec/cfx_codec_memory.h" #include "core/fxcodec/gif/cfx_gifcontext.h" -#include "core/fxcodec/jpeg/libjpeg_jpeg_context.h" #include "core/fxcodec/progressive_decoder_context.h" #include "core/fxcodec/tiff/libtiff_tiff_context.h" #include "core/fxcrt/check.h" @@ -42,11 +40,14 @@ #else #include "core/fxcodec/bmp/cfx_bmpcontext.h" #endif -// 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) + +#if defined(PDF_ENABLE_RUST_JPEG) +#include "core/fxcodec/jpeg/skia_jpeg_context.h" +#else +#include "core/fxcodec/jpeg/libjpeg_jpeg_context.h" +#endif + +#if defined(PDF_ENABLE_RUST_PNG) #include "core/fxcodec/png/skia_png_context.h" #else #include "core/fxcodec/png/libpng_png_context.h" @@ -71,11 +72,15 @@ std::unique_ptr CreateDecoderContext( case FXCODEC_IMAGE_GIF: return std::make_unique(delegate); case FXCODEC_IMAGE_JPG: { +#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 } case FXCODEC_IMAGE_PNG: #if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) @@ -400,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_, @@ -424,9 +446,23 @@ bool ProgressiveDecoder::JpegDetectImageTypeInBuffer( NOTREACHED(); } } +#endif } FXCODEC_STATUS ProgressiveDecoder::JpegStartDecode() { +#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_; + } + 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; @@ -434,9 +470,19 @@ FXCODEC_STATUS ProgressiveDecoder::JpegStartDecode() { context_.reset(); } return status_; +#endif } FXCODEC_STATUS ProgressiveDecoder::JpegContinueDecode() { +#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; + } + return status_; +#else status_ = static_cast(context_.get())->ContinueDecode(); if (status_ == FXCODEC_STATUS::kError) { context_.reset(); @@ -446,6 +492,7 @@ FXCODEC_STATUS ProgressiveDecoder::JpegContinueDecode() { file_ = nullptr; } return status_; +#endif } bool ProgressiveDecoder::PngDetectImageTypeInBuffer() { @@ -901,6 +948,13 @@ void ProgressiveDecoder::Resample(const RetainPtr& pDeviceBitmap, FXDIB_Format ProgressiveDecoder::GetBitmapFormat() const { switch (image_type_) { case FXCODEC_IMAGE_JPG: +#if defined(PDF_ENABLE_RUST_JPEG) + // When using the Skia/Rust decoder, Skia prefers standard 32-bit BGRA + // buffers. + return FXDIB_Format::kBgra; +#else + return GetBitsPerPixel() <= 24 ? FXDIB_Format::kBgr : FXDIB_Format::kBgrx; +#endif case FXCODEC_IMAGE_BMP: return GetBitsPerPixel() <= 24 ? FXDIB_Format::kBgr : FXDIB_Format::kBgrx; case FXCODEC_IMAGE_PNG: diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp index 1d79d945dd..38f3409969 100644 --- a/testing/embedder_test.cpp +++ b/testing/embedder_test.cpp @@ -450,7 +450,7 @@ int CompareBGRxPremultBitmapToPng(pdfium::span bitmap_span, bitmap_row[w] = UnPreMultiplyColor(bitmap_row[w]); } } - return CompareBGRxBitmapToPng(bitmap_span, bitmap_stride, decoded_png, + return CompareBGRxBitmapToPng(bitmap_data, bitmap_stride, decoded_png, max_pixel_per_channel_delta, max_mean_squared_error); } From 24765cea688a23baebf0ea8633cb08dfffcba57c Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Tue, 18 Aug 2026 15:27:22 -0700 Subject: [PATCH 065/123] Remove temporary pdf_enable_rust_jpeg override in pdfium.gni Remove the temporary forced false in pdfium.gni now that both scanline and progressive decoders support Skia/Rust JPEG decoding, allowing the flag to take effect when specified in build arguments. TAG=agy CONV=8b22303a-be4a-409b-80e8-f2f4beae92a7 Change-Id: If87bf01bfe2d14d5dc96d1590a0f53f4f80a733d Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154950 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- pdfium.gni | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pdfium.gni b/pdfium.gni index a70dda7d13..1c26ae04fb 100644 --- a/pdfium.gni +++ b/pdfium.gni @@ -100,10 +100,6 @@ declare_args() { use_system_zlib = false } -# Temporarily force false to allow recipe rollout without breaking tests. -# TODO(tsepez): remove this once XFA side supports rust jpeg as well. -pdf_enable_rust_jpeg = false - assert(!pdf_is_complete_lib || !is_component_build, "pdf_is_complete_lib=true requires is_component_build=false") From 78adc7c30182d34921cbe6ec0fe9beb2ea94d200 Mon Sep 17 00:00:00 2001 From: Alex Ovsienko Date: Tue, 18 Aug 2026 23:05:02 -0700 Subject: [PATCH 066/123] [DEPS] update gn and siso version. The update is required as the new version of siso contains fix for python paths remapping. That is required for remapping Python from runners embedded to DEPS provided one. Versions: 'siso_version': 'git_revision:bc45e8f67ae0f37d337190ad64aa8bb440c791eb', 'gn_version': 'git_revision:1c79e13d17a1760d6c6401c90061411842bd4881', Bug: 537680330 Change-Id: Ibfc9488a0e28103792bccca5c1eeb76e6fe73ee7 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155350 Commit-Queue: Lei Zhang Reviewed-by: Lei Zhang --- DEPS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 7ee176c15a..7e7f8d0d11 100644 --- a/DEPS +++ b/DEPS @@ -143,7 +143,7 @@ vars = { # 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:3fd3b0624d8cba16927853600130b2c33d4e7928', + 'gn_version': 'git_revision:1c79e13d17a1760d6c6401c90061411842bd4881', # 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. @@ -233,7 +233,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling siso # and whatever else without interference from each other. - 'siso_version': 'git_revision:7bc9a0bfe050ef97e1712ff61c6f11952799e951', + '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. From 2e7f123a919ff72222c68224f74984b558bd09c3 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Wed, 19 Aug 2026 10:35:43 -0700 Subject: [PATCH 067/123] Roll build/ 0d8b711d9..06f91b7a8 (1 commit) https://chromium.googlesource.com/chromium/src/build.git/+log/0d8b711d97d1..06f91b7a80ad Created with: roll-dep build This DEPS roll by itself causes Windows ASAN bots to hang when running death tests in pdfium_unittests. To fix that, update testing/unit_test_main.cpp to preload dbghelp.dll and msdia140.dll and initialize DbgHelp symbols on Windows before test execution starts, so ASAN crash symbolization continues to work with the new Windows SDK. This adapts crrev.com/1681985 for PDFium. TAG=agy CONV=f6d355f1-e2ad-4741-8c40-50933d68cfd1 Bug: 534305839, 548509159 Change-Id: I9e41e7e01de95ebadae07e25cc3b3061271b2569 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155370 Reviewed-by: Tom Sepez Commit-Queue: Lei Zhang --- DEPS | 2 +- testing/unit_test_main.cpp | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 7e7f8d0d11..ae9b9219af 100644 --- a/DEPS +++ b/DEPS @@ -95,7 +95,7 @@ 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': '0d8b711d97d1ec64909af024363817e480640fb7', + 'build_revision': '06f91b7a80ad35ee81ea742cf1e4ee8358f59560', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling buildtools # and whatever else without interference from each other. 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()); From c623d3113069bbf5914e75786d6cf941f9d45d42 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Wed, 19 Aug 2026 11:52:44 -0700 Subject: [PATCH 068/123] Reduce TIFFFaxTabEnt size to save .rodata space The Param member of TIFFFaxTabEnt only takes values in the range [0, 2560], which fits within a uint16_t. Upstream libtiff kept Param as uint32_t solely for legacy ABI compatibility with HylaFAX. PDFium does not link against HylaFAX, and does not need this compatibility. Changing Param to uint16_t removes the 2-byte structure padding and reduces sizeof(TIFFFaxTabEnt) from 8 bytes to 4 bytes. This saves ~48.5 KiB across the fax state machine tables in .rodata. TAG=agy CONV=2dd2a676-9be5-439f-97e4-11217a55a91d Change-Id: Iea9df2fc0201894cf506e28e8f858fd3fe0e772f Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155430 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- .../libtiff/0034-shrink-TIFFFaxTabEnt.patch | 14 ++++++++++++++ third_party/libtiff/README.pdfium | 1 + third_party/libtiff/tif_fax3.h | 3 +-- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 third_party/libtiff/0034-shrink-TIFFFaxTabEnt.patch 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[]; From 5a20d8eca6ee5e0ca2294ded784502beac19cd43 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Wed, 19 Aug 2026 12:55:48 -0700 Subject: [PATCH 069/123] Roll build/ 06f91b7a8..02e5d563d (55 commits) https://chromium.googlesource.com/chromium/src/build.git/+log/06f91b7a80ad..02e5d563d77f Created with: roll-dep build Change-Id: Ieb5f84f736df4bac2c603b9bb9bea1d036a88209 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155450 Auto-Submit: Lei Zhang Reviewed-by: Andy Phan Commit-Queue: Andy Phan --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index ae9b9219af..fe27e627ba 100644 --- a/DEPS +++ b/DEPS @@ -95,7 +95,7 @@ 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': '06f91b7a80ad35ee81ea742cf1e4ee8358f59560', + 'build_revision': '02e5d563d77fef191c552055f7ca861ee169c718', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling buildtools # and whatever else without interference from each other. From ce456f5e2cf0a1b6be3af8808b23a0ecac73b7ae Mon Sep 17 00:00:00 2001 From: Aryan Date: Wed, 19 Aug 2026 13:42:04 -0700 Subject: [PATCH 070/123] Replace C-style cast with FX_SAFE_UINT32 in scanline decoder This CL updates basicmodule.cpp to use FX_SAFE_UINT32 for safer buffer size calculations. This replaces the previous C-style cast and uses line_bytes_ multiplied by orig_height_, ensuring overflow checks are properly handled before validating against dest_size. Change-Id: I2a1890dcf1428ca2fda803cec0e30dbd6f1520f3 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155390 Reviewed-by: Lei Zhang Reviewed-by: Tom Sepez Commit-Queue: Tom Sepez --- core/fxcodec/basic/basicmodule.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/fxcodec/basic/basicmodule.cpp b/core/fxcodec/basic/basicmodule.cpp index 2572c46180..f9d7a3a1c4 100644 --- a/core/fxcodec/basic/basicmodule.cpp +++ b/core/fxcodec/basic/basicmodule.cpp @@ -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, From ec7ac67b6904c063f866d32cdf93e4ef9afb91be Mon Sep 17 00:00:00 2001 From: Aryan Date: Wed, 19 Aug 2026 13:42:35 -0700 Subject: [PATCH 071/123] Replace C-style casts with checked_cast for uint8_t This CL replaces C-style casts to uint8_t with pdfium::checked_cast<> in basicmodule.cpp. This prevents potential integer overflows when downcasting from larger integer types like uint32_t, improving overall type safety. Change-Id: Ie511ff6c4e3b60a42360b4f14b5fe37b3d57ae32 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155410 Reviewed-by: Lei Zhang Reviewed-by: Tom Sepez Commit-Queue: Tom Sepez --- core/fxcodec/basic/basicmodule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/fxcodec/basic/basicmodule.cpp b/core/fxcodec/basic/basicmodule.cpp index f9d7a3a1c4..0f04d459be 100644 --- a/core/fxcodec/basic/basicmodule.cpp +++ b/core/fxcodec/basic/basicmodule.cpp @@ -146,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()) { @@ -159,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; From c7a22e110aec1a79c8e055d75372d45c3efcc19f Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Wed, 19 Aug 2026 17:15:04 -0700 Subject: [PATCH 072/123] Simplify various STL operations - Switch std::ranges APIs in some places. - Avoid std::map::find() and call std::map::erase() directly in others. - Switch to Google C++ variable names along the way. Change-Id: I94e8a0173f7ed16be62025f55f8a6a1434fac5f9 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155470 Commit-Queue: Lei Zhang Reviewed-by: Tom Sepez --- core/fpdfapi/page/cpdf_contentmarks.cpp | 14 +++++++------- core/fpdfapi/page/cpdf_contentmarks.h | 2 +- fpdfsdk/cpdfsdk_interactiveform.cpp | 7 ++----- fpdfsdk/cpdfsdk_interactiveform.h | 2 +- fpdfsdk/formfiller/cffl_interactiveformfiller.cpp | 9 ++------- fpdfsdk/formfiller/cffl_interactiveformfiller.h | 2 +- fxjs/cjs_util.cpp | 3 +-- 7 files changed, 15 insertions(+), 24 deletions(-) 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/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/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/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; From 277d0d8b2744e4b06778a323833fb81e26b9e4d3 Mon Sep 17 00:00:00 2001 From: Alex Ovsienko Date: Wed, 19 Aug 2026 18:52:41 -0700 Subject: [PATCH 073/123] [DEPS] Add CPython CIPD package for hermetic Python toolchain Add third_party/cpython3 CIPD packages for Linux and host platforms ('\''infra/3pp/tools/cpython3/${platform}'\'' at '\''version:3@3.11.9.chromium.38'\''). This enables hermetic Python execution in Siso builds where Siso configs inherited from build/ require the hermetic Python binary at third_party/cpython3/linux-amd64/bin/python3 on remote workers. Also update tools/roll_pdfium_deps.py to sync cpython3_version in future rolls, and update .gitignore. Bug: 537680330 Change-Id: I601bf636e61ec6f126753fbd344675fc1466cdc7 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155310 Commit-Queue: Lei Zhang Reviewed-by: Lei Zhang --- .gitignore | 2 ++ DEPS | 30 +++++++++++++++- tools/roll_pdfium_deps.py | 1 + tools/roll_pdfium_deps_test.py | 63 ++++++++++++++++++++++++++++++++-- 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index aa5c65954a..b1b75574b4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,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/DEPS b/DEPS index fe27e627ba..ce587b3410 100644 --- a/DEPS +++ b/DEPS @@ -121,6 +121,10 @@ vars = { # 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': '732902ab86f8bd629bee00af102d5aa2d9336166', @@ -383,6 +387,30 @@ 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'), @@ -841,7 +869,7 @@ include_rules = [ specific_include_rules = { # Allow embedder tests to use public APIs. - '(.*embeddertest\.cpp)': [ + r'(.*embeddertest\.cpp)': [ '+public', ] } 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..93314d1356 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' @@ -1608,6 +1635,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 +1901,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'), @@ -2242,7 +2297,7 @@ specific_include_rules = { # Allow embedder tests to use public APIs. - '(.*embeddertest\.cpp)': [ + r'(.*embeddertest\.cpp)': [ '+public', ] } @@ -2410,6 +2465,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) From 2ca2e91deab056540c3e05049b4a6029c262ec90 Mon Sep 17 00:00:00 2001 From: Alex Ovsienko Date: Thu, 20 Aug 2026 00:12:21 -0700 Subject: [PATCH 074/123] [DEPS] replace internal python3 usage with DEPS provided one. Done to improve hermeticity of runners as prevent supply chain attacks through Docker. Additionally upgrading //build that introduces the code for cpython3 injection into runners. Roll build/ 02e5d563d77fe..617bbacedcc57e (1 commit) https://chromium.googlesource.com/chromium/src/build.git/+log/02e5d563d77fe..617bbacedcc57e Bug: 537680330 Change-Id: I45dafa44f70484413d2d47d6d701a42bb456f4e7 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155330 Reviewed-by: Lei Zhang Commit-Queue: Lei Zhang --- .gn | 2 +- DEPS | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gn b/.gn index f5d6cc8216..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. diff --git a/DEPS b/DEPS index ce587b3410..858b3b7617 100644 --- a/DEPS +++ b/DEPS @@ -95,7 +95,7 @@ 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': '02e5d563d77fef191c552055f7ca861ee169c718', + 'build_revision': '617bbacedcc57e59660ab838d3686854822c6ea7', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling buildtools # and whatever else without interference from each other. From 784a524ddaa26d86c0f499625b97902095c26dfe Mon Sep 17 00:00:00 2001 From: Wang Yongjie Date: Thu, 20 Aug 2026 16:44:35 -0700 Subject: [PATCH 075/123] Add FPDFPath_GetBezierControlPoints() API Expose both control points for cubic Bezier path segments so embedders can reconstruct curve geometry when reading paths. Take the path and cubic Bezier endpoint index to validate bounds and avoid returning the same curve for each point in its internal three-point representation. Find each endpoint's control points on demand to avoid adding per-point storage for this infrequently used API. Add public API export coverage and embedder tests for consecutive curves, endpoint-only behavior, and invalid parameters. Bug: 513613479 Change-Id: I204a7eeab076dd1bdd77e8d37f2302dbbdd0ed1d Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/154050 Reviewed-by: Andy Phan Reviewed-by: Lei Zhang Commit-Queue: Lei Zhang --- fpdfsdk/fpdf_edit_embeddertest.cpp | 75 ++++++++++++++++++++++++++++++ fpdfsdk/fpdf_editpath.cpp | 40 ++++++++++++++++ fpdfsdk/fpdf_view_c_api_test.c | 1 + public/fpdf_edit.h | 18 +++++++ 4 files changed, 134 insertions(+) diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp index 769c023fdf..04686ed6d6 100644 --- a/fpdfsdk/fpdf_edit_embeddertest.cpp +++ b/fpdfsdk/fpdf_edit_embeddertest.cpp @@ -2730,6 +2730,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_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c index 4e824de5ef..2e5222724c 100644 --- a/fpdfsdk/fpdf_view_c_api_test.c +++ b/fpdfsdk/fpdf_view_c_api_test.c @@ -268,6 +268,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/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. From 7af6136835bcfbddfc997a31af265e0645fde9c0 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Fri, 21 Aug 2026 00:06:36 -0700 Subject: [PATCH 076/123] Make fp16 DEPS entry conditional on checkout_v8 Make it clearer this 3P project is a transitive dependency for V8. Change-Id: I3a9dc88d39dc8527ac38ade6f6ea44212f12d244 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155270 Auto-Submit: Lei Zhang Commit-Queue: Andy Phan Reviewed-by: Andy Phan --- DEPS | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 858b3b7617..c51086629f 100644 --- a/DEPS +++ b/DEPS @@ -423,9 +423,11 @@ deps = { 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@' + From e2fba6a9286aea6fdb1bb2bc70eed03870410968 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Fri, 21 Aug 2026 12:38:27 -0700 Subject: [PATCH 077/123] Change CPDF_StreamParser::GetNextWord() to return a bool Update CPDF_StreamParser::GetNextWord() to return whether the parsed word is a number, rather than via an out-parameter. Besides being more intuitive, this also eliminates an address-taken local variable in CPDF_StreamParser::ReadNextObject(), which avoids forced stack allocation and ASAN redzone padding. Reducing the stack frame size helps avoid potential stack overflows during deep recursion when parsing nested arrays or dictionaries. TAG=agy CONV=d93e26e9-5db5-4c96-928d-df7e821e9792 Bug: 549231115 Change-Id: Iec7c51071f1a0bc07b2d328e223d951fc147a095 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155590 Commit-Queue: Lei Zhang Reviewed-by: Andy Phan --- core/fpdfapi/page/cpdf_streamparser.cpp | 33 ++++++++++++------------- core/fpdfapi/page/cpdf_streamparser.h | 4 ++- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/core/fpdfapi/page/cpdf_streamparser.cpp b/core/fpdfapi/page/cpdf_streamparser.cpp index fcd8aecbc7..670f8036ab 100644 --- a/core/fpdfapi/page/cpdf_streamparser.cpp +++ b/core/fpdfapi/page/cpdf_streamparser.cpp @@ -343,14 +343,13 @@ RetainPtr CPDF_StreamParser::ReadNextObject( bool bAllowNestedArray, bool bInArray, uint32_t dwRecursionLevel) { - bool bIsNumber; // Must get the next word before returning to avoid infinite loops. - GetNextWord(bIsNumber); + const bool is_number = GetNextWord(); if (!word_size_ || dwRecursionLevel > kMaxNestedParsingLevel) { return nullptr; } - if (bIsNumber) { + if (is_number) { word_buffer_[word_size_] = 0; return pdfium::MakeRetain(GetWord()); } @@ -373,7 +372,7 @@ RetainPtr CPDF_StreamParser::ReadNextObject( auto dict = pdfium::MakeRetain(pool_); while (true) { - GetNextWord(bIsNumber); + GetNextWord(); if (word_size_ == 2 && word_buffer_[0] == '>') { break; } @@ -427,18 +426,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 +447,7 @@ void CPDF_StreamParser::GetNextWord(bool& bIsNumber) { while (true) { if (!PositionIsInBounds()) { - return; + return true; } ch = buf_[pos_++]; if (PDFCharIsLineEnding(ch)) { @@ -459,17 +457,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 +474,7 @@ void CPDF_StreamParser::GetNextWord(bool& bIsNumber) { } } else if (ch == '<') { if (!PositionIsInBounds()) { - return; + return false; } ch = buf_[pos_++]; if (ch == '<') { @@ -487,7 +484,7 @@ void CPDF_StreamParser::GetNextWord(bool& bIsNumber) { } } else if (ch == '>') { if (!PositionIsInBounds()) { - return; + return false; } ch = buf_[pos_++]; if (ch == '>') { @@ -496,18 +493,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 +514,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..3f4fb9c6e0 100644 --- a/core/fpdfapi/page/cpdf_streamparser.h +++ b/core/fpdfapi/page/cpdf_streamparser.h @@ -50,7 +50,9 @@ 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(); ByteString ReadString(); DataVector ReadHexString(); bool PositionIsInBounds() const; From 162c9521f74c17bb0c8595608f23ab22cec3d407 Mon Sep 17 00:00:00 2001 From: "pdfium-autoroll@skia-public.iam.gserviceaccount.com" Date: Sun, 23 Aug 2026 21:34:18 -0700 Subject: [PATCH 078/123] Roll goldctl from 61fc12903d68 to 9379dad302e0 If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/goldctl-pdfium Please CC thestig@chromium.org,thestig@google.com on the revert to ensure that a human is aware of the problem. To file a bug in goldctl: https://issues.skia.org To file a bug in PDFium: https://crbug.com/pdfium To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Bug: None Tbr: thestig@google.com Change-Id: I8ac9c1807cd04af5645a04d1046d36c802ef4604 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155730 Commit-Queue: pdfium-autoroll@skia-public.iam.gserviceaccount.com Bot-Commit: pdfium-autoroll@skia-public.iam.gserviceaccount.com --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index c51086629f..e41a43b328 100644 --- a/DEPS +++ b/DEPS @@ -151,7 +151,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling goldctl CIPD package version # and whatever else without interference from each other. - 'goldctl_version': 'git_revision: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. From 5c96edb22eae0be219f8d399fb914eeb389f46e7 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Mon, 24 Aug 2026 10:59:49 -0700 Subject: [PATCH 079/123] Use Google C++ style in CPDF_StreamParser::ReadNextObject() Clean up the method before refactoring. Also remove some extra parentheses and improve the type for a local variable. Change-Id: I96c518e6a871c490a7d1742a9685dc600b523edc Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155690 Reviewed-by: Andy Phan Commit-Queue: Lei Zhang --- core/fpdfapi/page/cpdf_streamparser.cpp | 32 ++++++++++++------------- core/fpdfapi/page/cpdf_streamparser.h | 6 ++--- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/core/fpdfapi/page/cpdf_streamparser.cpp b/core/fpdfapi/page/cpdf_streamparser.cpp index 670f8036ab..b9cafd5c40 100644 --- a/core/fpdfapi/page/cpdf_streamparser.cpp +++ b/core/fpdfapi/page/cpdf_streamparser.cpp @@ -340,12 +340,12 @@ CPDF_StreamParser::ElementType CPDF_StreamParser::ParseNextElement() { } RetainPtr CPDF_StreamParser::ReadNextObject( - bool bAllowNestedArray, - bool bInArray, - uint32_t dwRecursionLevel) { + bool allow_nested_array, + bool in_array, + uint32_t recursion_level) { // Must get the next word before returning to avoid infinite loops. const bool is_number = GetNextWord(); - if (!word_size_ || dwRecursionLevel > kMaxNestedParsingLevel) { + if (!word_size_ || recursion_level > kMaxNestedParsingLevel) { return nullptr; } @@ -354,7 +354,7 @@ RetainPtr CPDF_StreamParser::ReadNextObject( return pdfium::MakeRetain(GetWord()); } - int first_char = word_buffer_[0]; + const uint8_t first_char = word_buffer_[0]; if (first_char == '/') { ByteString name = PDF_NameDecode(GetWord().Substr(1)); return pdfium::MakeRetain(pool_, name); @@ -382,35 +382,35 @@ 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; } if (GetWord() == kFalse) { diff --git a/core/fpdfapi/page/cpdf_streamparser.h b/core/fpdfapi/page/cpdf_streamparser.h index 3f4fb9c6e0..a2c4f154ae 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); From 3cbcab32533283ae2f35a4c9dbb2fa7cc2f3dfa4 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Mon, 24 Aug 2026 11:26:11 -0700 Subject: [PATCH 080/123] Split up CPDF_StreamParser::ReadNextObject() Extract parsing of non-container objects (names, strings, hex strings, booleans, and null) from CPDF_StreamParser::ReadNextObject() into a ReadLeafObject() helper method. This reduces the stack frame size of CPDF_StreamParser::ReadNextObject() by eliminating local variables and temporaries associated with leaf object parsing from intermediate recursive frames during deep recursion of nested dictionaries and arrays. TAG=agy CONV=d93e26e9-5db5-4c96-928d-df7e821e9792 Bug: 549231115 Change-Id: I15966edc654d84e4600afd9a36819c2f70d61b57 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155691 Commit-Queue: Lei Zhang Reviewed-by: Andy Phan --- core/fpdfapi/page/cpdf_streamparser.cpp | 36 ++++++++++++++----------- core/fpdfapi/page/cpdf_streamparser.h | 2 ++ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/core/fpdfapi/page/cpdf_streamparser.cpp b/core/fpdfapi/page/cpdf_streamparser.cpp index b9cafd5c40..980a18f39a 100644 --- a/core/fpdfapi/page/cpdf_streamparser.cpp +++ b/core/fpdfapi/page/cpdf_streamparser.cpp @@ -355,21 +355,7 @@ RetainPtr CPDF_StreamParser::ReadNextObject( } 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 == '<') { - if (word_size_ == 1) { - return pdfium::MakeRetain(pool_, ReadHexString(), - CPDF_String::DataType::kIsHex); - } - + if (first_char == '<' && word_size_ > 1) { auto dict = pdfium::MakeRetain(pool_); while (true) { GetNextWord(); @@ -413,6 +399,26 @@ RetainPtr CPDF_StreamParser::ReadNextObject( 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) { return pdfium::MakeRetain(false); } diff --git a/core/fpdfapi/page/cpdf_streamparser.h b/core/fpdfapi/page/cpdf_streamparser.h index a2c4f154ae..95b399210a 100644 --- a/core/fpdfapi/page/cpdf_streamparser.h +++ b/core/fpdfapi/page/cpdf_streamparser.h @@ -53,6 +53,8 @@ class CPDF_StreamParser { // 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; From d0f00ac9c7bbfa3239c7bf462e4439b62b01e833 Mon Sep 17 00:00:00 2001 From: Wang Yongjie Date: Mon, 24 Aug 2026 12:22:22 -0700 Subject: [PATCH 081/123] Keep annotation AP BBox when resizing FPDFAnnot_SetRect() should update the annotation /Rect without rewriting the normal appearance stream /BBox. The appearance Form XObject /BBox is the source rectangle that viewers map into /Rect, so changing it during resize corrupts scalable appearances. Add an embedder regression test that enlarges a stamp annotation after setting its normal AP and verifies that /Rect changes while the AP /BBox remains unchanged. Bug: 514242883 Change-Id: I8eb2009971270f64b095e216758c9aacf5f05c0c Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/149530 Reviewed-by: Lei Zhang Reviewed-by: Andy Phan Commit-Queue: Lei Zhang --- fpdfsdk/fpdf_annot.cpp | 15 ---------- fpdfsdk/fpdf_annot_embeddertest.cpp | 43 +++++++++++++++++++++++++++++ public/fpdf_annot.h | 5 +--- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/fpdfsdk/fpdf_annot.cpp b/fpdfsdk/fpdf_annot.cpp index c2ed6fbe7b..fb5c862d3f 100644 --- a/fpdfsdk/fpdf_annot.cpp +++ b/fpdfsdk/fpdf_annot.cpp @@ -920,21 +920,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; - } - - RetainPtr pStream = - GetAnnotAP(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 d5526566a4..bea5ed963b 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/public/fpdf_annot.h b/public/fpdf_annot.h index 0aa9f68866..b452cfaf1d 100644 --- a/public/fpdf_annot.h +++ b/public/fpdf_annot.h @@ -393,10 +393,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. From c9def552ab032f7525e2844b0dc2f3f3fb294e8b Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Mon, 24 Aug 2026 12:51:01 -0700 Subject: [PATCH 082/123] Speed up CStretchEngine: Zip() the tap loops, interchange StretchVert The inner loops read every interpolation weight through GetWeightForPosition(), which CHECKs twice per tap, and every source sample through a checked span access; StretchVert() additionally built a checked subspan per tap per destination column. These loops run taps x channels x pixels times per stretched image. The compiler cannot elide the checks, because the checked offsets are derived induction variables rather than the loops' own. Restructure so the checks are unnecessary: - PixelWeight replaces GetWeightForPosition() with GetWeights(), returning a correctly-sized span over its trailing array, and adds GetSrcStart(). GetWeights() is the only UNSAFE_BUFFERS() this CL adds, justified by the allocation invariant, and it becomes the only way the tap loops read weights_. The write-side accessors keep the UNSAFE_BUFFERS() they already had. - ContinueStretchHorz() walks each column's weights and source window with fxcrt::Zip(), comparing bounds once per column. For the >8bpp cases the source stride is structurally 3 or 4, so dispatching on it gives each branch a compile-time pixel size via reinterpret_span(). - StretchVert() is loop-interchanged so taps are the outer loop and each tap reads one contiguous row into a row-wide accumulator, replacing the strided column-major walk. Per byte the taps are still summed in tap order. Zip()ing the horizontal loops lets them vectorize but adds fixed setup per destination column, and most columns take only one or two taps: one is no scaling in that axis, two is bilinear. At that trip count the setup dominates, so the two hottest cases unroll one and two taps, summed in the same order, and fall through to the Zip() loop otherwise. The vertical accumulator is allocated once outside the row loop and reused, so a row whose first tap writes every element needs no zeroing: it is allocated uninitialised and the first tap assigns rather than accumulates, which removes a full-width pass per row. A row with no taps writes nothing and would otherwise repeat the row above, so that case zeroes the accumulator explicitly. GetSrcStart() hands its value straight to span indexing, so SetStartEnd() now CHECKs that the start position is non-negative. The area-average branch therefore has to clamp the position it stores for an empty source range, and three tests cover the geometries that reach it: EmptySourceRangeDownscale, MirroredDestinationEmptySourceClip and EmptySourceClipAtRightEdge. A fourth, EmptySourceRangeInVerticalPass, covers an empty tap range reaching StretchVert(), which the other three cannot: StartStretchHorz() rejects a zero-height source clip, so that path needs a mirrored destination whose clip box straddles the destination edge. Bug: 549523416 Change-Id: Ifecc733071c148a7f67c2a5904be0510bdfe9c64 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155550 Commit-Queue: Lei Zhang Reviewed-by: Lei Zhang Reviewed-by: Tom Sepez --- AUTHORS | 1 + core/fxge/dib/cstretchengine.cpp | 362 ++++++++++++++-------- core/fxge/dib/cstretchengine.h | 25 +- core/fxge/dib/cstretchengine_unittest.cpp | 130 +++++++- 4 files changed, 384 insertions(+), 134 deletions(-) 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/core/fxge/dib/cstretchengine.cpp b/core/fxge/dib/cstretchengine.cpp index 10622154f9..fb26b633fd 100644 --- a/core/fxge/dib/cstretchengine.cpp +++ b/core/fxge/dib/cstretchengine.cpp @@ -14,9 +14,12 @@ #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" @@ -114,12 +117,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)); @@ -141,7 +144,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; } @@ -325,7 +331,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_) { @@ -337,7 +343,7 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { rows_to_go = kStrechPauseRows; } - const uint8_t* src_scan = source_->GetScanline(cur_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; @@ -347,14 +353,14 @@ 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; - 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); } @@ -363,10 +369,27 @@ 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); } @@ -375,23 +398,24 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { case TransformMethod::k8BppToManyBpp: { 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); @@ -400,45 +424,93 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { break; } case TransformMethod::kManyBpptoManyBpp: { - 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, &dest_span_index, + Bpp](auto src_pixels) { + 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; + } + 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; } - 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); 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); @@ -466,89 +538,129 @@ 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 byte of each destination pixel carries data on + // these paths; any remaining bytes are left untouched. + if (DestBpp == 1) { + for (auto [dest, acc] : fxcrt::Zip(dest_row, accum_span)) { + dest = PixelFromFixed(acc); + } + } else { + size_t offset = 0; + for (int col = 0; col < dest_cols; ++col) { + dest_row[offset] = PixelFromFixed(accum_span[offset]); + offset += DestBpp; } - UNSAFE_TODO({ - *dest_scan = PixelFromFixed(dest_a); - dest_scan += DestBpp; - }); } break; } case TransformMethod::k8BppToManyBpp: case TransformMethod::kManyBpptoManyBpp: { - 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 e56411bd99..dd699f956d 100644 --- a/core/fxge/dib/cstretchengine.h +++ b/core/fxge/dib/cstretchengine.h @@ -54,18 +54,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_); @@ -73,6 +67,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; From bd682d8a6d97d70f055a5591658475bd2043924a Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Mon, 24 Aug 2026 14:43:03 -0700 Subject: [PATCH 083/123] Add unit test for PNG decoding with gAMA chunk Add ProgressiveDecoderTest.PngGammaChunk to verify PNG decoding behavior when a gAMA chunk with an explicit file gamma is present. -- Fix longstanding bug in skia_png_context.cpp. TAG=agy CONV=12ff62b8-5c29-4a0f-9381-d9ae70a29b47 Change-Id: Ibcf91ad39ac395eca18b936eef8409582aa79f50 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155750 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez Auto-Submit: Tom Sepez --- core/fxcodec/png/skia_png_context.cpp | 2 +- core/fxcodec/progressive_decoder_unittest.cpp | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/core/fxcodec/png/skia_png_context.cpp b/core/fxcodec/png/skia_png_context.cpp index 66ba3cdbdd..290b7da658 100644 --- a/core/fxcodec/png/skia_png_context.cpp +++ b/core/fxcodec/png/skia_png_context.cpp @@ -35,7 +35,7 @@ constexpr double kPngGamma = 2.2; sk_sp GetTargetColorSpace() { const skcms_TransferFunction fn = { - .g = static_cast(1.0 / kPngGamma), + .g = static_cast(kPngGamma), .a = 1.0f, }; return SkColorSpace::MakeRGB(fn, SkNamedGamut::kSRGB); diff --git a/core/fxcodec/progressive_decoder_unittest.cpp b/core/fxcodec/progressive_decoder_unittest.cpp index 3b8f396059..65da457b02 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; @@ -624,4 +625,53 @@ TEST_F(ProgressiveDecoderTest, BigPng) { ElementsAre(0x00, 0xFF, 0x00, 0xFF)); } +// 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 From 98f454243f8bd454613f373b4653b465017602f6 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Mon, 24 Aug 2026 15:36:21 -0700 Subject: [PATCH 084/123] Replace Skia PNG decoder with direct Rust FFI (png crate) When pdf_enable_rust_png is set, PNG decoding previously delegated to Skia's SkPngRustDecoder wrapper. This change introduces RustPngContext, which directly invokes the png Rust crate via CXX bridge FFI (rust_png_ffi). This should allow rust decoders to be used in a non-skia build. The unused skia_png_context implementation files have been removed. TAG=agy CONV=873eda5c-74cd-479c-b5f1-24997eba2b24 Change-Id: I369ff5c91ac7566e92ac6bbc0815531e33f2a0b4 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155670 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- BUILD.gn | 2 +- core/fxcodec/BUILD.gn | 31 +++-- core/fxcodec/png/rust_png_context.cpp | 113 +++++++++++++++ core/fxcodec/png/rust_png_context.h | 55 ++++++++ core/fxcodec/png/rust_png_ffi.rs | 178 ++++++++++++++++++++++++ core/fxcodec/png/skia_png_context.cpp | 191 -------------------------- core/fxcodec/png/skia_png_context.h | 90 ------------ core/fxcodec/progressive_decoder.cpp | 36 +++-- pdfium.gni | 5 +- 9 files changed, 395 insertions(+), 306 deletions(-) create mode 100644 core/fxcodec/png/rust_png_context.cpp create mode 100644 core/fxcodec/png/rust_png_context.h create mode 100644 core/fxcodec/png/rust_png_ffi.rs delete mode 100644 core/fxcodec/png/skia_png_context.cpp delete mode 100644 core/fxcodec/png/skia_png_context.h diff --git a/BUILD.gn b/BUILD.gn index 6c2ca42598..812a7ccc89 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -55,7 +55,7 @@ config("pdfium_common_config") { defines += [ "PDF_ENABLE_RUST_JPEG" ] } - if (pdf_use_skia && pdf_enable_rust_png) { + if (pdf_enable_rust_png) { defines += [ "PDF_ENABLE_RUST_PNG" ] } diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index 190c32c636..84aaef2f3f 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -5,6 +5,22 @@ import("../../pdfium.gni") import("../../testing/test.gni") +if (pdf_enable_rust_png) { + import("//build/rust/rust_static_library.gni") + + 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 ] + deps = [ + "//build/rust:cxx_cppdeps", + "//third_party/rust/png/v0_18:lib", + ] + } +} + source_set("fxcodec") { sources = [ "basic/basicmodule.cpp", @@ -133,16 +149,15 @@ source_set("fxcodec") { ] } - # 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) { + if (pdf_enable_rust_png) { sources += [ - "png/skia_png_context.cpp", - "png/skia_png_context.h", + "png/rust_png_context.cpp", + "png/rust_png_context.h", + ] + deps += [ + ":pdfium_rust_png_ffi", + "//build/rust:cxx_cppdeps", ] - deps += [ "//skia" ] } else { sources += [ "png/libpng_png_context.cpp", 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..6711841dda --- /dev/null +++ b/core/fxcodec/png/rust_png_ffi.rs @@ -0,0 +1,178 @@ +// 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" { + if 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 290b7da658..0000000000 --- a/core/fxcodec/png/skia_png_context.cpp +++ /dev/null @@ -1,191 +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/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/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 { - -constexpr double kPngGamma = 2.2; - -sk_sp GetTargetColorSpace() { - const skcms_TransferFunction fn = { - .g = static_cast(kPngGamma), - .a = 1.0f, - }; - return SkColorSpace::MakeRGB(fn, SkNamedGamut::kSRGB); -} - -} // namespace - -SkiaPngContext::SkiaPngContext(ProgressiveDecoderContextDelegate* delegate) - : delegate_(delegate) {} - -SkiaPngContext::~SkiaPngContext() = default; - -bool SkiaPngContext::ReadHeader(RetainPtr codec_memory) { - if (state_ == State::kGotDecoder || state_ == State::kStartedDecode || - state_ == State::kFinishedDecoding) { - return true; - } - if (state_ == State::kError) { - return false; - } - - CHECK_EQ(state_, State::kNoDecoder); - CHECK(!decoder_); - - // `ProgressiveDecoder` guarantees that all calls to - // `ReadHeader`/`ProcessData` 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); - - 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(); - // Notifies the delegate of image dimensions and metadata. - 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: - // 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 - } -} - -bool SkiaPngContext::ProcessData(RetainPtr codec_memory) { - if (state_ == State::kNoDecoder) { - if (!ReadHeader(std::move(codec_memory))) { - return false; - } - if (state_ == State::kNoDecoder) { - return true; // Needs more input to finish reading header. - } - } - - if (state_ == State::kGotDecoder) { - CHECK(bitmap_); - SkImageInfo dst_info = decoder_->getInfo() - .makeColorSpace(GetTargetColorSpace()) - .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_->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(); - 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 - } -} - -void SkiaPngContext::Input(RetainPtr codec_memory) { - codec_memory_ = std::move(codec_memory); -} - -FXCODEC_STATUS SkiaPngContext::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 SkiaPngContext::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/png/skia_png_context.h b/core/fxcodec/png/skia_png_context.h deleted file mode 100644 index 16e0593fac..0000000000 --- a/core/fxcodec/png/skia_png_context.h +++ /dev/null @@ -1,90 +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/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/fxge/dib/cfx_dibitmap.h" - -class SkCodec; - -namespace fxcodec { - -class ProgressiveDecoderContextDelegate; - -class SkiaPngContext final : public ProgressiveDecoderContext { - public: - // Caller needs to guarantee that `delegate` lives longer than - // `SkiaPngContext`. - explicit SkiaPngContext(ProgressiveDecoderContextDelegate* delegate); - ~SkiaPngContext() 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 { - // `decoder_` is null. - // - // This is the initial state. - kNoDecoder, - kGotDecoder, - // `decoder_` is ready to accept incremental decoding inputs. - // - // This state is entered via: - // * `StartDecode` - initial attempt to configure `decoder_` - // * `kError` when `ContinueDecode` retries after receiving more input - // data from `delegate_->ReadMoreData(...)` - // - // From this state `ContinueDecode` transitions to: - // * `kFinishedDecoding` - when all pixels have been decoded - // * `kError` - when `decoder_` needs more input or encounters a fatal error - kStartedDecode, - // `decoder_` is null. - // - // This state is entered when `ContinueDecode` successfully decodes all - // pixels. - kFinishedDecoding, - // `decoder_` is null. - // - // This state is entered when `ContinueDecode` encounters an error (either a - // fatal decoding error, or a recoverable error when `decoder_` runs out of - // input data). From this state `ContinueDecode` attempts to read more data - // from `delegate_->ReadMoreData(...)` and transitions to `kStartedDecode` - // on success. - kError, - }; - - bool ProcessData(RetainPtr codec_memory); - - State state_ = State::kNoDecoder; - UnownedPtr const delegate_; - RetainPtr bitmap_; - std::unique_ptr decoder_; - - // `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 18936a2ea6..b2f8f559f6 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -48,7 +48,7 @@ #endif #if defined(PDF_ENABLE_RUST_PNG) -#include "core/fxcodec/png/skia_png_context.h" +#include "core/fxcodec/png/rust_png_context.h" #else #include "core/fxcodec/png/libpng_png_context.h" #endif @@ -83,8 +83,8 @@ std::unique_ptr CreateDecoderContext( #endif } 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); @@ -502,14 +502,23 @@ bool ProgressiveDecoder::PngDetectImageTypeInBuffer() { return false; } -#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) - auto* ctx = static_cast(context_.get()); -#else - auto* ctx = static_cast(context_.get()); -#endif - 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()); if (ctx->ReadHeader(codec_memory_)) { FXCODEC_STATUS status = FXCODEC_STATUS::kError; while (src_width_ == 0 && ReadMoreData(std::nullopt, &status)) { @@ -518,6 +527,7 @@ bool ProgressiveDecoder::PngDetectImageTypeInBuffer() { } } } +#endif bool got_metadata = (src_width_ > 0 && src_height_ > 0); context_.reset(); @@ -533,8 +543,8 @@ FXCODEC_STATUS ProgressiveDecoder::PngStartDecode() { return status_; } context_->Input(codec_memory_); -#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) - auto* ctx = static_cast(context_.get()); +#if defined(PDF_ENABLE_RUST_PNG) + auto* ctx = static_cast(context_.get()); #else auto* ctx = static_cast(context_.get()); #endif @@ -543,8 +553,8 @@ FXCODEC_STATUS ProgressiveDecoder::PngStartDecode() { } FXCODEC_STATUS ProgressiveDecoder::PngContinueDecode() { -#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG) - auto* ctx = static_cast(context_.get()); +#if defined(PDF_ENABLE_RUST_PNG) + auto* ctx = static_cast(context_.get()); #else auto* ctx = static_cast(context_.get()); #endif diff --git a/pdfium.gni b/pdfium.gni index 1c26ae04fb..306e2d46f6 100644 --- a/pdfium.gni +++ b/pdfium.gni @@ -71,7 +71,6 @@ declare_args() { # 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 @@ -119,5 +118,5 @@ assert( "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") From cd0673cc522ce43f1c5dff4a275c35d95be1f006 Mon Sep 17 00:00:00 2001 From: Takuto Ikuta Date: Tue, 25 Aug 2026 08:26:43 -0700 Subject: [PATCH 085/123] Roll build/ 617bbaced..299066ef2 (3 commits) This is to fix https://crbug.com/537680330#comment67 https://chromium.googlesource.com/chromium/src/build.git/+log/617bbacedcc5..299066ef2d5d $ git log 617bbaced..299066ef2 --date=short --no-merges --format='%ad %ae %s' 2026-08-14 ovsienko DEPS: add the entire cpython except duplicate 3.11 binary and header files. 2026-08-13 chromium-internal-autoroll Roll Fuchsia Internal Images from 33.20260806.103.1 to 33.20260814.100.1 2026-08-13 aluh Make use_prebuilt_instrumented_libraries false on CrOS devices Created with: roll-dep build Bug: 537680330 Change-Id: I84d84a4a6b489acb45f008522c769cf6308a2de3 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155790 Reviewed-by: Nico Weber Auto-Submit: Takuto Ikuta Commit-Queue: Nico Weber Reviewed-by: Nico Weber --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index e41a43b328..169680f06a 100644 --- a/DEPS +++ b/DEPS @@ -95,7 +95,7 @@ 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': '617bbacedcc57e59660ab838d3686854822c6ea7', + 'build_revision': '299066ef2d5d1396f97e582627880cebe33f497a', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling buildtools # and whatever else without interference from each other. From 1458c54bc4add9fc8e47f231754db8dbf0c69c12 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Tue, 25 Aug 2026 09:08:53 -0700 Subject: [PATCH 086/123] Add unit tests for BMP progressive decoding edge cases Add test cases to ProgressiveDecoderTest covering: - Truncated BMP input handling across decoder configurations - Corrupt BMP header signature rejection - OS/2 1.x BITMAPCOREHEADER (12-byte header) decoding - 1-bit monochrome indexed BMP decoding - 4-bit indexed BMP decoding TAG=agy CONV=9cab3c29-059d-4a14-b2ad-e180513cda42 Change-Id: I3c39eba839863fb12a7cbd670df255212b64cd2f Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155770 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- core/fxcodec/progressive_decoder_unittest.cpp | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/core/fxcodec/progressive_decoder_unittest.cpp b/core/fxcodec/progressive_decoder_unittest.cpp index 65da457b02..f2eb9c4f9d 100644 --- a/core/fxcodec/progressive_decoder_unittest.cpp +++ b/core/fxcodec/progressive_decoder_unittest.cpp @@ -258,6 +258,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; From 5dd9f2f27c196d4acd72ce3bac45232ccba3f411 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Tue, 25 Aug 2026 12:30:45 -0700 Subject: [PATCH 087/123] Add still more unit tests for BMP progressive decoding Add tests covering top-down BMP decoding, INT32_MIN height handling, 16-bit bitfields with valid and overlapping channel masks, 8-bit RLE decoding, and excessive dimension/buffer rejection (>1GB and >2GB). TAG=agy CONV=9cab3c29-059d-4a14-b2ad-e180513cda42 Change-Id: Icb5496d08ac677ce357fc8d62e26add8eb45bb28 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155810 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- core/fxcodec/progressive_decoder_unittest.cpp | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/core/fxcodec/progressive_decoder_unittest.cpp b/core/fxcodec/progressive_decoder_unittest.cpp index f2eb9c4f9d..80a8fb38a5 100644 --- a/core/fxcodec/progressive_decoder_unittest.cpp +++ b/core/fxcodec/progressive_decoder_unittest.cpp @@ -475,6 +475,233 @@ TEST_F(ProgressiveDecoderTest, LargeBmp) { } } +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, From 8133598009ac7607339d1a78c6965e4d35dfa763 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Tue, 25 Aug 2026 12:37:21 -0700 Subject: [PATCH 088/123] Replace Skia BMP decoder with direct Rust FFI (image crate) When pdf_enable_rust_bmp is set, BMP decoding previously delegated to Skia's SkBmpRustDecoder wrapper. This change introduces RustBmpContext, which directly invokes the image Rust crate via CXX bridge FFI (rust_bmp_ffi). This should allow rust decoders to be used in a non-skia build. The unused skia_bmp_context implementation files have been removed. -- Add limits so that large allocation tests don't fall over. TAG=agy CONV=873eda5c-74cd-479c-b5f1-24997eba2b24 Change-Id: I5a3a084630b21c10bfb2f4b101d9b2fb40fa67e3 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155650 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- BUILD.gn | 2 +- core/fxcodec/BUILD.gn | 27 +- core/fxcodec/bmp/rust_bmp_context.cpp | 198 ++++++++++ ...{skia_bmp_context.h => rust_bmp_context.h} | 46 +-- core/fxcodec/bmp/rust_bmp_ffi.rs | 147 +++++++ core/fxcodec/bmp/skia_bmp_context.cpp | 358 ------------------ core/fxcodec/progressive_decoder.cpp | 6 +- pdfium.gni | 4 +- 8 files changed, 390 insertions(+), 398 deletions(-) create mode 100644 core/fxcodec/bmp/rust_bmp_context.cpp rename core/fxcodec/bmp/{skia_bmp_context.h => rust_bmp_context.h} (52%) create mode 100644 core/fxcodec/bmp/rust_bmp_ffi.rs delete mode 100644 core/fxcodec/bmp/skia_bmp_context.cpp diff --git a/BUILD.gn b/BUILD.gn index 812a7ccc89..daf497c5b8 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -47,7 +47,7 @@ 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" ] } diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index 84aaef2f3f..f2a4970c8d 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -5,9 +5,25 @@ import("../../pdfium.gni") import("../../testing/test.gni") -if (pdf_enable_rust_png) { +if (pdf_enable_rust_bmp || pdf_enable_rust_png) { import("//build/rust/rust_static_library.gni") +} + +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 ] + 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 @@ -123,10 +139,13 @@ source_set("fxcodec") { deps += [ "../../third_party:tiff" ] if (pdf_enable_rust_bmp) { sources += [ - "bmp/skia_bmp_context.cpp", - "bmp/skia_bmp_context.h", + "bmp/rust_bmp_context.cpp", + "bmp/rust_bmp_context.h", + ] + deps += [ + ":rust_bmp_ffi", + "//build/rust:cxx_cppdeps", ] - deps += [ "//skia" ] } else { sources += [ "bmp/cfx_bmpcontext.cpp", 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/skia_bmp_context.h b/core/fxcodec/bmp/rust_bmp_context.h similarity index 52% rename from core/fxcodec/bmp/skia_bmp_context.h rename to core/fxcodec/bmp/rust_bmp_context.h index 844f5bb1b4..b89ec88e6a 100644 --- a/core/fxcodec/bmp/skia_bmp_context.h +++ b/core/fxcodec/bmp/rust_bmp_context.h @@ -2,32 +2,27 @@ // 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_ +#ifndef CORE_FXCODEC_BMP_RUST_BMP_CONTEXT_H_ +#define CORE_FXCODEC_BMP_RUST_BMP_CONTEXT_H_ #include -#include -#include - -#include "core/fxcodec/cfx_codec_memory.h" #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" -class SkCodec; - namespace fxcodec { class CFX_DIBAttribute; class ProgressiveDecoderContextDelegate; -class SkiaBmpContext final : public ProgressiveDecoderContext { +class RustBmpContext final : public ProgressiveDecoderContext { public: - explicit SkiaBmpContext(ProgressiveDecoderContextDelegate* delegate); - ~SkiaBmpContext() override; + explicit RustBmpContext(ProgressiveDecoderContextDelegate* delegate); + ~RustBmpContext() override; void SetCodecMemory(RetainPtr codec_memory); @@ -43,29 +38,20 @@ class SkiaBmpContext final : public ProgressiveDecoderContext { pdfium::span* palette, CFX_DIBAttribute* attribute); - private: - bool ValidatePaletteIndices(); - ProgressiveDecoderContext::Status StartIncrementalDecode(); - void Forward3ComponentRow(int dest_row, - pdfium::span src_row, - pdfium::span row_buffer, - int width); + 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_; - 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_ +#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..c3166df046 --- /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.map_or(true, |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 b39def255f..0000000000 --- a/core/fxcodec/bmp/skia_bmp_context.cpp +++ /dev/null @@ -1,358 +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/cfx_codec_memory.h" -#include "core/fxcodec/codec_memory_sk_stream.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/check_op.h" -#include "core/fxcrt/fx_safe_types.h" -#include "core/fxge/calculate_pitch.h" -#include "third_party/skia/include/codec/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(ProgressiveDecoderContextDelegate* 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, - int32_t* components, - pdfium::span* palette, - CFX_DIBAttribute* attribute) { - if (header_read_) { - SkImageInfo info = decoder_->getInfo(); - *width = info.width(); - *height = info.height(); - *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; - - components_ = (bpp == 32) ? 4 : 3; - *components = components_; - *palette = {}; - if (attribute) { - attribute->dpi_unit_ = CFX_DIBAttribute::kResUnitMeter; - attribute->x_dpi_ = 0; - attribute->y_dpi_ = 0; - } - header_read_ = true; - return ProgressiveDecoderContext::Status::kSuccess; -} - -ProgressiveDecoderContext::Status SkiaBmpContext::DecodeImage() { - 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); - if (components_ == 4) { - delegate_->ResampleScanline(r, src_row); - } else { - Forward3ComponentRow(r, 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(int 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_->ResampleScanline(dest_row, row_buffer); -} - -} // namespace fxcodec diff --git a/core/fxcodec/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp index b2f8f559f6..ad07c35263 100644 --- a/core/fxcodec/progressive_decoder.cpp +++ b/core/fxcodec/progressive_decoder.cpp @@ -36,7 +36,7 @@ #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 @@ -65,7 +65,7 @@ std::unique_ptr CreateDecoderContext( switch (type) { 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 @@ -202,7 +202,7 @@ 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 diff --git a/pdfium.gni b/pdfium.gni index 306e2d46f6..937b50c046 100644 --- a/pdfium.gni +++ b/pdfium.gni @@ -110,8 +110,8 @@ 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), From 2089ec1487acaf1386f290f5c7824b693c1cd444 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Tue, 25 Aug 2026 14:11:05 -0700 Subject: [PATCH 089/123] Fix unit_test broken in prior commit. https://pdfium-review.googlesource.com/c/pdfium/+/155650 was missing an #ifdef from a prior merge. Change-Id: I77054285ca8b2591877fc573067de6650e0543e5 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155832 Reviewed-by: Lei Zhang Auto-Submit: Tom Sepez --- core/fxcodec/progressive_decoder_unittest.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/fxcodec/progressive_decoder_unittest.cpp b/core/fxcodec/progressive_decoder_unittest.cpp index 80a8fb38a5..719df58b6e 100644 --- a/core/fxcodec/progressive_decoder_unittest.cpp +++ b/core/fxcodec/progressive_decoder_unittest.cpp @@ -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) { From 463e0c5a0751bba4f94c17708721511d37cb9e89 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Tue, 25 Aug 2026 15:55:51 -0700 Subject: [PATCH 090/123] Move PNG implementation files to testing/utils Straight rename (more or less) to minimize diffs in the follow-on CL. Not particularly elegant on its own. Move image_diff_png_libpng.cpp, image_diff_png_skia.cpp, and image_diff_png_unittests.cpp from testing/image_diff to testing/utils. Update testing/DEPS to permit libpng and zlib includes in testing. Bypass-Check-License: Moved files TAG=agy CONV=de44a5df-02df-4aa1-8016-b97112d9525d Change-Id: Id4f632a17fa2ac62389366f65c2e63264d40c758 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155831 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- testing/DEPS | 4 +++- testing/image_diff/BUILD.gn | 6 +++--- testing/image_diff/DEPS | 5 ----- .../png_encode_libpng.cpp} | 3 +-- .../image_diff_png_skia.cpp => utils/png_encode_skia.cpp} | 3 +-- .../image_diff_png_unittests.cpp => utils/png_unittest.cpp} | 3 +-- 6 files changed, 9 insertions(+), 15 deletions(-) delete mode 100644 testing/image_diff/DEPS rename testing/{image_diff/image_diff_png_libpng.cpp => utils/png_encode_libpng.cpp} (99%) rename testing/{image_diff/image_diff_png_skia.cpp => utils/png_encode_skia.cpp} (99%) rename testing/{image_diff/image_diff_png_unittests.cpp => utils/png_unittest.cpp} (99%) diff --git a/testing/DEPS b/testing/DEPS index c071352f7c..b64a58f737 100644 --- a/testing/DEPS +++ b/testing/DEPS @@ -4,8 +4,10 @@ include_rules = [ '+fxjs', '+partition_alloc', '+public', + '+third_party/libpng', '+third_party/simdutf/simdutf.h', '+third_party/skia/include', + '+third_party/zlib', '+v8', - '+xfa' + '+xfa', ] diff --git a/testing/image_diff/BUILD.gn b/testing/image_diff/BUILD.gn index 6706ba2ba7..7076877d90 100644 --- a/testing/image_diff/BUILD.gn +++ b/testing/image_diff/BUILD.gn @@ -11,10 +11,10 @@ source_set("image_diff") { deps = [] if (pdf_use_skia) { - sources += [ "image_diff_png_skia.cpp" ] + sources += [ "../utils/png_encode_skia.cpp" ] deps += [ "//skia" ] } else { - sources += [ "image_diff_png_libpng.cpp" ] + sources += [ "../utils/png_encode_libpng.cpp" ] deps += [ "../../third_party:png", "../../third_party:zlib", @@ -29,7 +29,7 @@ source_set("image_diff") { } pdfium_unittest_source_set("unittests") { - sources = [ "image_diff_png_unittests.cpp" ] + sources = [ "../utils/png_unittest.cpp" ] deps = [ ":image_diff" ] pdfium_root_dir = "../../" } diff --git a/testing/image_diff/DEPS b/testing/image_diff/DEPS deleted file mode 100644 index fcac201c2d..0000000000 --- a/testing/image_diff/DEPS +++ /dev/null @@ -1,5 +0,0 @@ -include_rules = [ - '+third_party/libpng', - '+third_party/zlib', -] - diff --git a/testing/image_diff/image_diff_png_libpng.cpp b/testing/utils/png_encode_libpng.cpp similarity index 99% rename from testing/image_diff/image_diff_png_libpng.cpp rename to testing/utils/png_encode_libpng.cpp index f77bc06f8f..ecc9298d85 100644 --- a/testing/image_diff/image_diff_png_libpng.cpp +++ b/testing/utils/png_encode_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/image_diff/image_diff_png.h" #ifdef USE_SYSTEM_ZLIB #include diff --git a/testing/image_diff/image_diff_png_skia.cpp b/testing/utils/png_encode_skia.cpp similarity index 99% rename from testing/image_diff/image_diff_png_skia.cpp rename to testing/utils/png_encode_skia.cpp index 64ce821cc6..9298ff6eba 100644 --- a/testing/image_diff/image_diff_png_skia.cpp +++ b/testing/utils/png_encode_skia.cpp @@ -2,8 +2,6 @@ // 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 @@ -14,6 +12,7 @@ #include "core/fxcrt/numerics/checked_math.h" #include "core/fxcrt/span.h" #include "core/fxcrt/span_util.h" +#include "testing/image_diff/image_diff_png.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" diff --git a/testing/image_diff/image_diff_png_unittests.cpp b/testing/utils/png_unittest.cpp similarity index 99% rename from testing/image_diff/image_diff_png_unittests.cpp rename to testing/utils/png_unittest.cpp index b92c66b681..9caf911e59 100644 --- a/testing/image_diff/image_diff_png_unittests.cpp +++ b/testing/utils/png_unittest.cpp @@ -2,13 +2,12 @@ // 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/image_diff/image_diff_png.h" namespace image_diff_png { From 9a7ad119f8deddad709f4d9ee8c4c25aebd9bc63 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Wed, 26 Aug 2026 16:04:38 -0700 Subject: [PATCH 091/123] Fix Rust BMP/PNG build rules for embedders When building targets that use Rust FFI headers, make sure those targets inherit the GN config that specifies the correct include_dirs for the FFI headers. Change-Id: Id7e345b278104e930d371898eccdbae77adcff8b Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155930 Commit-Queue: Lei Zhang Reviewed-by: Tom Sepez --- core/fxcodec/BUILD.gn | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn index f2a4970c8d..f633ec75b5 100644 --- a/core/fxcodec/BUILD.gn +++ b/core/fxcodec/BUILD.gn @@ -9,6 +9,12 @@ 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" @@ -16,6 +22,7 @@ if (pdf_enable_rust_bmp) { 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", @@ -30,6 +37,7 @@ if (pdf_enable_rust_png) { 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", From 679c5ca4865cd3de8ee128f523ff793bba3a18d1 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Wed, 26 Aug 2026 17:12:48 -0700 Subject: [PATCH 092/123] Fix annoying Clippy warnings in Rust codec and font code Address various Clippy warnings in BMP, PNG, and Skrifa integrations: - Simplify map_or() to is_none_or() in rust_bmp_ffi.rs. - Collapse nested if blocks in rust_png_ffi.rs. - Allow large_enum_variant on boxed SkrifaFont and simplify manual map in skrifa/src/main.rs. TAG=agy CONV=10232370-6210-48dd-8cf4-fcd35ec00a46 Change-Id: Id5e2bc2c665db3b8c794ba20f664a5ba67fe7f64 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155912 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- core/fxcodec/bmp/rust_bmp_ffi.rs | 2 +- core/fxcodec/png/rust_png_ffi.rs | 20 +++++++++++--------- core/fxge/skrifa/src/main.rs | 14 +++++++------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/core/fxcodec/bmp/rust_bmp_ffi.rs b/core/fxcodec/bmp/rust_bmp_ffi.rs index c3166df046..4a14c41852 100644 --- a/core/fxcodec/bmp/rust_bmp_ffi.rs +++ b/core/fxcodec/bmp/rust_bmp_ffi.rs @@ -67,7 +67,7 @@ fn read_bmp_info(src: &[u8], info: &mut ffi::BmpHeaderInfo) -> ffi::DecodeStatus let uncompressed_size = (width as u64) .checked_mul(height as u64) .and_then(|pixels| pixels.checked_mul(components as u64)); - if uncompressed_size.map_or(true, |size| size > MAX_UNCOMPRESSED_BYTES) { + if uncompressed_size.is_none_or(|size| size > MAX_UNCOMPRESSED_BYTES) { return ffi::DecodeStatus::Error; } info.width = width; diff --git a/core/fxcodec/png/rust_png_ffi.rs b/core/fxcodec/png/rust_png_ffi.rs index 6711841dda..ef8853a2a8 100644 --- a/core/fxcodec/png/rust_png_ffi.rs +++ b/core/fxcodec/png/rust_png_ffi.rs @@ -18,15 +18,17 @@ mod ffi { } 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" { - if 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; - } + 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; } } 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() From 921adfa17c81b54ef982d9b448f1bf0025122653 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Wed, 26 Aug 2026 17:41:42 -0700 Subject: [PATCH 093/123] Reorganize testing/ directories and PNG codec structure Move pdfium_test.cc and all files from testing/helpers/ into a dedicated testing/pdfium_test/ directory with its own BUILD.gn. The files in helpers/ were internal sub-modules of pdfium_test rather than general test helpers. Pull image_diff.cpp up into testing/ directly and move the executable target definition into testing/BUILD.gn. Delete testing/image_diff/ as it no longer contains codec code or multiple files. Create dedicated testing/png_codec/ component containing png_codec.h, png_codec_libpng.cpp, png_codec_skia.cpp, png_codec_unittest.cpp, and its own BUILD.gn and DEPS. This encapsulates PNG encoding and decoding support for all test binaries and isolates third-party dependencies on libpng and zlib to testing/png_codec/. Resulting testing/ directory layout: testing/ |-- BUILD.gn |-- DEPS |-- image_diff.cpp |-- pdfium_test/ | |-- BUILD.gn | |-- pdfium_test.cc | |-- write.cc | `-- ... |-- png_codec/ | |-- BUILD.gn | |-- DEPS | |-- png_codec.h | |-- png_codec_libpng.cpp | |-- png_codec_skia.cpp | `-- png_codec_unittest.cpp `-- utils/ |-- bitmap_saver.cpp |-- file_util.cpp |-- pixel_diff_util.cpp `-- ... TAG=agy CONV=db6cb163-190b-47d4-bbdc-b519626ce208 Bypass-Check-License: renamed filed. Change-Id: I8d6840179df0f41b3154223a38862e3418d86ced Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155890 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- BUILD.gn | 22 ++----- core/fxcrt/BUILD.gn | 3 +- testing/BUILD.gn | 62 +++--------------- testing/DEPS | 2 - testing/embedder_test.cpp | 2 +- testing/{image_diff => }/image_diff.cpp | 2 +- testing/pdfium_test/BUILD.gn | 63 +++++++++++++++++++ testing/{helpers => pdfium_test}/dump.cc | 2 +- testing/{helpers => pdfium_test}/dump.h | 6 +- testing/{helpers => pdfium_test}/event.cc | 2 +- testing/{helpers => pdfium_test}/event.h | 6 +- .../{helpers => pdfium_test}/page_renderer.cc | 2 +- .../{helpers => pdfium_test}/page_renderer.h | 6 +- testing/{ => pdfium_test}/pdfium_test.cc | 10 +-- .../win32/com_factory.cc | 2 +- .../win32/com_factory.h | 6 +- testing/{helpers => pdfium_test}/write.cc | 2 +- testing/{helpers => pdfium_test}/write.h | 6 +- testing/{image_diff => png_codec}/BUILD.gn | 14 ++--- testing/png_codec/DEPS | 5 ++ .../png_codec.h} | 6 +- .../png_codec_libpng.cpp} | 2 +- .../png_codec_skia.cpp} | 2 +- .../png_codec_unittest.cpp} | 2 +- testing/tools/BUILD.gn | 4 +- testing/utils/png_encode.cpp | 2 +- 26 files changed, 125 insertions(+), 118 deletions(-) rename testing/{image_diff => }/image_diff.cpp (99%) create mode 100644 testing/pdfium_test/BUILD.gn rename testing/{helpers => pdfium_test}/dump.cc (99%) rename testing/{helpers => pdfium_test}/dump.h (77%) rename testing/{helpers => pdfium_test}/event.cc (99%) rename testing/{helpers => pdfium_test}/event.h (79%) rename testing/{helpers => pdfium_test}/page_renderer.cc (89%) rename testing/{helpers => pdfium_test}/page_renderer.h (89%) rename testing/{ => pdfium_test}/pdfium_test.cc (99%) rename testing/{helpers => pdfium_test}/win32/com_factory.cc (96%) rename testing/{helpers => pdfium_test}/win32/com_factory.h (74%) rename testing/{helpers => pdfium_test}/write.cc (99%) rename testing/{helpers => pdfium_test}/write.h (95%) rename testing/{image_diff => png_codec}/BUILD.gn (66%) create mode 100644 testing/png_codec/DEPS rename testing/{image_diff/image_diff_png.h => png_codec/png_codec.h} (92%) rename testing/{utils/png_encode_libpng.cpp => png_codec/png_codec_libpng.cpp} (99%) rename testing/{utils/png_encode_skia.cpp => png_codec/png_codec_skia.cpp} (99%) rename testing/{utils/png_unittest.cpp => png_codec/png_codec_unittest.cpp} (99%) diff --git a/BUILD.gn b/BUILD.gn index daf497c5b8..6e2a70d458 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -325,7 +325,7 @@ test("pdfium_unittests") { "fpdfsdk:unittests", "testing:unit_test_support", "testing:unittests", - "testing/image_diff:unittests", + "testing/png_codec:unittests", "//testing/gmock", "//testing/gtest", ] @@ -386,7 +386,7 @@ test("pdfium_embeddertests") { "fpdfsdk:embeddertests", "fpdfsdk/formfiller:embeddertests", "fpdfsdk/pwl:embeddertests", - "testing/image_diff", + "testing/png_codec", "//testing/gmock", "//testing/gtest", ] @@ -421,28 +421,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", ] if (pdf_is_standalone) { diff --git a/core/fxcrt/BUILD.gn b/core/fxcrt/BUILD.gn index 0e14e8b540..762592de50 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/testing/BUILD.gn b/testing/BUILD.gn index 898adcf090..e8fbcc6f33 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -50,7 +50,7 @@ source_set("test_support") { "../core/fxcrt", "../core/fxge", "../fpdfsdk", - "image_diff", + "png_codec", ] configs += [ "../:pdfium_strict_config", @@ -225,7 +225,7 @@ source_set("embedder_test_support") { "../core/fdrm", "../core/fxge", "../fpdfsdk", - "image_diff", + "png_codec", "//testing/gmock", "//testing/gtest", "//third_party/simdutf", @@ -292,61 +292,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 b64a58f737..b49801418b 100644 --- a/testing/DEPS +++ b/testing/DEPS @@ -4,10 +4,8 @@ include_rules = [ '+fxjs', '+partition_alloc', '+public', - '+third_party/libpng', '+third_party/simdutf/simdutf.h', '+third_party/skia/include', - '+third_party/zlib', '+v8', '+xfa', ] diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp index 38f3409969..cf9e449636 100644 --- a/testing/embedder_test.cpp +++ b/testing/embedder_test.cpp @@ -32,7 +32,7 @@ #include "public/fpdfview.h" #include "testing/embedder_test_environment.h" #include "testing/gmock/include/gmock/gmock.h" -#include "testing/image_diff/image_diff_png.h" +#include "testing/png_codec/png_codec.h" #include "testing/test_loader.h" #include "testing/utils/bitmap_saver.h" #include "testing/utils/file_util.h" diff --git a/testing/image_diff/image_diff.cpp b/testing/image_diff.cpp similarity index 99% rename from testing/image_diff/image_diff.cpp rename to testing/image_diff.cpp index 410af30296..e3d290075b 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" 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 4d56e580de..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 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 99% rename from testing/helpers/write.cc rename to testing/pdfium_test/write.cc index 04bc0717b3..9bf5788dc7 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 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/image_diff/BUILD.gn b/testing/png_codec/BUILD.gn similarity index 66% rename from testing/image_diff/BUILD.gn rename to testing/png_codec/BUILD.gn index 7076877d90..2afa9862d8 100644 --- a/testing/image_diff/BUILD.gn +++ b/testing/png_codec/BUILD.gn @@ -1,20 +1,20 @@ -# Copyright 2018 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. import("../../pdfium.gni") import("../../testing/test.gni") -source_set("image_diff") { +source_set("png_codec") { testonly = true - sources = [ "image_diff_png.h" ] + sources = [ "png_codec.h" ] deps = [] if (pdf_use_skia) { - sources += [ "../utils/png_encode_skia.cpp" ] + sources += [ "png_codec_skia.cpp" ] deps += [ "//skia" ] } else { - sources += [ "../utils/png_encode_libpng.cpp" ] + sources += [ "png_codec_libpng.cpp" ] deps += [ "../../third_party:png", "../../third_party:zlib", @@ -29,7 +29,7 @@ source_set("image_diff") { } pdfium_unittest_source_set("unittests") { - sources = [ "../utils/png_unittest.cpp" ] - deps = [ ":image_diff" ] + sources = [ "png_codec_unittest.cpp" ] + deps = [ ":png_codec" ] pdfium_root_dir = "../../" } diff --git a/testing/png_codec/DEPS b/testing/png_codec/DEPS new file mode 100644 index 0000000000..a9cc1c682e --- /dev/null +++ b/testing/png_codec/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + '+third_party/libpng', + '+third_party/skia/include', + '+third_party/zlib', +] diff --git a/testing/image_diff/image_diff_png.h b/testing/png_codec/png_codec.h similarity index 92% rename from testing/image_diff/image_diff_png.h rename to testing/png_codec/png_codec.h index 74ecfdfe96..0cf83de553 100644 --- a/testing/image_diff/image_diff_png.h +++ b/testing/png_codec/png_codec.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_IMAGE_DIFF_IMAGE_DIFF_PNG_H_ -#define TESTING_IMAGE_DIFF_IMAGE_DIFF_PNG_H_ +#ifndef TESTING_PNG_CODEC_PNG_CODEC_H_ +#define TESTING_PNG_CODEC_PNG_CODEC_H_ #include // for size_t. @@ -47,4 +47,4 @@ std::vector EncodeGrayPNG(pdfium::span input, } // namespace image_diff_png -#endif // TESTING_IMAGE_DIFF_IMAGE_DIFF_PNG_H_ +#endif // TESTING_PNG_CODEC_PNG_CODEC_H_ diff --git a/testing/utils/png_encode_libpng.cpp b/testing/png_codec/png_codec_libpng.cpp similarity index 99% rename from testing/utils/png_encode_libpng.cpp rename to testing/png_codec/png_codec_libpng.cpp index ecc9298d85..6a9a4202f4 100644 --- a/testing/utils/png_encode_libpng.cpp +++ b/testing/png_codec/png_codec_libpng.cpp @@ -19,7 +19,7 @@ #include "core/fxcrt/compiler_specific.h" #include "core/fxcrt/fx_memcpy_wrappers.h" #include "core/fxcrt/notreached.h" -#include "testing/image_diff/image_diff_png.h" +#include "testing/png_codec/png_codec.h" #ifdef USE_SYSTEM_ZLIB #include diff --git a/testing/utils/png_encode_skia.cpp b/testing/png_codec/png_codec_skia.cpp similarity index 99% rename from testing/utils/png_encode_skia.cpp rename to testing/png_codec/png_codec_skia.cpp index 9298ff6eba..f6daaf81d0 100644 --- a/testing/utils/png_encode_skia.cpp +++ b/testing/png_codec/png_codec_skia.cpp @@ -12,7 +12,7 @@ #include "core/fxcrt/numerics/checked_math.h" #include "core/fxcrt/span.h" #include "core/fxcrt/span_util.h" -#include "testing/image_diff/image_diff_png.h" +#include "testing/png_codec/png_codec.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" diff --git a/testing/utils/png_unittest.cpp b/testing/png_codec/png_codec_unittest.cpp similarity index 99% rename from testing/utils/png_unittest.cpp rename to testing/png_codec/png_codec_unittest.cpp index 9caf911e59..8cb4d7aa47 100644 --- a/testing/utils/png_unittest.cpp +++ b/testing/png_codec/png_codec_unittest.cpp @@ -7,7 +7,7 @@ #include #include "testing/gtest/include/gtest/gtest.h" -#include "testing/image_diff/image_diff_png.h" +#include "testing/png_codec/png_codec.h" namespace image_diff_png { diff --git a/testing/tools/BUILD.gn b/testing/tools/BUILD.gn index 15d841bcfe..61991e7e50 100644 --- a/testing/tools/BUILD.gn +++ b/testing/tools/BUILD.gn @@ -37,8 +37,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/utils/png_encode.cpp b/testing/utils/png_encode.cpp index 1d0035b997..d13630bf44 100644 --- a/testing/utils/png_encode.cpp +++ b/testing/utils/png_encode.cpp @@ -10,7 +10,7 @@ #include "core/fxcrt/fx_safe_types.h" #include "core/fxcrt/notreached.h" #include "public/fpdfview.h" -#include "testing/image_diff/image_diff_png.h" +#include "testing/png_codec/png_codec.h" #ifdef PDF_ENABLE_SKIA #include "third_party/skia/include/core/SkAlphaType.h" // nogncheck From 187ec5a255435b6bd9e06ec1008979a6a5d77ebc Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Wed, 26 Aug 2026 17:43:04 -0700 Subject: [PATCH 094/123] Use CPDF_SyntaxParser::kParserMaxRecursionDepth in CPDF_StreamParser Make CPDF_SyntaxParser::kParserMaxRecursionDepth public and reuse it in CPDF_StreamParser::ReadNextObject() instead of defining a separate kMaxNestedParsingLevel constant. This effectively reduces the maximum recursion level from 512 down to 64, which aligns stream parsing with document syntax parsing and prevents deep recursion stack overflow. TAG=agy CONV=d93e26e9-5db5-4c96-928d-df7e821e9792 Bug: 552951742 Change-Id: Ie7b3f9f23f233f028b3dbbfd84bae951606da8aa Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155910 Commit-Queue: Lei Zhang Reviewed-by: Andy Phan --- core/fpdfapi/page/cpdf_streamparser.cpp | 5 +++-- core/fpdfapi/parser/cpdf_syntax_parser.h | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/fpdfapi/page/cpdf_streamparser.cpp b/core/fpdfapi/page/cpdf_streamparser.cpp index 980a18f39a..4f8f6ccd4c 100644 --- a/core/fpdfapi/page/cpdf_streamparser.cpp +++ b/core/fpdfapi/page/cpdf_streamparser.cpp @@ -20,6 +20,7 @@ #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" @@ -39,7 +40,6 @@ namespace { -const uint32_t kMaxNestedParsingLevel = 512; const size_t kMaxStringLength = 32767; const char kTrue[] = "true"; @@ -345,7 +345,8 @@ RetainPtr CPDF_StreamParser::ReadNextObject( uint32_t recursion_level) { // Must get the next word before returning to avoid infinite loops. const bool is_number = GetNextWord(); - if (!word_size_ || recursion_level > kMaxNestedParsingLevel) { + if (!word_size_ || + recursion_level > CPDF_SyntaxParser::kParserMaxRecursionDepth) { return nullptr; } diff --git a/core/fpdfapi/parser/cpdf_syntax_parser.h b/core/fpdfapi/parser/cpdf_syntax_parser.h index d5f6e60eb3..c21fb355a4 100644 --- a/core/fpdfapi/parser/cpdf_syntax_parser.h +++ b/core/fpdfapi/parser/cpdf_syntax_parser.h @@ -38,6 +38,8 @@ class CPDF_SyntaxParser { bool is_number; }; + static constexpr int kParserMaxRecursionDepth = 64; + static std::unique_ptr CreateForTesting( RetainPtr pFileAccess, FX_FILESIZE HeaderOffset); @@ -94,7 +96,6 @@ class CPDF_SyntaxParser { friend class CPDF_DataAvail; friend class cpdf_syntax_parser_ReadHexString_Test; - static constexpr int kParserMaxRecursionDepth = 64; static int s_CurrentRecursionDepth; bool ReadBlockAt(FX_FILESIZE read_pos); From 0a809f87c8faba9e4cbdab120bf1d575e31db802 Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Wed, 26 Aug 2026 18:20:47 -0700 Subject: [PATCH 095/123] Composite opaque BGR rows via Zip() and skip per-pixel alpha division CompositeRow_Bgr2Bgra_NoBlend_NoClip() advanced through the source row by reassigning src_span via subspan(src_Bpp) once per pixel, which bounds-checks against the shrinking span's own size every iteration. The compiler cannot elide that check, because the checked offset is a derived induction variable rather than the loop's own. The caller knows the source format, so let it reinterpret the row into fixed-size pixel structs and hand the compositor a typed span. The row then becomes a single fxcrt::Zip() loop that compares sizes once and iterates checklessly. On the per-pixel BGRA no-blend path, the two callers already branch on src_alpha, so they now handle a fully opaque source directly. AlphaUnion() would be 255, the ratio 255, and AlphaMerge(..., 255) returns the source, so that case is a copy and skips the general path's division entirely. Opaque pixels dominate in real documents. Bug: 549552310 Change-Id: Iba8d9bf1326c8d9a539e1c4db614ee29405c83d3 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155530 Reviewed-by: Lei Zhang Reviewed-by: Tom Sepez Commit-Queue: Lei Zhang --- core/fxge/dib/cfx_scanlinecompositor.cpp | 51 +++++++++++++++++++----- 1 file changed, 42 insertions(+), 9 deletions(-) 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); } } From 0f6645c0b27aea36d21949ff87973c424f070d7d Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Thu, 27 Aug 2026 11:55:14 -0700 Subject: [PATCH 096/123] Reinterpret CStretchEngine destination spans as pixel structs Follow-up to the review of https://pdfium-review.googlesource.com/c/155550: the horizontal pass's typed source reinterpretation now has matching typed destinations wherever a loop writes multi-byte pixels -- the palette arm, the generic tap loop, and the alpha arm -- which replaces byte-index arithmetic and drops the per-pixel destination stride adjustment. In StretchVert(), the arm that writes only each pixel's first channel gets typed pixel views for the 3- and 4-byte destination widths, so all three widths share one loop shape instead of a strided index loop. Single-byte destinations keep their plain byte spans, where a pixel struct would add nothing. Benchmarks showed no performance changes. Change-Id: I1d37e45352c9ca5fdcbba628a78271a4832fb5c6 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155970 Reviewed-by: Lei Zhang Commit-Queue: Lei Zhang Reviewed-by: Tom Sepez --- core/fxge/dib/cstretchengine.cpp | 69 ++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/core/fxge/dib/cstretchengine.cpp b/core/fxge/dib/cstretchengine.cpp index fb26b633fd..c9fa6fd396 100644 --- a/core/fxge/dib/cstretchengine.cpp +++ b/core/fxge/dib/cstretchengine.cpp @@ -396,6 +396,10 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { 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(); @@ -417,9 +421,10 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { 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; } @@ -431,8 +436,14 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { // 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, &dest_span_index, - Bpp](auto src_pixels) { + 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(); @@ -469,10 +480,10 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { } break; } - 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; + DestPixel& dest = dest_pixels[dest_index++]; + dest.blue = PixelFromFixed(dest_b); + dest.green = PixelFromFixed(dest_g); + dest.red = PixelFromFixed(dest_r); } }; if (Bpp == 3) { @@ -496,6 +507,9 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { 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(); @@ -512,11 +526,11 @@ bool CStretchEngine::ContinueStretchHorz(PauseIndicatorIface* pPause) { 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; } @@ -605,18 +619,31 @@ void CStretchEngine::StretchVert() { case TransformMethod::k1BppTo8Bpp: case TransformMethod::k1BppToManyBpp: case TransformMethod::k8BppTo8Bpp: { - // Only the first byte of each destination pixel carries data on - // these paths; any remaining bytes are left untouched. + // 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); } + } else if (DestBpp == 3) { + write_first_channel( + fxcrt::reinterpret_span>(dest_row), + fxcrt::reinterpret_span>( + accum_span)); } else { - size_t offset = 0; - for (int col = 0; col < dest_cols; ++col) { - dest_row[offset] = PixelFromFixed(accum_span[offset]); - offset += DestBpp; - } + CHECK_EQ(DestBpp, 4); + write_first_channel( + fxcrt::reinterpret_span>(dest_row), + fxcrt::reinterpret_span>( + accum_span)); } break; } From d42d3e0977b91131c3c185cbf06ffe95f0ed1440 Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Thu, 27 Aug 2026 11:57:41 -0700 Subject: [PATCH 097/123] Make recursion variables unsigned in CPDF_SyntaxParser Make CPDF_SyntaxParser consistent with CPDF_StreamParser. Change-Id: Ibe6cd25648ed2c075714474b2d452d388d7dd53d Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155911 Commit-Queue: Lei Zhang Reviewed-by: Andy Phan --- core/fpdfapi/parser/cpdf_syntax_parser.cpp | 4 ++-- core/fpdfapi/parser/cpdf_syntax_parser.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/fpdfapi/parser/cpdf_syntax_parser.cpp b/core/fpdfapi/parser/cpdf_syntax_parser.cpp index ef02ae67c9..ee91e039bc 100644 --- a/core/fpdfapi/parser/cpdf_syntax_parser.cpp +++ b/core/fpdfapi/parser/cpdf_syntax_parser.cpp @@ -76,7 +76,7 @@ class ReadableSubStream final : public IFX_SeekableReadStream { } // namespace // static -int CPDF_SyntaxParser::s_CurrentRecursionDepth = 0; +uint32_t CPDF_SyntaxParser::s_CurrentRecursionDepth = 0; // static std::unique_ptr CPDF_SyntaxParser::CreateForTesting( @@ -537,7 +537,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 c21fb355a4..183aee554e 100644 --- a/core/fpdfapi/parser/cpdf_syntax_parser.h +++ b/core/fpdfapi/parser/cpdf_syntax_parser.h @@ -38,7 +38,7 @@ class CPDF_SyntaxParser { bool is_number; }; - static constexpr int kParserMaxRecursionDepth = 64; + static constexpr uint32_t kParserMaxRecursionDepth = 64; static std::unique_ptr CreateForTesting( RetainPtr pFileAccess, @@ -96,7 +96,7 @@ class CPDF_SyntaxParser { friend class CPDF_DataAvail; friend class cpdf_syntax_parser_ReadHexString_Test; - static int s_CurrentRecursionDepth; + static uint32_t s_CurrentRecursionDepth; bool ReadBlockAt(FX_FILESIZE read_pos); bool GetCharAtBackward(FX_FILESIZE pos, uint8_t* ch); From b132d1cdec94c11c5f1144890e36135ae8a8cbaa Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Thu, 27 Aug 2026 13:15:50 -0700 Subject: [PATCH 098/123] Add tests for the 24bpp default-decode scanline translate TranslateScanline24bppDefaultDecode()'s 8 bits-per-component arm is covered indirectly by existing tests, but its 16 and sub-8 bits-per-component arms have no coverage at all. Add unit tests that load a one-scanline DeviceRGB image at 4, 8 and 16 bits per component and check the bytes of the translated scanline. Bug: 553155247 Change-Id: I235584fcc57d18fa97e16d71ff29327a8c391b15 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156010 Reviewed-by: Lei Zhang Reviewed-by: Tom Sepez Commit-Queue: Lei Zhang --- core/fpdfapi/page/BUILD.gn | 1 + core/fpdfapi/page/cpdf_dib_unittest.cpp | 92 +++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 core/fpdfapi/page/cpdf_dib_unittest.cpp diff --git a/core/fpdfapi/page/BUILD.gn b/core/fpdfapi/page/BUILD.gn index 3558e03194..4f01a5e8e8 100644 --- a/core/fpdfapi/page/BUILD.gn +++ b/core/fpdfapi/page/BUILD.gn @@ -144,6 +144,7 @@ pdfium_unittest_source_set("unittests") { sources = [ "cpdf_colorspace_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_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)); +} From b1e25a915269097c65b7c5e7dcbe37c1a61e8f35 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Thu, 27 Aug 2026 17:14:53 -0700 Subject: [PATCH 099/123] Unify PNG codec API under namespace png_codec Consolidate PNG encoding and decoding functionality into testing/png_codec/png_codec.h under namespace png_codec: - Rename namespace from image_diff_png to png_codec. - Standardize function names to Decode(), EncodeBGR(), EncodeRGBA(), EncodeBGRA(), and EncodeGray(), and remove PNG suffix. - Move Encode(span, ...) into png_codec. - Move Encode(FPDF_BITMAP) into bitmap_saver, remove format parameter and let callers use ConvertToStraightAlpha(). - Remove obsolete testing/utils/png_encode.{h,cpp}. - Update all call sites across testing/. TAG=agy CONV=db6cb163-190b-47d4-bbdc-b519626ce208 Change-Id: Id011f60dd525f0bc4dbdfc6fc82384be5fe6c8a2 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155950 Commit-Queue: Tom Sepez Reviewed-by: Lei Zhang --- testing/BUILD.gn | 2 - testing/embedder_test.cpp | 7 +- testing/image_diff.cpp | 4 +- testing/pdfium_test/write.cc | 55 +++++-------- testing/png_codec/png_codec.h | 48 ++++++------ testing/png_codec/png_codec_libpng.cpp | 46 +++++------ testing/png_codec/png_codec_skia.cpp | 75 +++++++++--------- testing/png_codec/png_codec_unittest.cpp | 60 +++++++-------- testing/utils/bitmap_saver.cpp | 83 +++++++++++++++++++- testing/utils/bitmap_saver.h | 12 +++ testing/utils/png_encode.cpp | 98 ------------------------ testing/utils/png_encode.h | 22 ------ 12 files changed, 233 insertions(+), 279 deletions(-) delete mode 100644 testing/utils/png_encode.cpp delete mode 100644 testing/utils/png_encode.h diff --git a/testing/BUILD.gn b/testing/BUILD.gn index e8fbcc6f33..c28c713c30 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -34,8 +34,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", ] diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp index cf9e449636..bfb67368ba 100644 --- a/testing/embedder_test.cpp +++ b/testing/embedder_test.cpp @@ -39,7 +39,6 @@ #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 { @@ -355,8 +354,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; @@ -492,7 +491,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) { diff --git a/testing/image_diff.cpp b/testing/image_diff.cpp index e3d290075b..0ecff329b8 100644 --- a/testing/image_diff.cpp +++ b/testing/image_diff.cpp @@ -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; @@ -395,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; diff --git a/testing/pdfium_test/write.cc b/testing/pdfium_test/write.cc index 9bf5788dc7..821281e4d3 100644 --- a/testing/pdfium_test/write.cc +++ b/testing/pdfium_test/write.cc @@ -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/png_codec/png_codec.h b/testing/png_codec/png_codec.h index 0cf83de553..6714978afe 100644 --- a/testing/png_codec/png_codec.h +++ b/testing/png_codec/png_codec.h @@ -5,46 +5,46 @@ #ifndef TESTING_PNG_CODEC_PNG_CODEC_H_ #define TESTING_PNG_CODEC_PNG_CODEC_H_ -#include // for size_t. +#include #include #include "core/fxcrt/span.h" -namespace image_diff_png { +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 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); // Encode a BGR pixel array into a PNG. -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); // Encode an RGBA pixel array into a PNG. -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); // 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); +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 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); -} // namespace image_diff_png +} // namespace png_codec #endif // TESTING_PNG_CODEC_PNG_CODEC_H_ diff --git a/testing/png_codec/png_codec_libpng.cpp b/testing/png_codec/png_codec_libpng.cpp index 6a9a4202f4..f6ed1d3707 100644 --- a/testing/png_codec/png_codec_libpng.cpp +++ b/testing/png_codec/png_codec_libpng.cpp @@ -37,7 +37,7 @@ #error "If Rust PNG is enabled, then `libpng` should not be used." #endif -namespace image_diff_png { +namespace png_codec { namespace { @@ -679,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_skia.cpp b/testing/png_codec/png_codec_skia.cpp index f6daaf81d0..2e1b7ed4db 100644 --- a/testing/png_codec/png_codec_skia.cpp +++ b/testing/png_codec/png_codec_skia.cpp @@ -26,16 +26,16 @@ #include "third_party/skia/include/encode/SkPngEncoder.h" #endif -namespace image_diff_png { +namespace png_codec { namespace { -std::vector EncodePNG(pdfium::span input, - SkColorType color, - SkAlphaType alpha, - int width, - int height, - size_t row_byte_width) { +std::vector EncodeHelper(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. @@ -58,10 +58,10 @@ std::vector EncodePNG(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) { CHECK(width); CHECK(height); @@ -98,10 +98,10 @@ std::vector DecodePNG(pdfium::span input, return output; } -std::vector EncodeBGRPNG(pdfium::span bgr_input, - int width, - int height, - int row_byte_width) { +std::vector EncodeBGR(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. @@ -150,36 +150,37 @@ std::vector EncodeBGRPNG(pdfium::span bgr_input, } } - return EncodePNG(intermediate_bgra_buf, kBGRA_8888_SkColorType, - kOpaque_SkAlphaType, width, height, - intermediate_bgra_row_byte_width); + return EncodeHelper(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 EncodeRGBA(pdfium::span input, + int width, + int height, + int row_byte_width) { + return EncodeHelper(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( +std::vector EncodeBGRA(pdfium::span input, + int width, + int height, + int row_byte_width, + bool discard_transparency) { + return EncodeHelper( 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)); +std::vector EncodeGray(pdfium::span input, + int width, + int height, + int row_byte_width) { + return EncodeHelper(input, kGray_8_SkColorType, kOpaque_SkAlphaType, width, + height, pdfium::checked_cast(row_byte_width)); } -} // namespace image_diff_png +} // namespace png_codec diff --git a/testing/png_codec/png_codec_unittest.cpp b/testing/png_codec/png_codec_unittest.cpp index 8cb4d7aa47..ee4c1edd44 100644 --- a/testing/png_codec/png_codec_unittest.cpp +++ b/testing/png_codec/png_codec_unittest.cpp @@ -9,7 +9,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "testing/png_codec/png_codec.h" -namespace image_diff_png { +namespace png_codec { namespace { @@ -23,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; @@ -31,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); @@ -53,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); @@ -72,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); @@ -92,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); @@ -110,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); @@ -128,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/utils/bitmap_saver.cpp b/testing/utils/bitmap_saver.cpp index 39b652f9cd..90689b31d0 100644 --- a/testing/utils/bitmap_saver.cpp +++ b/testing/utils/bitmap_saver.cpp @@ -4,17 +4,75 @@ #include "testing/utils/bitmap_saver.h" +#include + #include #include #include "core/fxcrt/check.h" #include "core/fxcrt/check_op.h" -#include "testing/utils/png_encode.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_LT(filename.size(), 256u); @@ -30,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/png_encode.cpp b/testing/utils/png_encode.cpp deleted file mode 100644 index d13630bf44..0000000000 --- a/testing/utils/png_encode.cpp +++ /dev/null @@ -1,98 +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/check_op.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/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 - -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_GE(stride, 0); - CHECK_GE(width, 0); - CHECK_GE(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_ From ad5bc13610718aefe737bd9b2f1b258d08e39428 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Thu, 27 Aug 2026 18:04:17 -0700 Subject: [PATCH 100/123] Add Rust PNG codec backend for testing and remove Skia PNG encoder Replace the legacy Skia-based PNG backend in testing/png_codec with a pure Rust backend using the png crate via CXX bindings: - Add testing/png_codec/png_codec_rust_ffi.rs implementing PNG encoding (BGR, BGRA, RGBA, Gray) and decoding via the png crate. - Add testing/png_codec/png_codec_rust.cpp implementing the png_codec C++ API using the Rust FFI. - Delete testing/png_codec/png_codec_skia.cpp, decoupling PNG testing code completely from Skia. - Update testing/png_codec/BUILD.gn to switch between the Rust PNG and libpng backends based on pdf_enable_rust_png. - Update testing/png_codec/DEPS. TAG=agy CONV=db6cb163-190b-47d4-bbdc-b519626ce208 Change-Id: I60a69517a58f9d6d53990daca30782d0c3b20004 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156030 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- testing/png_codec/BUILD.gn | 27 +- testing/png_codec/DEPS | 2 +- testing/png_codec/png_codec_rust.cpp | 89 +++++++ testing/png_codec/png_codec_rust_ffi.rs | 322 ++++++++++++++++++++++++ testing/png_codec/png_codec_skia.cpp | 186 -------------- 5 files changed, 436 insertions(+), 190 deletions(-) create mode 100644 testing/png_codec/png_codec_rust.cpp create mode 100644 testing/png_codec/png_codec_rust_ffi.rs delete mode 100644 testing/png_codec/png_codec_skia.cpp diff --git a/testing/png_codec/BUILD.gn b/testing/png_codec/BUILD.gn index 2afa9862d8..5b1d7940ea 100644 --- a/testing/png_codec/BUILD.gn +++ b/testing/png_codec/BUILD.gn @@ -5,14 +5,35 @@ 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_use_skia) { - sources += [ "png_codec_skia.cpp" ] - deps += [ "//skia" ] + 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 += [ diff --git a/testing/png_codec/DEPS b/testing/png_codec/DEPS index a9cc1c682e..17b552adce 100644 --- a/testing/png_codec/DEPS +++ b/testing/png_codec/DEPS @@ -1,5 +1,5 @@ include_rules = [ '+third_party/libpng', - '+third_party/skia/include', + '+third_party/rust/png', '+third_party/zlib', ] 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/png_codec/png_codec_skia.cpp b/testing/png_codec/png_codec_skia.cpp deleted file mode 100644 index 2e1b7ed4db..0000000000 --- a/testing/png_codec/png_codec_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 -#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 "testing/png_codec/png_codec.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 png_codec { - -namespace { - -std::vector EncodeHelper(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 Decode(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 EncodeBGR(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 EncodeHelper(intermediate_bgra_buf, kBGRA_8888_SkColorType, - kOpaque_SkAlphaType, width, height, - intermediate_bgra_row_byte_width); -} - -std::vector EncodeRGBA(pdfium::span input, - int width, - int height, - int row_byte_width) { - return EncodeHelper(input, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType, - width, height, - pdfium::checked_cast(row_byte_width)); -} - -std::vector EncodeBGRA(pdfium::span input, - int width, - int height, - int row_byte_width, - bool discard_transparency) { - return EncodeHelper( - input, kBGRA_8888_SkColorType, - discard_transparency ? kOpaque_SkAlphaType : kUnpremul_SkAlphaType, width, - height, pdfium::checked_cast(row_byte_width)); -} - -std::vector EncodeGray(pdfium::span input, - int width, - int height, - int row_byte_width) { - return EncodeHelper(input, kGray_8_SkColorType, kOpaque_SkAlphaType, width, - height, pdfium::checked_cast(row_byte_width)); -} - -} // namespace png_codec From b1c9c9c06e8b5303b828e26fa2f96bbbc779df5e Mon Sep 17 00:00:00 2001 From: Aryan Date: Fri, 28 Aug 2026 10:23:17 -0700 Subject: [PATCH 101/123] Add FPDFBookmark_GetStyle() API This CL adds a public experimental API, FPDFBookmark_GetStyle(), to retrieve the text style (bold and/or italic) associated with a bookmark. The underlying CPDF_Bookmark class now supports reading the "F" key from the bookmark dictionary, validating that the value represents a valid two-bit bitfield per ISO 32000-1:2008 Table 154. Bug: 362783123 Change-Id: I030238b9bdb0e417b5460c7f67877b03c7ce51b9 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155630 Reviewed-by: Andy Phan Reviewed-by: Lei Zhang Commit-Queue: Aryan Krishnan --- core/fpdfdoc/cpdf_bookmark.cpp | 4 + core/fpdfdoc/cpdf_bookmark.h | 1 + fpdfsdk/fpdf_doc.cpp | 9 ++ fpdfsdk/fpdf_doc_embeddertest.cpp | 32 +++++++ fpdfsdk/fpdf_view_c_api_test.c | 1 + public/fpdf_doc.h | 12 +++ testing/resources/bookmarks_style.in | 130 ++++++++++++++++++++++++++ testing/resources/bookmarks_style.pdf | Bin 0 -> 1887 bytes 8 files changed, 189 insertions(+) create mode 100644 testing/resources/bookmarks_style.in create mode 100644 testing/resources/bookmarks_style.pdf 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/fpdfsdk/fpdf_doc.cpp b/fpdfsdk/fpdf_doc.cpp index 2fc473ccd9..05866dd6c1 100644 --- a/fpdfsdk/fpdf_doc.cpp +++ b/fpdfsdk/fpdf_doc.cpp @@ -216,6 +216,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; diff --git a/fpdfsdk/fpdf_doc_embeddertest.cpp b/fpdfsdk/fpdf_doc_embeddertest.cpp index 12799e3ea0..aa2ed522a3 100644 --- a/fpdfsdk/fpdf_doc_embeddertest.cpp +++ b/fpdfsdk/fpdf_doc_embeddertest.cpp @@ -659,6 +659,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_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c index 2e5222724c..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); 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/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 0000000000000000000000000000000000000000..d840c4a9f6d4dadee2de803b38d17ace032af7b7 GIT binary patch literal 1887 zcmbtV&2HN;48H3rcnOdm7F$l8pP?v_v^5Z{Yl9>MwiP`njnb-ctw2_WWKXlho^aR$ zjFep4b=pHQ11%&<{78QMsI;c%$p_x)v(~RazyC50A?W2JoSZO#V6opw2+o8RslF$} zRNTuPBD^%C?e*5CDnrY_Sx!%x%vR{m=tdL9`I6U}-ewjeT7FWi9Byz0&@Dg#j*HbT zs{mU-?vQPtmeN4jVRZeq2!uKnMpA?VCz#1xZ_OR%>B_w zW)TTf?9uTpfq#}OCB}LOHzC@3Jc9l(dOKC#VN}oiQ+-bF{kitvldEIG6a;k`(a_A5 zu2ALk?b13ykSsV^#v+#vAQ%}X(ii z+Z`XkI!u`4aHTDbGMHrc)d)UuPNQGTyxdNW{85$V(g`j9jx2_%RF14|;Lj zjbRO?4QXUiEZ5LMJTz3txtqDZ9^QQhHcP4v<-U) zaXB9%3om_nm!#FO9oO@-MyM2jb^LGQOye|_!!cJsC2{9oFk!4?lgId`wOSX~3Ht{_ Cl+#fF literal 0 HcmV?d00001 From 6f2272e1f3aaa141305475b83ef4eac2c1f527b8 Mon Sep 17 00:00:00 2001 From: Tom Sepez Date: Fri, 28 Aug 2026 11:56:08 -0700 Subject: [PATCH 102/123] Make third_party/libpng an optional dependency in DEPS Add checkout_libpng variable and condition to DEPS so developers and bots can skip checking out third_party/libpng when building with pure Rust PNG or system libpng: - Add checkout_libpng to gclient_gn_args in DEPS. - Add checkout_libpng var and condition to third_party/libpng. - Add assert in third_party/BUILD.gn when bundled libpng is needed without checkout_libpng. - Update tools/roll_pdfium_deps_test.py. TAG=agy CONV=db6cb163-190b-47d4-bbdc-b519626ce208 Change-Id: I0a182c860afec91158a791ec0f9ad4a7ddae9e0e Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156031 Reviewed-by: Lei Zhang Commit-Queue: Tom Sepez --- DEPS | 9 +++++++-- third_party/BUILD.gn | 5 +++++ tools/roll_pdfium_deps_test.py | 9 +++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/DEPS b/DEPS index 169680f06a..f37c703760 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: @@ -482,9 +485,11 @@ 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', diff --git a/third_party/BUILD.gn b/third_party/BUILD.gn index e4e4e8e7db..9cc3e72383 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") @@ -439,6 +440,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" ] diff --git a/tools/roll_pdfium_deps_test.py b/tools/roll_pdfium_deps_test.py index 93314d1356..b771f52cd6 100644 --- a/tools/roll_pdfium_deps_test.py +++ b/tools/roll_pdfium_deps_test.py @@ -1530,6 +1530,7 @@ 'android_ndk_version', 'build_with_chromium', 'checkout_android', + 'checkout_libpng', 'checkout_skia', ] @@ -1561,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: @@ -1994,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', From f9a8ad16a130c6847cf522a5ac677c7b10e6d849 Mon Sep 17 00:00:00 2001 From: "pdfium-autoroll@skia-public.iam.gserviceaccount.com" Date: Sun, 30 Aug 2026 21:48:02 -0700 Subject: [PATCH 103/123] Roll Catapult from e4d1a01c96de to 50c971fc0958 (10 revisions) https://chromium.googlesource.com/catapult.git/+log/e4d1a01c96de..50c971fc0958 If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/catapult-pdfium Please CC dhoss@chromium.org,thestig@chromium.org,thestig@google.com on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Bug: None Tbr: thestig@google.com Change-Id: Ie2e64c6e64c5894e58e760ee8dc6ce60b268f6ec Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156230 Bot-Commit: pdfium-autoroll@skia-public.iam.gserviceaccount.com Commit-Queue: pdfium-autoroll@skia-public.iam.gserviceaccount.com --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index f37c703760..a491a0ad37 100644 --- a/DEPS +++ b/DEPS @@ -106,7 +106,7 @@ vars = { # 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': 'e4d1a01c96deb68b0aa26a66dca45fc290885cbf', + '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. From aed37f0aef5cb529190f9e06b2fb068b3052d7c1 Mon Sep 17 00:00:00 2001 From: "pdfium-autoroll@skia-public.iam.gserviceaccount.com" Date: Sun, 30 Aug 2026 21:48:46 -0700 Subject: [PATCH 104/123] Roll Code Coverage from f2c64929fbd5 to e1878fbb1ff7 (4 revisions) https://chromium.googlesource.com/chromium/src/tools/code_coverage.git/+log/f2c64929fbd5..e1878fbb1ff7 If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/code-coverage-pdfium Please CC dhoss@chromium.org,thestig@chromium.org,thestig@google.com on the revert to ensure that a human is aware of the problem. To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md Bug: None Tbr: thestig@google.com Change-Id: Iff804bc368b263b106616b7b5a4957dd54c18c37 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156231 Bot-Commit: pdfium-autoroll@skia-public.iam.gserviceaccount.com Commit-Queue: pdfium-autoroll@skia-public.iam.gserviceaccount.com --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index a491a0ad37..42a7a1c0fd 100644 --- a/DEPS +++ b/DEPS @@ -118,7 +118,7 @@ vars = { # Three lines of non-changing comments so that # the commit queue can handle CLs rolling code_coverage # and whatever else without interference from each other. - 'code_coverage_revision': 'f2c64929fbd5ee89eb7e5c06d73bc9cbda7796ae', + '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. From 64af343eafd2b7fadac592b7777f6ff877ff200f Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Mon, 31 Aug 2026 11:41:03 -0700 Subject: [PATCH 105/123] Roll Harfbuzz and icu Roll third_party/harfbuzz/src/ 28f4dc622..886fc1e64 (302 commits) Roll third_party/icu/ d578f2e8b..8cc91d9b6 (1 commit) https://chromium.googlesource.com/external/github.com/harfbuzz/harfbuzz.git/+log/28f4dc622fef..886fc1e64538 https://chromium.googlesource.com/chromium/deps/icu.git/+log/d578f2e8b7bd..8cc91d9b6ab9 Created with: roll-dep third_party/harfbuzz/src roll-dep third_party/icu Change-Id: Icef4babfb6cc7eae9d1d25c11b516857aa0303ed Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156330 Commit-Queue: April Kallmeyer Reviewed-by: Lei Zhang --- DEPS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 42a7a1c0fd..291db8ae79 100644 --- a/DEPS +++ b/DEPS @@ -162,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. @@ -170,7 +170,7 @@ 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. From 4f86595414885130a3d8b2eba12faded91e1292e Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Mon, 31 Aug 2026 11:49:32 -0700 Subject: [PATCH 106/123] Roll third_party/freetype/src/ 656cb7777..4800589f7 (10 commits) https://chromium.googlesource.com/chromium/src/third_party/freetype2.git/+log/656cb777798f..4800589f76dc Created with: roll-dep third_party/freetype/src R=bungeman@chromium.org,drott@chromium.org,thestig@chromium.org Change-Id: I5de3e208ad453f780c381fa191a56a97cee113e8 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156350 Reviewed-by: Lei Zhang Commit-Queue: April Kallmeyer --- DEPS | 2 +- third_party/freetype/README.pdfium | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DEPS b/DEPS index 291db8ae79..bdd76614a6 100644 --- a/DEPS +++ b/DEPS @@ -146,7 +146,7 @@ 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. 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 From 6239caf35caf0fb2944237f9ee84b64f9fe22764 Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Mon, 31 Aug 2026 11:50:31 -0700 Subject: [PATCH 107/123] Roll tools/win/ 45843c2c1..13cb6e5d2 (1 commit) https://chromium.googlesource.com/chromium/src/tools/win.git/+log/45843c2c1e99..13cb6e5d223d Created with: roll-dep tools/win Change-Id: I79ec23cfbdfccd9ec082b9f11094d87b6cf09ff0 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156370 Commit-Queue: April Kallmeyer Reviewed-by: Lei Zhang --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index bdd76614a6..950e29b684 100644 --- a/DEPS +++ b/DEPS @@ -264,7 +264,7 @@ vars = { # 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. From 732796e6e54cdeb70a767abbbdfca0c152d7c106 Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Mon, 31 Aug 2026 11:55:57 -0700 Subject: [PATCH 108/123] Roll base/allocator/partition_allocator/ 29013c81d..89615a645 (48 commits) https://chromium.googlesource.com/chromium/src/base/allocator/partition_allocator.git/+log/29013c81d1fa..89615a645f02 Created with: roll-dep base/allocator/partition_allocator Change-Id: I7f3b3f03f84ad96cb0eb4ece2f7a066584d2d40b Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156311 Reviewed-by: Lei Zhang Commit-Queue: April Kallmeyer --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 950e29b684..502b7e7a1f 100644 --- a/DEPS +++ b/DEPS @@ -220,7 +220,7 @@ 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': '29013c81d1fa46475bdf6608d71a10b5ebab3f48', + '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. From 6f6c0a80b04922f3aca84e024927b88a7ee52388 Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Mon, 31 Aug 2026 13:26:52 -0700 Subject: [PATCH 109/123] Roll third_party/skia/ 7ef86a5b0..5549c93c9 (380 commits; 91 trivial rolls) https://skia.googlesource.com/skia.git/+log/7ef86a5b0eb9..5549c93c9a1c Created with: roll-dep third_party/skia Change-Id: Ia519a4516cbf8d028dce6be0ab9eb3a0d85648ce Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156331 Reviewed-by: Lei Zhang Commit-Queue: April Kallmeyer --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 502b7e7a1f..78a9448367 100644 --- a/DEPS +++ b/DEPS @@ -244,7 +244,7 @@ vars = { # 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': '7ef86a5b0eb91db85580bd71ebed3c4ba489d5ba', + '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. From b56125da405e6fafdc6779043ddf705f53152873 Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Mon, 31 Aug 2026 14:29:35 -0700 Subject: [PATCH 110/123] Roll build, clang, and rust llvmorg-23-init-19482-g53d18800-2 : llvmorg-24-init-3796-g20e97c4b-4 Roll build/ 299066ef2..2cd94c0ab (101 commits) Roll tools/clang/ e4816757c..450d82386 (15 commits) Roll tools/rust/ 63b87afd7..54d114869 (4 commits) https://chromium.googlesource.com/chromium/src/build.git/+log/299066ef2d5d..2cd94c0abead https://chromium.googlesource.com/chromium/src/tools/clang/+log/e4816757c45a..450d823868ea https://chromium.googlesource.com/chromium/src/tools/rust/+log/63b87afd7580..54d1148695db Created with: roll-dep build roll-dep tools/clang roll-dep tools/rust Change-Id: Ic3c5b150045c47983ff49d2aab71344ed52aa269 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156390 Reviewed-by: Lei Zhang Commit-Queue: April Kallmeyer --- DEPS | 224 ++++++++++++++++++++++++++++++----------------------------- 1 file changed, 115 insertions(+), 109 deletions(-) diff --git a/DEPS b/DEPS index 78a9448367..fe1dd5ec37 100644 --- a/DEPS +++ b/DEPS @@ -98,7 +98,7 @@ 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': '299066ef2d5d1396f97e582627880cebe33f497a', + '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. @@ -114,7 +114,7 @@ vars = { # 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': 'e4816757c45a072f08279139108a0a937bbeb239', + '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. @@ -260,7 +260,7 @@ vars = { # 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': '63b87afd75805d610ff67f540318664369932f0b', + '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. @@ -496,158 +496,164 @@ deps = { '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-2.tar.xz', - 'sha256sum': '94940b8da1c3e7fec9020a75cfc4e852922a4d163dab8957e691a36ae5d49f0b', - 'size_bytes': 59100444, - 'generation': 1785366626567993, - '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-2.tar.xz', - 'sha256sum': '355a938fd3ab277dee7b2df50264416a0ebbdfd101e4b6beb82cc176f0ae300d', - 'size_bytes': 14809560, - 'generation': 1785366626548898, + '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-2.tar.xz', - 'sha256sum': '7eab6663df18976874c774eb2dee38f79287ac2dbdd336fcda2d955438fe131a', - 'size_bytes': 14988320, - 'generation': 1785366626796669, + '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-2.tar.xz', - 'sha256sum': '2cb5220dceb4be408c306537e83b323219ed0129cf3512fb4bbf49aa0c334b7f', - 'size_bytes': 2332356, - 'generation': 1785366626986503, + '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-2.tar.xz', - 'sha256sum': '61ac1326d1fd10fe3527f1ca0ce78aa357923157442056eaa53a027931db986e', - 'size_bytes': 5873792, - 'generation': 1785366626833938, + '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-2.tar.xz', - 'sha256sum': '51350c8f6da5c92ab19b0b17a3cbc7762b960b29fd68530e9feb52eb5079ae19', - 'size_bytes': 56088216, - 'generation': 1785366628543558, + '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-2.tar.xz', - 'sha256sum': '0105a7a2e8e6f4b88b6414756a5bfaa04e3cb1e678d29e3df367752a203edb6a', - 'size_bytes': 1010844, - 'generation': 1785366639043429, + '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-2.tar.xz', - 'sha256sum': '66d4a4b66e5ad9bee6b6235e9a8fee213afa4c321232e897881c8e4dcf752d61', - 'size_bytes': 14767916, - 'generation': 1785366628634961, + '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-2.tar.xz', - 'sha256sum': '9866482056c17d3f5b4c13a693c6e7e8417c54e943cf00e58a82a51b65dfd183', - 'size_bytes': 16714728, - 'generation': 1785366628706685, + '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-2.tar.xz', - 'sha256sum': 'baec5b697375914d0ec1a377f66a6ac3826aad0103de2d6258b8a88cf7dd5c02', - 'size_bytes': 2372468, - 'generation': 1785366629000854, + '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-2.tar.xz', - 'sha256sum': 'ab7600041b0db44338b42c55b7ed1224edc10c96283d71e53a373820be9e62f9', - 'size_bytes': 5790116, - 'generation': 1785366628828288, + '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-2.tar.xz', - 'sha256sum': 'a9c5ab02bfdc07a6ad6c6711d884dccab1fdf52e05e71aa9328039cddaff3664', - 'size_bytes': 47045224, - 'generation': 1785366640567652, + '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-2.tar.xz', - 'sha256sum': '51eea196d67444cc33613791193acba52803e0b57cdcbecd8135b4578674145e', - 'size_bytes': 12837040, - 'generation': 1785366640711645, + '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-2.tar.xz', - 'sha256sum': '2e9c4180eaf40a5ffd4f26bfcb2c8b7172de1ad06bb81e98cfbec466cd5eabf9', - 'size_bytes': 13199252, - 'generation': 1785366640801569, + '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-2.tar.xz', - 'sha256sum': '9a2a764f9f2dbb46eba03ed29a87bf55b580a1f7e562ba4002056cb28e2c5902', - 'size_bytes': 1999328, - 'generation': 1785366641042033, + '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-2.tar.xz', - 'sha256sum': '39fad69229bd7f8b440809f74a711175f5f0ed3cc92c4d6f3652c0ca57e39c2e', - 'size_bytes': 5546692, - 'generation': 1785366640876543, + '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-2.tar.xz', - 'sha256sum': '0d42e6825a7b5cafb8b6d250e8aae3b9b1920f4cbc2fe4f5a8dd46154d95d25d', - 'size_bytes': 51315536, - 'generation': 1785366653133084, + '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-2.tar.xz', - 'sha256sum': 'fbd7f3a0aa321c144542a3d97f702ae676306b837febfd7663a036929f8eb0d5', - 'size_bytes': 14855356, - 'generation': 1785366653263211, + '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-2.tar.xz', - 'sha256sum': 'da5135a86ba5a585abc6dfb38c38803a14e9a83cd39ceaac85eedef50f193a1d', - 'size_bytes': 2622056, - 'generation': 1785366663332782, + '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-2.tar.xz', - 'sha256sum': '1e6d5c7e036b4740b7dabaca71e2e3e97a68477515ffc9faa10ba324781faa56', - 'size_bytes': 15260276, - 'generation': 1785366653344899, + '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-2.tar.xz', - 'sha256sum': '6d21c3aebf0238eb31dc8fe41c01f079dde57a06bf628cedca7ef94f4e9cbdcb', - 'size_bytes': 2494100, - 'generation': 1785366653543218, + '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-2.tar.xz', - 'sha256sum': 'ec5ac24513c7b650b27b638bc46c207b228072fd5cd7c45a86b988820c01ff95', - 'size_bytes': 5940460, - 'generation': 1785366653406139, + '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"', }, ] @@ -689,31 +695,31 @@ deps = { 'bucket': 'chromium-browser-clang', 'objects': [ { - 'object_name': 'Linux_x64/rust-toolchain-b998449636a48e2c4a362809085b600a0174e1f2-6-llvmorg-23-init-19482-g53d18800.tar.xz', - 'sha256sum': 'fa341a74b1567fe57af21aa7bccbdf3c0ec3b704fbb01829763faa5292eb4ddd', - 'size_bytes': 274829308, - 'generation': 1785459300476581, + '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-6-llvmorg-23-init-19482-g53d18800.tar.xz', - 'sha256sum': 'd256ca6d43eb8131d78cd908dbc8b07e59acb67902497de324c60b85a6d94635', - 'size_bytes': 262354296, - 'generation': 1785459303542909, + '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-6-llvmorg-23-init-19482-g53d18800.tar.xz', - 'sha256sum': 'a23256540cd7c7c8668abdf4044f2ecc905881089a6b0ca6346a94a97887cc77', - 'size_bytes': 246808776, - 'generation': 1785459306444750, + '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-6-llvmorg-23-init-19482-g53d18800.tar.xz', - 'sha256sum': '57272886008b441fa229d2e9997b185fa5670bca7cf1f338a77a07d9bcc8eb49', - 'size_bytes': 416157244, - 'generation': 1785459309430367, + '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"', }, ], From 59b9c0ae58b70d701f75db2327f2128665290abf Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Mon, 31 Aug 2026 14:33:43 -0700 Subject: [PATCH 111/123] Roll third_party/libunwind/src/ ec7ac638d..45120ee23 (3 commits) https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libunwind.git/+log/ec7ac638dae6..45120ee23311 Created with: roll-dep third_party/libunwind/src Change-Id: Ide819c091f097d9c688e3f4a5d371b7b3e1c5e02 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156410 Commit-Queue: April Kallmeyer Reviewed-by: Lei Zhang --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index fe1dd5ec37..d9bd694b25 100644 --- a/DEPS +++ b/DEPS @@ -200,7 +200,7 @@ 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. From 74d47ee7f62cf0306b047bc82a51cca897cbfa8b Mon Sep 17 00:00:00 2001 From: Jeroen Bobbeldijk Date: Mon, 31 Aug 2026 15:21:57 -0700 Subject: [PATCH 112/123] Add tests for ToUnicode mapping precedence Nothing currently covers what CPDF_ToUnicodeMap::Lookup() returns when a charcode is mapped more than once. CPDFToUnicodeMapTest.InsertIntoMaps maps CID 0 to both 'A' and 'B', but asserts only on ReverseLookup() and on entry counts. Add a unit test over the three interesting shapes: bfchar overriding an earlier catch-all bfrange, a later bfrange overriding an earlier one, and a later mapping whose unicode is numerically below the charcode. Add an embedder test over a new testing/resources template that draws such a page. These expectations record what PDFium does today, which is to keep the numerically lowest unicode rather than the last mapping. Adobe TN #5014 requires the opposite: "Code mappings (unlike codespace ranges) may overlap, but succeeding maps superceded preceding maps." So the tests document a spec violation, and the follow-up CL corrects the behaviour and updates them. Bug: 554790604 Change-Id: Ib38da896cc51b07bd61b4bd629031318acb18133 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156270 Reviewed-by: Lei Zhang Commit-Queue: Lei Zhang Reviewed-by: Tom Sepez --- .../font/cpdf_tounicodemap_unittest.cpp | 41 +++++++++ fpdfsdk/fpdf_text_embeddertest.cpp | 25 ++++++ testing/resources/bug_554790604.in | 80 ++++++++++++++++++ testing/resources/bug_554790604.pdf | Bin 0 -> 1175 bytes 4 files changed, 146 insertions(+) create mode 100644 testing/resources/bug_554790604.in create mode 100644 testing/resources/bug_554790604.pdf 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/fpdfsdk/fpdf_text_embeddertest.cpp b/fpdfsdk/fpdf_text_embeddertest.cpp index 5ce8b0688c..7740e9e817 100644 --- a/fpdfsdk/fpdf_text_embeddertest.cpp +++ b/fpdfsdk/fpdf_text_embeddertest.cpp @@ -2482,6 +2482,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/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 0000000000000000000000000000000000000000..454960385030773d465b2cf2e93a2c8338a9a062 GIT binary patch literal 1175 zcmZux!EW0y487|scnPo_wn%mqCx8*4aT2W9x)rH+YI;y?Idzd*hO7)-zh;Mh;jj-F zDZ5GQ%z$j_iF|ybC=ad|s}JI1#0J0q{Qk=Xa4_2kh+_sYOn=rIhKX`&Z*~M+tDSBj zK&LgE&6uuobY?W~2YF8lxzc&5=H>}*Il6_Mg9!K?tt949IS^!iF7pO%L*l)AMNGi| zh+8REV|c3tDb~6%2b-ZhuN{$_a=8WWv5u^osw-H?62ylcO5y(vt;y2`*5b==kF~=d^L;Ap$DVS|#a=EyyN{2^oO?IOl6lIm$uEwy{yRu>;fV|9HH}hEbsA@J$u2lV1J!0>yb!`pg zx@bY)P{Pke8%CvRYL#iLs-0#r=QD^`E8knawJu^?^jrahFpT#3vNmbSw?%fZEQ?11 zNjYDIGqeM5&o9Uh(GK`Ug0_?%UoOy|&QCsa;%)q}IB9v14(*l)>Dt6TQ(t04yNTMU z<@Ak2RO^d%tW|cf)X~18zJgSyveL(**QTaaFXQc*K3S|Whlo)v+6Tr`h7t-mVgh@P z9;i;(cyfjb8J%Il1TSMRHjK_=C9)s=oK@vs+wO0&HU_V|zXf!I%4dyoeYY#?y#DEU L!Uluow-x&bBG@|w literal 0 HcmV?d00001 From dea29fef30de9016e4983009fd6fdf4b08c49f55 Mon Sep 17 00:00:00 2001 From: Aryan Date: Tue, 1 Sep 2026 00:24:46 -0700 Subject: [PATCH 113/123] Clean up flatemodule.cpp This CL cleans up flatemodule.cpp by: - Renaming kFlate to kTiff, as this predictor is used for TIFF images - Renaming Foo_Predictor() and Foo_PredictLine() to follow UpperCamelCase - Renaming PathPredictor() to the correct PaethPredictor() This prepares the image predictors for use in BrotliDecode in future. Bug: 550512275 Change-Id: I2f9d55d9249427570069ce01052036441cc7314a Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/155990 Reviewed-by: Andy Phan Commit-Queue: Aryan Krishnan Reviewed-by: Lei Zhang --- core/fxcodec/flate/flatemodule.cpp | 69 +++++++++++++++--------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/core/fxcodec/flate/flatemodule.cpp b/core/fxcodec/flate/flatemodule.cpp index 8ceb2dd72c..1a0fc3a583 100644 --- a/core/fxcodec/flate/flatemodule.cpp +++ b/core/fxcodec/flate/flatemodule.cpp @@ -325,7 +325,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); @@ -336,11 +336,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); @@ -373,7 +373,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; } @@ -384,7 +384,7 @@ void PNG_PredictLine(pdfium::span dest_span, } } -std::optional> PNG_Predictor( +std::optional> PngPredictor( int Colors, int BitsPerComponent, int Columns, @@ -418,8 +418,8 @@ std::optional> PNG_Predictor( 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); @@ -427,10 +427,10 @@ std::optional> PNG_Predictor( return dest_buf; } -void TIFF_PredictLine(pdfium::span dest_span, - int BitsPerComponent, - int Colors, - int Columns) { +void TiffPredictLine(pdfium::span dest_span, + int BitsPerComponent, + int Colors, + int Columns) { if (BitsPerComponent == 1) { int row_bits = std::min(BitsPerComponent * Colors * Columns, pdfium::checked_cast(dest_span.size() * 8)); @@ -466,10 +466,10 @@ void TIFF_PredictLine(pdfium::span dest_span, } } -bool TIFF_Predictor(int Colors, - int BitsPerComponent, - int Columns, - pdfium::span data_span) { +bool TiffPredictor(int Colors, + int BitsPerComponent, + int Columns, + pdfium::span data_span) { const uint32_t row_size = fxge::CalculatePitch8(BitsPerComponent, Colors, Columns).value_or(0); if (row_size == 0) { @@ -479,7 +479,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, BitsPerComponent, Colors, Columns); data_span = data_span.subspan(row_span.size()); } return true; @@ -547,13 +547,14 @@ DataAndBytesConsumed FlateUncompress(pdfium::span src_buf, return {std::move(result_buf), bytes_consumed}; } -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; } @@ -708,15 +709,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: { @@ -742,18 +743,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; @@ -829,14 +830,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}; } } From 52b8727dc1f7bec410be26fd1e69d8fb45d50ca0 Mon Sep 17 00:00:00 2001 From: Aryan Date: Tue, 1 Sep 2026 01:02:54 -0700 Subject: [PATCH 114/123] Rename variables in flatemodule.cpp to follow snake_case This CL cleans up variable names in functions of type FooPredictor() and FooPredictLine() in flatemodule.cpp to follow the style guide better by using snake_case. Bug: 550512275 Change-Id: I9475fbfc2c58c7efde871dacc836f2881de34aae Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156130 Reviewed-by: Tom Sepez Reviewed-by: Lei Zhang Commit-Queue: Aryan Krishnan Reviewed-by: Andy Phan --- core/fxcodec/flate/flatemodule.cpp | 44 +++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/core/fxcodec/flate/flatemodule.cpp b/core/fxcodec/flate/flatemodule.cpp index 1a0fc3a583..d27a1fb927 100644 --- a/core/fxcodec/flate/flatemodule.cpp +++ b/core/fxcodec/flate/flatemodule.cpp @@ -385,12 +385,12 @@ void PngPredictLine(pdfium::span dest_span, } std::optional> PngPredictor( - int Colors, - int BitsPerComponent, - int Columns, + 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; } @@ -414,7 +414,7 @@ std::optional> PngPredictor( 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); @@ -428,11 +428,11 @@ std::optional> PngPredictor( } void TiffPredictLine(pdfium::span dest_span, - int BitsPerComponent, - int Colors, - int Columns) { - if (BitsPerComponent == 1) { - int row_bits = std::min(BitsPerComponent * Colors * Columns, + 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; @@ -450,28 +450,28 @@ void TiffPredictLine(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 TiffPredictor(int Colors, - int BitsPerComponent, - int Columns, +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; } @@ -479,7 +479,7 @@ bool TiffPredictor(int Colors, while (!data_span.empty()) { auto row_span = data_span.first(std::min(row_size, data_span.size())); - TiffPredictLine(row_span, BitsPerComponent, Colors, Columns); + TiffPredictLine(row_span, bits_per_component, colors, columns); data_span = data_span.subspan(row_span.size()); } return true; From 1d751dab3651b55ff274166cd945523ece387a31 Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Tue, 1 Sep 2026 10:35:03 -0700 Subject: [PATCH 115/123] Roll buildtools, absl, libc++, libc++abi, and llvm-libc Roll buildtools/ 9e7655f4e..6f6a5dbf0 (3 commits) Roll third_party/abseil-cpp/ df548c50b..435e7d977 (5 commits) Roll third_party/libc++/src/ b16984ce9..97b436da4 (215 commits) Roll third_party/libc++abi/src/ 14cefa839..fc1897a2c (3 commits) Roll third_party/llvm-libc/src/ 6622df0b6..320824188 (129 commits) https://chromium.googlesource.com/chromium/src/buildtools.git/+log/9e7655f4ee43..6f6a5dbf04b7 https://chromium.googlesource.com/chromium/src/third_party/abseil-cpp.git/+log/df548c50b2cd..435e7d977fb3 https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxx.git/+log/b16984ce99c7..97b436da4c33 https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libcxxabi.git/+log/14cefa83943c..fc1897a2c12a https://chromium.googlesource.com/external/github.com/llvm/llvm-project/libc.git/+log/6622df0b6261..320824188c37 Created with: roll-dep buildtools roll-dep third_party/abseil-cpp roll-dep third_party/libc++/src roll-dep third_party/libc++abi/src roll-dep third_party/llvm-libc/src Change-Id: I58c9b2290716aed8f1a006e7c2290fb387025bbd Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156430 Reviewed-by: Lei Zhang Commit-Queue: April Kallmeyer --- DEPS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/DEPS b/DEPS index d9bd694b25..c9c6799273 100644 --- a/DEPS +++ b/DEPS @@ -86,7 +86,7 @@ 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': 'df548c50b2cda67158364d3d23c63043881b391d', + '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. @@ -102,7 +102,7 @@ vars = { # 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': '9e7655f4ee433ef4c6efcffd57e379db8f8c0432', + '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. @@ -188,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': 'b16984ce99c702355a5b2b4c52574e82cec41fb9', + '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': '14cefa83943c62cf7c42f20bdf8c939c855a8b34', + '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. @@ -204,7 +204,7 @@ vars = { # 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': '6622df0b6261ceaccdbfe199040eb9d9e7166fe7', + '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. From 083f2178cdd24662efb77c650dbc3415a6a92cf6 Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Tue, 1 Sep 2026 12:05:48 -0700 Subject: [PATCH 116/123] Roll v8/ caa723ab9..df65d5c8f (603 commits) https://chromium.googlesource.com/v8/v8.git/+log/caa723ab9c62..df65d5c8f293 Version 15.4.80 Created with: roll-dep v8 Change-Id: Iec24c58226cbe0e654d3e6b1533db96e6a43aa39 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156530 Auto-Submit: April Kallmeyer Reviewed-by: Lei Zhang Commit-Queue: Lei Zhang --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index c9c6799273..5a596417f7 100644 --- a/DEPS +++ b/DEPS @@ -268,7 +268,7 @@ vars = { # 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': 'caa723ab9c621fcec3534de035b08d35e2947760', + '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. From 0ba187014c3af2bfb16c86a60e6a0e76ee42a288 Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Tue, 1 Sep 2026 12:06:52 -0700 Subject: [PATCH 117/123] Roll testing/scripts/rust/ 6712dc59f..ead1c91bc (1 commit) https://chromium.googlesource.com/chromium/src/testing/scripts/rust.git/+log/6712dc59f4a6..ead1c91bc31e Created with: roll-dep testing/scripts/rust Change-Id: Ifacc73b555833cb40000eb811b46499431f422c6 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156510 Reviewed-by: Lei Zhang Commit-Queue: Lei Zhang Auto-Submit: April Kallmeyer --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 5a596417f7..189e6cb691 100644 --- a/DEPS +++ b/DEPS @@ -252,7 +252,7 @@ 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. From 73b7ef7fcd1d1117ac9206a7db3d5d785464aebd Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Tue, 1 Sep 2026 12:10:26 -0700 Subject: [PATCH 118/123] Roll third_party/rust/ 7c4e7d9b0..4dec6f65b (81 commits) https://chromium.googlesource.com/chromium/src/third_party/rust/+log/7c4e7d9b0e7c..4dec6f65bcb3 Created with: roll-dep third_party/rust Change-Id: I761f61cfe20eefb890d902aca2704c321256efc4 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156490 Auto-Submit: April Kallmeyer Commit-Queue: Lei Zhang Reviewed-by: Lei Zhang Commit-Queue: April Kallmeyer --- DEPS | 2 +- core/fxge/BUILD.gn | 4 ++-- skia/BUILD.gn | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DEPS b/DEPS index 189e6cb691..5932a8de3b 100644 --- a/DEPS +++ b/DEPS @@ -232,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': '7c4e7d9b0e7c65639a375c3f0d338280ca0f28a4', + '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. diff --git a/core/fxge/BUILD.gn b/core/fxge/BUILD.gn index c21a5f0807..00c8cfe942 100644 --- a/core/fxge/BUILD.gn +++ b/core/fxge/BUILD.gn @@ -232,8 +232,8 @@ if (pdf_enable_fontations) { features = [ "agl" ] deps = [ "//build/rust:cxx_cppdeps", - "//third_party/rust/read_fonts/v0_41:lib", - "//third_party/rust/skrifa/v0_44:lib", + "//third_party/rust/read_fonts/v0_43:lib", + "//third_party/rust/skrifa/v0_46:lib", ] configs += [ ":skrifa_cxx_config" ] } diff --git a/skia/BUILD.gn b/skia/BUILD.gn index b50401e1a7..258f663d69 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_41:lib", - "//third_party/rust/skrifa/v0_44:lib", + "//third_party/rust/read_fonts/v0_43:lib", + "//third_party/rust/skrifa/v0_46:lib", ] configs -= [ "//build/config/compiler:chromium_code" ] configs += [ From 8e247564a4e4b238ef2c6693985bcb62f47feb7b Mon Sep 17 00:00:00 2001 From: Lei Zhang Date: Tue, 1 Sep 2026 12:32:58 -0700 Subject: [PATCH 119/123] Remove inactive owner Remove bungeman@, since they are not active on the project. Change-Id: I9f134be5429bc95979d057666c1b09ed71251a0d Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156550 Reviewed-by: Tom Sepez Auto-Submit: Lei Zhang Commit-Queue: Tom Sepez --- skia/OWNERS | 1 - third_party/freetype/OWNERS | 1 - 2 files changed, 2 deletions(-) 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/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 From 297182c08d90dc058f2e90c9de4c43d10964382b Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Tue, 1 Sep 2026 12:37:00 -0700 Subject: [PATCH 120/123] Roll third_party/clang-format/script/ 6eddfb5ec..705100819 (1 commit) https://chromium.googlesource.com/external/github.com/llvm/llvm-project/clang/tools/clang-format.git/+log/6eddfb5ec5f9..70510081984c Created with: roll-dep third_party/clang-format/script Change-Id: I8848890d01c7c98e5dc82c00faf53b7b3939bf92 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156511 Commit-Queue: Lei Zhang Reviewed-by: Lei Zhang Auto-Submit: April Kallmeyer --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 5932a8de3b..f1da0ca107 100644 --- a/DEPS +++ b/DEPS @@ -110,7 +110,7 @@ vars = { # 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. From cfe3a24139a013c4455c59f86092c7ffde92e776 Mon Sep 17 00:00:00 2001 From: April Kallmeyer Date: Tue, 1 Sep 2026 12:56:21 -0700 Subject: [PATCH 121/123] Update gn_version to 150a9d6ba0aa7f407aa4feeabc5f03ce9aa7e04b Change-Id: I9e8740e6aeeae5831a508983bf31733d55a7effa Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/156570 Reviewed-by: Lei Zhang Commit-Queue: April Kallmeyer --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index f1da0ca107..811613d299 100644 --- a/DEPS +++ b/DEPS @@ -150,7 +150,7 @@ vars = { # 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:1c79e13d17a1760d6c6401c90061411842bd4881', + '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. From 39eac5b29da051bcf653d8e735fadbd1b252ba8d Mon Sep 17 00:00:00 2001 From: Bob Singor Date: Tue, 13 Oct 2026 02:31:30 +0300 Subject: [PATCH 122/123] Fix Windows SDK pin for CI builds The rolled Windows toolchain pins SDK 10.0.28000.0, which is not available on GitHub-hosted Windows runners. This change lets the build read EMBEDPDF_WIN_SDK_VERSION in vs_toolchain.py and toolchain/win/setup_toolchain.py, falling back to the upstream SDK version when unset, and updates the release workflow to export the newest installed SDK from the runner before building. --- .github/workflows/release-libpdfium.yml | 3 +++ docs/embedpdf/syncs/2026-10-13-cfe3a2413.md | 8 +++++++ patches/embedpdf-runtime/win/build.patch | 26 +++++++++++++++++++++ 3 files changed, 37 insertions(+) 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/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md b/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md index b21b781799..2cfde196ec 100644 --- a/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md +++ b/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md @@ -83,6 +83,11 @@ darwin-arm64 release build: ok, 255 shared writable (S6: 254), 24 thread-local ( - `DEPS` changed in 45 commits; synced with `gclient sync --no-history --shallow`. - `skia/BUILD.gn`: `SkLogHandler.cpp` added to `redaction_pathops`. +- Windows SDK: the rolled `build/` pins SDK 10.0.28000.0 (`06f91b7a8` in `build/`), which the + GitHub Windows runners don't have, so both Windows builds failed in this PR's CI (run + 38320172945). `patches/embedpdf-runtime/win/build.patch` now reads the SDK version from + `EMBEDPDF_WIN_SDK_VERSION` in `vs_toolchain.py` and `toolchain/win/setup_toolchain.py`, + falling back to upstream's, and the workflow sets it to the newest SDK the runner has. - All of `patches/embedpdf-runtime/` apply: every target's patch dry-run against clean copies of the rolled files (offsets or fuzz only); darwin-arm64 and wasm32 applied and built. @@ -111,6 +116,9 @@ The darwin-arm64 exports gain exactly these two. - [x] tag check (`--base cfe3a2413`): 966 blocks, 0 untagged. - [x] C API check: ok, 521 functions in 22 headers. - [x] globals check: ok (see Globals). +- [x] CI (run 38320172945, this branch at `1a16a6e98`): source tests pass, and so do the + darwin, linux, linuxmusl-x64 and wasm32 builds (linuxmusl-arm64 was still running). Both + Windows builds failed on the Windows SDK pin (above), fixed in a follow-up commit. - [ ] wasm build: in the PR's CI (builds locally). - [ ] ThreadSanitizer soak: in the PR's CI. - [x] engine tests and engine-conformance with the local runtime (Oct 13): as before, against diff --git a/patches/embedpdf-runtime/win/build.patch b/patches/embedpdf-runtime/win/build.patch index aecd8d2423..fdb3849a92 100644 --- a/patches/embedpdf-runtime/win/build.patch +++ b/patches/embedpdf-runtime/win/build.patch @@ -11,3 +11,29 @@ 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/[^/]+)/.*') + From f6915df45f2bb6c11131aa14ff8485d55fb87f72 Mon Sep 17 00:00:00 2001 From: Bob Singor Date: Tue, 13 Oct 2026 09:19:39 +0300 Subject: [PATCH 123/123] Fix Windows SDK compatibility in runtime build This patch makes the Windows runtime build respect the installed SDK version when present, with a fallback to upstream defaults, and updates CI to use the newest runner SDK. The build was still failing because `NTDDI_WIN11_BR` requires SDK 10.0.28000+, while the runner provides 10.0.26100.0; the patch switches to `NTDDI_WIN11_GE` to match the older SDKs and the S6 build target without requiring newer Windows APIs. The sync note captures the CI investigation and the confirmed patch behavior. --- docs/embedpdf/syncs/2026-10-13-cfe3a2413.md | 5 ++++- patches/embedpdf-runtime/win/build.patch | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md b/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md index 2cfde196ec..e131315084 100644 --- a/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md +++ b/docs/embedpdf/syncs/2026-10-13-cfe3a2413.md @@ -87,7 +87,10 @@ darwin-arm64 release build: ok, 255 shared writable (S6: 254), 24 thread-local ( 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. + 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. diff --git a/patches/embedpdf-runtime/win/build.patch b/patches/embedpdf-runtime/win/build.patch index fdb3849a92..69926b32de 100644 --- a/patches/embedpdf-runtime/win/build.patch +++ b/patches/embedpdf-runtime/win/build.patch @@ -37,3 +37,17 @@ diff --git a/toolchain/win/setup_toolchain.py b/toolchain/win/setup_toolchain.py 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,