Подтвердить что ты не робот

PhantomJS: экспортированный PDF файл в stdout

Есть ли способ запустить функцию экспорта PDF в PhantomJS без указания выходного файла с расширением .pdf? Мы хотим использовать stdout для вывода PDF.

4b9b3361

Ответ 1

Как указано Niko, вы можете использовать renderBase64() для рендеринга веб-страницы в буфер изображения и вернуть результат в виде строки в кодировке base64.
Но пока это будет работать только для PNG, JPEG и GIF.

Чтобы написать что-то из phantomjs script в stdout, просто используйте API-интерфейс файловой системы.

Я использую что-то подобное для изображений:

var base64image = page.renderBase64('PNG');
var fs = require("fs");
fs.write("/dev/stdout", base64image, "w");

Я не знаю, будет ли формат PDF для renderBase64() в будущей версии phanthomjs, но в качестве обходного пути что-то в этих строках может работать для вас:

page.render(output);
var fs = require("fs");
var pdf = fs.read(output);
fs.write("/dev/stdout", pdf, "w");
fs.remove(output);

Где output - путь к файлу pdf.

Ответ 2

Извините за чрезвычайно долгий ответ; У меня такое чувство, что мне нужно будет обратиться к этому методу несколько десятков раз в жизни, поэтому я напишу "один ответ, чтобы править ими всеми". Сначала я немного забуду о файлах, дескрипторах файлов, (названных) трубах и перенаправлении вывода, а затем отвечу на ваш вопрос.


Рассмотрим эту простую программу 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. В конце & запрашивается оболочка для запуска этой команды в фоновом режиме. Вы получите что-то вроде [1] 10449, и элемент управления вернется к терминалу.

Затем просто перенаправьте вывод нашей программы на этот канал:

(./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

Мы успешно gzip наш текст!

Выполните gzip -d out.gz, чтобы распаковать этот файл gzip ed. Он будет удален, и будет создан новый файл (out). cat out получает нас:

some text...

что мы и ожидали.

Не забудьте удалить трубку с помощью rm my_pipe!


Теперь вернемся к PhantomJS.

Это простой PhantomJS script (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 в терминал, чтобы отобразить первую страницу Hacker News в файле hn.png. Он работает так, как ожидалось. Так что phantomjs render.coffee news.ycombinator.com hn.pdf.

Повторите то, что мы сделали ранее с нашей программой на C:

(phantomjs render.coffee news.ycombinator.com /dev/fd/5) 5> hn.pdf

Это не работает...:( Почему? Потому что, как указано в Руководство PhantomJS:

render (fileName)

Отображает веб-страницу в буфер изображения и сохраняет ее как указанный файл.

В настоящее время формат вывода автоматически устанавливается на основе файла расширение. Поддерживаемые форматы: PNG, JPEG и PDF.

Это не удается, просто потому, что ни /dev/fd/2, ни /dev/stdout не заканчиваются на .PNG и т.д.

Но ни один страх, названные трубы не помогут вам!

Создайте другой именованный канал, но на этот раз используйте расширение .pdf:

mkfifo my_pipe.pdf

Теперь скажите просто cat его inout на hn.pdf:

cat < my_pipe.pdf > hn.pdf &

Затем запустите:

phantomjs render.coffee news.ycombinator.com my_pipe.pdf 

И вот красивый hn.pdf!

Очевидно, что вы хотите сделать что-то более сложное, просто cat с выходом, но я уверен, теперь ясно, что вы должны делать:)


TL; DR:

  • Создайте именованный канал, используя расширение ".pdf" (так что он обманывает PhantomJS, считая его файлом PDF):

    mkfifo my_pipe.pdf
    
  • Сделайте все, что вы хотите сделать с содержимым файла, например:

    cat < my_pipe.pdf > hn.pdf
    

    который просто cat до hn.pdf

  • В PhantomJS отрисуйте этот файл/трубу.

  • Позже вы должны удалить трубку:

    rm my_pipe.pdf
    

Ответ 3

Вы можете напрямую выводить на stdout без необходимости временного файла.

page.render('/dev/stdout', { format: 'pdf' });

См. здесь для истории, когда это было добавлено.

Если вы хотите получить HTML из stdin и вывести PDF в stdout, см. здесь

Ответ 4

Я не знаю, будет ли это касаться вашей проблемы, но вы также можете проверить новый метод renderBase64(), добавленный в PhantomJS 1.6: https://github.com/ariya/phantomjs/blob/master/src/webpage.cpp#L623

К сожалению, эта функция еще не задокументирована в wiki:/