// Copyright 2017 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "core/fxcodec/gif/cfx_gifcontext.h"

#include <stdint.h>
#include <string.h>

#include <algorithm>
#include <array>
#include <iterator>
#include <optional>
#include <utility>

#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 {

namespace {

constexpr std::array<const int32_t, 4> kGifInterlaceStep = {{8, 8, 4, 2}};

std::optional<GifSignature> GifSignatureFromByte(uint8_t signature) {
  switch (signature) {
    case fxcrt::to_underlying(GifSignature::kExtension):
      return GifSignature::kExtension;
    case fxcrt::to_underlying(GifSignature::kImage):
      return GifSignature::kImage;
    case fxcrt::to_underlying(GifSignature::kTrailer):
      return GifSignature::kTrailer;
    default:
      return std::nullopt;
  }
}

}  // namespace

CFX_GifContext::CFX_GifContext(ProgressiveDecoderContextDelegate* delegate)
    : delegate_(delegate) {}

CFX_GifContext::~CFX_GifContext() = default;

ProgressiveDecoderContext::Status CFX_GifContext::ReadHeader(int* width,
                                                             int* height) {
  ProgressiveDecoderContext::Status ret = ReadHeader();
  if (ret != ProgressiveDecoderContext::Status::kSuccess) {
    return ret;
  }
  *width = width_;
  *height = height_;
  return ProgressiveDecoderContext::Status::kSuccess;
}

void CFX_GifContext::ReadScanline(int32_t row_num,
                                  pdfium::span<uint8_t> row_buf) {
  int32_t line = row_num + img_top_;
  if (line < 0 || line >= height_) {
    return;
  }
  const size_t img_width = static_cast<size_t>(img_width_);
  const pdfium::span<uint8_t> 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<size_t>(img_left_)));
  delegate_->ResampleScanline(line, scanline_buf_);
}

bool CFX_GifContext::GetRecordPosition(uint32_t cur_pos,
                                       int32_t sub_left,
                                       int32_t sub_top,
                                       int32_t sub_width,
                                       pdfium::span<CFX_GifPalette> pal,
                                       int32_t 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<FX_ARGB> 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<int>(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<int>(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<CFX_DIBitmap> 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() {
  ProgressiveDecoderContext::Status status = ReadGifSignature();
  if (status != ProgressiveDecoderContext::Status::kSuccess) {
    return status;
  }
  return ReadLogicalScreenDescriptor();
}

ProgressiveDecoderContext::Status CFX_GifContext::GetFrame() {
  ProgressiveDecoderContext::Status ret =
      ProgressiveDecoderContext::Status::kSuccess;
  while (true) {
    switch (decode_status_) {
      case GifDecoderStatus::kTail:
        return ProgressiveDecoderContext::Status::kSuccess;
      case GifDecoderStatus::kSig: {
        uint8_t signature;
        if (!ReadAllOrNone(pdfium::byte_span_from_ref(signature))) {
          return ProgressiveDecoderContext::Status::kContinue;
        }
        std::optional<GifSignature> gif_signature =
            GifSignatureFromByte(signature);
        if (!gif_signature.has_value()) {
          if (!input_buffer_->IsEOF()) {
            // The Gif File has non_standard Tag!
            SaveDecodingStatus(GifDecoderStatus::kSig);
            continue;
          }
          // The Gif File Doesn't have Trailer Tag!
          return ProgressiveDecoderContext::Status::kSuccess;
        }
        switch (gif_signature.value()) {
          case GifSignature::kExtension:
            SaveDecodingStatus(GifDecoderStatus::kExt);
            continue;
          case GifSignature::kImage:
            SaveDecodingStatus(GifDecoderStatus::kImgInfo);
            continue;
          case GifSignature::kTrailer:
            SaveDecodingStatus(GifDecoderStatus::kTail);
            return ProgressiveDecoderContext::Status::kSuccess;
        }
      }
      case GifDecoderStatus::kExt: {
        uint8_t extension;
        if (!ReadAllOrNone(pdfium::byte_span_from_ref(extension))) {
          return ProgressiveDecoderContext::Status::kContinue;
        }
        switch (extension) {
          case fxcrt::to_underlying(GifExtensionLabel::kComment):
            SaveDecodingStatus(GifDecoderStatus::kExtCe);
            continue;
          case fxcrt::to_underlying(GifExtensionLabel::kGraphicControl):
            SaveDecodingStatus(GifDecoderStatus::kExtGce);
            continue;
          case fxcrt::to_underlying(GifExtensionLabel::kPlainText):
            SaveDecodingStatus(GifDecoderStatus::kExtPte);
            continue;
          default: {
            GifDecoderStatus status = GifDecoderStatus::kExtUne;
            if (extension ==
                fxcrt::to_underlying(GifExtensionLabel::kPlainText)) {
              status = GifDecoderStatus::kExtPte;
            }
            SaveDecodingStatus(status);
            continue;
          }
        }
      }
      case GifDecoderStatus::kImgInfo: {
        ret = DecodeImageInfo();
        if (ret != ProgressiveDecoderContext::Status::kSuccess) {
          return ret;
        }

        continue;
      }
      case GifDecoderStatus::kImgData: {
        uint8_t img_data_size;
        size_t read_marker = input_buffer_->GetPosition();
        if (!ReadAllOrNone(pdfium::byte_span_from_ref(img_data_size))) {
          return ProgressiveDecoderContext::Status::kContinue;
        }
        while (img_data_size != kGifSubBlockTerminator) {
          if (!input_buffer_->Seek(input_buffer_->GetPosition() +
                                   img_data_size)) {
            input_buffer_->Seek(read_marker);
            return ProgressiveDecoderContext::Status::kContinue;
          }

          // This saving of the scan state on partial reads is why
          // ScanForTerminalMarker() cannot be used here.
          SaveDecodingStatus(GifDecoderStatus::kImgData);
          read_marker = input_buffer_->GetPosition();
          if (!ReadAllOrNone(pdfium::byte_span_from_ref(img_data_size))) {
            return ProgressiveDecoderContext::Status::kContinue;
          }
        }
        SaveDecodingStatus(GifDecoderStatus::kSig);
        continue;
      }
      default: {
        ret = DecodeExtension();
        if (ret != ProgressiveDecoderContext::Status::kSuccess) {
          return ret;
        }
        break;
      }
    }
  }
}

ProgressiveDecoderContext::Status CFX_GifContext::DecodeImage() {
  if (images_.empty()) {
    return ProgressiveDecoderContext::Status::kError;
  }

  // Always render frame 0.
  CFX_GifImage* gif_image = images_.front().get();
  if (gif_image->image_info.height == 0) {
    return ProgressiveDecoderContext::Status::kError;
  }

  uint32_t gif_img_row_bytes = gif_image->image_info.width;
  if (gif_img_row_bytes == 0) {
    return ProgressiveDecoderContext::Status::kError;
  }

  if (decode_status_ == GifDecoderStatus::kTail) {
    gif_image->row_buffer.resize(gif_img_row_bytes);
    CFX_GifGraphicControlExtension* gif_img_gce = gif_image->image_GCE.get();
    pdfium::span<CFX_GifPalette> 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, pLocalPalette, -1);
      if (!bRes) {
        gif_image->row_buffer.clear();
        return ProgressiveDecoderContext::Status::kError;
      }
    } else {
      bool bRes = GetRecordPosition(
          gif_image->data_pos, gif_image->image_info.left,
          gif_image->image_info.top, gif_image->image_info.width, pLocalPalette,
          gif_image->image_GCE->gce_flags.transparency
              ? static_cast<int32_t>(gif_image->image_GCE->trans_index)
              : -1);
      if (!bRes) {
        gif_image->row_buffer.clear();
        return ProgressiveDecoderContext::Status::kError;
      }
    }

    if (gif_image->code_exp > kGifMaxLzwExp) {
      gif_image->row_buffer.clear();
      return ProgressiveDecoderContext::Status::kError;
    }

    img_row_offset_ = 0;
    img_row_avail_size_ = 0;
    img_pass_num_ = 0;
    gif_image->row_num = 0;
    SaveDecodingStatus(GifDecoderStatus::kImgData);
  }

  uint8_t img_data_size;
  DataVector<uint8_t> img_data;
  size_t read_marker = input_buffer_->GetPosition();

  // TODO(crbug.com/42270805): This logic can be simplified a lot, but it
  // probably makes more sense to switch to a different GIF decoder altogether.
  if (decode_status_ == GifDecoderStatus::kImgData) {
    if (!ReadAllOrNone(pdfium::byte_span_from_ref(img_data_size))) {
      return ProgressiveDecoderContext::Status::kContinue;
    }
    if (img_data_size != kGifSubBlockTerminator) {
      img_data.resize(img_data_size);
      if (!ReadAllOrNone(img_data)) {
        input_buffer_->Seek(read_marker);
        return ProgressiveDecoderContext::Status::kContinue;
      }
      if (!lzw_decompressor_) {
        lzw_decompressor_ = LZWDecompressor::Create(GetPaletteExp(gif_image),
                                                    gif_image->code_exp);
        if (!lzw_decompressor_) {
          DecodingFailureAtTailCleanup(gif_image);
          return ProgressiveDecoderContext::Status::kError;
        }
      }
      lzw_decompressor_->SetSource(img_data);

      SaveDecodingStatus(GifDecoderStatus::kImgData);
      img_row_offset_ += img_row_avail_size_;
      img_row_avail_size_ = gif_img_row_bytes - img_row_offset_;
      auto img_row_span = pdfium::span(gif_image->row_buffer)
                              .subspan(img_row_offset_, img_row_avail_size_);
      LZWDecompressor::Status ret = UNSAFE_TODO(
          lzw_decompressor_->Decode(img_row_span.data(), &img_row_avail_size_));
      if (ret == LZWDecompressor::Status::kError) {
        DecodingFailureAtTailCleanup(gif_image);
        return ProgressiveDecoderContext::Status::kError;
      }

      while (ret != LZWDecompressor::Status::kError) {
        if (ret == LZWDecompressor::Status::kSuccess) {
          ReadScanline(gif_image->row_num, gif_image->row_buffer);
          gif_image->row_buffer.clear();
          SaveDecodingStatus(GifDecoderStatus::kTail);
          return ProgressiveDecoderContext::Status::kSuccess;
        }

        if (ret == LZWDecompressor::Status::kUnfinished) {
          read_marker = input_buffer_->GetPosition();
          if (!ReadAllOrNone(pdfium::byte_span_from_ref(img_data_size))) {
            return ProgressiveDecoderContext::Status::kContinue;
          }
          if (img_data_size != kGifSubBlockTerminator) {
            img_data.resize(img_data_size);
            if (!ReadAllOrNone(img_data)) {
              input_buffer_->Seek(read_marker);
              return ProgressiveDecoderContext::Status::kContinue;
            }
            lzw_decompressor_->SetSource(img_data);

            SaveDecodingStatus(GifDecoderStatus::kImgData);
            img_row_offset_ += img_row_avail_size_;
            img_row_avail_size_ = gif_img_row_bytes - img_row_offset_;
            img_row_span = pdfium::span(gif_image->row_buffer)
                               .subspan(img_row_offset_, img_row_avail_size_);
            ret = UNSAFE_TODO(lzw_decompressor_->Decode(img_row_span.data(),
                                                        &img_row_avail_size_));
          }
        }

        if (ret == LZWDecompressor::Status::kInsufficientDestSize) {
          if (gif_image->image_info.local_flags.interlace) {
            ReadScanline(gif_image->row_num, gif_image->row_buffer);
            gif_image->row_num += kGifInterlaceStep[img_pass_num_];
            if (gif_image->row_num >=
                static_cast<int32_t>(gif_image->image_info.height)) {
              img_pass_num_++;
              if (img_pass_num_ == std::size(kGifInterlaceStep)) {
                DecodingFailureAtTailCleanup(gif_image);
                return ProgressiveDecoderContext::Status::kError;
              }
              gif_image->row_num = kGifInterlaceStep[img_pass_num_] / 2;
            }
          } else {
            ReadScanline(gif_image->row_num++, gif_image->row_buffer);
          }

          img_row_offset_ = 0;
          img_row_avail_size_ = gif_img_row_bytes;
          img_row_span = pdfium::span(gif_image->row_buffer)
                             .subspan(img_row_offset_, img_row_avail_size_);
          ret = UNSAFE_TODO(lzw_decompressor_->Decode(img_row_span.data(),
                                                      &img_row_avail_size_));
        }

        if (ret == LZWDecompressor::Status::kError) {
          DecodingFailureAtTailCleanup(gif_image);
          return ProgressiveDecoderContext::Status::kError;
        }
      }
    }
    SaveDecodingStatus(GifDecoderStatus::kTail);
  }
  return ProgressiveDecoderContext::Status::kError;
}

void CFX_GifContext::SetInputBuffer(RetainPtr<CFX_CodecMemory> codec_memory) {
  input_buffer_ = std::move(codec_memory);
}

FX_FILESIZE CFX_GifContext::GetAvailInput() const {
  if (!input_buffer_) {
    return 0;
  }
  return input_buffer_->GetSize() - input_buffer_->GetPosition();
}

void CFX_GifContext::Input(RetainPtr<CFX_CodecMemory> codec_memory) {
  SetInputBuffer(std::move(codec_memory));
}

bool CFX_GifContext::ReadAllOrNone(pdfium::span<uint8_t> dest) {
  if (!input_buffer_ || dest.empty()) {
    return false;
  }
  size_t read_marker = input_buffer_->GetPosition();
  size_t read = input_buffer_->ReadBlock(dest);
  if (read < dest.size()) {
    input_buffer_->Seek(read_marker);
    return false;
  }
  return true;
}

ProgressiveDecoderContext::Status CFX_GifContext::ReadGifSignature() {
  CFX_GifHeader header;
  if (!ReadAllOrNone(pdfium::byte_span_from_ref(header))) {
    return ProgressiveDecoderContext::Status::kContinue;
  }
  if (UNSAFE_TODO(strncmp(header.signature, kGifSignature87, 6)) != 0 &&
      UNSAFE_TODO(strncmp(header.signature, kGifSignature89, 6)) != 0) {
    return ProgressiveDecoderContext::Status::kError;
  }
  return ProgressiveDecoderContext::Status::kSuccess;
}

ProgressiveDecoderContext::Status
CFX_GifContext::ReadLogicalScreenDescriptor() {
  CFX_GifLocalScreenDescriptor lsd;
  size_t read_marker = input_buffer_->GetPosition();
  if (!ReadAllOrNone(pdfium::byte_span_from_ref(lsd))) {
    return ProgressiveDecoderContext::Status::kContinue;
  }
  if (lsd.global_flags.global_pal) {
    uint32_t palette_count = unsigned(2 << lsd.global_flags.pal_bits);
    if (lsd.bc_index >= palette_count) {
      return ProgressiveDecoderContext::Status::kError;
    }
    bc_index_ = lsd.bc_index;

    std::vector<CFX_GifPalette> palette(palette_count);
    if (!ReadAllOrNone(pdfium::as_writable_byte_span(palette))) {
      // Roll back the read for the LSD
      input_buffer_->Seek(read_marker);
      return ProgressiveDecoderContext::Status::kContinue;
    }

    global_palette_exp_ = lsd.global_flags.pal_bits;
    global_sort_flag_ = lsd.global_flags.sort_flag;
    global_color_resolution_ = lsd.global_flags.color_resolution;
    std::swap(global_palette_, palette);
  }

  width_ = fxcrt::FromLE16(lsd.width);
  height_ = fxcrt::FromLE16(lsd.height);

  return ProgressiveDecoderContext::Status::kSuccess;
}

void CFX_GifContext::SaveDecodingStatus(GifDecoderStatus status) {
  decode_status_ = status;
}

ProgressiveDecoderContext::Status CFX_GifContext::DecodeExtension() {
  size_t read_marker = input_buffer_->GetPosition();

  switch (decode_status_) {
    case GifDecoderStatus::kExtCe: {
      if (!ScanForTerminalMarker()) {
        input_buffer_->Seek(read_marker);
        return ProgressiveDecoderContext::Status::kContinue;
      }
      break;
    }
    case GifDecoderStatus::kExtPte: {
      CFX_GifPlainTextExtension gif_pte;
      if (!ReadAllOrNone(pdfium::byte_span_from_ref(gif_pte))) {
        return ProgressiveDecoderContext::Status::kContinue;
      }
      graphic_control_extension_ = nullptr;
      if (!ScanForTerminalMarker()) {
        input_buffer_->Seek(read_marker);
        return ProgressiveDecoderContext::Status::kContinue;
      }
      break;
    }
    case GifDecoderStatus::kExtGce: {
      CFX_GifGraphicControlExtension gif_gce;
      if (!ReadAllOrNone(pdfium::byte_span_from_ref(gif_gce))) {
        return ProgressiveDecoderContext::Status::kContinue;
      }
      if (!graphic_control_extension_.get()) {
        graphic_control_extension_ =
            std::make_unique<CFX_GifGraphicControlExtension>();
      }
      graphic_control_extension_->block_size = gif_gce.block_size;
      graphic_control_extension_->gce_flags = gif_gce.gce_flags;
      graphic_control_extension_->delay_time =
          fxcrt::FromLE16(gif_gce.delay_time);
      graphic_control_extension_->trans_index = gif_gce.trans_index;
      break;
    }
    default: {
      if (decode_status_ == GifDecoderStatus::kExtPte) {
        graphic_control_extension_ = nullptr;
      }
      if (!ScanForTerminalMarker()) {
        input_buffer_->Seek(read_marker);
        return ProgressiveDecoderContext::Status::kContinue;
      }
    }
  }

  SaveDecodingStatus(GifDecoderStatus::kSig);
  return ProgressiveDecoderContext::Status::kSuccess;
}

ProgressiveDecoderContext::Status CFX_GifContext::DecodeImageInfo() {
  if (width_ <= 0 || height_ <= 0) {
    return ProgressiveDecoderContext::Status::kError;
  }

  size_t read_marker = input_buffer_->GetPosition();
  CFX_GifImageInfo img_info;
  if (!ReadAllOrNone(pdfium::byte_span_from_ref(img_info))) {
    return ProgressiveDecoderContext::Status::kContinue;
  }
  auto gif_image = std::make_unique<CFX_GifImage>();
  gif_image->image_info.left = fxcrt::FromLE16(img_info.left);
  gif_image->image_info.top = fxcrt::FromLE16(img_info.top);
  gif_image->image_info.width = fxcrt::FromLE16(img_info.width);
  gif_image->image_info.height = fxcrt::FromLE16(img_info.height);
  gif_image->image_info.local_flags = img_info.local_flags;
  if (gif_image->image_info.left + gif_image->image_info.width > width_ ||
      gif_image->image_info.top + gif_image->image_info.height > height_) {
    return ProgressiveDecoderContext::Status::kError;
  }

  CFX_GifLocalFlags* gif_img_info_lf = &img_info.local_flags;
  if (gif_img_info_lf->local_pal) {
    gif_image->local_palette_exp = gif_img_info_lf->pal_bits;
    uint32_t loc_pal_count = unsigned(2 << gif_img_info_lf->pal_bits);
    std::vector<CFX_GifPalette> loc_pal(loc_pal_count);
    if (!ReadAllOrNone(pdfium::as_writable_byte_span(loc_pal))) {
      input_buffer_->Seek(read_marker);
      return ProgressiveDecoderContext::Status::kContinue;
    }
    gif_image->local_palettes = std::move(loc_pal);
  }

  uint8_t code_size;
  if (!ReadAllOrNone(pdfium::span_from_ref(code_size))) {
    input_buffer_->Seek(read_marker);
    return ProgressiveDecoderContext::Status::kContinue;
  }

  gif_image->code_exp = code_size;
  gif_image->data_pos = delegate_->GetCurrentInputPosition();
  gif_image->image_GCE = nullptr;
  if (graphic_control_extension_.get()) {
    if (graphic_control_extension_->gce_flags.transparency) {
      // Need to test that the color that is going to be transparent is actually
      // in the palette being used.
      if (graphic_control_extension_->trans_index >=
          (2 << GetPaletteExp(gif_image.get()))) {
        return ProgressiveDecoderContext::Status::kError;
      }
    }
    gif_image->image_GCE = std::move(graphic_control_extension_);
    graphic_control_extension_ = nullptr;
  }

  images_.push_back(std::move(gif_image));
  SaveDecodingStatus(GifDecoderStatus::kImgData);
  return ProgressiveDecoderContext::Status::kSuccess;
}

void CFX_GifContext::DecodingFailureAtTailCleanup(CFX_GifImage* gif_image) {
  gif_image->row_buffer.clear();
  SaveDecodingStatus(GifDecoderStatus::kTail);
}

bool CFX_GifContext::ScanForTerminalMarker() {
  uint8_t data_size;
  if (!ReadAllOrNone(pdfium::span_from_ref(data_size))) {
    return false;
  }
  while (data_size != kGifSubBlockTerminator) {
    if (!input_buffer_->Seek(input_buffer_->GetPosition() + data_size) ||
        !ReadAllOrNone(pdfium::span_from_ref(data_size))) {
      return false;
    }
  }
  return true;
}

uint8_t CFX_GifContext::GetPaletteExp(CFX_GifImage* gif_image) const {
  return !gif_image->local_palettes.empty() ? gif_image->local_palette_exp
                                            : global_palette_exp_;
}

}  // namespace fxcodec
