3

I am trying to create a PDF file with PDFKit. I insert an image with like this:

var PDFDocument = require('pdfkit');
var doc = new PDFDocument();

doc.image(some_image_as_buffer);

and it is working like expected. But now want the image be trimmed and I found GraphicsMagick for node.js. But the problem that I have is to make it work with PDFKit. doc.image expects a filename or a 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).

The trimming 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 the asynchronous trimmed image in the synchronous code for PDFKit. PDFKit only works synchronously and gm doesn't 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 which add content to the PDF, but I don't want to put everything (couple of hundred lines) in one callback just because I need on asynchronous function to manipulate the image.

Is there any way to make this manipulation synchronous?

4

1 回答 1

3

更新_2

您基本上要求停止执行代码,直到某些异步操作完成。当然,在一般情况下是不可能的。

gm模块的情况下,也不可能。该gm模块生成一个用于执行命令的新进程(在您的情况下是trim()),并且用于生成新进程的 API 本质上是异步的。

更新

要在您的场景中使用 Promise:

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

  }
);

正如我在上面的评论中所写,基于 Promise 的解决方案应该可以优雅地工作。我使用Q library,但任何其他 promise 库也可以完成这项工作。

一种选择是在开始操作 pdf 之前收集所有异步性质的资源。然后您可以保证不会发生竞争条件,尽管它可能会减慢整个过程。我使用了一个玩具示例让它在浏览器环境中工作,如果您在将其转换为您的用例时遇到任何问题,请告诉我:

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>

其他选择是将过程拆分为步骤,如下所示:

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>

于 2015-01-05T10:46:04.483 回答