// Copyright 2014 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/progressive_decoder.h"

#include <algorithm>
#include <memory>
#include <utility>

#include "build/build_config.h"
#include "core/fxcodec/gif/cfx_gifcontext.h"
#include "core/fxcodec/progressive_decoder_context.h"
#include "core/fxcodec/tiff/libtiff_tiff_context.h"
#include "core/fxcrt/check.h"
#include "core/fxcrt/check_op.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/fx_2d_size.h"
#include "core/fxcrt/fx_memcpy_wrappers.h"
#include "core/fxcrt/fx_safe_types.h"
#include "core/fxcrt/fx_stream.h"
#include "core/fxcrt/fx_system.h"
#include "core/fxcrt/notreached.h"
#include "core/fxcrt/numerics/safe_conversions.h"
#include "core/fxcrt/span_util.h"
#include "core/fxcrt/stl_util.h"
#include "core/fxcrt/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"

#ifndef PDF_ENABLE_XFA
#error "XFA only"
#endif

#if defined(PDF_ENABLE_RUST_BMP)
#include "core/fxcodec/bmp/rust_bmp_context.h"
#else
#include "core/fxcodec/bmp/cfx_bmpcontext.h"
#endif

#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/rust_png_context.h"
#else
#include "core/fxcodec/png/libpng_png_context.h"
#endif

namespace fxcodec {

namespace {

constexpr size_t kBlockSize = 4096;

std::unique_ptr<ProgressiveDecoderContext> CreateDecoderContext(
    FXCODEC_IMAGE_TYPE type,
    ProgressiveDecoder* delegate) {
  switch (type) {
    case FXCODEC_IMAGE_BMP:
#if defined(PDF_ENABLE_RUST_BMP)
      return std::make_unique<RustBmpContext>(delegate);
#else
      return std::make_unique<CFX_BmpContext>(delegate);
#endif
    case FXCODEC_IMAGE_GIF:
      return std::make_unique<CFX_GifContext>(delegate);
    case FXCODEC_IMAGE_JPG: {
#if defined(PDF_ENABLE_RUST_JPEG)
      return std::make_unique<SkiaJpegContext>(delegate);
#else
      auto context = std::make_unique<LibjpegJpegContext>(delegate);
      if (!context->create_ok_) {
        return nullptr;
      }
      return context;
#endif
    }
    case FXCODEC_IMAGE_PNG:
#if defined(PDF_ENABLE_RUST_PNG)
      return std::make_unique<RustPngContext>(delegate);
#else
    {
      auto context = std::make_unique<LibpngPngContext>(delegate);
      if (!context->png_ || !context->info_) {
        return nullptr;
      }
      return context;
    }
#endif
    default:
      return nullptr;
  }
}

}  // namespace

ProgressiveDecoder::ProgressiveDecoder() = default;

ProgressiveDecoder::~ProgressiveDecoder() = default;

bool ProgressiveDecoder::ReadMoreData(std::optional<uint32_t> rcd_pos,
                                      FXCODEC_STATUS* err_status) {
  if (rcd_pos.has_value()) {
    offset_ = rcd_pos.value();
    codec_memory_->Seek(codec_memory_->GetSize());
  }
  size_t unconsumed_bytes = codec_memory_->GetUnconsumedSpan().size();
  if (!ReadMoreDataInternal(unconsumed_bytes, err_status)) {
    return false;
  }
  if (context_) {
    context_->Input(codec_memory_);
  }
  return true;
}

uint32_t ProgressiveDecoder::GetCurrentInputPosition() const {
  uint32_t remain_size =
      context_ ? pdfium::checked_cast<uint32_t>(context_->GetAvailInput()) : 0;
  return offset_ - remain_size;
}

bool ProgressiveDecoder::PrepareScanlineResampling(
    int src_width,
    int src_height,
    Format src_format,
    pdfium::span<const FX_ARGB> palette,
    std::optional<FX_ARGB> fill_argb) {
  if (!device_bitmap_) {
    return false;
  }
  src_width_ = src_width;
  src_height_ = src_height;
  src_format_ = src_format;
  SetTransMethod();
  decode_buf_.resize(GetScanlineSize());
  FXDIB_ResampleOptions options;
  options.bInterpolateBilinear = true;
  weight_horz_.CalculateWeights(src_width_, 0, src_width_, src_width_, 0,
                                src_width_, options);
  if (!palette.empty()) {
    device_bitmap_->SetPalette(palette);
    src_palette_ = DataVector<FX_ARGB>(palette.begin(), palette.end());
  }
  if (fill_argb.has_value()) {
    device_bitmap_->Clear(fill_argb.value());
  }
  return true;
}

void ProgressiveDecoder::ResampleScanline(
    int line,
    pdfium::span<const uint8_t> src_span) {
  CHECK(device_bitmap_);
  if (line < 0 || line >= src_height_) {
    return;
  }
  int scanline_size = GetScanlineSize();
  fxcrt::Copy(src_span.first(static_cast<size_t>(scanline_size)), decode_buf_);
  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<uint8_t> 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);
}

pdfium::span<uint8_t> ProgressiveDecoder::AskImageBuf() {
  CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra);
  CHECK_EQ(src_format_, Format::kArgb);
  return device_bitmap_->GetWritableBuffer();
}

bool ProgressiveDecoder::BmpDetectImageTypeInBuffer(
    CFX_DIBAttribute* pAttribute) {
  std::unique_ptr<ProgressiveDecoderContext> pBmcontext =
      CreateDecoderContext(FXCODEC_IMAGE_BMP, this);
  pBmcontext->Input(codec_memory_);

#if defined(PDF_ENABLE_RUST_BMP)
  auto* ctx = static_cast<RustBmpContext*>(pBmcontext.get());
#else
  auto* ctx = static_cast<CFX_BmpContext*>(pBmcontext.get());
#endif

  pdfium::span<const FX_ARGB> palette;
  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_,
                                  &src_components_count_, &palette, pAttribute);
  }

  if (read_result != ProgressiveDecoderContext::Status::kSuccess) {
    status_ = FXCODEC_STATUS::kError;
    return false;
  }

  FXDIB_Format format = FXDIB_Format::kInvalid;
  switch (src_components_count_) {
    case 1:
      src_format_ = Format::k8bppRgb;
      format = FXDIB_Format::k8bppRgb;
      break;
    case 3:
      src_format_ = Format::kRgb;
      format = FXDIB_Format::kBgr;
      break;
    case 4:
      src_format_ = Format::kRgb32;
      format = FXDIB_Format::kBgrx;
      break;
    default:
      status_ = FXCODEC_STATUS::kError;
      return false;
  }

  // Set to 0 to make CalculatePitchAndSize() calculate it.
  static constexpr uint32_t kNoPitch = 0;
  std::optional<CFX_DIBitmap::PitchAndSize> needed_data =
      CFX_DIBitmap::CalculatePitchAndSize(src_width_, src_height_, format,
                                          kNoPitch);
  if (!needed_data.has_value()) {
    status_ = FXCODEC_STATUS::kError;
    return false;
  }

  uint32_t available_data = pdfium::checked_cast<uint32_t>(
      file_->GetSize() - offset_ + pBmcontext->GetAvailInput());
  if (needed_data.value().size / src_components_count_ > available_data) {
    status_ = FXCODEC_STATUS::kError;
    return false;
  }

  src_bits_per_component_ = 8;
  context_ = std::move(pBmcontext);
  if (!palette.empty()) {
    src_palette_.resize(palette.size());
    fxcrt::Copy(palette, src_palette_);
  } else {
    src_palette_.clear();
  }
  return true;
}

bool ProgressiveDecoder::BmpReadMoreData(ProgressiveDecoderContext* bmp_context,
                                         FXCODEC_STATUS* err_status) {
  // TODO(lukasza): Can this just use
  // `codec_memory_->GetUnconsumedSpan().size()`? (IIUC this is what
  // `GetAvailInput` uses in the end, but I haven't investigated that this is
  // the same instance of `CFX_CodecMemory`.)
  FX_SAFE_SIZE_T avail_input = bmp_context->GetAvailInput();
  if (!avail_input.IsValid()) {
    return false;
  }
  if (!ReadMoreDataInternal(avail_input.ValueOrDie(), err_status)) {
    return false;
  }
  bmp_context->Input(codec_memory_);
  return true;
}

FXCODEC_STATUS ProgressiveDecoder::BmpStartDecode() {
  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);
  status_ = FXCODEC_STATUS::kDecodeToBeContinued;
  return status_;
}

FXCODEC_STATUS ProgressiveDecoder::BmpContinueDecode() {
  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)) {
      device_bitmap_ = nullptr;
      file_ = nullptr;
      status_ = error_status;
      return status_;
    }
    read_res = context_->DecodeImage();
  }

  device_bitmap_ = nullptr;
  file_ = nullptr;
  status_ = read_res == ProgressiveDecoderContext::Status::kSuccess
                ? FXCODEC_STATUS::kDecodeFinished
                : FXCODEC_STATUS::kError;
  return status_;
}

bool ProgressiveDecoder::GifReadMoreData(FXCODEC_STATUS* err_status) {
  // TODO(lukasza): Can this just use
  // `codec_memory_->GetUnconsumedSpan().size()`? (IIUC this is what
  // `GetAvailInput` uses in the end, but I haven't investigated that this is
  // the same instance of `CFX_CodecMemory`.)
  FX_SAFE_SIZE_T avail_input = context_->GetAvailInput();
  if (!avail_input.IsValid()) {
    return false;
  }
  if (!ReadMoreDataInternal(avail_input.ValueOrDie(), err_status)) {
    return false;
  }

  context_->Input(codec_memory_);
  return true;
}

bool ProgressiveDecoder::GifDetectImageTypeInBuffer() {
  context_ = CreateDecoderContext(FXCODEC_IMAGE_GIF, this);
  context_->Input(codec_memory_);
  src_components_count_ = 1;
  auto* ctx = static_cast<CFX_GifContext*>(context_.get());
  ProgressiveDecoderContext::Status readResult =
      ctx->ReadHeader(&src_width_, &src_height_);
  while (readResult == ProgressiveDecoderContext::Status::kContinue) {
    FXCODEC_STATUS error_status = FXCODEC_STATUS::kError;
    if (!GifReadMoreData(&error_status)) {
      context_ = nullptr;
      status_ = error_status;
      return false;
    }
    readResult = ctx->ReadHeader(&src_width_, &src_height_);
  }
  if (readResult == ProgressiveDecoderContext::Status::kSuccess) {
    src_bits_per_component_ = 8;
    return true;
  }
  context_ = nullptr;
  status_ = FXCODEC_STATUS::kError;
  return false;
}

FXCODEC_STATUS ProgressiveDecoder::GifStartDecode() {
  src_format_ = Format::k8bppRgb;
  SetTransMethod();
  decode_buf_.resize(GetScanlineSize());
  FXDIB_ResampleOptions options;
  options.bInterpolateBilinear = true;
  weight_horz_.CalculateWeights(src_width_, 0, src_width_, src_width_, 0,
                                src_width_, options);
  auto* ctx = static_cast<CFX_GifContext*>(context_.get());
  status_ = ctx->StartDecode(device_bitmap_);
  return status_;
}

FXCODEC_STATUS ProgressiveDecoder::GifContinueDecode() {
  auto* ctx = static_cast<CFX_GifContext*>(context_.get());
  status_ = ctx->ContinueDecode();  // Success or error, never continue.
  device_bitmap_ = nullptr;
  file_ = nullptr;
  return status_;
}

bool ProgressiveDecoder::JpegReadMoreData(FXCODEC_STATUS* err_status) {
  FX_SAFE_SIZE_T avail_input = context_->GetAvailInput();
  if (!avail_input.IsValid()) {
    return false;
  }
  if (!ReadMoreDataInternal(avail_input.ValueOrDie(), err_status)) {
    return false;
  }
  context_->Input(codec_memory_);
  return true;
}

bool ProgressiveDecoder::JpegDetectImageTypeInBuffer(
    CFX_DIBAttribute* pAttribute) {
  context_ = CreateDecoderContext(FXCODEC_IMAGE_JPG, this);
  if (!context_) {
    status_ = FXCODEC_STATUS::kError;
    return false;
  }
  context_->Input(codec_memory_);

#if defined(PDF_ENABLE_RUST_JPEG)
  auto* ctx = static_cast<SkiaJpegContext*>(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<LibjpegJpegContext*>(context_.get());
  while (1) {
    int read_result = ctx->ReadHeader(&src_width_, &src_height_,
                                      &src_components_count_, pAttribute);
    switch (read_result) {
      case LibjpegJpegContext::kFatal:
      case LibjpegJpegContext::kError:
        status_ = FXCODEC_STATUS::kError;
        return false;
      case LibjpegJpegContext::kOk:
        src_bits_per_component_ = 8;
        return true;
      case LibjpegJpegContext::kNeedsMoreInput: {
        FXCODEC_STATUS error_status = FXCODEC_STATUS::kError;
        if (!JpegReadMoreData(&error_status)) {
          status_ = error_status;
          return false;
        }
        break;
      }
      default:
        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<SkiaJpegContext*>(context_.get());
  status_ = ctx->StartDecode(device_bitmap_);
  return status_;
#else
  status_ = static_cast<LibjpegJpegContext*>(context_.get())->StartDecode();
  if (status_ == FXCODEC_STATUS::kError) {
    device_bitmap_ = nullptr;
    file_ = nullptr;
    context_.reset();
  }
  return status_;
#endif
}

FXCODEC_STATUS ProgressiveDecoder::JpegContinueDecode() {
#if defined(PDF_ENABLE_RUST_JPEG)
  auto* ctx = static_cast<SkiaJpegContext*>(context_.get());
  status_ = ctx->ContinueDecode();
  if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) {
    device_bitmap_ = nullptr;
    file_ = nullptr;
  }
  return status_;
#else
  status_ = static_cast<LibjpegJpegContext*>(context_.get())->ContinueDecode();
  if (status_ == FXCODEC_STATUS::kError) {
    context_.reset();
  }
  if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) {
    device_bitmap_ = nullptr;
    file_ = nullptr;
  }
  return status_;
#endif
}

bool ProgressiveDecoder::PngDetectImageTypeInBuffer() {
  context_ = CreateDecoderContext(FXCODEC_IMAGE_PNG, this);
  if (!context_) {
    status_ = FXCODEC_STATUS::kError;
    return false;
  }

  src_width_ = 0;
  src_height_ = 0;

#if defined(PDF_ENABLE_RUST_PNG)
  context_->Input(codec_memory_);
  auto* ctx = static_cast<RustPngContext*>(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<LibpngPngContext*>(context_.get());
  if (ctx->ReadHeader(codec_memory_)) {
    FXCODEC_STATUS status = FXCODEC_STATUS::kError;
    while (src_width_ == 0 && ReadMoreData(std::nullopt, &status)) {
      if (!ctx->ReadHeader(codec_memory_)) {
        break;
      }
    }
  }
#endif

  bool got_metadata = (src_width_ > 0 && src_height_ > 0);
  context_.reset();
  return got_metadata;
}

FXCODEC_STATUS ProgressiveDecoder::PngStartDecode() {
  context_ = CreateDecoderContext(FXCODEC_IMAGE_PNG, this);
  if (!context_) {
    device_bitmap_ = nullptr;
    file_ = nullptr;
    status_ = FXCODEC_STATUS::kError;
    return status_;
  }
  context_->Input(codec_memory_);
#if defined(PDF_ENABLE_RUST_PNG)
  auto* ctx = static_cast<RustPngContext*>(context_.get());
#else
  auto* ctx = static_cast<LibpngPngContext*>(context_.get());
#endif
  status_ = ctx->StartDecode(device_bitmap_);
  return status_;
}

FXCODEC_STATUS ProgressiveDecoder::PngContinueDecode() {
#if defined(PDF_ENABLE_RUST_PNG)
  auto* ctx = static_cast<RustPngContext*>(context_.get());
#else
  auto* ctx = static_cast<LibpngPngContext*>(context_.get());
#endif
  status_ = ctx->ContinueDecode();
  if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) {
    device_bitmap_ = nullptr;
    file_ = nullptr;
  }
  return status_;
}

bool ProgressiveDecoder::TiffDetectImageTypeFromFile(
    CFX_DIBAttribute* pAttribute) {
  auto ctx = std::make_unique<LibtiffTiffContext>();
  if (!ctx->InitDecoder(file_)) {
    status_ = FXCODEC_STATUS::kError;
    return false;
  }
  int32_t dummy_bpc;
  bool ret = ctx->LoadFrameInfo(0, &src_width_, &src_height_,
                                &src_components_count_, &dummy_bpc, pAttribute);
  src_components_count_ = 4;
  if (!ret) {
    status_ = FXCODEC_STATUS::kError;
    return false;
  }
  context_ = std::move(ctx);
  return true;
}

FXCODEC_STATUS ProgressiveDecoder::TiffContinueDecode() {
  // TODO(crbug.com/355630556): Consider adding support for
  // `FXDIB_Format::kBgraPremul`
  CHECK_EQ(device_bitmap_->GetFormat(), FXDIB_Format::kBgra);
  auto* ctx = static_cast<LibtiffTiffContext*>(context_.get());
  status_ = ctx->Decode(std::move(device_bitmap_))
                ? FXCODEC_STATUS::kDecodeFinished
                : FXCODEC_STATUS::kError;
  file_ = nullptr;
  return status_;
}

bool ProgressiveDecoder::DetectImageType(FXCODEC_IMAGE_TYPE imageType,
                                         CFX_DIBAttribute* pAttribute) {
  if (imageType == FXCODEC_IMAGE_TIFF) {
    return TiffDetectImageTypeFromFile(pAttribute);
  }

  size_t size = pdfium::checked_cast<size_t>(
      std::min<FX_FILESIZE>(file_->GetSize(), kBlockSize));
  codec_memory_ = pdfium::MakeRetain<CFX_CodecMemory>(size);
  offset_ = 0;
  if (!file_->ReadBlockAtOffset(codec_memory_->GetBufferSpan().first(size),
                                offset_)) {
    status_ = FXCODEC_STATUS::kError;
    return false;
  }
  offset_ += size;

  if (imageType == FXCODEC_IMAGE_JPG) {
    return JpegDetectImageTypeInBuffer(pAttribute);
  }

  if (imageType == FXCODEC_IMAGE_BMP) {
    return BmpDetectImageTypeInBuffer(pAttribute);
  }
  if (imageType == FXCODEC_IMAGE_GIF) {
    return GifDetectImageTypeInBuffer();
  }
  if (imageType == FXCODEC_IMAGE_PNG) {
    return PngDetectImageTypeInBuffer();
  }

  status_ = FXCODEC_STATUS::kError;
  return false;
}

bool ProgressiveDecoder::ReadMoreDataInternal(size_t unconsumed_bytes,
                                              FXCODEC_STATUS* err_status) {
  // Check for EOF.
  if (offset_ >= static_cast<uint32_t>(file_->GetSize())) {
    return false;
  }

  // Try to get whatever remains.
  uint32_t bytes_to_fetch_from_file =
      pdfium::checked_cast<uint32_t>(file_->GetSize() - offset_);

  if (unconsumed_bytes == codec_memory_->GetSize()) {
    // Codec couldn't make any progress against the bytes in the buffer.
    // Increase the buffer size so that there might be enough contiguous
    // bytes to allow whatever operation is having difficulty to succeed.
    bytes_to_fetch_from_file =
        std::min<uint32_t>(bytes_to_fetch_from_file, kBlockSize);
    size_t new_size = codec_memory_->GetSize() + bytes_to_fetch_from_file;
    if (!codec_memory_->TryResize(new_size)) {
      *err_status = FXCODEC_STATUS::kError;
      return false;
    }
  } else {
    // TODO(crbug.com/42270919): Simplify the `CFX_CodecMemory` API so we
    // don't need to do this awkward dance to free up exactly enough buffer
    // space for the next read.
    size_t already_read_bytes = codec_memory_->GetSize() - unconsumed_bytes;
    bytes_to_fetch_from_file = pdfium::checked_cast<uint32_t>(
        std::min<size_t>(bytes_to_fetch_from_file, already_read_bytes));
    codec_memory_->Consume(bytes_to_fetch_from_file);
    codec_memory_->Seek(already_read_bytes - bytes_to_fetch_from_file);
    unconsumed_bytes += codec_memory_->GetPosition();
  }

  // Append new data past the bytes not yet processed by the codec.
  if (!file_->ReadBlockAtOffset(codec_memory_->GetBufferSpan().subspan(
                                    unconsumed_bytes, bytes_to_fetch_from_file),
                                offset_)) {
    *err_status = FXCODEC_STATUS::kError;
    return false;
  }
  offset_ += bytes_to_fetch_from_file;
  return true;
}

FXCODEC_STATUS ProgressiveDecoder::LoadImageInfo(
    RetainPtr<IFX_SeekableReadStream> pFile,
    FXCODEC_IMAGE_TYPE imageType,
    CFX_DIBAttribute* pAttribute,
    bool bSkipImageTypeCheck) {
  DCHECK(pAttribute);

  switch (status_) {
    case FXCODEC_STATUS::kFrameReady:
    case FXCODEC_STATUS::kFrameToBeContinued:
    case FXCODEC_STATUS::kDecodeReady:
    case FXCODEC_STATUS::kDecodeToBeContinued:
      return FXCODEC_STATUS::kError;
    case FXCODEC_STATUS::kError:
    case FXCODEC_STATUS::kDecodeFinished:
      break;
  }
  file_ = std::move(pFile);
  if (!file_) {
    status_ = FXCODEC_STATUS::kError;
    return status_;
  }
  offset_ = 0;
  src_width_ = 0;
  src_height_ = 0;
  src_components_count_ = 0;
  src_bits_per_component_ = 0;
  if (imageType != FXCODEC_IMAGE_UNKNOWN &&
      DetectImageType(imageType, pAttribute)) {
    image_type_ = imageType;
    status_ = FXCODEC_STATUS::kFrameReady;
    return status_;
  }
  // If we got here then the image data does not match the requested decoder.
  // If we're skipping the type check then bail out at this point and return
  // the failed status.
  if (bSkipImageTypeCheck) {
    return status_;
  }

  for (int type = FXCODEC_IMAGE_UNKNOWN + 1; type < FXCODEC_IMAGE_MAX; type++) {
    if (DetectImageType(static_cast<FXCODEC_IMAGE_TYPE>(type), pAttribute)) {
      image_type_ = static_cast<FXCODEC_IMAGE_TYPE>(type);
      status_ = FXCODEC_STATUS::kFrameReady;
      return status_;
    }
  }
  status_ = FXCODEC_STATUS::kError;
  file_ = nullptr;
  return status_;
}

void ProgressiveDecoder::SetTransMethod() {
  switch (device_bitmap_->GetFormat()) {
    case FXDIB_Format::kInvalid:
    case FXDIB_Format::k1bppMask:
    case FXDIB_Format::k1bppRgb:
    case FXDIB_Format::k8bppMask:
    case FXDIB_Format::k8bppRgb:
      NOTREACHED();
    case FXDIB_Format::kBgr: {
      switch (src_format_) {
        case Format::kInvalid:
          trans_method_ = TransformMethod::kInvalid;
          break;
        case Format::k8bppGray:
          trans_method_ = TransformMethod::k8BppGrayToRgbMaybeAlpha;
          break;
        case Format::k8bppRgb:
          trans_method_ = TransformMethod::k8BppRgbToRgbNoAlpha;
          break;
        case Format::kRgb:
        case Format::kRgb32:
        case Format::kArgb:
          trans_method_ = TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha;
          break;
        case Format::kCmyk:
          trans_method_ = TransformMethod::kCmykToRgbMaybeAlpha;
          break;
      }
      break;
    }
    case FXDIB_Format::kBgrx:
    case FXDIB_Format::kBgra: {
      switch (src_format_) {
        case Format::kInvalid:
          trans_method_ = TransformMethod::kInvalid;
          break;
        case Format::k8bppGray:
          trans_method_ = TransformMethod::k8BppGrayToRgbMaybeAlpha;
          break;
        case Format::k8bppRgb:
          if (device_bitmap_->GetFormat() == FXDIB_Format::kBgra) {
            trans_method_ = TransformMethod::k8BppRgbToArgb;
          } else {
            trans_method_ = TransformMethod::k8BppRgbToRgbNoAlpha;
          }
          break;
        case Format::kRgb:
        case Format::kRgb32:
          trans_method_ = TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha;
          break;
        case Format::kCmyk:
          trans_method_ = TransformMethod::kCmykToRgbMaybeAlpha;
          break;
        case Format::kArgb:
          trans_method_ = TransformMethod::kArgbToArgb;
          break;
      }
      break;
    }
#if defined(PDF_USE_SKIA)
    case FXDIB_Format::kBgraPremul:
      // TODO(crbug.com/355630556): Consider adding support for
      // `FXDIB_Format::kBgraPremul`
      NOTREACHED();
#endif
  }
}

void ProgressiveDecoder::ResampleScanline(
    const RetainPtr<CFX_DIBitmap>& pDeviceBitmap,
    int dest_line,
    pdfium::span<uint8_t> src_span,
    Format src_format) {
  uint8_t* src_scan = src_span.data();
  uint8_t* dest_scan = pDeviceBitmap->GetWritableScanline(dest_line).data();
  const auto src_bytes_per_pixel =
      (fxcrt::to_underlying(src_format) & 0xff) / 8;
  const int dest_bytes_per_pixel = pDeviceBitmap->GetBPP() / 8;
  for (int dest_col = 0; dest_col < src_width_; dest_col++) {
    CStretchEngine::PixelWeight* pPixelWeights =
        weight_horz_.GetPixelWeight(dest_col);
    switch (trans_method_) {
      case TransformMethod::kInvalid:
        return;
      case TransformMethod::k8BppGrayToRgbMaybeAlpha: {
        UNSAFE_TODO({
          uint32_t dest_g = 0;
          for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_;
               j++) {
            uint32_t pixel_weight =
                pPixelWeights->weights_[j - pPixelWeights->src_start_];
            dest_g += pixel_weight * src_scan[j];
          }
          FXSYS_memset(dest_scan, CStretchEngine::PixelFromFixed(dest_g), 3);
          dest_scan += dest_bytes_per_pixel;
          break;
        });
      }
      case TransformMethod::k8BppRgbToRgbNoAlpha: {
        UNSAFE_TODO({
          uint32_t dest_r = 0;
          uint32_t dest_g = 0;
          uint32_t dest_b = 0;
          for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_;
               j++) {
            uint32_t pixel_weight =
                pPixelWeights->weights_[j - pPixelWeights->src_start_];
            uint32_t argb = src_palette_[src_scan[j]];
            dest_r += pixel_weight * FXARGB_R(argb);
            dest_g += pixel_weight * FXARGB_G(argb);
            dest_b += pixel_weight * FXARGB_B(argb);
          }
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r);
          dest_scan += dest_bytes_per_pixel - 3;
          break;
        });
      }
      case TransformMethod::k8BppRgbToArgb: {
        UNSAFE_TODO({
          uint32_t dest_a = 0;
          uint32_t dest_r = 0;
          uint32_t dest_g = 0;
          uint32_t dest_b = 0;
          for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_;
               j++) {
            uint32_t pixel_weight =
                pPixelWeights->weights_[j - pPixelWeights->src_start_];
            FX_ARGB argb = src_palette_[src_scan[j]];
            dest_a += pixel_weight * FXARGB_A(argb);
            dest_r += pixel_weight * FXARGB_R(argb);
            dest_g += pixel_weight * FXARGB_G(argb);
            dest_b += pixel_weight * FXARGB_B(argb);
          }
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_a);
          break;
        });
      }
      case TransformMethod::kRgbMaybeAlphaToRgbMaybeAlpha: {
        UNSAFE_TODO({
          uint32_t dest_b = 0;
          uint32_t dest_g = 0;
          uint32_t dest_r = 0;
          for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_;
               j++) {
            uint32_t pixel_weight =
                pPixelWeights->weights_[j - pPixelWeights->src_start_];
            const uint8_t* src_pixel = src_scan + j * src_bytes_per_pixel;
            dest_b += pixel_weight * (*src_pixel++);
            dest_g += pixel_weight * (*src_pixel++);
            dest_r += pixel_weight * (*src_pixel);
          }
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r);
          dest_scan += dest_bytes_per_pixel - 3;
          break;
        });
      }
      case TransformMethod::kCmykToRgbMaybeAlpha: {
        UNSAFE_TODO({
          uint32_t dest_b = 0;
          uint32_t dest_g = 0;
          uint32_t dest_r = 0;
          for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_;
               j++) {
            uint32_t pixel_weight =
                pPixelWeights->weights_[j - pPixelWeights->src_start_];
            const uint8_t* src_pixel = src_scan + j * src_bytes_per_pixel;
            FX_RGB_STRUCT<uint8_t> src_rgb =
                AdobeCmykToStandardRgb(255 - src_pixel[0], 255 - src_pixel[1],
                                       255 - src_pixel[2], 255 - src_pixel[3]);
            dest_b += pixel_weight * src_rgb.blue;
            dest_g += pixel_weight * src_rgb.green;
            dest_r += pixel_weight * src_rgb.red;
          }
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r);
          dest_scan += dest_bytes_per_pixel - 3;
          break;
        });
      }
      case TransformMethod::kArgbToArgb: {
        UNSAFE_TODO({
          uint32_t dest_alpha = 0;
          uint32_t dest_r = 0;
          uint32_t dest_g = 0;
          uint32_t dest_b = 0;
          for (int j = pPixelWeights->src_start_; j <= pPixelWeights->src_end_;
               j++) {
            uint32_t pixel_weight =
                pPixelWeights->weights_[j - pPixelWeights->src_start_];
            const uint8_t* src_pixel = src_scan + j * src_bytes_per_pixel;
            pixel_weight = pixel_weight * src_pixel[3] / 255;
            dest_b += pixel_weight * (*src_pixel++);
            dest_g += pixel_weight * (*src_pixel++);
            dest_r += pixel_weight * (*src_pixel);
            dest_alpha += pixel_weight;
          }
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_b);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_g);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_r);
          *dest_scan++ = CStretchEngine::PixelFromFixed(dest_alpha * 255);
          break;
        });
      }
    }
  }
}

void ProgressiveDecoder::Resample(const RetainPtr<CFX_DIBitmap>& pDeviceBitmap,
                                  int32_t src_line,
                                  uint8_t* src_scan,
                                  Format src_format) {
  if (src_line < 0 || src_line >= src_height_) {
    return;
  }

  ResampleScanline(pDeviceBitmap, src_line, decode_buf_, src_format);
}

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:
    case FXCODEC_IMAGE_TIFF:
    default:
      // TODO(crbug.com/355630556): Consider adding support for
      // `FXDIB_Format::kBgraPremul`
      return FXDIB_Format::kBgra;
  }
}

std::pair<FXCODEC_STATUS, size_t> ProgressiveDecoder::GetFrames() {
  if (!(status_ == FXCODEC_STATUS::kFrameReady ||
        status_ == FXCODEC_STATUS::kFrameToBeContinued)) {
    return {FXCODEC_STATUS::kError, 0};
  }

  switch (image_type_) {
    case FXCODEC_IMAGE_JPG:
    case FXCODEC_IMAGE_BMP:
    case FXCODEC_IMAGE_PNG:
    case FXCODEC_IMAGE_TIFF:
      frame_number_ = 1;
      status_ = FXCODEC_STATUS::kDecodeReady;
      return {status_, 1};
    case FXCODEC_IMAGE_GIF: {
      auto* ctx = static_cast<CFX_GifContext*>(context_.get());
      while (true) {
        ProgressiveDecoderContext::Status readResult = ctx->GetFrame();
        frame_number_ =
            readResult == ProgressiveDecoderContext::Status::kSuccess
                ? ctx->GetFrameNum()
                : 0;
        while (readResult == ProgressiveDecoderContext::Status::kContinue) {
          FXCODEC_STATUS error_status = FXCODEC_STATUS::kError;
          if (!GifReadMoreData(&error_status)) {
            return {error_status, 0};
          }

          readResult = ctx->GetFrame();
          frame_number_ =
              readResult == ProgressiveDecoderContext::Status::kSuccess
                  ? ctx->GetFrameNum()
                  : 0;
        }
        if (readResult == ProgressiveDecoderContext::Status::kSuccess) {
          status_ = FXCODEC_STATUS::kDecodeReady;
          return {status_, frame_number_};
        }
        context_ = nullptr;
        status_ = FXCODEC_STATUS::kError;
        return {status_, 0};
      }
    }
    default:
      return {FXCODEC_STATUS::kError, 0};
  }
}

FXCODEC_STATUS ProgressiveDecoder::StartDecode(RetainPtr<CFX_DIBitmap> bitmap) {
  CHECK(bitmap);
  CHECK_EQ(bitmap->GetWidth(), src_width_);
  CHECK_EQ(bitmap->GetHeight(), src_height_);
  CHECK_GT(src_width_, 0);
  CHECK_GT(src_height_, 0);

  const FXDIB_Format format = bitmap->GetFormat();
  CHECK(format == FXDIB_Format::kBgra || format == FXDIB_Format::kBgr ||
        format == FXDIB_Format::kBgrx);

  if (status_ != FXCODEC_STATUS::kDecodeReady) {
    return FXCODEC_STATUS::kError;
  }

  if (frame_number_ == 0) {
    return FXCODEC_STATUS::kError;
  }

  if (bitmap->GetWidth() > 65535 || bitmap->GetHeight() > 65535) {
    return FXCODEC_STATUS::kError;
  }

  device_bitmap_ = std::move(bitmap);
  switch (image_type_) {
    case FXCODEC_IMAGE_JPG:
      return JpegStartDecode();
    case FXCODEC_IMAGE_BMP:
      return BmpStartDecode();
    case FXCODEC_IMAGE_GIF:
      return GifStartDecode();
    case FXCODEC_IMAGE_PNG:
      return PngStartDecode();
    case FXCODEC_IMAGE_TIFF:
      status_ = FXCODEC_STATUS::kDecodeToBeContinued;
      return status_;
    default:
      return FXCODEC_STATUS::kError;
  }
}

FXCODEC_STATUS ProgressiveDecoder::ContinueDecode() {
  if (status_ != FXCODEC_STATUS::kDecodeToBeContinued) {
    return FXCODEC_STATUS::kError;
  }

  switch (image_type_) {
    case FXCODEC_IMAGE_JPG:
      return JpegContinueDecode();
    case FXCODEC_IMAGE_BMP:
      return BmpContinueDecode();
    case FXCODEC_IMAGE_GIF:
      return GifContinueDecode();
    case FXCODEC_IMAGE_PNG:
      return PngContinueDecode();
    case FXCODEC_IMAGE_TIFF:
      return TiffContinueDecode();
    default:
      return FXCODEC_STATUS::kError;
  }
}

int ProgressiveDecoder::GetScanlineSize() const {
  // Can't be called before basic image metadata is decoded.
  CHECK_NE(src_components_count_, 0);
  CHECK_NE(src_width_, 0);

  FX_SAFE_INT32 result = src_width_;
  result *= src_components_count_;

  return FxAlignToBoundary<4>(result).ValueOrDie();
}

}  // namespace fxcodec
