【发布时间】:2012-07-09 15:15:01
【问题描述】:
有没有办法在 PhantomJS 中触发 PDF 导出功能,而无需指定扩展名为 .pdf 的输出文件?我们想使用stdout 来输出PDF。
【问题讨论】:
标签: pdf pipe file-descriptor phantomjs io-redirection
有没有办法在 PhantomJS 中触发 PDF 导出功能,而无需指定扩展名为 .pdf 的输出文件?我们想使用stdout 来输出PDF。
【问题讨论】:
标签: pdf pipe file-descriptor phantomjs io-redirection
抱歉,答案太长了;我有一种感觉,我一生中需要参考这种方法几十次,所以我会写“一个答案来统治他们”。我会先唠叨一下文件、文件描述符、(命名的)管道和输出重定向,然后回答你的问题。
考虑这个简单的 C99 程序:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
if (argc < 2) {
printf("Usage: %s file_name\n", argv[0]);
return 1;
}
FILE* file = fopen(argv[1], "w");
if (!file) {
printf("No such file: %s\n", argv[1]);
return 2;
}
fprintf(file, "some text...");
fclose(file);
return 0;
}
非常简单。它接受一个参数(文件名)并将一些文本打印到其中。再简单不过了。
用clang write_to_file.c -o write_to_file.o 或gcc write_to_file.c -o write_to_file.o 编译它。
现在,运行./write_to_file.o some_file(打印到some_file)。然后运行cat some_file。结果正如预期的那样是some text...
现在让我们变得更花哨。在终端中输入(./write_to_file.o /dev/stdout) > some_file。我们要求程序写入其标准输出(而不是常规文件),然后我们将stdout 重定向到some_file(使用> some_file)。我们可以使用以下任何方法来实现这一点:
(./write_to_file.o /dev/stdout) > some_file,意思是“使用stdout”
(./write_to_file.o /dev/stderr) 2> some_file,表示“使用stderr,并使用2>重定向”
(./write_to_file.o /dev/fd/2) 2> some_file,同上; stderr 是默认分配给 Unix 进程的第三个文件描述符(在 stdin 和 stdout 之后)
(./write_to_file.o /dev/fd/5) 5> some_file,意思是“使用你的第六个文件描述符,并将其重定向到some_file”
如果不清楚,我们使用的是 Unix 管道而不是实际文件(在 Unix 中一切都是文件)。我们可以用这个管道做各种花哨的事情:将其写入文件,或将其写入命名管道并在不同进程之间共享。
现在,让我们创建一个命名管道:
mkfifo my_pipe
如果你现在输入ls -l,你会看到:
total 32
prw-r--r-- 1 pooriaazimi staff 0 Jul 15 09:12 my_pipe
-rw-r--r-- 1 pooriaazimi staff 336 Jul 15 08:29 write_to_file.c
-rwxr-xr-x 1 pooriaazimi staff 8832 Jul 15 08:34 write_to_file.o
注意第二行开头的p。这意味着my_pipe 是一个(命名的)管道。
现在,让我们指定我们想用管道做什么:
gzip -c < my_pipe > out.gz &
它的意思是:gzip 我在my_pipe 中输入的内容,并将结果写入out.gz。最后的& 要求shell 在后台运行这个命令。您会得到类似[1] 10449 的信息,并且控件会返回到终端。
然后,简单地将我们的 C 程序的输出重定向到这个管道:
(./write_to_file.o /dev/fd/5) 5> my_pipe
或者
./write_to_file.o my_pipe
你会得到
[1]+ Done gzip -c < my_pipe > out.gz
这意味着gzip 命令已经完成。
现在,再做一次ls -l:
total 40
prw-r--r-- 1 pooriaazimi staff 0 Jul 15 09:14 my_pipe
-rw-r--r-- 1 pooriaazimi staff 32 Jul 15 09:14 out.gz
-rw-r--r-- 1 pooriaazimi staff 336 Jul 15 08:29 write_to_file.c
-rwxr-xr-x 1 pooriaazimi staff 8832 Jul 15 08:34 write_to_file.o
我们已经成功gziped 我们的文字!
执行gzip -d out.gz解压这个gziped文件。它将被删除并创建一个新文件 (out)。 cat out 得到我们:
some text...
这是我们所期望的。
别忘了用rm my_pipe移除管道!
现在回到 PhantomJS。
这是一个简单的 PhantomJS 脚本(render.coffee,用 CoffeeScript 编写),它接受两个参数:一个 URL 和一个文件名。它加载 URL,呈现它并将其写入给定的文件名:
system = require 'system'
renderUrlToFile = (url, file, callback) ->
page = require('webpage').create()
page.viewportSize = { width: 1024, height : 800 }
page.settings.userAgent = 'Phantom.js bot'
page.open url, (status) ->
if status isnt 'success'
console.log "Unable to render '#{url}'"
else
page.render file
delete page
callback url, file
url = system.args[1]
file_name = system.args[2]
console.log "Will render to #{file_name}"
renderUrlToFile "http://#{url}", file_name, (url, file) ->
console.log "Rendered '#{url}' to '#{file}'"
phantom.exit()
现在在终端中输入phantomjs render.coffee news.ycombinator.com hn.png,将黑客新闻首页呈现到文件hn.png中。它按预期工作。 phantomjs render.coffee news.ycombinator.com hn.pdf 也是如此。
让我们重复之前对 C 程序所做的操作:
(phantomjs render.coffee news.ycombinator.com /dev/fd/5) 5> hn.pdf
它不起作用... :( 为什么?因为,正如 PhantomJS's manual 所述:
渲染(文件名)
将网页渲染到图像缓冲区并保存 作为指定文件。
目前输出格式是根据文件自动设置的 延期。支持的格式为 PNG、JPEG 和 PDF。
它失败了,仅仅是因为/dev/fd/2 和/dev/stdout 都没有以.PNG 结尾,等等。
但不用担心,命名管道可以帮助您!
创建另一个命名管道,但这次使用扩展名.pdf:
mkfifo my_pipe.pdf
现在,告诉它简单地将cat 其输入输出到hn.pdf:
cat < my_pipe.pdf > hn.pdf &
然后运行:
phantomjs render.coffee news.ycombinator.com my_pipe.pdf
看看美丽的hn.pdf!
显然你想做一些比cat输出更复杂的事情,但我相信现在你应该做什么很清楚:)
创建一个命名管道,使用“.pdf”文件扩展名(所以它会让 PhantomJS 误以为它是一个 PDF 文件):
mkfifo my_pipe.pdf
对文件的内容做任何你想做的事情,比如:
cat < my_pipe.pdf > hn.pdf
简单地将cats 发送到hn.pdf
在 PhantomJS 中,渲染到这个文件/管道。
稍后,您应该移除管道:
rm my_pipe.pdf
【讨论】:
我不知道它是否能解决您的问题,但您也可以查看添加到 PhantomJS 1.6 中的新 renderBase64() 方法:https://github.com/ariya/phantomjs/blob/master/src/webpage.cpp#L623
很遗憾,该功能尚未在 wiki 上记录:/
【讨论】:
正如 Niko 所指出的,您可以使用 renderBase64() 将网页渲染到图像缓冲区并将结果作为 base64 编码的字符串返回。
但目前这仅适用于 PNG、JPEG 和 GIF .
要将 phantomjs 脚本中的内容写入标准输出,只需使用文件系统 API。
我对图像使用这样的东西:
var base64image = page.renderBase64('PNG');
var fs = require("fs");
fs.write("/dev/stdout", base64image, "w");
我不知道renderBase64() 的 PDF 格式是否会出现在 phanthomjs 的未来版本中,但作为一种解决方法,这些方面的内容可能对您有用:
page.render(output);
var fs = require("fs");
var pdf = fs.read(output);
fs.write("/dev/stdout", pdf, "w");
fs.remove(output);
output 是 pdf 文件的路径。
【讨论】:
fs.read 行更改为var pdf = fs.open(output, 'rb').read();——以二进制形式读取文件很重要(否则将标准输出重定向到文件会导致不正确的PDF)。然而,我后来能够让它与 no临时文件 一起工作 - 请参阅 stackoverflow.com/a/17282463/137067
【讨论】:
page.viewportSize 设置。