当在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#代码,我进行了以下实验:
byte[] bytesFromGoodFile = File.ReadAllBytes("valid_file.png");
byte[] bytesFromBadFile = File.ReadAllBytes("invalid_file.png");
string iso88591String = Encoding.GetEncoding("iso-8859-1").GetString(bytesFromGoodFile);
byte[] bytesFromIso88591String = Encoding.UTF8.GetBytes(iso88591String);
Debug.Assert(bytesFromBadFile
.Select((b, i) => b == bytesFromIso88591String[i])
.All(c => c));
请注意,我使用了ISO-8859-1编码,因为QT
将其作为c-字符串的默认编码。事实证明,所有这些字节都是相同的。这个练习的目的是看看我是否能够模拟导致有效数据变为无效数据的编码步骤。
为了进一步证明,我调查了\phantomjs\src\system.cpp和\phantomjs\src\filesystem.cpp。
在
system.cpp
中,
System
类包含对
stdout
、
stdin
和
stderr
的
File
对象的引用,这些对象被设置为使用
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);
}
在\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中的说明,所以应该没问题。