How can I use async functions with node-pdfkit?
I am trying to create a PDF with PDFKit . I am inserting an image like this:
var PDFDocument = require('pdfkit');
var doc = new PDFDocument();
doc.image(some_image_as_buffer);
and works as expected. But now I want the image to be cut off, and I found GraphicsMagick for node.js . But the problem is I have to work with PDFKit. doc.image
expects a filename or buffer, but since I already have a buffer, I want to work with buffers (there is no file anywhere, because the buffer comes directly from the database).
Cropping works like this:
var gm = require('gm');
gm(some_image_as_buffer, 'image.png')
.trim()
.toBuffer(function(err, trimmed_image_buffer) {
// trimmed_image_buffer is correct,
// but I can't put it to the document like this:
doc.image(trimmed_image_buffer);
// beacause I don't know which page and/or position
// the doc is currently on, because of the asynchronous
// nature of this callback.
});
UPDATE
For clarification: I want to be able to use asynchronous cropped image in synchronous code for PDFKit. PDFKit only works synchronously and gm
does not offer a synchronous interface.
UPDATE2
var gm = require('gm');
gm(some_image_as_buffer, 'image.png')
.trim()
.toBuffer(function(err, trimmed_image_buffer) {
// trimmed_image_buffer is correct,
// but I can't put it to the document like this:
doc.image(trimmed_image_buffer);
// beacause I don't know which page and/or position
// the doc is currently on, because of the asynchronous
// nature of this callback.
});
doc.text('some text');
// is not guaranteed to run after image is inserted
// and a couple of hundred lines more
After the last line in this example, there are a lot more lines of code that add content to the PDF, but I don't want to put everything (a couple hundred lines) in one callback just because I need an asynchronous function to manipulate the image.
Is there a way to make this manipulation synchronous?
source to share
UPDATE_2
Basically, you are requesting to stop executing code before the asynchronous operation completes. Of course, this is generally impossible.
In the case of a module, gm
this is also not possible. The module gm
generates a new process to execute the command (trim () in your case), and the API for creating new processes is asynchronous in nature.
UPDATE
Use a promise in your script:
var gm = require('gm'),
Q = require('Q'),
PDFDocument = require('pdfkit'),
doc = new PDFDocument();
function getTrimmedImage(some_image_as_buffer){
var deferred = Q.defer();
gm(some_image_as_buffer, 'image.png')
.trim()
.toBuffer(function(err, trimmed_image_buffer) {
if(err) { deferred.reject(err); }
else { deferred.resolve(trimmed_image_buffer); }
});
return deferred.promise;
}
// here goes all manipulations before the trimmed image is inserted
getTrimmedImage(some_image_as_buffer).then(
function(trimmed_image_buffer){
doc.image(trimmed_image_buffer);
// here goes all manipulations after the trimmed image is inserted
}
);
As I wrote in the comment above, a promise based solution should work elegantly. I'm using the Q library , but any other promise library will do the job as well.
One option is to collect all the resources of an asynchronous nature before starting to manipulate the pdf. Then you are guaranteed that a race condition will not happen, although it can slow down the whole process. I used the toys example to make it work in a browser environment, let me know if you have any trouble converting it to your use case:
function getAsyncResource(){
var defer = Q.defer();
setTimeout(function(){
var result = "Some value: " + Date.now();
console.log("Async resource resolved: " + result);
defer.resolve(result);
}, Math.random() * 5000);
return defer.promise;
}
function someOperationThatNeedsAsyncResources(A, B, C){
console.log("Have all resources: ", A, B, C);
}
var A = getAsyncResource(),
B = getAsyncResource(),
C = getAsyncResource();
Q.all([A,B,C]).spread(someOperationThatNeedsAsyncResources);
<script src="//cdnjs.cloudflare.com/ajax/libs/q.js/1.1.2/q.js"></script>
Another option would be to split the process into stages:
function getAsyncResource(value){
var defer = Q.defer();
setTimeout(function(){
var result = "Some value: " + value;
console.log("Async resource resolved: " + result);
defer.resolve(result);
}, Math.random() * 5000);
return defer.promise;
}
function nextStep(resource){
console.log("Next step: " + resource);
}
var A = getAsyncResource("A"),
B = getAsyncResource("B"),
C = getAsyncResource("C");
A.then(nextStep)
.then(function(){return B;})
.then(nextStep)
.then(function(){return C;})
.then(nextStep);
<script src="//cdnjs.cloudflare.com/ajax/libs/q.js/1.1.2/q.js"></script>
source to share