пятница, 20 августа 2010 г.

Создание PDF-файлов на Perl

Введение

Начну с того, что есть несколько различных подходов к решению данной задачи:
  1. Создание PDF "с нуля" (PDF::API2, PDF::Haru, PDF::CreateSimple)
  2. Модификация уже существующего PDF-бланка (PDF::Reuse, PDF::API2, PDF::Haru, PDF::CreateSimple)
  3. Создание PDF из XML-шаблона при помощи XSLT-преобразования (XML::ApacheFOP)
  4. Конвертация LaTeX или HTML файла в PDF внешними утилитами (исходые файлы при этом можно генерировать по шаблону, к примеру, Template::Toolkit)
У каждого подхода есть свои плюсы и минусы, так что выбор зависит от конкретной ситуации. В этой же статье четь пойдёт о первом подходе, а точнее о создании PDF-файлов при помощи модуля PDF::API2.

Краткий обзор PDF::API2

Модуль PDF::API2 - ветеран среди PDF-модулей на CPAN. Проект насчитывает уже несколько лет и обладает внушительным функционалом. Стоит отметить, однако, что интерфейс PDF::API2 довольно запутан и не всегда очевиден, кроме того, работать придется с довольно низкоуровневыми элементами (графические примитивы, строки текста и т.д.). Последний недостаток отчасти компенсируют модули-обёртки с более удобным интерфейсом: PDF::API2::Simple, PDF::Table.

В целом, работа с PDF::API2 напоминает рисование: получить графический контекст, задать необходимые атрибуты (цвет, фон, стиль начертания), вызвать соответствующий метод-примитив (текст, прямоугольник, дуга и т.д.).

Ниже приводится небольшой скрипт для создания минимального PDF-файла. Следующим шагом рекомендую ознакомиться с отличным tutorial-ом по данной теме: Using PDF::API2. Ну а осилив и его, можно уже открывать документацию по PDF::API2. Итак, пример:

Пример

#!/usr/bin/perl

#===============================================================================
#    REVISION:  $Id$
# DESCRIPTION:  Минимальный пример использования PDF::API2
#      AUTHOR:  Alexander Simakov, <xdr (dot) box (at) Gmail>
#               http://alexander-simakov.blogspot.com/
#     LICENSE:  Public domain
#===============================================================================

use strict;
use warnings;

our $VERSION = qw($Revision$) [1];

use Readonly;
use PDF::API2;

# Размер бумаги A4
Readonly my $PAGE_WIDTH  => 210;
Readonly my $PAGE_HEIGHT => 297;

# Внутренняя единица измерения PDF::API2 - пункты
# см. http://ru.wikipedia.org/wiki/Типографский_пункт
Readonly my $ONE_MILLIMETER_IN_POINTS => 72 / 25.4;

# Перевод миллиметров в типографские пункты
sub _mm_to_pt {
    my $mm = shift;

    return $mm * $ONE_MILLIMETER_IN_POINTS;
}

sub main {

    # Создаём PDF-объект
    my $pdf = PDF::API2->new( -file => 'example.pdf' );

    # Создаём новую станицу размера A4
    my $page = $pdf->page();
    $page->mediabox( _mm_to_pt($PAGE_WIDTH), _mm_to_pt($PAGE_HEIGHT) );

    # Встраиваем в PDF-файл шрифты
    my %fonts = (
        Helvetica => {
            Bold   => $pdf->corefont('Helvetica-Bold'),
            Roman  => $pdf->corefont('Helvetica'),
            Italic => $pdf->corefont('Helvetica-Oblique'),
        },
    );

    # Напишем строку текста
    my $text_ctx = $page->text();

    $text_ctx->font( $fonts{'Helvetica'}{'Bold'}, 12 );
    $text_ctx->fillcolor('red');

    # В PDF::API2 - начало координат в левом нижнем углу
    $text_ctx->translate(
        _mm_to_pt( $PAGE_WIDTH / 2 ),
        _mm_to_pt( $PAGE_HEIGHT / 2 )
    );

    # Выводим строку с выравниванием по центру
    $text_ctx->text_center('Hello PDF::API2');

    $pdf->save();

    return;
}

main();

А вот результат: example.pdf
Скачать скрипт: create_example_pdf.tar.gz

Заключение

Модуль PDF::API2 позволяет достаточно тонко и точно контроллировать процесс создания PDF-файла, но за всё приходится платить. В данном случае плата - это довольно большое количество усилий необходимых для выполнения относительно простых операций.

Ссылки

8 комментариев:

  1. Еще существует вариант использовать fop. К перлу это иммет мало отношения, но штука удобная (но не очень быстрая). На xml c данными накладывается xslt со специальным шаблоном для отображения, результат этого действия засовывается в fop и на выходе получается pdf.

    ОтветитьУдалить
  2. В перле есть интерфейс к fop-у --- модуль XML::ApacheFOP, но к сожалению этот модуль не обновлялся с 2005 года. К тому же для его запуска требуется Java-сервер, что добавит хлопот, тем то не каждый день сталкивается с Java.

    ОтветитьУдалить
  3. Я смотрел этот модуль, но решил его не использовать (уже причины этого не помню). Для работы с fop выполнятся просто одна команда через system и все.

    Java — да плохо, зато у fop огромный плюс в том, что он идеологически правильный: данные отдельно, шаблон отдельно. И на мой вкус интрфейс fop лучше чем у перловых модулей создания xml.

    ОтветитьУдалить
  4. Попробую fop при случае. Вариант с шаблоном (будь то XML или Template::Toolkit) действительно более гибкий и удобный.

    ОтветитьУдалить
  5. Підскажіть як багатосторінковий пдф розбити на окремі файли

    ОтветитьУдалить
  6. Вот пример как сделать это на Python (с Perl-ом я сейчас работаю редко):
    http://stackoverflow.com/questions/490195/split-a-multi-page-pdf-file-into-multiple-pdf-files-with-python

    ОтветитьУдалить
  7. Дякую! Я вже поділив в Perl-и. Виникла інша проблема, потрібно ПДФ конвертувати в ГИф (також в Перл). Буду вдячний за підказку!

    ОтветитьУдалить
    Ответы
    1. Я бы воспользовался программой ImageMagick:
      http://www.imagemagick.org/script/binary-releases.php
      В подавляющем большинстве Linux-дистрибутивов
      его можно установить штатным образом, например
      в Debian или Ubuntu:

      sudo apt-get install imagemagick

      Из командной строки PDF в набор картинок
      можно преобразовать так:

      convert book.pdf page.png

      В итоге появятся файлы вида
      page-1.png, page-2.png и т.д.

      Можно указать диапазон страниц, например:

      convert book.pdf[1-10] page.png

      По умолчанию, если преобразовывать в GIF то
      получается один большой анимированный файл.
      Возможно есть опция, которая позволяет сделать
      много отдельных GIF файлов, надо посмотреть
      руководство. Если такой возможности нет и нужен
      именно GIF, тогда:

      convert page-1.png page-1.gif

      Вызвать "convert" из Perl-а можно при помощи system():

      system('convert', 'book.pdf', 'page.png');

      Удалить