Создание сайтов с возможностью печати PDF на примере PDF::API2

В данной статье рассматриваются преимущества формата PDF (Portable Document Format), разработанного компанией Adobe, а также обсуждается, где и почему стоит использовать формат PDF при создании web-сайтов.

[Голубев Павел (http://www.golubeff.ru)]

Пожалуй, вам несколько раз встречалась необходимость печати документов прямо из Интернета. Это могут быть счета, квитанции, данные для печати на шаблоне.

Возможно, вам также приходилось встречаться с особенностями печати подобных документов, оформленных в виде HTML кода напрямую из Internet Explorer. Если всё же не приходилось - обязательно придётся в ближайшем будущем. Суть этих особенностей заключается в том, что этот броузер, несмотря на то, что является самым распространенным на момент написания статьи, не умеет корректно печатать web-страницы. Информация на распечатанной странице начинает странным образом съезжать, фон и другое оформление куда-то изчезает, а часть страницы может быть вообще обрезана. Как вы понимаете, подобные вещи непозволительны, когда необходимо предоставить пользователю возможность распечатать документ, с которым он пойдет в банк.

Ситуация осложняется еще больше, когда необходимо распечатать некий документ на заранее подготовленном шаблоне. Конечно, заранее подготовленные шаблоны редко используются на классических веб-сайтах, однако создание сайтов не ограничивается созданием HTML-страниц. Сайтом может быть внутрикорпоративный портал, B2B-система, рабочее место оператора в конце концов.

Поскольку практически у каждого рядового пользователя на компьютере установлен Adobe Acrobat Reader или подобный, можно с уверенностью заявить, что документы, оформленные в формате PDF, прочитать не составит возможности.

Так в чём же преимущества формата PDF? Перечислить все их мне, например, не представляется возможным, поэтому постараюсь упомянуть об основных. Прежде всего, как уже говорилось выше, формат PDF гарантирует, что документ будет распечатан в точности так, как он выглядит на экране пользователя. Во-вторых, любой, абсолютно любой принтер распечатает документ с теми отступами и позиционированием, какое вы зададите в сгенерированном файле PDF, а не в соответствии с зашитыми в принтер настройками. Помимо этого, некоторые действия над PDF (например, изменение) могут быть защищены паролем. Ко всему прочему, существенно упрощается генерация многостраничных документов. Те, кто занимался этим и использовал HTML понимают, что не так уж просто заставить разрываться страницы имеено там, где вам нужно. Как видите, преимуществ использования PDF перед обычным HTML более, чем достаточно.

Теперь, когда разобраны основные преимущества создания сайтов с использованием системы печати документов в формате PDF, попробуем разобрать пример с использованием Perl и PDF::API2.

Будем считать, что установленный и сконфигурированный web-сервер, например, Apache, а также Perl вы под рукой уже имеете.

Первым делом, нужно установить модуль PDF::API2 для Perl, если вы еще не сделали этого. Сделать это необычайно просто. Просто выполните следующую команду из под пользователя Root:

perl -e shell -MCPAN
Возможно, если вы раньше не пользовались CPAN, необходимо будет ответить на несколько вопросов, чтобы инициализировать интсаллятор. В большинстве случаев достаточно ответить <<no>> на первый же вопрос <<Are you ready for manual configuration?>>

После того, как настроите CPAN, вы попадёте в оболочку CPAN, перед вами будет примерно следующее:

cpan shell -- CPAN exploration and modules installation (v1.7602)
ReadLine support available (try 'install Bundle::CPAN')

cpan>
Наберите команду install PDF::API2 и подождите, пока всё установится. Если возникнут проблемы, можно воспользоваться исходным кодом PDF::API2, расположенным на search.cpan.org.

После установки PDF::API2, выйдите из устанощика CPAN, набрав

exit
Проверить, всё ли установилось так, как надо, можно набрав в консоли:
perl -e 'use PDF::API2'
Если после ввода указанной команды не последовало никакого вывода, значит всё хорошо.

Я бы также посоветовал установить модуль XML::Simple. Сделать это можно аналогично установке PDF::API2. Этот модуль позволит нам создавать XML файлы с конфигурацией печати документов в PDF.

Итак, мы имеем установленный PDF::API2 и можем начинать заниматься созданием сайтов с использованием указанного модуля и генерировать PDF файлы налету.

Создайте файл PrintPDF.pm и запишите в него следующее:

	    package PrintPDF;

	    use strict;
	    use XML::Simple;
	    use PDF::API2;

	    sub PrintPDF {
	        my $blobref = shift;
	        my $configref = shift;
	        my $hashref = shift;
	    }

	    1;
Мы создали новый модуль Perl, который имеет одну единственную функцию PrintPDF и принимает параметры: ссылку на переменную, содержащую бинарный код шаблона PDF, на котором будем печатать, ссылку на переменную содержащую XML код конфигурационного файла и ссылку на хэш, значения которого будут использоваться при печати на шаблоне.

Дополним функцию PrintPDF:

	sub PrintPDF {
	    my $blobref = shift;
	    my $configref = shift;
	    my $hashref = shift;

	    my $pdf;
	    if (scalar $blobref=~m!^PDF::API2=HASH!) {
	        $pdf = $blobref;
	    } else {
	        $pdf = PDF::API2->openScalar($$blobref) or die $!;
	    }

	    my $fnt_orig = $pdf->corefont('Verdana', -encode=>'windows-1251');
	    my $size_orig = 7;
	    my ($fnt, $size, $color, $align);
	    my $xml = XMLin($$configref, ForceArray=>1);
	    return $pdf;
	}
Теперь наша функция, помимо того, что принимает параметры, делает еще и следующие вещи:
  1. инициализирует объект PDF::API2, записывая копию объекта в $pdf
  2. может получать в качестве входного параметра не только ссылку на бинарный код шаблона, но и уже готовый, созданный объект PDF::API2
  3. задаёт значения по умолчанию: шрифт, размер, кодировку, цвет и выравнивание
  4. считывает в память конфигурационный XML файл
Следующим этапом, самым сложным будет обработка XML конфигурационного файла и печать данных:
	sub PrintPDF {
	    my $blobref = shift;
	    my $configref = shift;
	    my $hashref = shift;

	    my $pdf;
	    if (scalar $blobref=~m!^PDF::API2=HASH!) {
	        $pdf = $blobref;
	    } else {
	        $pdf = PDF::API2->openScalar($$blobref) or die $!;
	    }

	    my $fnt_orig = $pdf->corefont('Verdana', -encode=>'windows-1251');
	    my $size_orig = 7;
	    my ($fnt, $size, $color, $align);
	    my $xml = XMLin($$configref, ForceArray=>1);

	    foreach my $page(@{$xml->{page}}) {
	        my $pdfpage = $pdf->openpage(${$page->{id}}[0]) or next;
	        my $gfx = $pdfpage -> gfx();
	        foreach my $label(@{$page->{label}}) {
	            my $text;
	            my $x = $label->{x};
	            my $y = $label->{y};
	            if (defined $label->{font}) {
	                $fnt = $pdf->corefont(${$label->{font}}[0], -encode=>'windows-1251');
	            } else {
	                $fnt = $fnt_orig;
	            }

	            if (defined $label->{size}) {
	                $size = ${$label->{size}}[0];
	            } else {
	                $size = $size_orig;
	            }
	
	            if (defined $label->{color}) {
	                $color = ${$label->{color}}[0]
	            } else {
	                $color = 'black';
	            }

	            if (defined $label->{align}) {
	                $align = ${$label->{align}}[0];
	            } else {
	                $align = 'left';
	            }

	            foreach my $type(@{$label->{type}}) {
	                my $key = $type->{value};
	                my $current = $hashref->{$type->{value}};
	                $text .= $current.' ' if $current;

	            }
	            $gfx->textlabel($label->{x},
	                $label->{y},
	                $fnt, $size,
	                $text, -color=>$color, -align=>$align
	            );
	        }
	    }
	    return $pdf;
	}
Давайте разбираться, что же здесь происходит. А происходит очень простая вещь: выполняется цикл для каждой страницы, описанной в XML файле. Для каждой страницы выполняются следующие действия:
  1. открывается запрошенная страница в PDF-шаблоне
  2. считываются параметры запрошенной текстовой метки (шрифт, размер, цвет, выравнивание, координаты) и при необходимости становятся эквивалентными заданным по-умолчанию чуть выше
  3. каждая текстовая метка может состоять из нескольких полей, каждое из которых будет разделено пробелом
  4. считывается значение для каждого поля и записывается в переменную $text
  5. в pdf шаблоне размещается текстовая метка с заданными параметрами
После выполнения указанного цикла получаем сгенерированный PDF файл, который правда хранится пока в виде объекта в переменной $pdf.

Как вы понимаете, еще одним необходимым этапом является создание PDF-шаблона и конфигурационного файла. В качестве шаблона будем использовать пустой PDF-шаблон размера А4.

А вот пример XML-файла, содержимое которого необходимо будет передать нашей функции:

Обратите внимание, что точка (0,0) в координатной плоскости PDF файла находится в нижнем левом углу, а не верхнем левом.

	<?xml version="1.0" encoding="windows-1251"?>
	<document>
	    <page>
	        <id>1</id>
	        <label x="300" y="673" >
	            <size>30</size>
	            <align>center</align>
	            <type value="prefix" />
	        </label>
	        <label x="300" y="640">
	            <size>20</size>
	            <align>center</align>
	            <type value="name" />
	        </label>
	        <label x="50" y="600" >
	            <size>10</size>
	            <type value="text" />
	        </label>
	        <label x="50" y="620">
	            <size>10</size>
	            <type value="copyright" />
	        </label>
	    </page>
	</document>
Последнее, что нам необходимо сделать, чтобы наконец сгенерировать PDF - написать небольшое обращение к нашему модулю PrintPDF.pm. Для этого создайте файл PrintPDF.pl и запишите в него следующее:
	#!/usr/bin/perl

	use strict;
	use PrintPDF;
	use CGI qw (param);
	
	my $hashref = {};
	$hashref->{name} = param('name');
	$hashref->{text} = param('text');
	if ($hashref->{name}=~m![уеэёоаяию]$!) {
	    $hashref->{prefix} = 'Уважаемая';
	} else {
	    $hashref->{prefix} = 'Уважаемый';
	}
	
	my ($xml, $blob);
	open(HANDLE, 'config.xml') or die $!;
	$xml = join('', <HANDLE>);
	close HANDLE;
	
	open(HANDLE, 'blank.pdf') or die $!;
	$blob = join('', >HANDLE<);
	close HANDLE;
	
	print "Content-type:application/pdf\n";
	print "Content-disposition: inline; name=".rand(32768).".pdf\n\n";

	my $pdf = PrintPDF::PrintPDF(\$blob, \$xml, $hashref);
	print $pdf->stringify();
В тот же каталог кладём файлы config.xml и blank.pdf, рисуем для этого файла форму подобную нижеследующей и смотрим, как же это здорово - печатать документы в PDF.

В процессе экспериментирования, вам возможно понадобится:

  • $pdf->importpage($pdf, $source_page, $new_page) копирует страницу номер $source_page в страницу с номером $new_page в документе $pdf

  • my $gif = $pdf->image_gif($image_path); $gfx->image($gif, $width, $height); а так можно вставить gif на страницу

Об авторе

Павел Голубев (pavel AT golubeff DOT ru) - основатель компании Голубев.ру, специализирующейся на создании сайтов с использованием современных технологий.

[ опубликовано 04/05/2006 ]

Голубев Павел (http://www.golubeff.ru) - Создание сайтов с возможностью печати PDF на примере PDF::API2   Версия для печати