mirror of
https://github.com/qpdf/qpdf.git
synced 2025-04-02 14:41:50 +00:00
Add QPDF::emptyPDF() and pdf_from_scratch test code
This commit is contained in:
parent
9eb8c9159b
commit
a0768e4190
@ -1,5 +1,9 @@
|
|||||||
2012-06-21 Jay Berkenbilt <ejb@ql.org>
|
2012-06-21 Jay Berkenbilt <ejb@ql.org>
|
||||||
|
|
||||||
|
* Add QPDF::emptyPDF() to create an empty QPDF object suitable for
|
||||||
|
adding pages and other objects to. pdf_from_scratch.cc is test
|
||||||
|
code that exercises it.
|
||||||
|
|
||||||
* make/libtool.mk: Place user-specified CPPFLAGS and LDFLAGS later
|
* make/libtool.mk: Place user-specified CPPFLAGS and LDFLAGS later
|
||||||
in the compilation so that if a user installs things in a
|
in the compilation so that if a user installs things in a
|
||||||
non-standard place that they have to tell the build about, earlier
|
non-standard place that they have to tell the build about, earlier
|
||||||
|
31
TODO
31
TODO
@ -13,6 +13,35 @@ Next
|
|||||||
- update README-windows.txt docs to indicate that MSVC 2010 is the
|
- update README-windows.txt docs to indicate that MSVC 2010 is the
|
||||||
supported version and to update the information about mingw.
|
supported version and to update the information about mingw.
|
||||||
|
|
||||||
|
* Testing for files > 4GB
|
||||||
|
|
||||||
|
- Create a PDF from scratch. Each page has a page number as text
|
||||||
|
and an image. The image can be 5000x5000 pixels using 8-bit
|
||||||
|
gray scale. It will be divided into 10 stripes of 500 pixels
|
||||||
|
each. The left and right 500 pixels of each stripe will
|
||||||
|
alternate black and white. The remaining part of the image will
|
||||||
|
have white stripes indicating 1 and black stripes indicating 0
|
||||||
|
with the most-significant bit on top to indicate the page
|
||||||
|
number. In this way, every page will be unique and will consume
|
||||||
|
approximately 25 megabytes. Creating 200 pages like this will
|
||||||
|
make a file that is 5 GB.
|
||||||
|
|
||||||
|
- The file will have to have object streams since a regular xref
|
||||||
|
table won't be able to support offsets that large.
|
||||||
|
|
||||||
|
- A separate test program can create this file and do various
|
||||||
|
manipulations on it. This can be enabled with an environment
|
||||||
|
variable controlled by configure in much the same way image
|
||||||
|
comparison tests are enabled now. The argument to
|
||||||
|
--enable-large-file-test should be a path that has enough disk
|
||||||
|
space to do the tests, probably enough space for two coipes of
|
||||||
|
the file. The test program should also have an interactive mode
|
||||||
|
so we can generate the large file and then look at it with a
|
||||||
|
PDF viewer like Adobe Reader.
|
||||||
|
|
||||||
|
* Consider adding an example that uses the page APIs, or update the
|
||||||
|
documentation to refer the user to the test suite.
|
||||||
|
|
||||||
Soon
|
Soon
|
||||||
====
|
====
|
||||||
|
|
||||||
@ -24,8 +53,6 @@ Soon
|
|||||||
* See if I can support the new encryption formats mentioned in the
|
* See if I can support the new encryption formats mentioned in the
|
||||||
open bug on sourceforge. Check other sourceforge bugs.
|
open bug on sourceforge. Check other sourceforge bugs.
|
||||||
|
|
||||||
* Would be nice to confirm that it's working for > 4GB files.
|
|
||||||
|
|
||||||
* Splitting/merging concepts
|
* Splitting/merging concepts
|
||||||
|
|
||||||
newPDF() could create a PDF with just a trailer, no pages, and a
|
newPDF() could create a PDF with just a trailer, no pages, and a
|
||||||
|
@ -69,6 +69,16 @@ class QPDF
|
|||||||
char const* buf, size_t length,
|
char const* buf, size_t length,
|
||||||
char const* password = 0);
|
char const* password = 0);
|
||||||
|
|
||||||
|
// Create a QPDF object for an empty PDF. This PDF has no pages
|
||||||
|
// or objects other than a minimal trailer, a document catalog,
|
||||||
|
// and a /Pages tree containing zero pages. Pages and other
|
||||||
|
// objects can be added to the file in the normal way, and the
|
||||||
|
// trailer and document catalog can be mutated. Calling this
|
||||||
|
// method is equivalent to calling processFile on an equivalent
|
||||||
|
// PDF file.
|
||||||
|
QPDF_DLL
|
||||||
|
void emptyPDF();
|
||||||
|
|
||||||
// Parameter settings
|
// Parameter settings
|
||||||
|
|
||||||
// By default, warning messages are issued to std::cerr and output
|
// By default, warning messages are issued to std::cerr and output
|
||||||
|
@ -17,6 +17,24 @@
|
|||||||
|
|
||||||
std::string QPDF::qpdf_version = "2.3.1";
|
std::string QPDF::qpdf_version = "2.3.1";
|
||||||
|
|
||||||
|
static char const* EMPTY_PDF =
|
||||||
|
"%PDF-1.3\n"
|
||||||
|
"1 0 obj\n"
|
||||||
|
"<< /Type /Catalog /Pages 2 0 R >>\n"
|
||||||
|
"endobj\n"
|
||||||
|
"2 0 obj\n"
|
||||||
|
"<< /Type /Pages /Kids [] /Count 0 >>\n"
|
||||||
|
"endobj\n"
|
||||||
|
"xref\n"
|
||||||
|
"0 3\n"
|
||||||
|
"0000000000 65535 f \n"
|
||||||
|
"0000000009 00000 n \n"
|
||||||
|
"0000000058 00000 n \n"
|
||||||
|
"trailer << /Size 3 /Root 1 0 R >>\n"
|
||||||
|
"startxref\n"
|
||||||
|
"110\n"
|
||||||
|
"%%EOF\n";
|
||||||
|
|
||||||
void
|
void
|
||||||
QPDF::InputSource::setLastOffset(qpdf_offset_t offset)
|
QPDF::InputSource::setLastOffset(qpdf_offset_t offset)
|
||||||
{
|
{
|
||||||
@ -349,6 +367,12 @@ QPDF::processMemoryFile(char const* description,
|
|||||||
parse(password);
|
parse(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDF::emptyPDF()
|
||||||
|
{
|
||||||
|
processMemoryFile("empty file", EMPTY_PDF, strlen(EMPTY_PDF));
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
QPDF::setIgnoreXRefStreams(bool val)
|
QPDF::setIgnoreXRefStreams(bool val)
|
||||||
{
|
{
|
||||||
|
@ -365,7 +365,7 @@ QPDF::optimizePagesTreeInternal(
|
|||||||
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
||||||
this->last_object_description,
|
this->last_object_description,
|
||||||
this->file->getLastOffset(),
|
this->file->getLastOffset(),
|
||||||
"invalid Type in page tree");
|
"invalid Type " + type + " in page tree");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ QPDF::getAllPagesInternal(QPDFObjectHandle cur_pages,
|
|||||||
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
throw QPDFExc(qpdf_e_damaged_pdf, this->file->getName(),
|
||||||
this->last_object_description,
|
this->last_object_description,
|
||||||
this->file->getLastOffset(),
|
this->file->getLastOffset(),
|
||||||
": invalid Type in page tree");
|
"invalid Type " + type + " in page tree");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
BINS_qpdf = qpdf test_driver
|
BINS_qpdf = qpdf test_driver pdf_from_scratch
|
||||||
CBINS_qpdf = qpdf-ctest
|
CBINS_qpdf = qpdf-ctest
|
||||||
|
|
||||||
TARGETS_qpdf = $(foreach B,$(BINS_qpdf) $(CBINS_qpdf),qpdf/$(OUTPUT_DIR)/$(call binname,$(B)))
|
TARGETS_qpdf = $(foreach B,$(BINS_qpdf) $(CBINS_qpdf),qpdf/$(OUTPUT_DIR)/$(call binname,$(B)))
|
||||||
|
137
qpdf/pdf_from_scratch.cc
Normal file
137
qpdf/pdf_from_scratch.cc
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
#include <qpdf/QPDF.hh>
|
||||||
|
|
||||||
|
#include <qpdf/QUtil.hh>
|
||||||
|
#include <qpdf/QTC.hh>
|
||||||
|
#include <qpdf/QPDFWriter.hh>
|
||||||
|
#include <qpdf/QPDFObjectHandle.hh>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
static char const* whoami = 0;
|
||||||
|
|
||||||
|
void usage()
|
||||||
|
{
|
||||||
|
std::cerr << "Usage: " << whoami << " n" << std::endl;
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QPDFObjectHandle createPageContents(QPDF& pdf, std::string const& text)
|
||||||
|
{
|
||||||
|
std::string contents = "BT /F1 15 Tf 72 720 Td (" + text + ") Tj ET\n";
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void runtest(int n)
|
||||||
|
{
|
||||||
|
QPDF pdf;
|
||||||
|
pdf.emptyPDF();
|
||||||
|
if (n == 0)
|
||||||
|
{
|
||||||
|
// Create a minimal PDF from scratch.
|
||||||
|
|
||||||
|
std::map<std::string, QPDFObjectHandle> keys;
|
||||||
|
std::vector<QPDFObjectHandle> items;
|
||||||
|
|
||||||
|
keys.clear();
|
||||||
|
keys["/Type"] = newName("/Font");
|
||||||
|
keys["/Subtype"] = newName("/Type1");
|
||||||
|
keys["/Name"] = newName("/F1");
|
||||||
|
keys["/BaseFont"] = newName("/Helvetica");
|
||||||
|
keys["/Encoding"] = newName("/WinAnsiEncoding");
|
||||||
|
QPDFObjectHandle font = pdf.makeIndirectObject(
|
||||||
|
QPDFObjectHandle::newDictionary(keys));
|
||||||
|
|
||||||
|
items.clear();
|
||||||
|
items.push_back(newName("/PDF"));
|
||||||
|
items.push_back(newName("/Text"));
|
||||||
|
QPDFObjectHandle procset = pdf.makeIndirectObject(
|
||||||
|
QPDFObjectHandle::newArray(items));
|
||||||
|
|
||||||
|
QPDFObjectHandle contents = createPageContents(pdf, "First Page");
|
||||||
|
|
||||||
|
items.clear();
|
||||||
|
items.push_back(QPDFObjectHandle::newInteger(0));
|
||||||
|
items.push_back(QPDFObjectHandle::newInteger(0));
|
||||||
|
items.push_back(QPDFObjectHandle::newInteger(612));
|
||||||
|
items.push_back(QPDFObjectHandle::newInteger(792));
|
||||||
|
QPDFObjectHandle mediabox = QPDFObjectHandle::newArray(items);
|
||||||
|
|
||||||
|
keys.clear();
|
||||||
|
keys["/F1"] = font;
|
||||||
|
QPDFObjectHandle rfont = QPDFObjectHandle::newDictionary(keys);
|
||||||
|
|
||||||
|
keys.clear();
|
||||||
|
keys["/ProcSet"] = procset;
|
||||||
|
keys["/Font"] = rfont;
|
||||||
|
QPDFObjectHandle resources = QPDFObjectHandle::newDictionary(keys);
|
||||||
|
|
||||||
|
keys.clear();
|
||||||
|
keys["/Type"] = newName("/Page");
|
||||||
|
keys["/MediaBox"] = mediabox;
|
||||||
|
keys["/Contents"] = contents;
|
||||||
|
keys["/Resources"] = resources;
|
||||||
|
QPDFObjectHandle page = pdf.makeIndirectObject(
|
||||||
|
QPDFObjectHandle::newDictionary(keys));
|
||||||
|
|
||||||
|
pdf.addPage(page, true);
|
||||||
|
|
||||||
|
QPDFWriter w(pdf, "a.pdf");
|
||||||
|
w.setStaticID(true);
|
||||||
|
w.setStreamDataMode(qpdf_s_preserve);
|
||||||
|
w.write();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error(std::string("invalid test ") +
|
||||||
|
QUtil::int_to_string(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "test " << n << " done" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
QUtil::setLineBuf(stdout);
|
||||||
|
if ((whoami = strrchr(argv[0], '/')) == NULL)
|
||||||
|
{
|
||||||
|
whoami = argv[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++whoami;
|
||||||
|
}
|
||||||
|
// For libtool's sake....
|
||||||
|
if (strncmp(whoami, "lt-", 3) == 0)
|
||||||
|
{
|
||||||
|
whoami += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc != 2)
|
||||||
|
{
|
||||||
|
usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int n = atoi(argv[1]);
|
||||||
|
runtest(n);
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -352,6 +352,17 @@ $td->runtest("shallow copy a stream",
|
|||||||
|
|
||||||
show_ntests();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
|
$td->notify("--- PDF From Scratch ---");
|
||||||
|
$n_tests += 2;
|
||||||
|
|
||||||
|
$td->runtest("basic qpdf from scratch",
|
||||||
|
{$td->COMMAND => "pdf_from_scratch 0"},
|
||||||
|
{$td->STRING => "test 0 done\n", $td->EXIT_STATUS => 0},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
$td->runtest("check output",
|
||||||
|
{$td->FILE => "a.pdf"},
|
||||||
|
{$td->FILE => "from-scratch-0.pdf"});
|
||||||
|
# ----------
|
||||||
$td->notify("--- Error Condition Tests ---");
|
$td->notify("--- Error Condition Tests ---");
|
||||||
# $n_tests incremented after initialization of badfiles below.
|
# $n_tests incremented after initialization of badfiles below.
|
||||||
|
|
||||||
|
36
qpdf/qtest/qpdf/from-scratch-0.pdf
Normal file
36
qpdf/qtest/qpdf/from-scratch-0.pdf
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
%PDF-1.3
|
||||||
|
%¿÷¢þ
|
||||||
|
1 0 obj
|
||||||
|
<< /Pages 2 0 R /Type /Catalog >>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<< /Count 1 /Kids [ 3 0 R ] /Type /Pages >>
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<< /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 >>
|
||||||
|
endobj
|
||||||
|
4 0 obj
|
||||||
|
<< /Length 42 >>
|
||||||
|
stream
|
||||||
|
BT /F1 15 Tf 72 720 Td (First Page) Tj ET
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
[ /PDF /Text ]
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 7
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000015 00000 n
|
||||||
|
0000000064 00000 n
|
||||||
|
0000000123 00000 n
|
||||||
|
0000000266 00000 n
|
||||||
|
0000000357 00000 n
|
||||||
|
0000000464 00000 n
|
||||||
|
trailer << /Root 1 0 R /Size 7 /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >>
|
||||||
|
startxref
|
||||||
|
494
|
||||||
|
%%EOF
|
Loading…
x
Reference in New Issue
Block a user