• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2012 Canonical Ltd.
3  * Copyright 2013 ALT Linux, Andrew V. Stepanov <stanv@altlinux.com>
4  * Copyright 2018 Sahil Arora <sahilarora.535@gmail.com>
5  *
6  * This program is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 3, as published
8  * by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranties of
12  * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
13  * PURPOSE.  See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <config.h>
20 #include "pdf.h"
21 #include <vector>
22 #include <string>
23 #include <cstring>
24 #include <qpdf/QPDF.hh>
25 #include <qpdf/QPDFObjectHandle.hh>
26 #include <qpdf/QPDFWriter.hh>
27 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
28 #include <qpdf/QPDFPageDocumentHelper.hh>
29 
30 /*
31  * Useful reference:
32  *
33  * http://www.gnupdf.org/Indirect_Object
34  * http://www.gnupdf.org/Introduction_to_PDF
35  * http://blog.idrsolutions.com/2011/05/understanding-the-pdf-file-format-%E2%80%93-pdf-xref-tables-explained
36  * http://labs.appligent.com/pdfblog/pdf-hello-world/
37  * https://github.com/OpenPrinting/cups-filters/pull/25
38 */
39 
40 
41 /**
42  * 'makeRealBox()' - Return a QPDF array object of real values for a box.
43  * O - QPDFObjectHandle for an array
44  * I - Dimensions of the box in a float array
45  */
makeRealBox(float values[4])46 static QPDFObjectHandle makeRealBox(float values[4])
47 {
48   QPDFObjectHandle ret = QPDFObjectHandle::newArray();
49   for (int i = 0; i < 4; ++i) {
50     ret.appendItem(QPDFObjectHandle::newReal(values[i]));
51   }
52   return ret;
53 }
54 
55 
56 /**
57  * 'pdf_load_template()' - Load an existing PDF file and do initial parsing
58  *                         using QPDF.
59  * I - Filename to open
60  */
pdf_load_template(const char * filename)61 extern "C" pdf_t * pdf_load_template(const char *filename)
62 {
63   QPDF *pdf = new QPDF();
64   pdf->processFile(filename);
65   unsigned pages = (pdf->getAllPages()).size();
66 
67   if (pages != 1) {
68     fprintf(stderr, "ERROR: PDF template must contain exactly 1 page: %s\n",
69             filename);
70     delete pdf;
71     return NULL;
72   }
73 
74   return pdf;
75 }
76 
77 
78 /**
79  * 'pdf_free()' - Free pointer used by PDF object
80  * I - Pointer to PDF object
81  */
pdf_free(pdf_t * pdf)82 extern "C" void pdf_free(pdf_t *pdf)
83 {
84   delete pdf;
85 }
86 
87 /*
88  * 'pdf_pages()' - Count number of pages in file
89  *                         using QPDF.
90  * I - Filename to open
91  * O - Number of pages or -1 on error
92  */
pdf_pages(const char * filename)93 int pdf_pages(const char *filename)
94 {
95   QPDF *pdf = new QPDF();
96   if (pdf) {
97     try{
98       pdf->processFile(filename);
99     } catch(...) {
100       pdf_free(pdf);
101       return -1;
102     }
103     int pages = (pdf->getAllPages()).size();
104     pdf_free(pdf);
105     return pages;
106   } else
107     return -1;
108 }
109 
110 
111 /**
112  * 'pdf_prepend_stream' - Prepend a stream to the contents of a specified
113  *                        page in PDF file.
114  * I - Pointer to QPDF object
115  * I - page number of page to prepend stream to
116  * I - buffer containing data to be prepended
117  * I - length of buffer
118  */
pdf_prepend_stream(pdf_t * pdf,unsigned page_num,char const * buf,size_t len)119 extern "C" void pdf_prepend_stream(pdf_t *pdf,
120                                    unsigned page_num,
121                                    char const *buf,
122                                    size_t len)
123 {
124   std::vector<QPDFObjectHandle> pages = pdf->getAllPages();
125   if (pages.empty() || page_num > pages.size()) {
126     fprintf(stderr, "ERROR: Unable to prepend stream to requested PDF page\n");
127     return;
128   }
129 
130   QPDFObjectHandle page = pages[page_num - 1];
131 
132   // get page contents stream / array
133   QPDFObjectHandle contents = page.getKey("/Contents");
134   if (!contents.isStream() && !contents.isArray())
135   {
136     fprintf(stderr, "ERROR: Malformed PDF.\n");
137     return;
138   }
139 
140   // prepare the new stream which is to be prepended
141   PointerHolder<Buffer> stream_data = PointerHolder<Buffer>(new Buffer(len));
142   memcpy(stream_data->getBuffer(), buf, len);
143   QPDFObjectHandle stream = QPDFObjectHandle::newStream(pdf, stream_data);
144   stream = pdf->makeIndirectObject(stream);
145 
146   // if the contents is an array, prepend the new stream to the array,
147   // else convert the contents to an array and the do the same.
148   if (contents.isStream())
149   {
150     QPDFObjectHandle old_streamdata = contents;
151     contents = QPDFObjectHandle::newArray();
152     contents.appendItem(old_streamdata);
153   }
154 
155   contents.insertItem(0, stream);
156   page.replaceKey("/Contents", contents);
157 }
158 
159 
160 /**
161  * 'pdf_add_type1_font()' - Add the specified type1 fontface to the specified
162  *                          page in a PDF document.
163  * I - QPDF object
164  * I - page number of the page to which the font is to be added
165  * I - name of the font to be added
166  */
pdf_add_type1_font(pdf_t * pdf,unsigned page_num,const char * name)167 extern "C" void pdf_add_type1_font(pdf_t *pdf,
168                                    unsigned page_num,
169                                    const char *name)
170 {
171   std::vector<QPDFObjectHandle> pages = pdf->getAllPages();
172   if (pages.empty() || page_num > pages.size()) {
173     fprintf(stderr, "ERROR: Unable to add type1 font to requested PDF page\n");
174     return;
175   }
176 
177   QPDFObjectHandle page = pages[page_num - 1];
178 
179   QPDFObjectHandle resources = page.getKey("/Resources");
180   if (!resources.isDictionary())
181   {
182     fprintf(stderr, "ERROR: Malformed PDF.\n");
183     return;
184   }
185 
186   QPDFObjectHandle font = QPDFObjectHandle::newDictionary();
187   font.replaceKey("/Type", QPDFObjectHandle::newName("/Font"));
188   font.replaceKey("/Subtype", QPDFObjectHandle::newName("/Type1"));
189   font.replaceKey("/BaseFont",
190                   QPDFObjectHandle::newName(std::string("/") + std::string(name)));
191 
192   QPDFObjectHandle fonts = resources.getKey("/Font");
193   if (fonts.isNull())
194   {
195     fonts = QPDFObjectHandle::newDictionary();
196   }
197   else if (!fonts.isDictionary())
198   {
199     fprintf(stderr, "ERROR: Can't recognize Font resource in PDF template.\n");
200     return;
201   }
202 
203   font = pdf->makeIndirectObject(font);
204   fonts.replaceKey("/bannertopdf-font", font);
205   resources.replaceKey("/Font", fonts);
206 }
207 
208 
209 /**
210  * 'dict_lookup_rect()' - Lookup for an array of rectangle dimensions in a QPDF
211  *                        dictionary object. If it is found, store the values in
212  *                        an array and return true, else return false.
213  * O - True / False, depending on whether the key is found in the dictionary
214  * I - PDF dictionary object
215  * I - Key to lookup for
216  * I - array to store values if key is found
217  */
dict_lookup_rect(QPDFObjectHandle object,std::string const & key,float rect[4],bool inheritable)218 static bool dict_lookup_rect(QPDFObjectHandle object,
219                              std::string const& key,
220                              float rect[4],
221                              bool inheritable)
222 {
223   // preliminary checks
224   if (!object.isDictionary())
225     return false;
226 
227   QPDFObjectHandle value;
228   if (!object.hasKey(key) && inheritable){
229     QPDFFormFieldObjectHelper helper(object);
230     value = helper.getInheritableFieldValue(key);
231     if (value.isNull()) {
232       return false;
233     }
234   } else {
235     value = object.getKey(key);
236   }
237 
238   // check if the key is array or some other type
239   if (!value.isArray())
240     return false;
241 
242   // get values in a vector and assign it to rect
243   std::vector<QPDFObjectHandle> array = value.getArrayAsVector();
244   for (int i = 0; i < 4; ++i) {
245     // if the value in the array is not real, we have an invalid array
246     if (!array[i].isReal() && !array[i].isInteger())
247       return false;
248 
249     rect[i] = array[i].getNumericValue();
250   }
251 
252   return array.size() == 4;
253 }
254 
255 
256 /**
257  * 'fit_rect()' - Update the scale of the page using old media box dimensions
258  *                and new media box dimensions.
259  * I - Old media box
260  * I - New media box
261  * I - Pointer to scale which needs to be updated
262  */
fit_rect(float oldrect[4],float newrect[4],float * scale)263 static void fit_rect(float oldrect[4],
264                      float newrect[4],
265                      float *scale)
266 {
267   float oldwidth = oldrect[2] - oldrect[0];
268   float oldheight = oldrect[3] - oldrect[1];
269   float newwidth = newrect[2] - newrect[0];
270   float newheight = newrect[3] - newrect[1];
271 
272   *scale = newwidth / oldwidth;
273   if (oldheight * *scale > newheight)
274     *scale = newheight / oldheight;
275 }
276 
277 
278 /**
279  * 'pdf_resize_page()' - Resize page in a PDF with the given dimensions.
280  * I - Pointer to QPDF object
281  * I - Page number
282  * I - Width of page to set
283  * I - Length of page to set
284  * I - Scale of page to set
285  */
pdf_resize_page(pdf_t * pdf,unsigned page_num,float width,float length,float * scale)286 extern "C" void pdf_resize_page (pdf_t *pdf,
287                                  unsigned page_num,
288                                  float width,
289                                  float length,
290                                  float *scale)
291 {
292   std::vector<QPDFObjectHandle> pages = pdf->getAllPages();
293   if (pages.empty() || page_num > pages.size()) {
294     fprintf(stderr, "ERROR: Unable to resize requested PDF page\n");
295     return;
296   }
297 
298   QPDFObjectHandle page = pages[page_num - 1];
299   float new_mediabox[4] = { 0.0, 0.0, width, length };
300   float old_mediabox[4];
301   QPDFObjectHandle media_box;
302 
303   if (!dict_lookup_rect(page, "/MediaBox", old_mediabox, true)) {
304     fprintf(stderr, "ERROR: pdf doesn't contain a valid mediabox\n");
305     return;
306   }
307 
308   fit_rect(old_mediabox, new_mediabox, scale);
309   media_box = makeRealBox(new_mediabox);
310 
311   page.replaceKey("/ArtBox", media_box);
312   page.replaceKey("/BleedBox", media_box);
313   page.replaceKey("/CropBox", media_box);
314   page.replaceKey("/MediaBox", media_box);
315   page.replaceKey("/TrimBox", media_box);
316 }
317 
318 
319 /**
320  * 'pdf_duplicate_page()' - Duplicate a specified pdf page in a PDF
321  * I - Pointer to QPDF object
322  * I - page number of the page to be duplicated
323  * I - number of copies to be duplicated
324  */
pdf_duplicate_page(pdf_t * pdf,unsigned page_num,unsigned count)325 extern "C" void pdf_duplicate_page (pdf_t *pdf,
326                                     unsigned page_num,
327                                     unsigned count)
328 {
329   std::vector<QPDFObjectHandle> pages = pdf->getAllPages();
330   if (pages.empty() || page_num > pages.size()) {
331     fprintf(stderr, "ERROR: Unable to duplicate requested PDF page\n");
332     return;
333   }
334 
335   QPDFObjectHandle page = pages[page_num - 1];
336   for (unsigned i = 0; i < count; ++i)
337   {
338     page = pdf->makeIndirectObject(page);
339     pdf->addPage(page, false);
340   }
341 }
342 
343 
344 /**
345  * 'pdf_write()' - Write the contents of PDF object to an already open FILE*.
346  * I - pointer to QPDF structure
347  * I - File pointer to write to
348  */
pdf_write(pdf_t * pdf,FILE * file)349 extern "C" void pdf_write(pdf_t *pdf, FILE *file)
350 {
351   QPDFWriter output(*pdf, "pdf_write", file, false);
352   output.write();
353 }
354 
355 
356 
357 /*
358  * 'lookup_opt()' - Get value according to key in the options list.
359  * I - pointer to the opt_t type list
360  * I - key to be found in the list
361  * O - character string which corresponds to the value of the key or
362  *     NULL if key is not found in the list.
363  */
lookup_opt(opt_t * opt,std::string const & key)364 std::string lookup_opt(opt_t *opt, std::string const& key) {
365     if ( ! opt || key.empty() ) {
366         return "";
367     }
368 
369     while (opt) {
370         if (opt->key && opt->val) {
371             if ( strcmp(opt->key, key.c_str()) == 0 ) {
372                 return std::string(opt->val);
373             }
374         }
375         opt = opt->next;
376     }
377 
378     return "";
379 }
380 
381 
382 /*
383  * 'pdf_fill_form()' -  1. Lookup in PDF template file for form.
384  *                      2. Lookup for form fields' names.
385  *                      3. Fill recognized fields with information.
386  * I - Pointer to the QPDF structure
387  * I - Pointer to the opt_t type list
388  * O - status of form fill - 0 for failure, 1 for success
389  */
pdf_fill_form(pdf_t * doc,opt_t * opt)390 extern "C" int pdf_fill_form(pdf_t *doc, opt_t *opt)
391 {
392     // initialize AcroFormDocumentHelper and PageDocumentHelper objects
393     // to work with forms in the PDF
394     QPDFAcroFormDocumentHelper afdh(*doc);
395     QPDFPageDocumentHelper pdh(*doc);
396 
397     // check if the PDF has a form or not
398     if ( !afdh.hasAcroForm() ) {
399         fprintf(stderr, "DEBUG: PDF template file doesn't have form. It's okay.\n");
400         return 0;
401     }
402 
403     // get the first page from the PDF to fill the form. Since this
404     // is a banner file,it must contain only a single page, and that
405     // check has already been performed in the `pdf_load_template()` function
406     std::vector<QPDFPageObjectHelper> pages = pdh.getAllPages();
407     if (pages.empty()) {
408         fprintf(stderr, "ERROR: Can't get page from PDF tamplate file.\n");
409         return 0;
410     }
411     QPDFPageObjectHelper page = pages.front();
412 
413     // get the annotations in the page
414     std::vector<QPDFAnnotationObjectHelper> annotations =
415                   afdh.getWidgetAnnotationsForPage(page);
416 
417     for (std::vector<QPDFAnnotationObjectHelper>::iterator annot_iter =
418                      annotations.begin();
419                  annot_iter != annotations.end(); ++annot_iter) {
420         // For each annotation, find its associated field. If it's a
421         // text field, we try to set its value. This will automatically
422         // update the document to indicate that appearance streams need
423         // to be regenerated. At the time of this writing, qpdf doesn't
424         // have any helper code to assist with appearance stream generation,
425         // though there's nothing that prevents it from being possible.
426         QPDFFormFieldObjectHelper ffh =
427             afdh.getFieldForAnnotation(*annot_iter);
428         if (ffh.getFieldType() == "/Tx") {
429             // Lookup the options setting for value of this field and fill the
430             // value accordingly. This will automatically set
431             // /NeedAppearances to true.
432             std::string const name = ffh.getFullyQualifiedName();
433             std::string fill_with = lookup_opt(opt, name);
434             if (fill_with.empty()) {
435                 std::cerr << "DEBUG: Lack information for widget: " << name << ".\n";
436                 fill_with = "N/A";
437             }
438 
439             // convert the 'fill_with' string to UTF16 before filling to the widget
440             QPDFObjectHandle fill_with_utf_16 = QPDFObjectHandle::newUnicodeString(fill_with);
441             ffh.setV(fill_with_utf_16);
442             std::cerr << "DEBUG: Fill widget name " << name << " with value "
443                       << fill_with_utf_16.getUTF8Value() << ".\n";
444         }
445     }
446 
447     // status 1 notifies that the function successfully filled all the
448     // identifiable fields in the form
449     return 1;
450 }
451