PhantomJS: экспорт PDF в стандартный вывод

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


person Dan Burzo    schedule 10.07.2012    source источник


Ответы (4)


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

Чтобы написать что-то из сценария phantomjs в стандартный вывод, просто используйте 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 файлу.

person Enrico Mischorr    schedule 16.07.2012
comment
Чтобы заставить этот метод работать, мне пришлось изменить строку fs.read на var pdf = fs.open(output, 'rb').read(); — чтение файла в двоичном виде было важно (иначе перенаправление stdout в файл приводило к неправильному PDF). Однако позже мне удалось заставить это работать вообще без временного файла - см. stackoverflow.com/a /17282463/137067 - person philfreo; 24.06.2013

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

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

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

Если вы хотите получить HTML из стандартного ввода и вывести PDF на стандартный вывод, см. здесь

person philfreo    schedule 24.06.2013
comment
Работает хорошо, но если вы читаете стандартный вывод в Node, как описано здесь npmjs.org/package/phantomjs вам нужно установить параметры execFile для двоичного файла и, возможно, увеличить размер буфера, как описано здесь stackoverflow.com/a/6170723/2297380< /а> - person poof; 16.05.2014
comment
Вот документация из вики PhantomJS. Но для меня этот метод не соответствует page.viewportSize настройкам. - person Rick Mohr; 09.03.2015
comment
По какой-то причине он работает в Mac OS X, но не работает в Linux (версия PhantomJS 1.9.8). - person guidoman; 29.06.2015
comment
даже добавление параметров: {encoding: 'binary', maxBuffer: 5000*1024} это не поступает в стандартный вывод в узле - person gabrielAnzaldo; 10.11.2016
comment
У меня не работало в windows 7 и phantomjs-prebuilt 2.1.13 - person gabrielAnzaldo; 03.12.2016

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


Рассмотрим эту простую программу 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, и управление вернется к терминалу.

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

(./write_to_file.o /dev/fd/5) 5> my_pipe

Or

./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, чтобы распаковать этот файл 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 в терминале, чтобы отобразить главную страницу 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:

рендеринг(имя файла)

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

В настоящее время выходной формат устанавливается автоматически на основе расширения файла. Поддерживаемые форматы: 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:

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

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

    cat < my_pipe.pdf > hn.pdf
    

    который просто cats это hn.pdf

  3. В PhantomJS выполните рендеринг в этот файл/канал.

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

    rm my_pipe.pdf
    
person Pooria Azimi    schedule 15.07.2012

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

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

person NiKo    schedule 15.07.2012