blob: 673e14d7551594328aa19eca53a66632b054fb72 [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/fax/faxmodule.h"
#include <stdint.h>
#include <array>
#include <memory>
#include "core/fxcodec/scanlinedecoder.h"
#include "core/fxcrt/data_vector.h"
#include "core/fxcrt/span.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace fxcodec {
namespace {
// Real-world 44x46 1-D CCITT Group 3 encoded fax stream (containing 38 rows).
constexpr uint8_t kFax1DData[] = {
0x26, 0xa0, 0x86, 0x80, 0xde, 0x08, 0x1e, 0x11, 0xe1, 0x87, 0x84,
0x08, 0x30, 0xf4, 0x13, 0x7a, 0x4d, 0xf0, 0xef, 0x4d, 0xbe, 0xdb,
0xff, 0xff, 0x6f, 0xfd, 0xaf, 0x0c, 0x2f, 0x1f, 0xff, 0x21, 0xb3,
0x4c, 0x82, 0xe3, 0x30, 0x44, 0xe7, 0xc1, 0x1a, 0x09, 0xc2, 0x05,
0xe1, 0x05, 0xe9, 0x7a, 0x5e, 0x97, 0xa5, 0xff, 0xa5, 0xfe, 0x0a,
0x3f, 0x5f, 0xfa, 0xff, 0xfb, 0x5c, 0x9a, 0xaa, 0x30, 0xbe, 0xd2,
0x7a, 0xdb, 0x4a, 0xd7, 0x0d, 0xa4, 0xda, 0x5b, 0x61, 0x28, 0xf0,
0xc4, 0x26, 0xb2, 0x16, 0x81, 0x85, 0x00, 0x10, 0x01, 0x00, 0x0a,
};
// Real-world 548x238 2-D CCITT Group 4 encoded fax stream (containing
// 177 rows).
constexpr uint8_t kFaxG4Data[] = {
0x2e, 0x5e, 0x46, 0xc0, 0xd2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xc2, 0x20, 0x6c, 0x5e, 0x45, 0xc4, 0x5e, 0x3f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfc, 0x5e, 0x40, 0x40, 0x5e, 0x44,
};
} // namespace
TEST(FaxModuleTest, BadDimensions) {
// Reject non-positive dimensions.
EXPECT_FALSE(
FaxModule::CreateDecoder({}, 0, 10, 0, false, false, false, 0, 0));
EXPECT_FALSE(
FaxModule::CreateDecoder({}, 10, 0, 0, false, false, false, 0, 0));
EXPECT_FALSE(
FaxModule::CreateDecoder({}, -1, 10, 0, false, false, false, 0, 0));
EXPECT_FALSE(
FaxModule::CreateDecoder({}, 10, -1, 0, false, false, false, 0, 0));
// Reject dimensions exceeding `kFaxMaxImageDimension`.
EXPECT_FALSE(
FaxModule::CreateDecoder({}, 65536, 10, 0, false, false, false, 0, 0));
EXPECT_FALSE(
FaxModule::CreateDecoder({}, 10, 65536, 0, false, false, false, 0, 0));
// Reject invalid Columns and Rows overrides.
EXPECT_FALSE(
FaxModule::CreateDecoder({}, 10, 10, 0, false, false, false, -1, 10));
EXPECT_FALSE(
FaxModule::CreateDecoder({}, 10, 10, 0, false, false, false, 65536, 10));
EXPECT_FALSE(
FaxModule::CreateDecoder({}, 10, 10, 0, false, false, false, 10, -1));
EXPECT_FALSE(
FaxModule::CreateDecoder({}, 10, 10, 0, false, false, false, 10, 65536));
}
TEST(FaxModuleTest, EmptyInput) {
auto decoder =
FaxModule::CreateDecoder({}, 10, 10, 0, false, false, false, 0, 0);
ASSERT_TRUE(decoder);
EXPECT_EQ(10, decoder->GetWidth());
EXPECT_EQ(10, decoder->GetHeight());
EXPECT_TRUE(decoder->GetScanline(0).empty());
EXPECT_EQ(0u, decoder->GetSrcOffset());
}
TEST(FaxModuleTest, Group3_1D_Decode) {
auto decoder = FaxModule::CreateDecoder(kFax1DData, 44, 46, /*K=*/0,
/*EndOfLine=*/false,
/*EncodedByteAlign=*/false,
/*BlackIs1=*/false,
/*Columns=*/44, /*Rows=*/46);
ASSERT_TRUE(decoder);
EXPECT_EQ(44, decoder->GetWidth());
EXPECT_EQ(46, decoder->GetHeight());
// Data has 38 rows.
for (int i = 0; i < 38; ++i) {
pdfium::span<const uint8_t> line = decoder->GetScanline(i);
EXPECT_FALSE(line.empty()) << "Failed at line " << i;
EXPECT_GE(line.size(), 6u);
}
// Subsequent rows past the end of stream data should be empty.
EXPECT_TRUE(decoder->GetScanline(38).empty());
EXPECT_GT(decoder->GetSrcOffset(), 0u);
// Rewinding to line 0 should succeed and produce line 0 again.
pdfium::span<const uint8_t> line0 = decoder->GetScanline(0);
EXPECT_FALSE(line0.empty());
}
TEST(FaxModuleTest, Group3_1D_BlackIs1) {
auto normal_decoder = FaxModule::CreateDecoder(
kFax1DData, 44, 46, /*K=*/0, /*EndOfLine=*/false,
/*EncodedByteAlign=*/false, /*BlackIs1=*/false, /*Columns=*/44,
/*Rows=*/46);
auto black1_decoder = FaxModule::CreateDecoder(
kFax1DData, 44, 46, /*K=*/0, /*EndOfLine=*/false,
/*EncodedByteAlign=*/false, /*BlackIs1=*/true, /*Columns=*/44,
/*Rows=*/46);
ASSERT_TRUE(normal_decoder);
ASSERT_TRUE(black1_decoder);
for (int i = 0; i < 38; ++i) {
pdfium::span<const uint8_t> normal_line = normal_decoder->GetScanline(i);
pdfium::span<const uint8_t> black1_line = black1_decoder->GetScanline(i);
ASSERT_FALSE(normal_line.empty()) << "Line " << i;
ASSERT_EQ(normal_line.size(), black1_line.size()) << "Line " << i;
for (size_t j = 0; j < normal_line.size(); ++j) {
EXPECT_EQ(normal_line[j], static_cast<uint8_t>(~black1_line[j]))
<< "Mismatch at line " << i << " byte " << j;
}
}
}
TEST(FaxModuleTest, Group4_Decode) {
auto decoder = FaxModule::CreateDecoder(kFaxG4Data, 548, 238, /*K=*/-1,
/*EndOfLine=*/false,
/*EncodedByteAlign=*/false,
/*BlackIs1=*/false,
/*Columns=*/548, /*Rows=*/238);
ASSERT_TRUE(decoder);
EXPECT_EQ(548, decoder->GetWidth());
EXPECT_EQ(238, decoder->GetHeight());
// Data has 177 rows.
for (int i = 0; i < 177; ++i) {
pdfium::span<const uint8_t> line = decoder->GetScanline(i);
EXPECT_FALSE(line.empty()) << "Failed at line " << i;
EXPECT_GE(line.size(), 69u);
}
// Subsequent rows past the end of stream data should be empty.
EXPECT_TRUE(decoder->GetScanline(177).empty());
EXPECT_GT(decoder->GetSrcOffset(), 0u);
}
TEST(FaxModuleTest, Group4_FaxG4Decode) {
constexpr int kWidth = 548;
constexpr int kHeight = 238;
constexpr int kPitch = (kWidth + 7) / 8;
DataVector<uint8_t> dest_buf(kPitch * kHeight);
uint32_t end_bitpos = FaxModule::FaxG4Decode(
kFaxG4Data, /*starting_bitpos=*/0, kWidth, kHeight, kPitch, dest_buf);
EXPECT_GT(end_bitpos, 0u);
EXPECT_LE(end_bitpos, sizeof(kFaxG4Data) * 8);
}
} // namespace fxcodec