[rust png] Support using Rust-based PNG codec in XFA implementation.

After this CL, if `pdf_use_skia && pdf_enable_rust_png` then the
Rust-based PNG codec will be used in XFA implementation.

Bug: 444045690
Change-Id: Ia6ec12d0335b1fa74c278acc94973e97aaa30b60
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/135730
Commit-Queue: Ɓukasz Anforowicz <lukasza@google.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/build_overrides/pdfium.gni b/build_overrides/pdfium.gni
index 3870d39..f0cf35a 100644
--- a/build_overrides/pdfium.gni
+++ b/build_overrides/pdfium.gni
@@ -42,6 +42,4 @@
 # 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.
-#
-# TODO(https://crbug.com/444045690): Cover decoding path in production code.
 pdf_enable_rust_png_override = false
diff --git a/core/fxcodec/BUILD.gn b/core/fxcodec/BUILD.gn
index c5ecba0..f6930a1 100644
--- a/core/fxcodec/BUILD.gn
+++ b/core/fxcodec/BUILD.gn
@@ -81,6 +81,7 @@
     "../fxge",
     "//third_party:jpeg",
   ]
+  defines = []
   if (pdf_enable_xfa) {
     sources += [
       "cfx_codec_memory.cpp",
@@ -115,12 +116,25 @@
       ]
     }
     if (pdf_enable_xfa_png) {
-      sources += [
-        "png/libpng_png_decoder.cpp",
-        "png/libpng_png_decoder.h",
-        "png/png_decoder_delegate.h",
-      ]
-      deps += [ "../../third_party:png" ]
+      sources += [ "png/png_decoder_delegate.h" ]
+
+      # TODO(https://crbug.com/444045690): Remove `pdf_enable_rust_png` from the
+      # condition below once this build mode has been tested and stabilized.
+      # (Chromium already sets `pdf_use_skia_override = true` so having an extra
+      # condition avoids affecting the Chromium behavior.)
+      if (pdf_use_skia && pdf_enable_rust_png) {
+        sources += [
+          "png/skia_png_decoder.cpp",
+          "png/skia_png_decoder.h",
+        ]
+        deps += [ "//skia" ]
+      } else {
+        sources += [
+          "png/libpng_png_decoder.cpp",
+          "png/libpng_png_decoder.h",
+        ]
+        deps += [ "../../third_party:png" ]
+      }
     }
     if (pdf_enable_xfa_tiff) {
       sources += [
diff --git a/core/fxcodec/png/DEPS b/core/fxcodec/png/DEPS
index 9caa4d8..a3d387b 100644
--- a/core/fxcodec/png/DEPS
+++ b/core/fxcodec/png/DEPS
@@ -1,3 +1,8 @@
-include_rules = [
-  '+third_party/libpng/png.h',
-]
+specific_include_rules = {
+  "libpng_png_decoder\.cpp": [
+    '+third_party/libpng/png.h',
+  ],
+  "skia_png_decoder\.cpp": [
+    '+third_party/skia/include',
+  ],
+}
diff --git a/core/fxcodec/png/libpng_png_decoder.cpp b/core/fxcodec/png/libpng_png_decoder.cpp
index dc14179..383456f 100644
--- a/core/fxcodec/png/libpng_png_decoder.cpp
+++ b/core/fxcodec/png/libpng_png_decoder.cpp
@@ -24,6 +24,10 @@
 #include "third_party/libpng/png.h"
 #endif
 
+#ifdef PDF_ENABLE_RUST_PNG
+#error "If Rust PNG is enabled, then `libpng` should not be used."
+#endif
+
 #define PNG_ERROR_SIZE 256
 
 using PngDecoderDelegate = fxcodec::PngDecoderDelegate;
diff --git a/core/fxcodec/png/png_decoder_delegate.h b/core/fxcodec/png/png_decoder_delegate.h
index d41395c..aa01a9c 100644
--- a/core/fxcodec/png/png_decoder_delegate.h
+++ b/core/fxcodec/png/png_decoder_delegate.h
@@ -11,12 +11,7 @@
 
 namespace fxcodec {
 
-// Abstract interface used by the `libpng`-based decoder from `png_decoder.h`
-// (in the future by Skia-based decoder from `skia_png_decoder.h`).
-//
-// TODO(https://crbug.com/444045690): Update the comment above once the
-// Skia-based decoder is implemented and/or the `libpng`-based decoder moved
-// to renamed .h/.cc files.
+// Abstract interface used by `libpng_png_decoder.h` and `skia_png_decoder.h`.
 class PngDecoderDelegate {
  public:
   // Called by `PngDecoder` after decoding the image header with
@@ -32,13 +27,18 @@
                              double* gamma) = 0;
 
   // Called by `PngDecoder` to ask where to write decoded BGRA/8 pixels.
-  // Implementation should return a span to the buffer where the
-  // decoded pixels should be written to.
+  // 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<uint8_t> 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<uint8_t> PngAskImageBuf() = 0;
+
   // Called by `PngDecoder` to communicate that all pixels have been decoded.
   virtual void PngFinishedDecoding() = 0;
 };
diff --git a/core/fxcodec/png/skia_png_decoder.cpp b/core/fxcodec/png/skia_png_decoder.cpp
new file mode 100644
index 0000000..6a13fd6
--- /dev/null
+++ b/core/fxcodec/png/skia_png_decoder.cpp
@@ -0,0 +1,254 @@
+// 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 "core/fxcodec/png/skia_png_decoder.h"
+
+#include <utility>
+
+#include "core/fxcodec/cfx_codec_memory.h"
+#include "core/fxcodec/png/png_decoder_delegate.h"
+#include "core/fxcodec/progressive_decoder_context.h"
+#include "core/fxcrt/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 "core/fxcrt/span.h"
+#include "core/fxcrt/unowned_ptr.h"
+#include "third_party/skia/include/codec/SkCodec.h"
+#include "third_party/skia/include/core/SkStream.h"
+
+#ifdef PDF_ENABLE_RUST_PNG
+#include "third_party/skia/include/codec/SkPngRustDecoder.h"
+#else
+#include "third_party/skia/include/codec/SkPngDecoder.h"
+#endif
+
+namespace fxcodec {
+
+namespace {
+
+sk_sp<SkColorSpace> GetTargetColorSpace(double gamma) {
+  skcms_ICCProfile profile;
+  skcms_Init(&profile);
+
+  skcms_TransferFunction fn;
+  fn.a = 1.0f;
+  fn.b = fn.c = fn.d = fn.e = fn.f = 0.0f;
+  fn.g = 1.0f / gamma;
+  skcms_SetTransferFunction(&profile, &fn);
+
+  skcms_Matrix3x3 to_xyzd50 = skcms_sRGB_profile()->toXYZD50;
+  skcms_SetXYZD50(&profile, &to_xyzd50);
+
+  return SkColorSpace::Make(profile);
+}
+
+// Implements/exposes `SkStream` API on top of `CFX_CodecMemory`.
+//
+// Note that the exposed API does *not* support seeking/rewinding, because this
+// layer only has access to the `CFX_CodecMemory` pushed into `ContinueDecode`
+// and can't reach for `IFX_SeekableReadStream` from which the bytes have been
+// pulled into `CFX_CodecMemory`.  This lack of support for seeking/rewinding
+// requires avoiding calling any Skia APIs that may necessitate this
+// functionality later (e.g. calling `SkCodec::getFrameCount` may attempt to
+// read metadata of subsequent frames).  This is not an issue today, because
+// PDFium only needs to decode the first (or only) image / animation frame.
+class CodecMemoryStream final : public SkStream {
+ public:
+  explicit CodecMemoryStream(RetainPtr<CFX_CodecMemory> codec_memory)
+      : codec_memory_(std::move(codec_memory)) {}
+
+  size_t read(void* buffer, size_t size) override {
+    // SAFETY: Relying on the caller to pass correct `buffer` and `size`.
+    uint8_t* bytes = static_cast<uint8_t*>(buffer);
+    auto byte_span = UNSAFE_BUFFERS(pdfium::span(bytes, size));
+
+    return codec_memory_->ReadBlock(byte_span);
+  }
+
+  bool isAtEnd() const override { return codec_memory_->IsEOF(); }
+
+ private:
+  RetainPtr<CFX_CodecMemory> const codec_memory_;
+};
+
+class SkiaPngContext final : public ProgressiveDecoderContext {
+ public:
+  // Caller needs to guarantee that `delegate` lives longer than
+  // `SkiaPngContext`.
+  explicit SkiaPngContext(PngDecoderDelegate* delegate) : delegate_(delegate) {}
+  ~SkiaPngContext() override = default;
+
+  // 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<CFX_CodecMemory> codec_memory);
+
+ private:
+  enum class State {
+    // `decoder_` is null.
+    //
+    // This is the initial state.
+    kNoDecoder,
+
+    // `decoder_` is not null and `delegate_->PngReadHeader` succeeded
+    // and provided `target_gamma_`.
+    //
+    // `startIncrementalDecode` didn't run, or returned `kIncompleteInput`.
+    kGotDecoder,
+
+    // `decoder_` is not null, got `target_gamma_`, and `startIncrementalDecode`
+    // has already suceeded.
+    //
+    // `incrementalDecode` didn't run yet, or returned `kIncompleteInput`.
+    kStartedDecode,
+
+    // `decoder_` is null.  All pixels have been decoded to
+    // `delegate_->PngAskImageBuf`.
+    //
+    // This is a terminal state.
+    kFinishedDecoding,
+
+    // `decoder_` is null.  A non-recoverable (i.e. non-`kIncompleteInput`-kind)
+    // error has been encountered.
+    //
+    // This is a terminal state.
+    kError,
+  };
+  State state_ = State::kNoDecoder;
+
+  UnownedPtr<PngDecoderDelegate> const delegate_;
+  std::unique_ptr<SkCodec> decoder_;
+  double target_gamma_ = 0.0;
+
+  // `CFX_CodecMemory` received in `ContinueDecode` may get wrapped in
+  // `CodecMemoryStream` 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
+  // `CodecMemoryStream` won't accidentally use stale input data if a future,
+  // hypothetical refactoring of `ProgressiveDecoder` changes how it manages
+  // `CFX_CodecMemory`.
+  RetainPtr<CFX_CodecMemory> codec_memory_;
+};
+
+bool SkiaPngContext::ContinueDecode(RetainPtr<CFX_CodecMemory> codec_memory) {
+  // `ProgressiveDecoder` guarantees that all calls to `ContinueDecode` use the
+  // same `codec_memory`.  Therefore `SkiaPngContext` expects that
+  // `codec_memory` passed to `CodecMemoryStream` 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<CodecMemoryStream>(std::move(codec_memory));
+      SkCodec::Result result = SkCodec::kSuccess;
+#ifdef PDF_ENABLE_RUST_PNG
+      decoder_ = SkPngRustDecoder::Decode(std::move(stream), &result);
+#else
+      decoder_ = SkPngDecoder::Decode(std::move(stream), &result);
+#endif
+      switch (result) {
+        case SkCodec::kSuccess: {
+          SkImageInfo info = decoder_->getInfo();
+          if (!delegate_->PngReadHeader(info.width(), info.height(),
+                                        &target_gamma_)) {
+            decoder_.reset();
+            state_ = State::kError;
+            return false;
+          }
+          state_ = State::kGotDecoder;
+          break;  // continue decoding
+        }
+        case SkCodec::kIncompleteInput:
+          // Rewind to start from the beginning of input when retrying later.
+          // This will also prompt `ProgressiveDecoder::ReadMoreData` to grow
+          // the `codec_memory_` as needed.
+          codec_memory_->Seek(0);
+          return true;  // retry when called later with more data
+        default:
+          decoder_.reset();
+          state_ = State::kError;
+          return false;  // fatal error
+      }
+      break;
+    }
+    case State::kGotDecoder:
+    case State::kStartedDecode:
+      break;
+    case State::kFinishedDecoding:
+    case State::kError:
+      NOTREACHED();
+  }
+
+  if (state_ == State::kGotDecoder) {
+    SkImageInfo dst_info =
+        decoder_->getInfo()
+            .makeColorSpace(GetTargetColorSpace(target_gamma_))
+            .makeColorType(kBGRA_8888_SkColorType);
+
+    pdfium::span<uint8_t> dst_buffer = delegate_->PngAskImageBuf();
+    FX_SAFE_SIZE_T row_bytes = dst_buffer.size();
+    row_bytes /= dst_info.height();
+
+    SkCodec::Result result = decoder_->startIncrementalDecode(
+        dst_info, dst_buffer.data(), row_bytes.ValueOrDie());
+    switch (result) {
+      case SkCodec::kSuccess:
+        state_ = State::kStartedDecode;
+        break;  // continue decoding
+      case SkCodec::kIncompleteInput:
+        return true;  // retry when called later with more data
+      default:
+        decoder_.reset();
+        state_ = State::kError;
+        return false;  // fatal error
+    }
+  }
+
+  CHECK_EQ(state_, State::kStartedDecode);
+  SkCodec::Result result = decoder_->incrementalDecode(nullptr);
+  switch (result) {
+    case SkCodec::kSuccess:
+      decoder_.reset();
+      delegate_->PngFinishedDecoding();
+      state_ = State::kFinishedDecoding;
+      return true;  // finished decoding
+    case SkCodec::kIncompleteInput:
+      return true;  // retry when called later with more data
+    default:
+      decoder_.reset();
+      state_ = State::kError;
+      return false;  // fatal error
+  }
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<ProgressiveDecoderContext> SkiaPngDecoder::StartDecode(
+    PngDecoderDelegate* delegate) {
+  return std::make_unique<SkiaPngContext>(delegate);
+}
+
+// static
+bool SkiaPngDecoder::ContinueDecode(ProgressiveDecoderContext* context,
+                                    RetainPtr<CFX_CodecMemory> codec_memory) {
+  auto* ctx = static_cast<SkiaPngContext*>(context);
+  return ctx->ContinueDecode(std::move(codec_memory));
+}
+
+}  // namespace fxcodec
diff --git a/core/fxcodec/png/skia_png_decoder.h b/core/fxcodec/png/skia_png_decoder.h
new file mode 100644
index 0000000..eccac70
--- /dev/null
+++ b/core/fxcodec/png/skia_png_decoder.h
@@ -0,0 +1,46 @@
+// 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_SKIA_PNG_DECODER_H_
+#define CORE_FXCODEC_PNG_SKIA_PNG_DECODER_H_
+
+#include <memory>
+
+#include "core/fxcodec/cfx_codec_memory.h"
+#include "core/fxcrt/retain_ptr.h"
+
+#ifndef PDF_ENABLE_XFA_PNG
+#error "PNG must be enabled"
+#endif
+
+#ifndef PDF_USE_SKIA
+#error "Skia must be enabled"
+#endif
+
+namespace fxcodec {
+
+class PngDecoderDelegate;
+class ProgressiveDecoderContext;
+
+// PNG decoder that uses the Skia library to decode pixels.  (Whether
+// `SkPngDecoder` vs `SkPngRustDecoder` is used depends on whether the build
+// configuration sets certain macro definitions like `PDF_ENABLE_RUST_PNG`).
+class SkiaPngDecoder {
+ public:
+  static std::unique_ptr<ProgressiveDecoderContext> StartDecode(
+      PngDecoderDelegate* delegate);
+
+  static bool ContinueDecode(ProgressiveDecoderContext* context,
+                             RetainPtr<CFX_CodecMemory> codec_memory);
+
+  SkiaPngDecoder() = delete;
+  SkiaPngDecoder(const SkiaPngDecoder&) = delete;
+  SkiaPngDecoder& operator=(const SkiaPngDecoder&) = delete;
+};
+
+}  // namespace fxcodec
+
+using PngDecoder = fxcodec::SkiaPngDecoder;
+
+#endif  // CORE_FXCODEC_PNG_SKIA_PNG_DECODER_H_
diff --git a/core/fxcodec/progressive_decoder.cpp b/core/fxcodec/progressive_decoder.cpp
index cb00ef3..68159f6 100644
--- a/core/fxcodec/progressive_decoder.cpp
+++ b/core/fxcodec/progressive_decoder.cpp
@@ -39,8 +39,16 @@
 #endif  // PDF_ENABLE_XFA_GIF
 
 #ifdef PDF_ENABLE_XFA_PNG
-#include "core/fxcodec/png/libpng_png_decoder.h"
 #include "core/fxcodec/png/png_decoder_delegate.h"
+// TODO(https://crbug.com/444045690): Remove `pdf_enable_rust_png` from the
+// condition below once this build mode has been tested and stabilized.
+// (Chromium already sets `pdf_use_skia_override = true` so having an extra
+// condition avoids affecting the Chromium behavior.)
+#if defined(PDF_USE_SKIA) && defined(PDF_ENABLE_RUST_PNG)
+#include "core/fxcodec/png/skia_png_decoder.h"
+#else
+#include "core/fxcodec/png/libpng_png_decoder.h"
+#endif
 #endif  // PDF_ENABLE_XFA_PNG
 
 #ifdef PDF_ENABLE_XFA_TIFF
@@ -114,6 +122,12 @@
   return device_bitmap_->GetWritableScanline(line);
 }
 
+pdfium::span<uint8_t> ProgressiveDecoder::PngAskImageBuf() {
+  CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra);
+  CHECK_EQ(src_format_, FXCodec_Argb);
+  return device_bitmap_->GetWritableBuffer();
+}
+
 void ProgressiveDecoder::PngFinishedDecoding() {
   status_ = FXCODEC_STATUS::kDecodeFinished;
 }
diff --git a/core/fxcodec/progressive_decoder.h b/core/fxcodec/progressive_decoder.h
index 260bf18..1c3d783 100644
--- a/core/fxcodec/progressive_decoder.h
+++ b/core/fxcodec/progressive_decoder.h
@@ -90,6 +90,7 @@
                      int height,
                      double* gamma) override;
   pdfium::span<uint8_t> PngAskScanlineBuf(int line) override;
+  pdfium::span<uint8_t> PngAskImageBuf() override;
   void PngFinishedDecoding() override;
 #endif  // PDF_ENABLE_XFA_PNG
 
diff --git a/pdfium.gni b/pdfium.gni
index e2d3ef4..73ea9f4 100644
--- a/pdfium.gni
+++ b/pdfium.gni
@@ -65,8 +65,6 @@
   # 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.
-  #
-  # TODO(https://crbug.com/444045690): Cover decoding path in production code.
   pdf_enable_rust_png = pdf_enable_rust_png_override
 
   # Build PDFium standalone. Now only controls whether the test binaries