Commit a0768e419064b66ea6eb3e06a4398806b24311e8
1 parent
9eb8c915
Add QPDF::emptyPDF() and pdf_from_scratch test code
Showing
10 changed files
with
254 additions
and
5 deletions
ChangeLog
| 1 | 2012-06-21 Jay Berkenbilt <ejb@ql.org> | 1 | 2012-06-21 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | ||
| 3 | + * Add QPDF::emptyPDF() to create an empty QPDF object suitable for | ||
| 4 | + adding pages and other objects to. pdf_from_scratch.cc is test | ||
| 5 | + code that exercises it. | ||
| 6 | + | ||
| 3 | * make/libtool.mk: Place user-specified CPPFLAGS and LDFLAGS later | 7 | * make/libtool.mk: Place user-specified CPPFLAGS and LDFLAGS later |
| 4 | in the compilation so that if a user installs things in a | 8 | in the compilation so that if a user installs things in a |
| 5 | non-standard place that they have to tell the build about, earlier | 9 | non-standard place that they have to tell the build about, earlier |
TODO
| @@ -13,6 +13,35 @@ Next | @@ -13,6 +13,35 @@ Next | ||
| 13 | - update README-windows.txt docs to indicate that MSVC 2010 is the | 13 | - update README-windows.txt docs to indicate that MSVC 2010 is the |
| 14 | supported version and to update the information about mingw. | 14 | supported version and to update the information about mingw. |
| 15 | 15 | ||
| 16 | + * Testing for files > 4GB | ||
| 17 | + | ||
| 18 | + - Create a PDF from scratch. Each page has a page number as text | ||
| 19 | + and an image. The image can be 5000x5000 pixels using 8-bit | ||
| 20 | + gray scale. It will be divided into 10 stripes of 500 pixels | ||
| 21 | + each. The left and right 500 pixels of each stripe will | ||
| 22 | + alternate black and white. The remaining part of the image will | ||
| 23 | + have white stripes indicating 1 and black stripes indicating 0 | ||
| 24 | + with the most-significant bit on top to indicate the page | ||
| 25 | + number. In this way, every page will be unique and will consume | ||
| 26 | + approximately 25 megabytes. Creating 200 pages like this will | ||
| 27 | + make a file that is 5 GB. | ||
| 28 | + | ||
| 29 | + - The file will have to have object streams since a regular xref | ||
| 30 | + table won't be able to support offsets that large. | ||
| 31 | + | ||
| 32 | + - A separate test program can create this file and do various | ||
| 33 | + manipulations on it. This can be enabled with an environment | ||
| 34 | + variable controlled by configure in much the same way image | ||
| 35 | + comparison tests are enabled now. The argument to | ||
| 36 | + --enable-large-file-test should be a path that has enough disk | ||
| 37 | + space to do the tests, probably enough space for two coipes of | ||
| 38 | + the file. The test program should also have an interactive mode | ||
| 39 | + so we can generate the large file and then look at it with a | ||
| 40 | + PDF viewer like Adobe Reader. | ||
| 41 | + | ||
| 42 | + * Consider adding an example that uses the page APIs, or update the | ||
| 43 | + documentation to refer the user to the test suite. | ||
| 44 | + | ||
| 16 | Soon | 45 | Soon |
| 17 | ==== | 46 | ==== |
| 18 | 47 | ||
| @@ -24,8 +53,6 @@ Soon | @@ -24,8 +53,6 @@ Soon | ||
| 24 | * See if I can support the new encryption formats mentioned in the | 53 | * See if I can support the new encryption formats mentioned in the |
| 25 | open bug on sourceforge. Check other sourceforge bugs. | 54 | open bug on sourceforge. Check other sourceforge bugs. |
| 26 | 55 | ||
| 27 | - * Would be nice to confirm that it's working for > 4GB files. | ||
| 28 | - | ||
| 29 | * Splitting/merging concepts | 56 | * Splitting/merging concepts |
| 30 | 57 | ||
| 31 | newPDF() could create a PDF with just a trailer, no pages, and a | 58 | newPDF() could create a PDF with just a trailer, no pages, and a |
include/qpdf/QPDF.hh
| @@ -69,6 +69,16 @@ class QPDF | @@ -69,6 +69,16 @@ class QPDF | ||
| 69 | char const* buf, size_t length, | 69 | char const* buf, size_t length, |
| 70 | char const* password = 0); | 70 | char const* password = 0); |
| 71 | 71 | ||
| 72 | + // Create a QPDF object for an empty PDF. This PDF has no pages | ||
| 73 | + // or objects other than a minimal trailer, a document catalog, | ||
| 74 | + // and a /Pages tree containing zero pages. Pages and other | ||
| 75 | + // objects can be added to the file in the normal way, and the | ||
| 76 | + // trailer and document catalog can be mutated. Calling this | ||
| 77 | + // method is equivalent to calling processFile on an equivalent | ||
| 78 | + // PDF file. | ||
| 79 | + QPDF_DLL | ||
| 80 | + void emptyPDF(); | ||
| 81 | + | ||
| 72 | // Parameter settings | 82 | // Parameter settings |
| 73 | 83 | ||
| 74 | // By default, warning messages are issued to std::cerr and output | 84 | // By default, warning messages are issued to std::cerr and output |
libqpdf/QPDF.cc
| @@ -17,6 +17,24 @@ | @@ -17,6 +17,24 @@ | ||
| 17 | 17 | ||
| 18 | std::string QPDF::qpdf_version = "2.3.1"; | 18 | std::string QPDF::qpdf_version = "2.3.1"; |
| 19 | 19 | ||
| 20 | +static char const* EMPTY_PDF = | ||
| 21 | + "%PDF-1.3\n" | ||
| 22 | + "1 0 obj\n" | ||
| 23 | + "<< /Type /Catalog /Pages 2 0 R >>\n" | ||
| 24 | + "endobj\n" | ||
| 25 | + "2 0 obj\n" | ||
| 26 | + "<< /Type /Pages /Kids [] /Count 0 >>\n" | ||
| 27 | + "endobj\n" | ||
| 28 | + "xref\n" | ||
| 29 | + "0 3\n" | ||
| 30 | + "0000000000 65535 f \n" | ||
| 31 | + "0000000009 00000 n \n" | ||
| 32 | + "0000000058 00000 n \n" | ||
| 33 | + "trailer << /Size 3 /Root 1 0 R >>\n" | ||
| 34 | + "startxref\n" | ||
| 35 | + "110\n" | ||
| 36 | + "%%EOF\n"; | ||
| 37 | + | ||
| 20 | void | 38 | void |
| 21 | QPDF::InputSource::setLastOffset(qpdf_offset_t offset) | 39 | QPDF::InputSource::setLastOffset(qpdf_offset_t offset) |
| 22 | { | 40 | { |
| @@ -350,6 +368,12 @@ QPDF::processMemoryFile(char const* description, | @@ -350,6 +368,12 @@ QPDF::processMemoryFile(char const* description, | ||
| 350 | } | 368 | } |
| 351 | 369 | ||
| 352 | void | 370 | void |
| 371 | +QPDF::emptyPDF() | ||
| 372 | +{ | ||
| 373 | + processMemoryFile("empty file", EMPTY_PDF, strlen(EMPTY_PDF)); | ||
| 374 | +} | ||
| 375 | + | ||
| 376 | +void | ||
| 353 | QPDF::setIgnoreXRefStreams(bool val) | 377 | QPDF::setIgnoreXRefStreams(bool val) |
| 354 | { | 378 | { |
| 355 | this->ignore_xref_streams = val; | 379 | this->ignore_xref_streams = val; |
libqpdf/QPDF_optimization.cc
| @@ -365,7 +365,7 @@ QPDF::optimizePagesTreeInternal( | @@ -365,7 +365,7 @@ QPDF::optimizePagesTreeInternal( | ||
| 365 | throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), | 365 | throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), |
| 366 | this->last_object_description, | 366 | this->last_object_description, |
| 367 | this->file->getLastOffset(), | 367 | this->file->getLastOffset(), |
| 368 | - "invalid Type in page tree"); | 368 | + "invalid Type " + type + " in page tree"); |
| 369 | } | 369 | } |
| 370 | } | 370 | } |
| 371 | 371 |
libqpdf/QPDF_pages.cc
| @@ -73,7 +73,7 @@ QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages, | @@ -73,7 +73,7 @@ QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages, | ||
| 73 | throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), | 73 | throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(), |
| 74 | this->last_object_description, | 74 | this->last_object_description, |
| 75 | this->file->getLastOffset(), | 75 | this->file->getLastOffset(), |
| 76 | - ": invalid Type in page tree"); | 76 | + "invalid Type " + type + " in page tree"); |
| 77 | } | 77 | } |
| 78 | } | 78 | } |
| 79 | 79 |
qpdf/build.mk
| 1 | -BINS_qpdf = qpdf test_driver | 1 | +BINS_qpdf = qpdf test_driver pdf_from_scratch |
| 2 | CBINS_qpdf = qpdf-ctest | 2 | CBINS_qpdf = qpdf-ctest |
| 3 | 3 | ||
| 4 | TARGETS_qpdf = $(foreach B,$(BINS_qpdf) $(CBINS_qpdf),qpdf/$(OUTPUT_DIR)/$(call binname,$(B))) | 4 | TARGETS_qpdf = $(foreach B,$(BINS_qpdf) $(CBINS_qpdf),qpdf/$(OUTPUT_DIR)/$(call binname,$(B))) |
qpdf/pdf_from_scratch.cc
0 → 100644
| 1 | +#include <qpdf/QPDF.hh> | ||
| 2 | + | ||
| 3 | +#include <qpdf/QUtil.hh> | ||
| 4 | +#include <qpdf/QTC.hh> | ||
| 5 | +#include <qpdf/QPDFWriter.hh> | ||
| 6 | +#include <qpdf/QPDFObjectHandle.hh> | ||
| 7 | +#include <iostream> | ||
| 8 | +#include <stdio.h> | ||
| 9 | +#include <string.h> | ||
| 10 | +#include <stdlib.h> | ||
| 11 | +#include <assert.h> | ||
| 12 | + | ||
| 13 | +static char const* whoami = 0; | ||
| 14 | + | ||
| 15 | +void usage() | ||
| 16 | +{ | ||
| 17 | + std::cerr << "Usage: " << whoami << " n" << std::endl; | ||
| 18 | + exit(2); | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +static QPDFObjectHandle createPageContents(QPDF& pdf, std::string const& text) | ||
| 22 | +{ | ||
| 23 | + std::string contents = "BT /F1 15 Tf 72 720 Td (" + text + ") Tj ET\n"; | ||
| 24 | + PointerHolder<Buffer> b = new Buffer(contents.length()); | ||
| 25 | + unsigned char* bp = b->getBuffer(); | ||
| 26 | + memcpy(bp, (char*)contents.c_str(), contents.length()); | ||
| 27 | + return QPDFObjectHandle::newStream(&pdf, b); | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +QPDFObjectHandle newName(std::string const& name) | ||
| 31 | +{ | ||
| 32 | + return QPDFObjectHandle::newName(name); | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +void runtest(int n) | ||
| 36 | +{ | ||
| 37 | + QPDF pdf; | ||
| 38 | + pdf.emptyPDF(); | ||
| 39 | + if (n == 0) | ||
| 40 | + { | ||
| 41 | + // Create a minimal PDF from scratch. | ||
| 42 | + | ||
| 43 | + std::map<std::string, QPDFObjectHandle> keys; | ||
| 44 | + std::vector<QPDFObjectHandle> items; | ||
| 45 | + | ||
| 46 | + keys.clear(); | ||
| 47 | + keys["/Type"] = newName("/Font"); | ||
| 48 | + keys["/Subtype"] = newName("/Type1"); | ||
| 49 | + keys["/Name"] = newName("/F1"); | ||
| 50 | + keys["/BaseFont"] = newName("/Helvetica"); | ||
| 51 | + keys["/Encoding"] = newName("/WinAnsiEncoding"); | ||
| 52 | + QPDFObjectHandle font = pdf.makeIndirectObject( | ||
| 53 | + QPDFObjectHandle::newDictionary(keys)); | ||
| 54 | + | ||
| 55 | + items.clear(); | ||
| 56 | + items.push_back(newName("/PDF")); | ||
| 57 | + items.push_back(newName("/Text")); | ||
| 58 | + QPDFObjectHandle procset = pdf.makeIndirectObject( | ||
| 59 | + QPDFObjectHandle::newArray(items)); | ||
| 60 | + | ||
| 61 | + QPDFObjectHandle contents = createPageContents(pdf, "First Page"); | ||
| 62 | + | ||
| 63 | + items.clear(); | ||
| 64 | + items.push_back(QPDFObjectHandle::newInteger(0)); | ||
| 65 | + items.push_back(QPDFObjectHandle::newInteger(0)); | ||
| 66 | + items.push_back(QPDFObjectHandle::newInteger(612)); | ||
| 67 | + items.push_back(QPDFObjectHandle::newInteger(792)); | ||
| 68 | + QPDFObjectHandle mediabox = QPDFObjectHandle::newArray(items); | ||
| 69 | + | ||
| 70 | + keys.clear(); | ||
| 71 | + keys["/F1"] = font; | ||
| 72 | + QPDFObjectHandle rfont = QPDFObjectHandle::newDictionary(keys); | ||
| 73 | + | ||
| 74 | + keys.clear(); | ||
| 75 | + keys["/ProcSet"] = procset; | ||
| 76 | + keys["/Font"] = rfont; | ||
| 77 | + QPDFObjectHandle resources = QPDFObjectHandle::newDictionary(keys); | ||
| 78 | + | ||
| 79 | + keys.clear(); | ||
| 80 | + keys["/Type"] = newName("/Page"); | ||
| 81 | + keys["/MediaBox"] = mediabox; | ||
| 82 | + keys["/Contents"] = contents; | ||
| 83 | + keys["/Resources"] = resources; | ||
| 84 | + QPDFObjectHandle page = pdf.makeIndirectObject( | ||
| 85 | + QPDFObjectHandle::newDictionary(keys)); | ||
| 86 | + | ||
| 87 | + pdf.addPage(page, true); | ||
| 88 | + | ||
| 89 | + QPDFWriter w(pdf, "a.pdf"); | ||
| 90 | + w.setStaticID(true); | ||
| 91 | + w.setStreamDataMode(qpdf_s_preserve); | ||
| 92 | + w.write(); | ||
| 93 | + } | ||
| 94 | + else | ||
| 95 | + { | ||
| 96 | + throw std::runtime_error(std::string("invalid test ") + | ||
| 97 | + QUtil::int_to_string(n)); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + std::cout << "test " << n << " done" << std::endl; | ||
| 101 | +} | ||
| 102 | + | ||
| 103 | +int main(int argc, char* argv[]) | ||
| 104 | +{ | ||
| 105 | + QUtil::setLineBuf(stdout); | ||
| 106 | + if ((whoami = strrchr(argv[0], '/')) == NULL) | ||
| 107 | + { | ||
| 108 | + whoami = argv[0]; | ||
| 109 | + } | ||
| 110 | + else | ||
| 111 | + { | ||
| 112 | + ++whoami; | ||
| 113 | + } | ||
| 114 | + // For libtool's sake.... | ||
| 115 | + if (strncmp(whoami, "lt-", 3) == 0) | ||
| 116 | + { | ||
| 117 | + whoami += 3; | ||
| 118 | + } | ||
| 119 | + | ||
| 120 | + if (argc != 2) | ||
| 121 | + { | ||
| 122 | + usage(); | ||
| 123 | + } | ||
| 124 | + | ||
| 125 | + try | ||
| 126 | + { | ||
| 127 | + int n = atoi(argv[1]); | ||
| 128 | + runtest(n); | ||
| 129 | + } | ||
| 130 | + catch (std::exception& e) | ||
| 131 | + { | ||
| 132 | + std::cerr << e.what() << std::endl; | ||
| 133 | + exit(2); | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + return 0; | ||
| 137 | +} |
qpdf/qtest/qpdf.test
| @@ -352,6 +352,17 @@ $td->runtest("shallow copy a stream", | @@ -352,6 +352,17 @@ $td->runtest("shallow copy a stream", | ||
| 352 | 352 | ||
| 353 | show_ntests(); | 353 | show_ntests(); |
| 354 | # ---------- | 354 | # ---------- |
| 355 | +$td->notify("--- PDF From Scratch ---"); | ||
| 356 | +$n_tests += 2; | ||
| 357 | + | ||
| 358 | +$td->runtest("basic qpdf from scratch", | ||
| 359 | + {$td->COMMAND => "pdf_from_scratch 0"}, | ||
| 360 | + {$td->STRING => "test 0 done\n", $td->EXIT_STATUS => 0}, | ||
| 361 | + $td->NORMALIZE_NEWLINES); | ||
| 362 | +$td->runtest("check output", | ||
| 363 | + {$td->FILE => "a.pdf"}, | ||
| 364 | + {$td->FILE => "from-scratch-0.pdf"}); | ||
| 365 | +# ---------- | ||
| 355 | $td->notify("--- Error Condition Tests ---"); | 366 | $td->notify("--- Error Condition Tests ---"); |
| 356 | # $n_tests incremented after initialization of badfiles below. | 367 | # $n_tests incremented after initialization of badfiles below. |
| 357 | 368 |
qpdf/qtest/qpdf/from-scratch-0.pdf
0 → 100644
| 1 | +%PDF-1.3 | ||
| 2 | +%¿÷¢þ | ||
| 3 | +1 0 obj | ||
| 4 | +<< /Pages 2 0 R /Type /Catalog >> | ||
| 5 | +endobj | ||
| 6 | +2 0 obj | ||
| 7 | +<< /Count 1 /Kids [ 3 0 R ] /Type /Pages >> | ||
| 8 | +endobj | ||
| 9 | +3 0 obj | ||
| 10 | +<< /Contents 4 0 R /MediaBox [ 0 0 612 792 ] /Parent 2 0 R /Resources << /Font << /F1 5 0 R >> /ProcSet 6 0 R >> /Type /Page >> | ||
| 11 | +endobj | ||
| 12 | +4 0 obj | ||
| 13 | +<< /Length 42 >> | ||
| 14 | +stream | ||
| 15 | +BT /F1 15 Tf 72 720 Td (First Page) Tj ET | ||
| 16 | +endstream | ||
| 17 | +endobj | ||
| 18 | +5 0 obj | ||
| 19 | +<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >> | ||
| 20 | +endobj | ||
| 21 | +6 0 obj | ||
| 22 | +[ /PDF /Text ] | ||
| 23 | +endobj | ||
| 24 | +xref | ||
| 25 | +0 7 | ||
| 26 | +0000000000 65535 f | ||
| 27 | +0000000015 00000 n | ||
| 28 | +0000000064 00000 n | ||
| 29 | +0000000123 00000 n | ||
| 30 | +0000000266 00000 n | ||
| 31 | +0000000357 00000 n | ||
| 32 | +0000000464 00000 n | ||
| 33 | +trailer << /Root 1 0 R /Size 7 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >> | ||
| 34 | +startxref | ||
| 35 | +494 | ||
| 36 | +%%EOF |