К списку статей
Автор: Голубев Павел (http://www.golubeff.ru)
Обложка статьи

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

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

Возможно, вам также приходилось встречаться с особенностями печати подобных документов, оформленных в виде 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) - основатель компании Голубев.ру, специализирующейся на создании сайтов с использованием современных технологий.

  • Обложка статьи FreeBSD. Настраиваем файловые системы

    FreeBSD. Настраиваем файловые системы

    FreeBSD. Свободные записки о свободной системе. В качестве объекта для изучения был избран однодисковый вариант FreeBSD стабильной версии - 4.2

    Читать далее
  • Обложка статьи Поддерживаю РФ: Кириллические домены должны поддерживаться в российском ПО и сервисах

    Поддерживаю РФ: Кириллические домены должны поддерживаться в российском ПО и сервисах

    Поддержка российским ПО и отечественными сервисами кириллических доменов и адресов электронной почты станет ключевой задачей проекта Поддерживаю.РФ в 2021 году. По словам директора Координационного центра доменов .RU/.РФ Андрея Воробьева, национальный дом

    Читать далее
  • Обложка статьи Защищаем Apache 2. Шаг за шагом

    Защищаем Apache 2. Шаг за шагом

    В этой статье мы расскажем о пошаговой установке и конфигурировании Apache 2.0, чтобы снизить риск неавторизованного доступа или успешного взлома в случае применения новой уязвимости, обнаруженной в Apache Web сервере. В результате, можно будет пользовать

    Читать далее
  • Обложка статьи Защита ваших данных. PGP & Linux

    Защита ваших данных. PGP & Linux

    Эта статья написана для тех, кому необходимо сохранить некоторую информацию в секрете и кто пока не решил как это сделать....

    Читать далее
  • Обложка статьи DragonFlyBSD: загрузка и инициализация

    DragonFlyBSD: загрузка и инициализация

    В этом цикле статей я хочу рассказать об операционной системе, родившейся прямо на наших глазах - летом 2004 года. Имя ей - DragonFlyBSD, и являет она собой представителя славного племени BSD-систем. В сущности, исходно это fork (порождение) FreeBSD 4-й в

    Читать далее

Специальные предложения
интернет-магазина

  • Чехол для переноски Portable Hard Shell для Oculus Quest 2 VR
    3300 руб

    Чехол для переноски Portable Hard Shell для Oculus Quest 2 VR

  • Книга: Дронов В.А. "Laravel 9. Быстрая разработка веб-сайтов на PHP"
    1550 руб

    Книга: Дронов В.А. "Laravel 9. Быстрая разработка веб-сайтов на PHP"

  • №18 Патрон с впаянной лампой 2,5 V/ 0,3A
    212 руб

    №18 Патрон с впаянной лампой 2,5 V/ 0,3A

  • Книга: Аль-Халили Джим "Мир физики и физика мира. Простые законы мироздания"
    1000 руб

    Книга: Аль-Халили Джим "Мир физики и физика мира. Простые законы мироздания"

  • Набор выводных резисторов 0.25W (100 Ом—910 Ом), 24 номинала по 10 шт.
    275 руб

    Набор выводных резисторов 0.25W (100 Ом—910 Ом), 24 номинала по 10 шт.