1 //
2 // This is an example of creating a PDF file from scratch. It
3 // illustrates use of several QPDF operations for creating objects and
4 // streams. It also serves as an illustration of how to use
5 // StreamDataProvider with different types of filters.
6 //
7
8 #include <qpdf/QPDF.hh>
9 #include <qpdf/QPDFPageDocumentHelper.hh>
10 #include <qpdf/QPDFPageObjectHelper.hh>
11 #include <qpdf/QPDFWriter.hh>
12 #include <qpdf/QPDFObjectHandle.hh>
13 #include <qpdf/QUtil.hh>
14 #include <qpdf/Pl_Buffer.hh>
15 #include <qpdf/Pl_RunLength.hh>
16 #include <qpdf/Pl_DCT.hh>
17 #include <qpdf/QIntC.hh>
18 #include <iostream>
19 #include <string.h>
20 #include <stdlib.h>
21
22 static char const* whoami = 0;
23
24 // This is a simple StreamDataProvider that writes image data for an
25 // orange square of the given width and height.
26 class ImageProvider: public QPDFObjectHandle::StreamDataProvider
27 {
28 public:
29 ImageProvider(std::string const& color_space,
30 std::string const& filter);
31 virtual ~ImageProvider();
32 virtual void provideStreamData(int objid, int generation,
33 Pipeline* pipeline);
34 size_t getWidth() const;
35 size_t getHeight() const;
36
37 private:
38 size_t width;
39 size_t stripe_height;
40 std::string color_space;
41 std::string filter;
42 size_t n_stripes;
43 std::vector<std::string> stripes;
44 J_COLOR_SPACE j_color_space;
45 };
46
ImageProvider(std::string const & color_space,std::string const & filter)47 ImageProvider::ImageProvider(std::string const& color_space,
48 std::string const& filter) :
49 width(400),
50 stripe_height(80),
51 color_space(color_space),
52 filter(filter),
53 n_stripes(6),
54 j_color_space(JCS_UNKNOWN)
55 {
56 if (color_space == "/DeviceCMYK")
57 {
58 j_color_space = JCS_CMYK;
59 stripes.push_back(std::string("\xff\x00\x00\x00", 4));
60 stripes.push_back(std::string("\x00\xff\x00\x00", 4));
61 stripes.push_back(std::string("\x00\x00\xff\x00", 4));
62 stripes.push_back(std::string("\xff\x00\xff\x00", 4));
63 stripes.push_back(std::string("\xff\xff\x00\x00", 4));
64 stripes.push_back(std::string("\x00\x00\x00\xff", 4));
65 }
66 else if (color_space == "/DeviceRGB")
67 {
68 j_color_space = JCS_RGB;
69 stripes.push_back(std::string("\xff\x00\x00", 3));
70 stripes.push_back(std::string("\x00\xff\x00", 3));
71 stripes.push_back(std::string("\x00\x00\xff", 3));
72 stripes.push_back(std::string("\xff\x00\xff", 3));
73 stripes.push_back(std::string("\xff\xff\x00", 3));
74 stripes.push_back(std::string("\x00\x00\x00", 3));
75 }
76 else if (color_space == "/DeviceGray")
77 {
78 j_color_space = JCS_GRAYSCALE;
79 stripes.push_back(std::string("\xee", 1));
80 stripes.push_back(std::string("\xcc", 1));
81 stripes.push_back(std::string("\x99", 1));
82 stripes.push_back(std::string("\x66", 1));
83 stripes.push_back(std::string("\x33", 1));
84 stripes.push_back(std::string("\x00", 1));
85 }
86 }
87
~ImageProvider()88 ImageProvider::~ImageProvider()
89 {
90 }
91
92 size_t
getWidth() const93 ImageProvider::getWidth() const
94 {
95 return width;
96 }
97
98 size_t
getHeight() const99 ImageProvider::getHeight() const
100 {
101 return stripe_height * n_stripes;
102 }
103
104 void
provideStreamData(int objid,int generation,Pipeline * pipeline)105 ImageProvider::provideStreamData(int objid, int generation,
106 Pipeline* pipeline)
107 {
108 std::vector<PointerHolder<Pipeline> > to_delete;
109 Pipeline* p = pipeline;
110
111 if (filter == "/DCTDecode")
112 {
113 p = new Pl_DCT(
114 "image encoder", pipeline,
115 QIntC::to_uint(width), QIntC::to_uint(getHeight()),
116 QIntC::to_int(stripes[0].length()), j_color_space);
117 to_delete.push_back(p);
118 }
119 else if (filter == "/RunLengthDecode")
120 {
121 p = new Pl_RunLength(
122 "image encoder", pipeline, Pl_RunLength::a_encode);
123 to_delete.push_back(p);
124 }
125
126 for (size_t i = 0; i < n_stripes; ++i)
127 {
128 for (size_t j = 0; j < width * stripe_height; ++j)
129 {
130 p->write(
131 QUtil::unsigned_char_pointer(stripes[i].c_str()),
132 stripes[i].length());
133 }
134 }
135 p->finish();
136 }
137
usage()138 void usage()
139 {
140 std::cerr << "Usage: " << whoami << " filename" << std::endl
141 << "Creates a simple PDF and writes it to filename" << std::endl;
142 exit(2);
143 }
144
createPageContents(QPDF & pdf,std::string const & text)145 static QPDFObjectHandle createPageContents(QPDF& pdf, std::string const& text)
146 {
147 // Create a stream that displays our image and the given text in
148 // our font.
149 std::string contents =
150 "BT /F1 24 Tf 72 320 Td (" + text + ") Tj ET\n"
151 "q 244 0 0 144 184 100 cm /Im1 Do Q\n";
152 return QPDFObjectHandle::newStream(&pdf, contents);
153 }
154
newName(std::string const & name)155 QPDFObjectHandle newName(std::string const& name)
156 {
157 return QPDFObjectHandle::newName(name);
158 }
159
newInteger(size_t val)160 QPDFObjectHandle newInteger(size_t val)
161 {
162 return QPDFObjectHandle::newInteger(QIntC::to_int(val));
163 }
164
add_page(QPDFPageDocumentHelper & dh,QPDFObjectHandle font,std::string const & color_space,std::string const & filter)165 void add_page(QPDFPageDocumentHelper& dh, QPDFObjectHandle font,
166 std::string const& color_space,
167 std::string const& filter)
168 {
169 QPDF& pdf(dh.getQPDF());
170
171 // Create a stream to encode our image. QPDFWriter will fill in
172 // the length and will respect our filters based on stream data
173 // mode. Since we are not specifying, QPDFWriter will compress
174 // with /FlateDecode if we don't provide any other form of
175 // compression.
176 ImageProvider* p = new ImageProvider(color_space, filter);
177 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider(p);
178 size_t width = p->getWidth();
179 size_t height = p->getHeight();
180 QPDFObjectHandle image = QPDFObjectHandle::newStream(&pdf);
181 image.replaceDict(QPDFObjectHandle::parse(
182 "<<"
183 " /Type /XObject"
184 " /Subtype /Image"
185 " /BitsPerComponent 8"
186 ">>"));
187 QPDFObjectHandle image_dict = image.getDict();
188 image_dict.replaceKey("/ColorSpace", newName(color_space));
189 image_dict.replaceKey("/Width", newInteger(width));
190 image_dict.replaceKey("/Height", newInteger(height));
191
192 // Provide the stream data.
193 image.replaceStreamData(provider,
194 QPDFObjectHandle::parse(filter),
195 QPDFObjectHandle::newNull());
196
197 // Create direct objects as needed by the page dictionary.
198 QPDFObjectHandle procset = QPDFObjectHandle::parse(
199 "[/PDF /Text /ImageC]");
200
201 QPDFObjectHandle rfont = QPDFObjectHandle::newDictionary();
202 rfont.replaceKey("/F1", font);
203
204 QPDFObjectHandle xobject = QPDFObjectHandle::newDictionary();
205 xobject.replaceKey("/Im1", image);
206
207 QPDFObjectHandle resources = QPDFObjectHandle::newDictionary();
208 resources.replaceKey("/ProcSet", procset);
209 resources.replaceKey("/Font", rfont);
210 resources.replaceKey("/XObject", xobject);
211
212 QPDFObjectHandle mediabox = QPDFObjectHandle::newArray();
213 mediabox.appendItem(newInteger(0));
214 mediabox.appendItem(newInteger(0));
215 mediabox.appendItem(newInteger(612));
216 mediabox.appendItem(newInteger(392));
217
218 // Create the page content stream
219 QPDFObjectHandle contents = createPageContents(
220 pdf, color_space + " with filter " + filter);
221
222 // Create the page dictionary
223 QPDFObjectHandle page = pdf.makeIndirectObject(
224 QPDFObjectHandle::newDictionary());
225 page.replaceKey("/Type", newName("/Page"));
226 page.replaceKey("/MediaBox", mediabox);
227 page.replaceKey("/Contents", contents);
228 page.replaceKey("/Resources", resources);
229
230 // Add the page to the PDF file
231 dh.addPage(page, false);
232 }
233
check(char const * filename,std::vector<std::string> const & color_spaces,std::vector<std::string> const & filters)234 static void check(char const* filename,
235 std::vector<std::string> const& color_spaces,
236 std::vector<std::string> const& filters)
237 {
238 // Each stream is compressed the way it is supposed to be. We will
239 // add additional tests in qpdf.test to exercise QPDFWriter more
240 // fully. In this case, we want to make sure that we actually have
241 // RunLengthDecode and DCTDecode where we are supposed to and
242 // FlateDecode where we provided no filters.
243
244 // Each image is correct. For non-lossy image compression, the
245 // uncompressed image data should exactly match what ImageProvider
246 // provided. For the DCTDecode data, allow for some fuzz to handle
247 // jpeg compression as well as its variance on different systems.
248
249 // These tests should use QPDFObjectHandle's stream data retrieval
250 // methods, but don't try to fully exercise them here. That is
251 // done elsewhere.
252
253 size_t n_color_spaces = color_spaces.size();
254 size_t n_filters = filters.size();
255
256 QPDF pdf;
257 pdf.processFile(filename);
258 QPDFPageDocumentHelper dh(pdf);
259 std::vector<QPDFPageObjectHelper> pages = dh.getAllPages();
260 if (n_color_spaces * n_filters != pages.size())
261 {
262 throw std::logic_error("incorrect number of pages");
263 }
264 size_t pageno = 1;
265 bool errors = false;
266 for (std::vector<QPDFPageObjectHelper>::iterator page_iter =
267 pages.begin();
268 page_iter != pages.end(); ++page_iter)
269 {
270 QPDFPageObjectHelper& page(*page_iter);
271 std::map<std::string, QPDFObjectHandle> images = page.getImages();
272 if (images.size() != 1)
273 {
274 throw std::logic_error("incorrect number of images on page");
275 }
276
277 // Check filter and color space.
278 std::string desired_color_space =
279 color_spaces[(pageno - 1) / n_color_spaces];
280 std::string desired_filter =
281 filters[(pageno - 1) % n_filters];
282 // In the default mode, QPDFWriter will compress with
283 // /FlateDecode if no filters are provided.
284 if (desired_filter == "null")
285 {
286 desired_filter = "/FlateDecode";
287 }
288 QPDFObjectHandle image = images.begin()->second;
289 QPDFObjectHandle image_dict = image.getDict();
290 QPDFObjectHandle color_space = image_dict.getKey("/ColorSpace");
291 QPDFObjectHandle filter = image_dict.getKey("/Filter");
292 bool this_errors = false;
293 if (! (filter.isName() && (filter.getName() == desired_filter)))
294 {
295 this_errors = errors = true;
296 std::cout << "page " << pageno << ": expected filter "
297 << desired_filter << "; actual filter = "
298 << filter.unparse() << std::endl;
299 }
300 if (! (color_space.isName() &&
301 (color_space.getName() == desired_color_space)))
302 {
303 this_errors = errors = true;
304 std::cout << "page " << pageno << ": expected color space "
305 << desired_color_space << "; actual color space = "
306 << color_space.unparse() << std::endl;
307 }
308
309 if (! this_errors)
310 {
311 // Check image data
312 PointerHolder<Buffer> actual_data =
313 image.getStreamData(qpdf_dl_all);
314 ImageProvider* p = new ImageProvider(desired_color_space, "null");
315 PointerHolder<QPDFObjectHandle::StreamDataProvider> provider(p);
316 Pl_Buffer b_p("get image data");
317 provider->provideStreamData(0, 0, &b_p);
318 PointerHolder<Buffer> desired_data(b_p.getBuffer());
319
320 if (desired_data->getSize() != actual_data->getSize())
321 {
322 std::cout << "page " << pageno
323 << ": image data length mismatch" << std::endl;
324 this_errors = errors = true;
325 }
326 else
327 {
328 // Compare bytes. For JPEG, allow a certain number of
329 // the bytes to be off desired by more than a given
330 // tolerance. Any of the samples may be a little off
331 // because of lossy compression, and around sharp
332 // edges, things can be quite off. For non-lossy
333 // compression, do not allow any tolerance.
334 unsigned char const* actual_bytes = actual_data->getBuffer();
335 unsigned char const* desired_bytes = desired_data->getBuffer();
336 size_t len = actual_data->getSize();
337 unsigned int mismatches = 0;
338 int tolerance = (
339 desired_filter == "/DCTDecode" ? 10 : 0);
340 size_t threshold = (
341 desired_filter == "/DCTDecode" ? len / 40U : 0);
342 for (size_t i = 0; i < len; ++i)
343 {
344 int delta = actual_bytes[i] - desired_bytes[i];
345 if ((delta > tolerance) || (delta < -tolerance))
346 {
347 ++mismatches;
348 }
349 }
350 if (mismatches > threshold)
351 {
352 std::cout << "page " << pageno
353 << ": " << desired_color_space << ", "
354 << desired_filter
355 << ": mismatches: " << mismatches
356 << " of " << len << std::endl;
357 this_errors = errors = true;
358 }
359 }
360 }
361
362 ++pageno;
363 }
364 if (errors)
365 {
366 throw std::logic_error("errors found");
367 }
368 else
369 {
370 std::cout << "all checks passed" << std::endl;
371 }
372 }
373
create_pdf(char const * filename)374 static void create_pdf(char const* filename)
375 {
376 QPDF pdf;
377
378 // Start with an empty PDF that has no pages or non-required objects.
379 pdf.emptyPDF();
380
381 // Add an indirect object to contain a font descriptor for the
382 // built-in Helvetica font.
383 QPDFObjectHandle font = pdf.makeIndirectObject(
384 QPDFObjectHandle::parse(
385 "<<"
386 " /Type /Font"
387 " /Subtype /Type1"
388 " /Name /F1"
389 " /BaseFont /Helvetica"
390 " /Encoding /WinAnsiEncoding"
391 ">>"));
392
393 std::vector<std::string> color_spaces;
394 color_spaces.push_back("/DeviceCMYK");
395 color_spaces.push_back("/DeviceRGB");
396 color_spaces.push_back("/DeviceGray");
397 std::vector<std::string> filters;
398 filters.push_back("null");
399 filters.push_back("/DCTDecode");
400 filters.push_back("/RunLengthDecode");
401 QPDFPageDocumentHelper dh(pdf);
402 for (std::vector<std::string>::iterator c_iter = color_spaces.begin();
403 c_iter != color_spaces.end(); ++c_iter)
404 {
405 for (std::vector<std::string>::iterator f_iter = filters.begin();
406 f_iter != filters.end(); ++f_iter)
407 {
408 add_page(dh, font, *c_iter, *f_iter);
409 }
410 }
411
412 QPDFWriter w(pdf, filename);
413 w.write();
414
415 // For test suite, verify that everything is the way it is
416 // supposed to be.
417 check(filename, color_spaces, filters);
418 }
419
main(int argc,char * argv[])420 int main(int argc, char* argv[])
421 {
422 whoami = QUtil::getWhoami(argv[0]);
423
424 // For libtool's sake....
425 if (strncmp(whoami, "lt-", 3) == 0)
426 {
427 whoami += 3;
428 }
429 if (argc != 2)
430 {
431 usage();
432 }
433 char const* filename = argv[1];
434
435 try
436 {
437 create_pdf(filename);
438 }
439 catch (std::exception& e)
440 {
441 std::cerr << e.what() << std::endl;
442 exit(2);
443 }
444
445 return 0;
446 }
447