summaryrefslogtreecommitdiffstats
path: root/qpdf/test_large_file.cc
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2012-06-24 21:26:28 +0200
committerJay Berkenbilt <ejb@ql.org>2012-06-24 21:56:50 +0200
commit8318d81ada86d4ec8e343c47103932b6bbe45a42 (patch)
treea1ff22dc1584d84829d32c7b1d8698a332877763 /qpdf/test_large_file.cc
parent781c313058e26b6ab6fda060a652a395d27cdb7a (diff)
downloadqpdf-8318d81ada86d4ec8e343c47103932b6bbe45a42.tar.zst
Fix and test support for files >= 4 GB
Diffstat (limited to 'qpdf/test_large_file.cc')
-rw-r--r--qpdf/test_large_file.cc368
1 files changed, 368 insertions, 0 deletions
diff --git a/qpdf/test_large_file.cc b/qpdf/test_large_file.cc
new file mode 100644
index 00000000..c1ee4060
--- /dev/null
+++ b/qpdf/test_large_file.cc
@@ -0,0 +1,368 @@
+#include <qpdf/QPDF.hh>
+#include <qpdf/QPDFWriter.hh>
+#include <qpdf/QPDFObjectHandle.hh>
+#include <qpdf/QUtil.hh>
+#include <iostream>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+// 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.
+
+static char const* whoami = 0;
+
+// Height should be a multiple of 10
+static int const nstripes = 10;
+static int const stripesize_large = 500;
+static int const stripesize_small = 5;
+static int const npages = 200;
+
+// initialized in main
+int stripesize = 0;
+int width = 0;
+int height = 0;
+static unsigned char* buf = 0;
+
+static inline unsigned char get_pixel_color(int n, int row)
+{
+ return (n & (1 << (nstripes - 1 - row))) ? '\xc0' : '\x40';
+}
+
+class ImageChecker: public Pipeline
+{
+ public:
+ ImageChecker(int n);
+ virtual ~ImageChecker();
+ virtual void write(unsigned char* data, size_t len);
+ virtual void finish();
+
+ private:
+ int n;
+ size_t offset;
+ bool okay;
+};
+
+ImageChecker::ImageChecker(int n) :
+ Pipeline("image checker", 0),
+ n(n),
+ offset(0),
+ okay(true)
+{
+}
+
+ImageChecker::~ImageChecker()
+{
+}
+
+void
+ImageChecker::write(unsigned char* data, size_t len)
+{
+ for (size_t i = 0; i < len; ++i)
+ {
+ int y = (this->offset + i) / width / stripesize;
+ 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:
+ ImageProvider(int n);
+ virtual ~ImageProvider();
+ virtual void provideStreamData(int objid, int generation,
+ Pipeline* pipeline);
+ size_t getLength() const;
+
+ private:
+ int n;
+};
+
+ImageProvider::ImageProvider(int n) :
+ n(n)
+{
+}
+
+ImageProvider::~ImageProvider()
+{
+}
+
+void
+ImageProvider::provideStreamData(int objid, int generation,
+ Pipeline* pipeline)
+{
+ if (buf == 0)
+ {
+ buf = new unsigned char[width * stripesize];
+ }
+ std::cout << "page " << n << " of " << npages << std::endl;
+ for (int y = 0; y < nstripes; ++y)
+ {
+ unsigned char color = get_pixel_color(n, y);
+ memset(buf, (int) color, width * stripesize);
+ pipeline->write(buf, width * stripesize);
+ }
+ pipeline->finish();
+}
+
+size_t
+ImageProvider::getLength() const
+{
+ return width * height;
+}
+
+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;
+}
+
+std::string generate_page_contents(int pageno)
+{
+ std::string contents =
+ "BT /F1 24 Tf 72 720 Td (page " + QUtil::int_to_string(pageno) +
+ ") Tj ET\n"
+ "q 468 0 0 468 72 72 cm /Im1 Do Q\n";
+ return contents;
+}
+
+static QPDFObjectHandle create_page_contents(QPDF& pdf, int pageno)
+{
+ std::string contents = generate_page_contents(pageno);
+ PointerHolder<Buffer> b = new Buffer(contents.length());
+ unsigned char* bp = b->getBuffer();
+ memcpy(bp, (char*)contents.c_str(), contents.length());
+ return QPDFObjectHandle::newStream(&pdf, b);
+}
+
+QPDFObjectHandle newName(std::string const& name)
+{
+ return QPDFObjectHandle::newName(name);
+}
+
+QPDFObjectHandle newInteger(int val)
+{
+ return QPDFObjectHandle::newInteger(val);
+}
+
+static void create_pdf(char const* filename)
+{
+ QPDF pdf;
+
+ pdf.emptyPDF();
+
+ QPDFObjectHandle font = pdf.makeIndirectObject(
+ QPDFObjectHandle::newDictionary());
+ 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"));
+
+ QPDFObjectHandle procset =
+ pdf.makeIndirectObject(QPDFObjectHandle::newArray());
+ procset.appendItem(newName("/PDF"));
+ procset.appendItem(newName("/Text"));
+ procset.appendItem(newName("/ImageC"));
+
+ QPDFObjectHandle rfont = QPDFObjectHandle::newDictionary();
+ rfont.replaceKey("/F1", font);
+
+ QPDFObjectHandle mediabox = QPDFObjectHandle::newArray();
+ mediabox.appendItem(newInteger(0));
+ mediabox.appendItem(newInteger(0));
+ mediabox.appendItem(newInteger(612));
+ mediabox.appendItem(newInteger(792));
+
+ for (int pageno = 1; pageno <= npages; ++pageno)
+ {
+ QPDFObjectHandle image = QPDFObjectHandle::newStream(&pdf);
+ QPDFObjectHandle image_dict = image.getDict();
+ 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));
+ ImageProvider* p = new ImageProvider(pageno);
+ PointerHolder<QPDFObjectHandle::StreamDataProvider> provider(p);
+ image.replaceStreamData(provider,
+ QPDFObjectHandle::newNull(),
+ QPDFObjectHandle::newNull(),
+ p->getLength());
+
+ QPDFObjectHandle xobject = QPDFObjectHandle::newDictionary();
+ xobject.replaceKey("/Im1", image);
+
+ QPDFObjectHandle resources = QPDFObjectHandle::newDictionary();
+ resources.replaceKey("/ProcSet", procset);
+ resources.replaceKey("/Font", rfont);
+ resources.replaceKey("/XObject", xobject);
+
+ QPDFObjectHandle contents = create_page_contents(pdf, pageno);
+
+ QPDFObjectHandle page = pdf.makeIndirectObject(
+ QPDFObjectHandle::newDictionary());
+ page.replaceKey("/Type", newName("/Page"));
+ page.replaceKey("/MediaBox", mediabox);
+ page.replaceKey("/Contents", contents);
+ page.replaceKey("/Resources", resources);
+
+ pdf.addPage(page, false);
+ }
+
+ QPDFWriter w(pdf, filename);
+ w.setStaticID(true); // for testing only
+ w.setStreamDataMode(qpdf_s_preserve);
+ w.setObjectStreamMode(qpdf_o_disable);
+ w.write();
+}
+
+static void check_page_contents(int pageno, QPDFObjectHandle page)
+{
+ PointerHolder<Buffer> buf =
+ page.getKey("/Contents").getStreamData();
+ std::string actual_contents =
+ std::string((char *)(buf->getBuffer()), buf->getSize());
+ 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";
+ }
+}
+
+static void check_image(int pageno, QPDFObjectHandle page)
+{
+ QPDFObjectHandle image =
+ page.getKey("/Resources").getKey("/XObject").getKey("/Im1");
+ ImageChecker ic(pageno);
+ image.pipeStreamData(&ic, true, false, false);
+}
+
+static void check_pdf(char const* filename)
+{
+ QPDF pdf;
+ pdf.processFile(filename);
+ std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
+ assert(pages.size() == (size_t)npages);
+ for (int i = 0; i < npages; ++i)
+ {
+ int pageno = i + 1;
+ std::cout << "page " << pageno << " of " << npages << std::endl;
+ check_page_contents(pageno, pages[i]);
+ check_image(pageno, pages[i]);
+ }
+}
+
+int main(int argc, char* argv[])
+{
+ whoami = QUtil::getWhoami(argv[0]);
+ QUtil::setLineBuf(stdout);
+
+ // For libtool's sake....
+ if (strncmp(whoami, "lt-", 3) == 0)
+ {
+ whoami += 3;
+ }
+ if (argc != 4)
+ {
+ usage();
+ }
+ 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)
+ {
+ std::cerr << e.what() << std::endl;
+ exit(2);
+ }
+
+ delete [] buf;
+
+ return 0;
+}