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?

+3


source to share


1 answer


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>
      

Run codeHide result


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>
      

Run codeHide result


+3


source







All Articles