Commit ffb96ee17e9721bb6cf3982249be537cacc68814

Authored by Jay Berkenbilt
1 parent bf059a60

Add pdf-from-scratch example

ChangeLog
1 2012-06-22 Jay Berkenbilt <ejb@ql.org> 1 2012-06-22 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * examples/pdf-create.cc: Provide an example of creating a PDF
  4 + from scratch. This simple PDF has a single page with some text
  5 + and an image.
  6 +
3 * Add empty QPDFObjectHandle factories for array and dictionary. 7 * Add empty QPDFObjectHandle factories for array and dictionary.
4 With PDF-from-scratch capability, it is useful to be able to 8 With PDF-from-scratch capability, it is useful to be able to
5 create empty arrays and dictionaries and add keys to them. 9 create empty arrays and dictionaries and add keys to them.
@@ -42,8 +42,6 @@ Next @@ -42,8 +42,6 @@ Next
42 contents to make sure the file is really correct. We need to 42 contents to make sure the file is really correct. We need to
43 test normal writing and linearization. 43 test normal writing and linearization.
44 44
45 - * Consider adding an example that uses the page APIs, or update the  
46 - documentation to refer the user to the test suite.  
47 45
48 Soon 46 Soon
49 ==== 47 ====
examples/build.mk
@@ -3,7 +3,8 @@ BINS_examples = \ @@ -3,7 +3,8 @@ BINS_examples = \
3 pdf-mod-info \ 3 pdf-mod-info \
4 pdf-npages \ 4 pdf-npages \
5 pdf-double-page-size \ 5 pdf-double-page-size \
6 - pdf-invert-images 6 + pdf-invert-images \
  7 + pdf-create
7 CBINS_examples = pdf-linearize 8 CBINS_examples = pdf-linearize
8 9
9 TARGETS_examples = $(foreach B,$(BINS_examples) $(CBINS_examples),examples/$(OUTPUT_DIR)/$(call binname,$(B))) 10 TARGETS_examples = $(foreach B,$(BINS_examples) $(CBINS_examples),examples/$(OUTPUT_DIR)/$(call binname,$(B)))
examples/pdf-create.cc 0 โ†’ 100644
  1 +#include <qpdf/QPDF.hh>
  2 +#include <qpdf/QPDFWriter.hh>
  3 +#include <qpdf/QPDFObjectHandle.hh>
  4 +#include <qpdf/QUtil.hh>
  5 +#include <iostream>
  6 +#include <string.h>
  7 +#include <stdlib.h>
  8 +
  9 +static char const* whoami = 0;
  10 +
  11 +// This is a simple StreamDataProvider that writes image data for an
  12 +// orange square of the given width and height.
  13 +class ImageProvider: public QPDFObjectHandle::StreamDataProvider
  14 +{
  15 + public:
  16 + ImageProvider(int width, int height);
  17 + virtual ~ImageProvider();
  18 + virtual void provideStreamData(int objid, int generation,
  19 + Pipeline* pipeline);
  20 + size_t getLength() const;
  21 +
  22 + private:
  23 + int width;
  24 + int height;
  25 +};
  26 +
  27 +ImageProvider::ImageProvider(int width, int height) :
  28 + width(width),
  29 + height(height)
  30 +{
  31 +}
  32 +
  33 +ImageProvider::~ImageProvider()
  34 +{
  35 +}
  36 +
  37 +void
  38 +ImageProvider::provideStreamData(int objid, int generation,
  39 + Pipeline* pipeline)
  40 +{
  41 + for (int i = 0; i < width * height; ++i)
  42 + {
  43 + pipeline->write((unsigned char*)"\xff\x7f\x00", 3);
  44 + }
  45 + pipeline->finish();
  46 +}
  47 +
  48 +size_t
  49 +ImageProvider::getLength() const
  50 +{
  51 + return 3 * width * height;
  52 +}
  53 +
  54 +void usage()
  55 +{
  56 + std::cerr << "Usage: " << whoami << " filename" << std::endl
  57 + << "Creates a simple PDF and writes it to filename" << std::endl;
  58 + exit(2);
  59 +}
  60 +
  61 +static QPDFObjectHandle createPageContents(QPDF& pdf, std::string const& text)
  62 +{
  63 + // Create a stream that displays our image and the given text in
  64 + // our font.
  65 + std::string contents =
  66 + "BT /F1 24 Tf 72 720 Td (" + text + ") Tj ET\n"
  67 + "q 144 0 0 144 234 324 cm /Im1 Do Q\n";
  68 + PointerHolder<Buffer> b = new Buffer(contents.length());
  69 + unsigned char* bp = b->getBuffer();
  70 + memcpy(bp, (char*)contents.c_str(), contents.length());
  71 + return QPDFObjectHandle::newStream(&pdf, b);
  72 +}
  73 +
  74 +QPDFObjectHandle newName(std::string const& name)
  75 +{
  76 + return QPDFObjectHandle::newName(name);
  77 +}
  78 +
  79 +QPDFObjectHandle newInteger(int val)
  80 +{
  81 + return QPDFObjectHandle::newInteger(val);
  82 +}
  83 +
  84 +static void create_pdf(char const* filename)
  85 +{
  86 + QPDF pdf;
  87 +
  88 + // Start with an empty PDF that has no pages or non-required objects.
  89 + pdf.emptyPDF();
  90 +
  91 + // Add an indirect object to contain a font descriptor for the
  92 + // built-in Helvetica font.
  93 + QPDFObjectHandle font = pdf.makeIndirectObject(
  94 + QPDFObjectHandle::newDictionary());
  95 + font.replaceKey("/Type", newName("/Font"));
  96 + font.replaceKey("/Subtype", newName("/Type1"));
  97 + font.replaceKey("/Name", newName("/F1"));
  98 + font.replaceKey("/BaseFont", newName("/Helvetica"));
  99 + font.replaceKey("/Encoding", newName("/WinAnsiEncoding"));
  100 +
  101 + // Create a stream to encode our image. We don't have to set the
  102 + // length or filters. QPDFWriter will fill in the length and
  103 + // compress the stream data using FlateDecode by default.
  104 + QPDFObjectHandle image = QPDFObjectHandle::newStream(&pdf);
  105 + QPDFObjectHandle image_dict = image.getDict();
  106 + image_dict.replaceKey("/Type", newName("/XObject"));
  107 + image_dict.replaceKey("/Subtype", newName("/Image"));
  108 + image_dict.replaceKey("/ColorSpace", newName("/DeviceRGB"));
  109 + image_dict.replaceKey("/BitsPerComponent", newInteger(8));
  110 + image_dict.replaceKey("/Width", newInteger(100));
  111 + image_dict.replaceKey("/Height", newInteger(100));
  112 + // Provide the stream data.
  113 + ImageProvider* p = new ImageProvider(100, 100);
  114 + PointerHolder<QPDFObjectHandle::StreamDataProvider> provider(p);
  115 + image.replaceStreamData(provider,
  116 + QPDFObjectHandle::newNull(),
  117 + QPDFObjectHandle::newNull(),
  118 + p->getLength());
  119 +
  120 + // Create direct objects as needed by the page dictionary.
  121 + QPDFObjectHandle procset = QPDFObjectHandle::newArray();
  122 + procset.appendItem(newName("/PDF"));
  123 + procset.appendItem(newName("/Text"));
  124 + procset.appendItem(newName("/ImageC"));
  125 +
  126 + QPDFObjectHandle rfont = QPDFObjectHandle::newDictionary();
  127 + rfont.replaceKey("/F1", font);
  128 +
  129 + QPDFObjectHandle xobject = QPDFObjectHandle::newDictionary();
  130 + xobject.replaceKey("/Im1", image);
  131 +
  132 + QPDFObjectHandle resources = QPDFObjectHandle::newDictionary();
  133 + resources.replaceKey("/ProcSet", procset);
  134 + resources.replaceKey("/Font", rfont);
  135 + resources.replaceKey("/XObject", xobject);
  136 +
  137 + QPDFObjectHandle mediabox = QPDFObjectHandle::newArray();
  138 + mediabox.appendItem(newInteger(0));
  139 + mediabox.appendItem(newInteger(0));
  140 + mediabox.appendItem(newInteger(612));
  141 + mediabox.appendItem(newInteger(792));
  142 +
  143 + // Create the page content stream
  144 + QPDFObjectHandle contents = createPageContents(
  145 + pdf, "Look at the pretty, orange square!");
  146 +
  147 + // Create the page dictionary
  148 + QPDFObjectHandle page = pdf.makeIndirectObject(
  149 + QPDFObjectHandle::newDictionary());
  150 + page.replaceKey("/Type", newName("/Page"));
  151 + page.replaceKey("/MediaBox", mediabox);
  152 + page.replaceKey("/Contents", contents);
  153 + page.replaceKey("/Resources", resources);
  154 +
  155 + // Add the page to the PDF file
  156 + pdf.addPage(page, true);
  157 +
  158 + // Write the results. A real application would not call
  159 + // setStaticID here, but this example does it for the sake of its
  160 + // test suite.
  161 + QPDFWriter w(pdf, filename);
  162 + w.setStaticID(true); // for testing only
  163 + w.write();
  164 +}
  165 +
  166 +int main(int argc, char* argv[])
  167 +{
  168 + whoami = QUtil::getWhoami(argv[0]);
  169 +
  170 + // For libtool's sake....
  171 + if (strncmp(whoami, "lt-", 3) == 0)
  172 + {
  173 + whoami += 3;
  174 + }
  175 + if (argc != 2)
  176 + {
  177 + usage();
  178 + }
  179 + char const* filename = argv[1];
  180 +
  181 + try
  182 + {
  183 + create_pdf(filename);
  184 + }
  185 + catch (std::exception& e)
  186 + {
  187 + std::cerr << e.what() << std::endl;
  188 + exit(2);
  189 + }
  190 +
  191 + return 0;
  192 +}
examples/qtest/create.test 0 โ†’ 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +use warnings;
  4 +use strict;
  5 +
  6 +chdir("create") or die "chdir testdir failed: $!\n";
  7 +
  8 +require TestDriver;
  9 +
  10 +cleanup();
  11 +
  12 +my $td = new TestDriver('create');
  13 +
  14 +$td->runtest("create a simple PDF",
  15 + {$td->COMMAND => "pdf-create a.pdf"},
  16 + {$td->STRING => "", $td->EXIT_STATUS => 0});
  17 +
  18 +$td->runtest("check",
  19 + {$td->FILE => "a.pdf"},
  20 + {$td->FILE => "orange-square.pdf"});
  21 +
  22 +cleanup();
  23 +
  24 +$td->report(2);
  25 +
  26 +sub cleanup
  27 +{
  28 + unlink "a.pdf";
  29 +}
examples/qtest/create/orange-square.pdf 0 โ†’ 100644
No preview for this file type
include/qpdf/QPDF.hh
@@ -75,7 +75,8 @@ class QPDF @@ -75,7 +75,8 @@ class QPDF
75 // objects can be added to the file in the normal way, and the 75 // objects can be added to the file in the normal way, and the
76 // trailer and document catalog can be mutated. Calling this 76 // trailer and document catalog can be mutated. Calling this
77 // method is equivalent to calling processFile on an equivalent 77 // method is equivalent to calling processFile on an equivalent
78 - // PDF file. 78 + // PDF file. See the pdf-create.cc example for a demonstration of
  79 + // how to use this method to create a PDF file from scratch.
79 QPDF_DLL 80 QPDF_DLL
80 void emptyPDF(); 81 void emptyPDF();
81 82