【问题标题】:phantomjs pdf to stdoutphantomjs pdf 到标准输出
【发布时间】:2013-10-31 00:50:21
【问题描述】:

我正在拼命尝试将 phantomJS 生成的 PDF 输出到标准输出,例如 here

我得到的是一个空的 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/stdout 更改为system.stdout,但运气不佳。将 PDF 直接写入文件没有任何问题。

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

【问题讨论】:

  • 什么版本的phantomjs?尝试升级到最新版本。
  • 我在 1.9.2 Win8x64 上看到了同样的问题。没有管道输出似乎在控制台中有一些 pdf 内容,但是通过 phantomjs rasterize.js > test.pdf 直接将输出管道传输到文件没有任何结果。
  • @philfreo 我在 Win7 上使用 1.9.2
  • 您能否将生成的 pdf 文件上传到某处,可能值得研究一下 pdf 生成的代码是否存在视觉问题。

标签: javascript pdf stdout phantomjs


【解决方案1】:

是的,没错 ISO-8859-1 是 QT 的默认编码,因此您需要在命令行中添加所需的参数 --output-encoding=ISO-8859-1 这样 pdf 输出就不会是损坏的

phantomjs.exe rasterize.js --output-encoding=ISO-8859-1 output.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);

或者您可以使用 stdout 设置编码,如果您从 UTF-8 流中读取,那么您可能还必须为 stdin 设置编码;

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

【讨论】:

  • 谢谢老兄,这么老的问题怎么会有新的答案,谢谢你的时间!我有一段时间没有从事那个项目了,但很快就会重新审视它。
  • system.stdout.setEncoding('ISO-8859-1');
  • @Khan 别担心 :)
  • 哇。这也是使用 NReco.PhantomJS 包装器在 C# MVC 应用程序中将 PDF 输出渲染到 /dev/stdout 的关键。
【解决方案2】:

在 Windows 上将输出写入 /dev/stdout//dev/stderr/ 时,PhantomJS 会执行以下步骤(如 \phantomjs\src\webpage.cpp 中的 render 方法所示):

  1. 如果没有/dev/stdout//dev/stderr/,则会分配一个临时文件路径。
  2. 使用临时文件路径调用renderPdf
  3. 将网页呈现到此文件路径。
  4. 将此文件的内容读入QByteArray
  5. 在字节数组上调用QString::fromAscii并写入stdoutstderr
  6. 删除临时文件。

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

到了这个阶段,我怀疑是否存在一些文本编码恶作剧。在之前的运行中,我拥有同一文件的有效和无效版本(在本例中为 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 类包含对stdoutstdinstderrFile 对象的引用,这些对象设置为使用UTF-8 编码。
  • 写入stdout 时,会调用File 对象的write 函数。此函数支持写入文本文件和二进制文件,但由于 System 类初始化它们的方式,所有写入都将被视为写入文本文件。

所以问题归结为:我们需要对stdout 执行二进制写入,但我们的写入最终被视为文本并对其应用了编码,导致生成的文件无效。


鉴于上述问题,如果不更改PhantomJS 代码,我看不出有任何方法可以在 Windows 上按照您想要的方式工作。所以他们在这里:

第一个更改将提供一个函数,我们可以调用 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);
}

\phantomjs\src\webpage.cpp 的第 920 行附近,您会看到如下代码块:

    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,所以应该没问题。

【讨论】:

  • 这太棒了,非常感谢您为这个答案付出的帮助、时间和精力!
【解决方案3】:

是否必须将 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

【讨论】:

  • 这就是我正在做的工作,但我的想法是动态创建 pdf。在 node-webkit push 和 phantomjs 之间来回推送数据。
  • 我会深入研究一下,可能有一些字符弄乱了 PDF 结构。
猜你喜欢
  • 2012-07-09
  • 2013-05-01
  • 2018-08-21
  • 1970-01-01
  • 2021-12-13
  • 2014-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多