diff options
author | Jay Berkenbilt <ejb@ql.org> | 2012-06-24 21:26:28 +0200 |
---|---|---|
committer | Jay Berkenbilt <ejb@ql.org> | 2012-06-24 21:56:50 +0200 |
commit | 8318d81ada86d4ec8e343c47103932b6bbe45a42 (patch) | |
tree | a1ff22dc1584d84829d32c7b1d8698a332877763 /qpdf/test_large_file.cc | |
parent | 781c313058e26b6ab6fda060a652a395d27cdb7a (diff) | |
download | qpdf-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.cc | 368 |
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; +} |