diff --git a/README.md b/README.md index db72aaf..82e2d12 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,46 @@
-**pdf-fill-form** is Node.js native C++ library for filling PDF forms. Created PDF file is returned back as Node.js Buffer object for further processing or saving - *whole process is done in memory*. Library offers methods to return filled PDF also as PDF file where pages are converted to images. +PDF Fill Form (**pdf-fill-form**) is Node.js native C++ library for filling PDF forms. Created PDF file is returned back as Node.js Buffer object for further processing or saving - *whole process is done in memory*. Library offers methods to return filled PDF also as PDF file where pages are converted to images. Libary uses internally Poppler QT4 for PDF form reading and filling. Cairo is used for PDF creation from page images (when parameter { "save": "imgpdf" } is used). +##Features +* Supports reading and writing the following PDF form field types: TextField and Checkbox +* You can write following files: + * PDF + * PDF where pages are converted to images +* All the work is done in memory - no temporary files created +* Results are returned in Node.js Buffer -object +* Not using the PDFtk -executable - instead we use the Poppler library + ##Examples +###Using promises +```javascript +var pdfFillForm = require('pdf-fill-form'); + +pdfFillForm.read('test.pdf') +.then(function(result) { + console.log(result); +}, function(err) { + console.log(err); +}); +``` +```javascript +var pdfFillForm = require('pdf-fill-form'); +var fs = require('fs'); + +pdfFillForm.write('test.pdf', { "myField": "myField fill value" }, { "save": "pdf" } ) +.then(function(result) { + fs.writeFile("test123.pdf", result, function(err) { + if(err) { + return console.log(err); + } + console.log("The file was saved!"); + }); +}, function(err) { + console.log(err); +}); + +``` +###Using callbacks To **read** all form fields: ```javascript @@ -78,6 +116,7 @@ $ npm install pdf-fill-form ##Todo * Tests * Refactoring +* Support for other form field types than CheckBox and TextField ##Authors - [Tommi Pisto](https://github.com/tpisto) diff --git a/lib/pdf-fill-form.js b/lib/pdf-fill-form.js index 4d0b3f5..b5aed94 100644 --- a/lib/pdf-fill-form.js +++ b/lib/pdf-fill-form.js @@ -1,13 +1,49 @@ -(function () { +(function () { "use strict"; + + var makePromises = function(myLib) { + + // Read promise (sync) + myLib.read = function(fileName) { + return new Promise(function(resolve, reject) { + try { + var myFile = myLib.readSync(fileName); + resolve(myFile); + } + catch(error) { + reject(error); + } + }); + } + + // Write promise (async) + myLib.write = function(fileName, fields, params) { + return new Promise(function(resolve, reject) { + try { + myLib.writeAsync(fileName, fields, params, function(err, result) { + if(err) { reject(err); } + else { + resolve(result); + } + }); + } + catch(error) { + reject(error); + } + }); + } + + return myLib; + } + try { try { - module.exports = require(__dirname +'/../build/Debug/pdf_fill_form'); + module.exports = makePromises(require(__dirname +'/../build/Debug/pdf_fill_form')); } catch (e) { - module.exports = require(__dirname+'/../build/Release/pdf_fill_form'); + module.exports = makePromises(require(__dirname+'/../build/Release/pdf_fill_form')); } } catch (e) { console.log(e); - module.exports = require(__dirname+'/../build/default/pdf_fill_form'); + module.exports = makePromises(require(__dirname+'/../build/default/pdf_fill_form')); } })(); \ No newline at end of file diff --git a/package.json b/package.json index 7ffbc81..7974c14 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "keywords": [ "pdf", "forms", "poppler", "qt4", "cairo" ], - "version": "0.1.3", + "version": "1.0.0", "repository": { "type": "git", "url": "git://github.com/tpisto/pdf-fill-form.git" diff --git a/src/NodePoppler.cc b/src/NodePoppler.cc index ce226de..7a3f529 100644 --- a/src/NodePoppler.cc +++ b/src/NodePoppler.cc @@ -25,14 +25,15 @@ using v8::Object; using v8::Local; using v8::Array; using v8::Value; +using v8::Boolean; inline bool fileExists (const std::string& name) { if (FILE *file = fopen(name.c_str(), "r")) { - fclose(file); - return true; + fclose(file); + return true; } else { - return false; - } + return false; + } } // Cairo write and read functions (to QBuffer) @@ -41,7 +42,7 @@ static cairo_status_t readPngFromBuffer(void *closure, unsigned char *data, unsi size_t bytes_read; bytes_read = myBuffer->read((char *)data, length); if (bytes_read != length) - return CAIRO_STATUS_READ_ERROR; + return CAIRO_STATUS_READ_ERROR; return CAIRO_STATUS_SUCCESS; } @@ -50,7 +51,7 @@ static cairo_status_t writePngToBuffer(void *closure, const unsigned char *data, size_t bytes_wrote; bytes_wrote = myBuffer->write((char *)data, length); if (bytes_wrote != length) - return CAIRO_STATUS_READ_ERROR; + return CAIRO_STATUS_READ_ERROR; return CAIRO_STATUS_SUCCESS; } @@ -68,9 +69,9 @@ void createPdf(QBuffer *buffer, Poppler::Document *document) { // FileLockedError, // OpenOutputError, // NotSupportedInputFileError - // }; + // }; ss << "Error occurred when converting PDF: " << converter->lastError(); - throw ss.str(); + throw ss.str(); } } @@ -88,7 +89,7 @@ void createImgPdf(QBuffer *buffer, Poppler::Document *document) { // Save the page as PNG image to buffer. (We could use QFile if we want to save images to files) QBuffer *pageImage = new QBuffer(); pageImage->open(QIODevice::ReadWrite); - QImage img = page->renderToImage(360,360); + QImage img = page->renderToImage(360, 360); img.save(pageImage, "png"); pageImage->seek(0); // Reading does not work if we don't reset the pointer @@ -120,7 +121,7 @@ WriteFieldsParams v8ParamsToCpp(const v8::FunctionCallbackInfo& args) { string sourcePdfFileName = *NanAsciiString(args[0]); Local changeFields = args[1]->ToObject(); - + // Check if any configuration parameters if (args.Length() > 2) { parameters = args[2]->ToObject(); @@ -128,7 +129,7 @@ WriteFieldsParams v8ParamsToCpp(const v8::FunctionCallbackInfo& args) { if (!saveParam->IsUndefined()) { saveFormat = *NanAsciiString(parameters->Get(NanNew("save"))); } - } + } // Convert form fields to c++ map Local fieldArray = changeFields->GetPropertyNames(); @@ -149,9 +150,9 @@ QBuffer *writePdfFields(struct WriteFieldsParams params) { ostringstream ss; // If source file does not exist, throw error and return false - if(!fileExists(params.sourcePdfFileName)) { - ss << "File \"" << params.sourcePdfFileName << "\" does not exist"; - throw ss.str(); + if (!fileExists(params.sourcePdfFileName)) { + ss << "File \"" << params.sourcePdfFileName << "\" does not exist"; + throw ss.str(); } // Open document and return false and throw error if something goes wrong @@ -167,13 +168,30 @@ QBuffer *writePdfFields(struct WriteFieldsParams params) { for (int i = 0; i < n; i += 1) { Poppler::Page *page = document->page(i); - foreach(Poppler::FormField *field, page->formFields()) { - string fieldName = field->fullyQualifiedName().toStdString(); + foreach (Poppler::FormField *field, page->formFields()) { + string fieldName = field->fullyQualifiedName().toStdString(); if (!field->isReadOnly() && field->isVisible() && params.fields.count(fieldName)) { + + // Text if (field->type() == Poppler::FormField::FormText) { Poppler::FormFieldText *textField = (Poppler::FormFieldText *) field; textField->setText(QString::fromUtf8(params.fields[fieldName].c_str())); - } + } + + // Button + if (field->type() == Poppler::FormField::FormButton) { + Poppler::FormFieldButton *buttonField = (Poppler::FormFieldButton *) field; + + // Checkbox !TODO! - enable also other types. Note. Poppler doesn't support checkboxes with hashtag names (aka using exportValue). + if (buttonField->buttonType() == Poppler::FormFieldButton::CheckBox) { + if (params.fields[fieldName].compare("true") == 0) { + buttonField->setState(true); + } + else { + buttonField->setState(false); + } + } + } } } } @@ -181,12 +199,12 @@ QBuffer *writePdfFields(struct WriteFieldsParams params) { // Now save and return the document QBuffer *bufferDevice = new QBuffer(); bufferDevice->open(QIODevice::ReadWrite); - + // Get save parameters if (params.saveFormat == "imgpdf") { createImgPdf(bufferDevice, document); - } - else { + } + else { createPdf(bufferDevice, document); } @@ -195,13 +213,13 @@ QBuffer *writePdfFields(struct WriteFieldsParams params) { //*********************************** -// +// // Node.js methods -// +// //*********************************** // Read PDF form fields -NAN_METHOD(ReadSync) { +NAN_METHOD(ReadSync) { NanScope(); // expect a number as the first argument @@ -210,10 +228,10 @@ NAN_METHOD(ReadSync) { int n = 0; // If file does not exist, throw error and return false - if(!fileExists(**fileName)) { - ss << "File \"" << **fileName << "\" does not exist"; - NanThrowError(NanNew(ss.str())); - NanReturnValue(NanFalse()); + if (!fileExists(**fileName)) { + ss << "File \"" << **fileName << "\" does not exist"; + NanThrowError(NanNew(ss.str())); + NanReturnValue(NanFalse()); } // Open document and return false and throw error if something goes wrong @@ -221,8 +239,8 @@ NAN_METHOD(ReadSync) { if (document != NULL) { // Get field list n = document->numPages(); - } else { - ss << "Error occurred when reading \"" << **fileName << "\""; + } else { + ss << "Error occurred when reading \"" << **fileName << "\""; NanThrowError(NanNew(ss.str())); NanReturnValue(NanFalse()); } @@ -234,22 +252,65 @@ NAN_METHOD(ReadSync) { for (int i = 0; i < n; i += 1) { Poppler::Page *page = document->page(i); foreach (Poppler::FormField *field, page->formFields()) { - - if (!field->isReadOnly() && field->isVisible()) { - - // Currently we only handle text fields - !TODO! - if (field->type() == Poppler::FormField::FormText) { - // Make JavaScript object out of the fieldnames - Local obj = NanNew(); - obj->Set(NanNew("name"), NanNew(field->fullyQualifiedName().toStdString())); - obj->Set(NanNew("value"), NanNew(((Poppler::FormFieldText *)field)->text().toStdString())); - obj->Set(NanNew("type"), NanNew("text")); - obj->Set(NanNew("page"), NanNew(i)); + if (!field->isReadOnly() && field->isVisible()) { - fieldArray->Set(fieldNum, obj); - fieldNum++; + // Make JavaScript object out of the fieldnames + Local obj = NanNew(); + obj->Set(NanNew("name"), NanNew(field->fullyQualifiedName().toStdString())); + obj->Set(NanNew("page"), NanNew(i)); + + // ! TODO ! Now supports values only for "checkbox" and "text". Note. Poppler doesn't support checkboxes with hashtag names (aka using exportValue). + string fieldType; + // Set default value undefined + obj->Set(NanNew("value"), NanUndefined()); + Poppler::FormFieldButton *myButton; + Poppler::FormFieldChoice *myChoice; + + switch (field->type()) { + + // FormButton + case Poppler::FormField::FormButton: + myButton = (Poppler::FormFieldButton *)field; + switch (myButton->buttonType()) { + // Push + case Poppler::FormFieldButton::Push: fieldType = "push_button"; break; + case Poppler::FormFieldButton::CheckBox: + fieldType = "checkbox"; + obj->Set(NanNew("value"), NanNew(myButton->state())); + break; + case Poppler::FormFieldButton::Radio: fieldType = "radio"; break; + } + break; + + // FormText + case Poppler::FormField::FormText: + obj->Set(NanNew("value"), NanNew(((Poppler::FormFieldText *)field)->text().toStdString())); + fieldType = "text"; + break; + + // FormChoice + case Poppler::FormField::FormChoice: + myChoice = (Poppler::FormFieldChoice *)field; + switch (myChoice->choiceType()) { + case Poppler::FormFieldChoice::ComboBox: fieldType = "combobox"; break; + case Poppler::FormFieldChoice::ListBox: fieldType = "listbox"; break; + } + break; + + // FormSignature + case Poppler::FormField::FormSignature: fieldType = "formsignature"; break; + + default: + fieldType = "undefined"; + break; } + + obj->Set(NanNew("type"), NanNew(fieldType.c_str())); + + fieldArray->Set(fieldNum, obj); + fieldNum++; + } } } @@ -258,24 +319,24 @@ NAN_METHOD(ReadSync) { } // Write PDF form fields -NAN_METHOD(WriteSync) { +NAN_METHOD(WriteSync) { NanScope(); // Check and return parameters given at JavaScript function call WriteFieldsParams params = v8ParamsToCpp(args); - + // Create and return pdf - try + try { QBuffer *buffer = writePdfFields(params); - Local returnPdf = NanNewBufferHandle(buffer->data().data(), buffer->size()); - NanReturnValue(returnPdf); - } - catch (string error) + Local returnPdf = NanNewBufferHandle(buffer->data().data(), buffer->size()); + NanReturnValue(returnPdf); + } + catch (string error) { NanThrowError(NanNew(error)); NanReturnValue(NanNull()); - } + } } diff --git a/src/NodePoppler.h b/src/NodePoppler.h index 8ee52ac..83d8872 100644 --- a/src/NodePoppler.h +++ b/src/NodePoppler.h @@ -8,7 +8,6 @@ using namespace std; -// class Poppler : public node::ObjectWrap { struct WriteFieldsParams { WriteFieldsParams(string a, string b, map c) : sourcePdfFileName(a), saveFormat(b), fields(c){} string sourcePdfFileName; @@ -24,4 +23,4 @@ QBuffer *writePdfFields(WriteFieldsParams params); // } -#endif // EPISTONPOPPLER_H_ \ No newline at end of file +#endif // PISTONPOPPLER_H_ \ No newline at end of file