blob: 677a8625e963c71a97d5eaeeae9f2dd27cccbd52 [file]
// 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 <utility>
#include <vector>
#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<uint8_t> header, size_t total_size) {
if (header.size() >= kMinHeaderBytes && header[0] == 'B' &&
header[1] == 'M') {
uint32_t bi_size =
fxcrt::GetUInt32LSBFirst(header.subspan<kHeaderSizeOffset, 4>());
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<CFX_CodecMemory> 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<CFX_CodecMemory> 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<const FX_ARGB>* palette,
CFX_DIBAttribute* attribute) {
if (header_read_) {
*width = static_cast<int32_t>(width_);
*height = static_cast<int32_t>(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<const uint8_t> 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<kHeaderSizeOffset, 4>());
if (bi_size >= 40) {
dpi_x_ =
static_cast<int32_t>(fxcrt::GetUInt32LSBFirst(span.subspan<38, 4>()));
dpi_y_ =
static_cast<int32_t>(fxcrt::GetUInt32LSBFirst(span.subspan<42, 4>()));
}
}
*width = static_cast<int32_t>(width_);
*height = static_cast<int32_t>(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<const uint8_t> src_span = codec_memory_->GetBufferSpan();
rust::Slice<const uint8_t> 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<uint8_t> decoded_buf(buf_size.ValueOrDie());
rust::Slice<uint8_t> 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<const uint8_t> buf_span(decoded_buf);
for (size_t y = 0; y < height_; ++y) {
pdfium::span<const uint8_t> row_span =
buf_span.subspan(y * row_bytes, row_bytes);
delegate_->ResampleScanline(static_cast<int>(y), row_span);
}
return ProgressiveDecoderContext::Status::kSuccess;
}
} // namespace fxcodec