2013-10-22 41 views
8

我拼命地输出经phantomJS生成的PDF到stdout像herephantomjs PDF到stdout

我所得到的是一个空的PDF文件,但它不为0的大小,它会显示一个空白页。

var page = require('webpage').create(), 
system = require('system'), 
address; 

address = system.args[1]; 
page.paperSize = {format: 'A4'}; 

page.open(address, function (status) { 
    if (status !== 'success') { 
     console.log('Unable to load the address!'); 
     phantom.exit(); 
    } else { 
     window.setTimeout(function() { 
      page.render('/dev/stdout', { format: 'pdf' }); 
      phantom.exit(); 
     }, 1000); 
    } 
}); 

我叫它像这样:phantomjs rasterize.js http://google.com>test.pdf

我试图改变/dev/stdoutsystem.stdout但不是运气。将PDF直接写入文件可以毫无问题地工作。

我在寻找一个跨平台的实现,所以我希望这可以在非Linux系统上实现。

+0

什么版本的phantomjs?尝试升级到最新版本。 – philfreo

+1

我在1.9.2 Win8x64上看到了同样的问题。不输出管道似乎在控制台中有一些pdf内容,但通过phantomjs将输出直接输出到文件rasterize.js> test.pdf没有任何进展。 –

+0

@philfreo我在Win7上使用1.9.2 – michaeltintiuc

回答

15

当输出写入到/dev/stdout//dev/stderr/在Windows上,PhantomJS经过以下步骤(如被看见在在\phantomjs\src\webpage.cpprender法):

  1. 在缺乏/dev/stdout//dev/stderr/的TEM porary文件路径被分配。
  2. 用临时文件路径调用renderPdf
  3. 将网页呈现到此文件路径。
  4. 阅读本文件的内容到QByteArray
  5. 在字节阵列上调用QString::fromAscii并写入stdoutstderr
  6. 删除临时文件。

首先,我构建了PhantomJS的源代码,但注释掉了文件删除。在下一次运行中,我能够检查它所呈现的临时文件,结果证明它是完全正确的。我也尝试运行phantomjs.exe rasterize.js http://google.com > test.png,结果相同。这立即排除了渲染问题,或任何与PDF相关的问题,这意味着问题必须与数据写入stdout的方式相关。

在这个阶段,我怀疑是否有一些文本编码shenanigans正在进行。从以前的运行中,我有同一个文件的有效和无效版本(在这种情况下是PNG)。

使用一些C#代码,我跑了如下实验:

//Read the contents of the known good file. 
byte[] bytesFromGoodFile = File.ReadAllBytes("valid_file.png"); 
//Read the contents of the known bad file. 
byte[] bytesFromBadFile = File.ReadAllBytes("invalid_file.png"); 

//Take the bytes from the valid file and convert to a string 
//using the Latin-1 encoding. 
string iso88591String = Encoding.GetEncoding("iso-8859-1").GetString(bytesFromGoodFile); 
//Take the Latin-1 encoded string and retrieve its bytes using the UTF-8 encoding. 
byte[] bytesFromIso88591String = Encoding.UTF8.GetBytes(iso88591String); 

//If the bytes from the Latin-1 string are all the same as the ones from the 
//known bad file, we have an encoding problem. 
Debug.Assert(bytesFromBadFile 
    .Select((b, i) => b == bytesFromIso88591String[i]) 
    .All(c => c)); 

请注意,我用ISO-8859-1编码为QT使用此名称作为default encoding for c-strings。事实证明,所有这些字节都是一样的。该练习的重点在于看我是否可以模仿导致有效数据无效的编码步骤。

有关进一步的证据,我调查了\phantomjs\src\system.cpp\phantomjs\src\filesystem.cpp

  • system.cpp,所述System类持有引用,除其他外,File对象为stdoutstdinstderr,其被设置为使用UTF-8编码。
  • 当写入stdout时,将调用File对象的write函数。此函数支持写入文本和二进制文件,但由于System类初始化它们的方式,所有写入都将被视为文本文件。

所以,问题归结为:我们需要将执行二进制写stdout,但我们写操作最终会被视为文本,并具有适用于他们的编码,导致生成的文件是无效的。


鉴于上述问题,我看不出有什么办法让这个工作,你想在Windows的方式未做更改PhantomJS代码。因此,它们是:

第一次更改将提供一个函数,我们可以调用File对象来明确执行二进制写入。

添加下面的函数原型\phantomjs\src\filesystem.h

bool binaryWrite(const QString &data); 

,并放置在\phantomjs\src\filesystem.cpp其定义(代码此方法来源于write方法在这个文件中):

bool File::binaryWrite(const QString &data) 
{ 
    if (!m_file->isWritable()) { 
     qDebug() << "File::write - " << "Couldn't write:" << m_file->fileName(); 
     return true; 
    } 

    QByteArray bytes(data.size(), Qt::Uninitialized); 
    for(int i = 0; i < data.size(); ++i) { 
     bytes[i] = data.at(i).toAscii(); 
    } 
    return m_file->write(bytes); 
} 

在身边第0123行\phantomjs\src\webpage.cpp您会看到一段代码,如下所示:

if(fileName == STDOUT_FILENAME){ 
#ifdef Q_OS_WIN32 
     _setmode(_fileno(stdout), O_BINARY);    
#endif  

     ((File *)system->_stderr())->write(QString::fromAscii(name.constData(), name.size())); 

#ifdef Q_OS_WIN32 
     _setmode(_fileno(stdout), O_TEXT); 
#endif   
    } 

它改成这样:

if(fileName == STDOUT_FILENAME){ 
#ifdef Q_OS_WIN32 
     _setmode(_fileno(stdout), O_BINARY); 
     ((File *)system->_stdout())->binaryWrite(QString::fromAscii(ba.constData(), ba.size())); 
#elif    
     ((File *)system->_stderr())->write(QString::fromAscii(name.constData(), name.size())); 
#endif  

#ifdef Q_OS_WIN32 
     _setmode(_fileno(stdout), O_TEXT); 
#endif   
    } 

那么,什么是代码替换所做的就是调用我们的新binaryWrite功能,而是通过#ifdef Q_OS_WIN32块不那么森严。我这样做是为了保留非Windows系统上的旧功能,这些功能似乎没有表现出这种问题(或者他们是这样做的?)。请注意,此修正仅适用于写入stdout - 如果您希望始终可以将其应用于stderr,但在此情况下可能无关紧要。

如果你只是想要一个预先构建的二进制文件(谁不会?),你可以在我的SkyDrive上找到phantomjs.exe这些修复程序。我的版本大约是19MB,而我之前下载的版本只有大约6MB,但我按照指示here,所以它应该没问题。

+0

这真是太神奇了,非常感谢您的帮助,时间和精力投入了这个答案! – michaeltintiuc

0

是否必须输出PDF到标准输出?你不能更改代码以:

var page = require('webpage').create(), 
system = require('system'), 
address; 

address = system.args[1]; 
output = system.args[2]; 
page.paperSize = {format: 'A4'}; 

page.open(address, function (status) { 
    if (status !== 'success') { 
     console.log('Unable to load the address!'); 
     phantom.exit(); 
    } else { 
     window.setTimeout(function() { 
      page.render(output, { format: 'pdf' }); 
      phantom.exit(); 
     }, 1000); 
    } 
}); 

,并使用它像这样:

phantomjs rasterize.js http://google.com test.pdf 
+0

这就是我正在做的工作,但我的想法是即时创建pdf。在node-webkit push和phantomjs之间来回传送数据。 – michaeltintiuc

+0

我会仔细研究一下,可能有一些人物正在搞乱PDF结构。 –

7

是的,这是正确的ISO-8859-1是QT的默认编码,因此您需要将所需的参数添加到命令行--output-encoding = ISO-8859-1,以便PDF输出不会被破坏

ie

phantomjs.exe rasterize.js --output编码= ISO-8859-1 < input.html>输出.pdf

和rasterize.js看起来像这样(测试时,适用于Unix和Windows)

var page = require('webpage').create(), 
system = require('system'); 

page.viewportSize = {width: 600, height: 600}; 
page.paperSize = {format: 'A4', orientation: system.args[1], margin: '1cm'}; 

page.content = system.stdin.read(); 

window.setTimeout(function() { 
    try { 
     page.render('/dev/stdout', {format: 'pdf'}); 
    } 
    catch (e) { 
     console.log(e.message + ';;' + output_file); 
    } 
    phantom.exit(); 
}, 1000); 

或者可选地可以设置使用标准输出编码和如果从UTF-8流中读取,那么您可能需要设置编码标准输入为好;

system.stdout.setEncoding('ISO-8859-1'); 
system.stdin.setEncoding('UTF-8'); 
page.content = system.stdin.read(); 
+1

谢谢老兄,疯狂的老问题是如何得到一个新答案的,感谢您的时间!我暂时没有在这个项目上工作,但很快就会重新讨论这个项目。 – michaeltintiuc

+1

'system.stdout.setEncoding('ISO-8859-1');'< - 经过几个小时的调试,此行节省了我的时间。非常感谢你的回答! – Khan

+0

@汗不用担心:) – Pinchy