/* * Copyright 2012 Canonical Ltd. * Copyright 2013 ALT Linux, Andrew V. Stepanov * Copyright 2018 Sahil Arora * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include #include "pdf.h" #include #include #include #include #include #include #include #include /* * Useful reference: * * http://www.gnupdf.org/Indirect_Object * http://www.gnupdf.org/Introduction_to_PDF * http://blog.idrsolutions.com/2011/05/understanding-the-pdf-file-format-%E2%80%93-pdf-xref-tables-explained * http://labs.appligent.com/pdfblog/pdf-hello-world/ * https://github.com/OpenPrinting/cups-filters/pull/25 */ /** * 'makeRealBox()' - Return a QPDF array object of real values for a box. * O - QPDFObjectHandle for an array * I - Dimensions of the box in a float array */ static QPDFObjectHandle makeRealBox(float values[4]) { QPDFObjectHandle ret = QPDFObjectHandle::newArray(); for (int i = 0; i < 4; ++i) { ret.appendItem(QPDFObjectHandle::newReal(values[i])); } return ret; } /** * 'pdf_load_template()' - Load an existing PDF file and do initial parsing * using QPDF. * I - Filename to open */ extern "C" pdf_t * pdf_load_template(const char *filename) { QPDF *pdf = new QPDF(); pdf->processFile(filename); unsigned pages = (pdf->getAllPages()).size(); if (pages != 1) { fprintf(stderr, "ERROR: PDF template must contain exactly 1 page: %s\n", filename); delete pdf; return NULL; } return pdf; } /** * 'pdf_free()' - Free pointer used by PDF object * I - Pointer to PDF object */ extern "C" void pdf_free(pdf_t *pdf) { delete pdf; } /* * 'pdf_pages()' - Count number of pages in file * using QPDF. * I - Filename to open * O - Number of pages or -1 on error */ int pdf_pages(const char *filename) { QPDF *pdf = new QPDF(); if (pdf) { try{ pdf->processFile(filename); } catch(...) { pdf_free(pdf); return -1; } int pages = (pdf->getAllPages()).size(); pdf_free(pdf); return pages; } else return -1; } /** * 'pdf_prepend_stream' - Prepend a stream to the contents of a specified * page in PDF file. * I - Pointer to QPDF object * I - page number of page to prepend stream to * I - buffer containing data to be prepended * I - length of buffer */ extern "C" void pdf_prepend_stream(pdf_t *pdf, unsigned page_num, char const *buf, size_t len) { std::vector pages = pdf->getAllPages(); if (pages.empty() || page_num > pages.size()) { fprintf(stderr, "ERROR: Unable to prepend stream to requested PDF page\n"); return; } QPDFObjectHandle page = pages[page_num - 1]; // get page contents stream / array QPDFObjectHandle contents = page.getKey("/Contents"); if (!contents.isStream() && !contents.isArray()) { fprintf(stderr, "ERROR: Malformed PDF.\n"); return; } // prepare the new stream which is to be prepended PointerHolder stream_data = PointerHolder(new Buffer(len)); memcpy(stream_data->getBuffer(), buf, len); QPDFObjectHandle stream = QPDFObjectHandle::newStream(pdf, stream_data); stream = pdf->makeIndirectObject(stream); // if the contents is an array, prepend the new stream to the array, // else convert the contents to an array and the do the same. if (contents.isStream()) { QPDFObjectHandle old_streamdata = contents; contents = QPDFObjectHandle::newArray(); contents.appendItem(old_streamdata); } contents.insertItem(0, stream); page.replaceKey("/Contents", contents); } /** * 'pdf_add_type1_font()' - Add the specified type1 fontface to the specified * page in a PDF document. * I - QPDF object * I - page number of the page to which the font is to be added * I - name of the font to be added */ extern "C" void pdf_add_type1_font(pdf_t *pdf, unsigned page_num, const char *name) { std::vector pages = pdf->getAllPages(); if (pages.empty() || page_num > pages.size()) { fprintf(stderr, "ERROR: Unable to add type1 font to requested PDF page\n"); return; } QPDFObjectHandle page = pages[page_num - 1]; QPDFObjectHandle resources = page.getKey("/Resources"); if (!resources.isDictionary()) { fprintf(stderr, "ERROR: Malformed PDF.\n"); return; } QPDFObjectHandle font = QPDFObjectHandle::newDictionary(); font.replaceKey("/Type", QPDFObjectHandle::newName("/Font")); font.replaceKey("/Subtype", QPDFObjectHandle::newName("/Type1")); font.replaceKey("/BaseFont", QPDFObjectHandle::newName(std::string("/") + std::string(name))); QPDFObjectHandle fonts = resources.getKey("/Font"); if (fonts.isNull()) { fonts = QPDFObjectHandle::newDictionary(); } else if (!fonts.isDictionary()) { fprintf(stderr, "ERROR: Can't recognize Font resource in PDF template.\n"); return; } font = pdf->makeIndirectObject(font); fonts.replaceKey("/bannertopdf-font", font); resources.replaceKey("/Font", fonts); } /** * 'dict_lookup_rect()' - Lookup for an array of rectangle dimensions in a QPDF * dictionary object. If it is found, store the values in * an array and return true, else return false. * O - True / False, depending on whether the key is found in the dictionary * I - PDF dictionary object * I - Key to lookup for * I - array to store values if key is found */ static bool dict_lookup_rect(QPDFObjectHandle object, std::string const& key, float rect[4], bool inheritable) { // preliminary checks if (!object.isDictionary()) return false; QPDFObjectHandle value; if (!object.hasKey(key) && inheritable){ QPDFFormFieldObjectHelper helper(object); value = helper.getInheritableFieldValue(key); if (value.isNull()) { return false; } } else { value = object.getKey(key); } // check if the key is array or some other type if (!value.isArray()) return false; // get values in a vector and assign it to rect std::vector array = value.getArrayAsVector(); for (int i = 0; i < 4; ++i) { // if the value in the array is not real, we have an invalid array if (!array[i].isReal() && !array[i].isInteger()) return false; rect[i] = array[i].getNumericValue(); } return array.size() == 4; } /** * 'fit_rect()' - Update the scale of the page using old media box dimensions * and new media box dimensions. * I - Old media box * I - New media box * I - Pointer to scale which needs to be updated */ static void fit_rect(float oldrect[4], float newrect[4], float *scale) { float oldwidth = oldrect[2] - oldrect[0]; float oldheight = oldrect[3] - oldrect[1]; float newwidth = newrect[2] - newrect[0]; float newheight = newrect[3] - newrect[1]; *scale = newwidth / oldwidth; if (oldheight * *scale > newheight) *scale = newheight / oldheight; } /** * 'pdf_resize_page()' - Resize page in a PDF with the given dimensions. * I - Pointer to QPDF object * I - Page number * I - Width of page to set * I - Length of page to set * I - Scale of page to set */ extern "C" void pdf_resize_page (pdf_t *pdf, unsigned page_num, float width, float length, float *scale) { std::vector pages = pdf->getAllPages(); if (pages.empty() || page_num > pages.size()) { fprintf(stderr, "ERROR: Unable to resize requested PDF page\n"); return; } QPDFObjectHandle page = pages[page_num - 1]; float new_mediabox[4] = { 0.0, 0.0, width, length }; float old_mediabox[4]; QPDFObjectHandle media_box; if (!dict_lookup_rect(page, "/MediaBox", old_mediabox, true)) { fprintf(stderr, "ERROR: pdf doesn't contain a valid mediabox\n"); return; } fit_rect(old_mediabox, new_mediabox, scale); media_box = makeRealBox(new_mediabox); page.replaceKey("/ArtBox", media_box); page.replaceKey("/BleedBox", media_box); page.replaceKey("/CropBox", media_box); page.replaceKey("/MediaBox", media_box); page.replaceKey("/TrimBox", media_box); } /** * 'pdf_duplicate_page()' - Duplicate a specified pdf page in a PDF * I - Pointer to QPDF object * I - page number of the page to be duplicated * I - number of copies to be duplicated */ extern "C" void pdf_duplicate_page (pdf_t *pdf, unsigned page_num, unsigned count) { std::vector pages = pdf->getAllPages(); if (pages.empty() || page_num > pages.size()) { fprintf(stderr, "ERROR: Unable to duplicate requested PDF page\n"); return; } QPDFObjectHandle page = pages[page_num - 1]; for (unsigned i = 0; i < count; ++i) { page = pdf->makeIndirectObject(page); pdf->addPage(page, false); } } /** * 'pdf_write()' - Write the contents of PDF object to an already open FILE*. * I - pointer to QPDF structure * I - File pointer to write to */ extern "C" void pdf_write(pdf_t *pdf, FILE *file) { QPDFWriter output(*pdf, "pdf_write", file, false); output.write(); } /* * 'lookup_opt()' - Get value according to key in the options list. * I - pointer to the opt_t type list * I - key to be found in the list * O - character string which corresponds to the value of the key or * NULL if key is not found in the list. */ std::string lookup_opt(opt_t *opt, std::string const& key) { if ( ! opt || key.empty() ) { return ""; } while (opt) { if (opt->key && opt->val) { if ( strcmp(opt->key, key.c_str()) == 0 ) { return std::string(opt->val); } } opt = opt->next; } return ""; } /* * 'pdf_fill_form()' - 1. Lookup in PDF template file for form. * 2. Lookup for form fields' names. * 3. Fill recognized fields with information. * I - Pointer to the QPDF structure * I - Pointer to the opt_t type list * O - status of form fill - 0 for failure, 1 for success */ extern "C" int pdf_fill_form(pdf_t *doc, opt_t *opt) { // initialize AcroFormDocumentHelper and PageDocumentHelper objects // to work with forms in the PDF QPDFAcroFormDocumentHelper afdh(*doc); QPDFPageDocumentHelper pdh(*doc); // check if the PDF has a form or not if ( !afdh.hasAcroForm() ) { fprintf(stderr, "DEBUG: PDF template file doesn't have form. It's okay.\n"); return 0; } // get the first page from the PDF to fill the form. Since this // is a banner file,it must contain only a single page, and that // check has already been performed in the `pdf_load_template()` function std::vector pages = pdh.getAllPages(); if (pages.empty()) { fprintf(stderr, "ERROR: Can't get page from PDF tamplate file.\n"); return 0; } QPDFPageObjectHelper page = pages.front(); // get the annotations in the page std::vector annotations = afdh.getWidgetAnnotationsForPage(page); for (std::vector::iterator annot_iter = annotations.begin(); annot_iter != annotations.end(); ++annot_iter) { // For each annotation, find its associated field. If it's a // text field, we try to set its value. This will automatically // update the document to indicate that appearance streams need // to be regenerated. At the time of this writing, qpdf doesn't // have any helper code to assist with appearance stream generation, // though there's nothing that prevents it from being possible. QPDFFormFieldObjectHelper ffh = afdh.getFieldForAnnotation(*annot_iter); if (ffh.getFieldType() == "/Tx") { // Lookup the options setting for value of this field and fill the // value accordingly. This will automatically set // /NeedAppearances to true. std::string const name = ffh.getFullyQualifiedName(); std::string fill_with = lookup_opt(opt, name); if (fill_with.empty()) { std::cerr << "DEBUG: Lack information for widget: " << name << ".\n"; fill_with = "N/A"; } // convert the 'fill_with' string to UTF16 before filling to the widget QPDFObjectHandle fill_with_utf_16 = QPDFObjectHandle::newUnicodeString(fill_with); ffh.setV(fill_with_utf_16); std::cerr << "DEBUG: Fill widget name " << name << " with value " << fill_with_utf_16.getUTF8Value() << ".\n"; } } // status 1 notifies that the function successfully filled all the // identifiable fields in the form return 1; }