2022-05-03 12:21:01 +00:00
|
|
|
#include <qpdf/assert_test.h>
|
|
|
|
|
2012-06-25 14:51:44 +00:00
|
|
|
// NOTE: This test program doesn't do anything special to enable large
|
|
|
|
// file support. This is important since it verifies that programs
|
|
|
|
// don't have to do anything special -- all the work is done
|
|
|
|
// internally by the library as long as they don't do their own file
|
|
|
|
// I/O.
|
|
|
|
|
2019-06-21 03:35:23 +00:00
|
|
|
#include <qpdf/QIntC.hh>
|
2012-06-24 19:26:28 +00:00
|
|
|
#include <qpdf/QPDF.hh>
|
|
|
|
#include <qpdf/QPDFObjectHandle.hh>
|
2018-06-18 20:38:15 +00:00
|
|
|
#include <qpdf/QPDFPageDocumentHelper.hh>
|
2012-06-24 19:26:28 +00:00
|
|
|
#include <qpdf/QPDFWriter.hh>
|
|
|
|
#include <qpdf/QUtil.hh>
|
2023-05-20 11:22:32 +00:00
|
|
|
#include <cstdlib>
|
|
|
|
#include <cstring>
|
2012-06-24 19:26:28 +00:00
|
|
|
#include <iostream>
|
2022-03-07 13:46:53 +00:00
|
|
|
|
2012-06-24 19:26:28 +00:00
|
|
|
// Run "test_large_file write small a.pdf" to get a PDF file that you
|
|
|
|
// can look at in a reader.
|
|
|
|
|
|
|
|
// This program reads and writes specially crafted files for testing
|
|
|
|
// large file support. In write mode, write a file of npages pages
|
|
|
|
// where each page contains unique text and a unique image. The image
|
|
|
|
// is a binary representation of the page number. The image contains
|
|
|
|
// horizontal stripes with light stripes representing 1, dark stripes
|
|
|
|
// representing 0, and the high bit on top. In read mode, read the
|
|
|
|
// file back checking to make sure all the image data and page
|
|
|
|
// contents are as expected.
|
|
|
|
|
|
|
|
// Running this is small mode produces a small file that is easy to
|
|
|
|
// look at in any viewer. Since there is no question about proper
|
|
|
|
// functionality for small files, writing and reading the small file
|
|
|
|
// allows the qpdf library to test this test program. Writing and
|
|
|
|
// reading the large file then allows us to verify large file support
|
|
|
|
// with confidence.
|
|
|
|
|
2023-05-20 11:46:50 +00:00
|
|
|
static char const* whoami = nullptr;
|
2012-06-24 19:26:28 +00:00
|
|
|
|
|
|
|
// Height should be a multiple of 10
|
2019-06-21 03:35:23 +00:00
|
|
|
static size_t const nstripes = 10;
|
|
|
|
static size_t const stripesize_large = 500;
|
|
|
|
static size_t const stripesize_small = 5;
|
|
|
|
static size_t const npages = 200;
|
2012-06-24 19:26:28 +00:00
|
|
|
|
|
|
|
// initialized in main
|
2019-06-21 03:35:23 +00:00
|
|
|
size_t stripesize = 0;
|
|
|
|
size_t width = 0;
|
|
|
|
size_t height = 0;
|
2023-05-20 11:46:50 +00:00
|
|
|
static unsigned char* buf = nullptr;
|
2012-06-24 19:26:28 +00:00
|
|
|
|
2019-06-21 03:35:23 +00:00
|
|
|
static inline unsigned char
|
|
|
|
get_pixel_color(size_t n, size_t row)
|
2012-06-24 19:26:28 +00:00
|
|
|
{
|
2019-06-21 03:35:23 +00:00
|
|
|
return (
|
|
|
|
(n & (1LLU << (nstripes - 1LLU - row))) ? static_cast<unsigned char>('\xc0')
|
|
|
|
: static_cast<unsigned char>('\x40'));
|
2012-06-24 19:26:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class ImageChecker: public Pipeline
|
|
|
|
{
|
|
|
|
public:
|
2019-06-21 03:35:23 +00:00
|
|
|
ImageChecker(size_t n);
|
2023-06-01 14:21:32 +00:00
|
|
|
~ImageChecker() override = default;
|
|
|
|
void write(unsigned char const* data, size_t len) override;
|
|
|
|
void finish() override;
|
2012-06-24 19:26:28 +00:00
|
|
|
|
|
|
|
private:
|
2019-06-21 03:35:23 +00:00
|
|
|
size_t n;
|
2023-06-01 13:12:39 +00:00
|
|
|
size_t offset{0};
|
|
|
|
bool okay{true};
|
2012-06-24 19:26:28 +00:00
|
|
|
};
|
|
|
|
|
2019-06-21 03:35:23 +00:00
|
|
|
ImageChecker::ImageChecker(size_t n) :
|
2023-05-20 11:46:50 +00:00
|
|
|
Pipeline("image checker", nullptr),
|
2023-06-01 13:12:39 +00:00
|
|
|
n(n)
|
2012-06-24 19:26:28 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2022-05-03 21:43:07 +00:00
|
|
|
ImageChecker::write(unsigned char const* data, size_t len)
|
2012-06-24 19:26:28 +00:00
|
|
|
{
|
|
|
|
for (size_t i = 0; i < len; ++i) {
|
2012-06-26 01:01:55 +00:00
|
|
|
size_t y = (this->offset + i) / width / stripesize;
|
2012-06-24 19:26:28 +00:00
|
|
|
unsigned char color = get_pixel_color(n, y);
|
|
|
|
if (data[i] != color) {
|
|
|
|
okay = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this->offset += len;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ImageChecker::finish()
|
|
|
|
{
|
|
|
|
if (!okay) {
|
|
|
|
std::cout << "errors found checking image data for page " << n << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ImageProvider: public QPDFObjectHandle::StreamDataProvider
|
|
|
|
{
|
|
|
|
public:
|
2019-06-21 03:35:23 +00:00
|
|
|
ImageProvider(size_t n);
|
2023-06-01 14:21:32 +00:00
|
|
|
~ImageProvider() override = default;
|
|
|
|
void provideStreamData(int objid, int generation, Pipeline* pipeline) override;
|
2012-06-24 19:26:28 +00:00
|
|
|
|
|
|
|
private:
|
2019-06-21 03:35:23 +00:00
|
|
|
size_t n;
|
2012-06-24 19:26:28 +00:00
|
|
|
};
|
|
|
|
|
2019-06-21 03:35:23 +00:00
|
|
|
ImageProvider::ImageProvider(size_t n) :
|
2012-06-24 19:26:28 +00:00
|
|
|
n(n)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ImageProvider::provideStreamData(int objid, int generation, Pipeline* pipeline)
|
|
|
|
{
|
2023-05-20 11:46:50 +00:00
|
|
|
if (buf == nullptr) {
|
2012-06-24 19:26:28 +00:00
|
|
|
buf = new unsigned char[width * stripesize];
|
|
|
|
}
|
|
|
|
std::cout << "page " << n << " of " << npages << std::endl;
|
2019-06-21 03:35:23 +00:00
|
|
|
for (size_t y = 0; y < nstripes; ++y) {
|
2012-06-24 19:26:28 +00:00
|
|
|
unsigned char color = get_pixel_color(n, y);
|
2013-02-24 02:46:21 +00:00
|
|
|
memset(buf, color, width * stripesize);
|
2012-06-24 19:26:28 +00:00
|
|
|
pipeline->write(buf, width * stripesize);
|
|
|
|
}
|
|
|
|
pipeline->finish();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
usage()
|
|
|
|
{
|
|
|
|
std::cerr << "Usage: " << whoami << " {read|write} {large|small} outfile" << std::endl;
|
|
|
|
exit(2);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
set_parameters(bool large)
|
|
|
|
{
|
|
|
|
stripesize = large ? stripesize_large : stripesize_small;
|
|
|
|
height = nstripes * stripesize;
|
|
|
|
width = height;
|
|
|
|
}
|
|
|
|
|
2019-06-21 03:35:23 +00:00
|
|
|
std::string
|
|
|
|
generate_page_contents(size_t pageno)
|
2012-06-24 19:26:28 +00:00
|
|
|
{
|
2019-06-21 03:35:23 +00:00
|
|
|
std::string contents = "BT /F1 24 Tf 72 720 Td (page " + QUtil::uint_to_string(pageno) +
|
2012-06-24 19:26:28 +00:00
|
|
|
") Tj ET\n"
|
|
|
|
"q 468 0 0 468 72 72 cm /Im1 Do Q\n";
|
|
|
|
return contents;
|
|
|
|
}
|
|
|
|
|
2019-06-21 03:35:23 +00:00
|
|
|
static QPDFObjectHandle
|
|
|
|
create_page_contents(QPDF& pdf, size_t pageno)
|
2012-06-24 19:26:28 +00:00
|
|
|
{
|
2012-06-27 04:00:58 +00:00
|
|
|
return QPDFObjectHandle::newStream(&pdf, generate_page_contents(pageno));
|
2012-06-24 19:26:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QPDFObjectHandle
|
|
|
|
newName(std::string const& name)
|
|
|
|
{
|
|
|
|
return QPDFObjectHandle::newName(name);
|
|
|
|
}
|
|
|
|
|
2019-06-21 03:35:23 +00:00
|
|
|
QPDFObjectHandle
|
|
|
|
newInteger(size_t val)
|
2012-06-24 19:26:28 +00:00
|
|
|
{
|
2019-06-21 03:35:23 +00:00
|
|
|
return QPDFObjectHandle::newInteger(QIntC::to_longlong(val));
|
2012-06-24 19:26:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
create_pdf(char const* filename)
|
|
|
|
{
|
|
|
|
QPDF pdf;
|
|
|
|
|
|
|
|
pdf.emptyPDF();
|
|
|
|
|
|
|
|
QPDFObjectHandle font = pdf.makeIndirectObject(QPDFObjectHandle::newDictionary());
|
2022-05-17 22:28:50 +00:00
|
|
|
font.replaceKey("/Type", newName("/Font"));
|
|
|
|
font.replaceKey("/Subtype", newName("/Type1"));
|
|
|
|
font.replaceKey("/Name", newName("/F1"));
|
|
|
|
font.replaceKey("/BaseFont", newName("/Helvetica"));
|
|
|
|
font.replaceKey("/Encoding", newName("/WinAnsiEncoding"));
|
2012-06-24 19:26:28 +00:00
|
|
|
|
|
|
|
QPDFObjectHandle procset = pdf.makeIndirectObject(QPDFObjectHandle::newArray());
|
2022-05-17 22:28:50 +00:00
|
|
|
procset.appendItem(newName("/PDF"));
|
|
|
|
procset.appendItem(newName("/Text"));
|
|
|
|
procset.appendItem(newName("/ImageC"));
|
2012-06-24 19:26:28 +00:00
|
|
|
|
|
|
|
QPDFObjectHandle rfont = QPDFObjectHandle::newDictionary();
|
|
|
|
rfont.replaceKey("/F1", font);
|
|
|
|
|
|
|
|
QPDFObjectHandle mediabox = QPDFObjectHandle::newArray();
|
2022-05-17 22:28:50 +00:00
|
|
|
mediabox.appendItem(newInteger(0));
|
|
|
|
mediabox.appendItem(newInteger(0));
|
|
|
|
mediabox.appendItem(newInteger(612));
|
|
|
|
mediabox.appendItem(newInteger(792));
|
2012-06-24 19:26:28 +00:00
|
|
|
|
2018-06-18 20:38:15 +00:00
|
|
|
QPDFPageDocumentHelper dh(pdf);
|
2019-06-21 03:35:23 +00:00
|
|
|
for (size_t pageno = 1; pageno <= npages; ++pageno) {
|
2012-06-24 19:26:28 +00:00
|
|
|
QPDFObjectHandle image = QPDFObjectHandle::newStream(&pdf);
|
|
|
|
QPDFObjectHandle image_dict = image.getDict();
|
2022-05-17 22:28:50 +00:00
|
|
|
image_dict.replaceKey("/Type", newName("/XObject"));
|
|
|
|
image_dict.replaceKey("/Subtype", newName("/Image"));
|
|
|
|
image_dict.replaceKey("/ColorSpace", newName("/DeviceGray"));
|
|
|
|
image_dict.replaceKey("/BitsPerComponent", newInteger(8));
|
|
|
|
image_dict.replaceKey("/Width", newInteger(width));
|
|
|
|
image_dict.replaceKey("/Height", newInteger(height));
|
2023-05-20 12:24:10 +00:00
|
|
|
auto* p = new ImageProvider(pageno);
|
2022-04-09 18:49:10 +00:00
|
|
|
std::shared_ptr<QPDFObjectHandle::StreamDataProvider> provider(p);
|
2012-07-07 21:33:45 +00:00
|
|
|
image.replaceStreamData(provider, QPDFObjectHandle::newNull(), QPDFObjectHandle::newNull());
|
2012-06-24 19:26:28 +00:00
|
|
|
|
|
|
|
QPDFObjectHandle xobject = QPDFObjectHandle::newDictionary();
|
|
|
|
xobject.replaceKey("/Im1", image);
|
|
|
|
|
|
|
|
QPDFObjectHandle resources = QPDFObjectHandle::newDictionary();
|
2022-05-17 22:28:50 +00:00
|
|
|
resources.replaceKey("/ProcSet", procset);
|
|
|
|
resources.replaceKey("/Font", rfont);
|
|
|
|
resources.replaceKey("/XObject", xobject);
|
2012-06-24 19:26:28 +00:00
|
|
|
|
|
|
|
QPDFObjectHandle contents = create_page_contents(pdf, pageno);
|
|
|
|
|
|
|
|
QPDFObjectHandle page = pdf.makeIndirectObject(QPDFObjectHandle::newDictionary());
|
2022-05-17 22:28:50 +00:00
|
|
|
page.replaceKey("/Type", newName("/Page"));
|
|
|
|
page.replaceKey("/MediaBox", mediabox);
|
|
|
|
page.replaceKey("/Contents", contents);
|
|
|
|
page.replaceKey("/Resources", resources);
|
2012-06-24 19:26:28 +00:00
|
|
|
|
2018-06-18 20:38:15 +00:00
|
|
|
dh.addPage(page, false);
|
2012-06-24 19:26:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QPDFWriter w(pdf, filename);
|
|
|
|
w.setStaticID(true); // for testing only
|
|
|
|
w.setStreamDataMode(qpdf_s_preserve);
|
|
|
|
w.setObjectStreamMode(qpdf_o_disable);
|
|
|
|
w.write();
|
|
|
|
}
|
|
|
|
|
2019-06-21 03:35:23 +00:00
|
|
|
static void
|
|
|
|
check_page_contents(size_t pageno, QPDFObjectHandle page)
|
2012-06-24 19:26:28 +00:00
|
|
|
{
|
2022-04-09 18:49:10 +00:00
|
|
|
std::shared_ptr<Buffer> buf = page.getKey("/Contents").getStreamData();
|
2012-06-24 19:26:28 +00:00
|
|
|
std::string actual_contents =
|
2013-02-24 02:46:21 +00:00
|
|
|
std::string(reinterpret_cast<char*>(buf->getBuffer()), buf->getSize());
|
2012-06-24 19:26:28 +00:00
|
|
|
std::string expected_contents = generate_page_contents(pageno);
|
|
|
|
if (expected_contents != actual_contents) {
|
|
|
|
std::cout << "page contents wrong for page " << pageno << std::endl
|
|
|
|
<< "ACTUAL: " << actual_contents << "EXPECTED: " << expected_contents << "----\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-21 03:35:23 +00:00
|
|
|
static void
|
|
|
|
check_image(size_t pageno, QPDFObjectHandle page)
|
2012-06-24 19:26:28 +00:00
|
|
|
{
|
|
|
|
QPDFObjectHandle image = page.getKey("/Resources").getKey("/XObject").getKey("/Im1");
|
|
|
|
ImageChecker ic(pageno);
|
2017-08-19 13:18:14 +00:00
|
|
|
image.pipeStreamData(&ic, 0, qpdf_dl_specialized);
|
2012-06-24 19:26:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
check_pdf(char const* filename)
|
|
|
|
{
|
|
|
|
QPDF pdf;
|
|
|
|
pdf.processFile(filename);
|
|
|
|
std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
|
2019-06-21 03:35:23 +00:00
|
|
|
assert(pages.size() == QIntC::to_size(npages));
|
|
|
|
for (size_t i = 0; i < npages; ++i) {
|
|
|
|
size_t pageno = i + 1;
|
2012-06-24 19:26:28 +00:00
|
|
|
std::cout << "page " << pageno << " of " << npages << std::endl;
|
2013-10-05 23:42:39 +00:00
|
|
|
check_page_contents(pageno, pages.at(i));
|
|
|
|
check_image(pageno, pages.at(i));
|
2012-06-24 19:26:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main(int argc, char* argv[])
|
|
|
|
{
|
|
|
|
whoami = QUtil::getWhoami(argv[0]);
|
|
|
|
QUtil::setLineBuf(stdout);
|
|
|
|
|
|
|
|
if (argc != 4) {
|
2022-02-08 14:18:08 +00:00
|
|
|
usage();
|
2012-06-24 19:26:28 +00:00
|
|
|
}
|
|
|
|
char const* operation = argv[1];
|
|
|
|
char const* size = argv[2];
|
|
|
|
char const* filename = argv[3];
|
|
|
|
|
|
|
|
bool op_write = false;
|
|
|
|
bool size_large = false;
|
|
|
|
|
|
|
|
if (strcmp(operation, "write") == 0) {
|
|
|
|
op_write = true;
|
|
|
|
} else if (strcmp(operation, "read") == 0) {
|
|
|
|
op_write = false;
|
|
|
|
} else {
|
|
|
|
usage();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strcmp(size, "large") == 0) {
|
|
|
|
size_large = true;
|
|
|
|
} else if (strcmp(size, "small") == 0) {
|
|
|
|
size_large = false;
|
|
|
|
} else {
|
|
|
|
usage();
|
|
|
|
}
|
|
|
|
|
|
|
|
set_parameters(size_large);
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (op_write) {
|
|
|
|
create_pdf(filename);
|
|
|
|
} else {
|
|
|
|
check_pdf(filename);
|
|
|
|
}
|
|
|
|
} catch (std::exception& e) {
|
2022-02-08 14:18:08 +00:00
|
|
|
std::cerr << e.what() << std::endl;
|
|
|
|
exit(2);
|
2012-06-24 19:26:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
delete[] buf;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|