在 Windows 上将输出写入 /dev/stdout/ 或 /dev/stderr/ 时,PhantomJS 会执行以下步骤(如 \phantomjs\src\webpage.cpp 中的 render 方法所示):
- 如果没有
/dev/stdout/ 和/dev/stderr/,则会分配一个临时文件路径。
- 使用临时文件路径调用
renderPdf。
- 将网页呈现到此文件路径。
- 将此文件的内容读入
QByteArray。
- 在字节数组上调用
QString::fromAscii并写入stdout或stderr。
- 删除临时文件。
首先,我为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 类包含对stdout、stdin 和stderr 的File 对象的引用,这些对象设置为使用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,所以应该没问题。