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