Перевод статьи «PDF Processing with Perl», написанной Detlef Groth с сайта Perl.com. PDF от Adobe стал стандартом текстовых документов. Большинство офисных продуктов умеют экспортировать контент в PDF. Тем не менее, такое программное обеспечение не подойдет вам, если вы хотите выполнить более сложные действия, такие как соединение разных PDF в один или добавление и настройка панели закладок для лучшей навигации. Представьте, что вы хотите собрать все релевантные статьи с Perl.com в один PDF файл с обновляющейся панелью закладок. Вы можете использовать такую утилиту, как HTMLDOC, но добавление статьи под номером 51 потребует снова сделать выборку из Сети статей от первой до 50. В большинстве случаев вы не будете удовлетворены панелью закладок, полученной в результате. Эта статья рассказывает о том, как использовать PDF::Reuse, от Ларса Лунберга, для соединения разных PDF документов и добавления закладок к ним. Материалы для примера. Несмотря на то, что его возможности ограничены в этой области, вы также можете использовать PDF::Reuse для создания PDF документов. Если вы хотите создавать более сложные документы, вы должны открыть для себя другие PDF-модули, такие как PDF::API2 от Альфреда Рейбенщуха или Text::PDF от Мартина Хоскена. Тем не менее, PDF::Reuse достаточно для создания простого PDF для использования в дальнейших примерах. Следующий листинг должен быть вполне понятным. # file: examples/create-pdfs.pl use strict; use PDF::Reuse; mkdir "out" if (!-e "out") ; foreach my $x (1..4) { prFile("out/file-$x.pdf"); foreach my $y (1..10) { prText(35,800,"File: file-$x.pdf"); prText(510,800,"Page: $y"); foreach my $z (1..15) { prText(35,700-$z*16,"Line $z"); } # добавление графики с помощью # функции prAdd # цвет строки prAdd("0.1 0.1 0.9 RG\n"); # цвет заливки prAdd("0.9 0.1 0.1 rg\n"); my $pos = 750 - ($y * 40); prAdd("540 $pos 10 40 re\n"); prAdd("B\n"); if ($y < 10) { prPage(); } } Открываем новый файл с помощью prFile($filename) и закрываем его с помощью prEnd. Между этими двумя вызовами, добавляем текст с помощью функции prText. Вы также можете рисовать графику, используя низкоуровневую команду prAdd с PDF разметкой в качестве параметра. Начинаем новую страницу с помощью prPage. prFile начинают первую автоматически, так что вам нужно добавлять новую страницу, только если ваш документ больше одной страницы. Учтите, что для команды prText(x,y,Text) используется система координат, начинающаяся с левого нижнего угла страницы. В качестве примера добавления PDF разметки с помощью prAdd, код создает красный прямоугольник с голубыми границами. В случае, если вы желаете добавить больше графики или сложную графику в ваш PDF, вы можете изучить примеры Руководство по PDF. В этом случае, подумайте о переключении на PDF::API2 или Text::PDF вместо использования prAdd, так как они оба предоставляют удобный уровень абстракции для языка разметки PDF. Соединения PDF документов Главная возможность PDF::Reuse — модификация и пересборка существующих PDF документов. Следующий пример соединяет новый файл из материалов для примера. # file: examples/combine-pdfs.pl use strict; use PDF::Reuse; prFile("out/resultat.pdf"); prDoc('out/file-1.pdf',1,4); prDoc('out/file-2.pdf',2,9); prDoc('out/file-3.pdf',8); prDoc('out/file-4.pdf'); prEnd(); И снова, prFile($filename) открывает файл. Затем, prDoc($filename, $firstPage, $lastPage) добавляет к новому файлу указанные интервалы страниц из файла для примера. Аргументы $firstPage и $lastPage опциональны. Пренебрежение ими приведет к добавлению всего документа. Если указан только $firstPage, такой вызов добавит все с указанной страницы до конца. Наконец, prEnd закрывает файл. Повторное использование существующих PDF файлов. С помощью PDF::Reuse возможно использование существующих PDF файлов в качестве шаблонов для создания новых документов. Предположим, что у вас есть файл customer.txt, содержащий список клиентов, которым надо послать письма. Раньше вы использовали инструменты для создания PDF документа, такие как OpenOffice.org или Adobe Acrobat для создания письма собственноручно. Теперь вы можете написать короткую программу для добавления даты, имен и адресов ваших клиентов в письмо. # file: examples/reuse-letter.pl use PDF::Reuse; use Date::Formatter; use strict; my $date = Date::Formatter->now(); $date->createDateFormatter("(DD).(MM). (YYYY)"); my $n = 1; my $incr = 14; my $infile = 'examples/customer.txt'; prFile("examples/sample-letters.pdf"); prCompress(1); prFont('Arial'); prForm("examples/sample-letter.pdf"); open (my $fh, "<$infile") || die "Couldn't open $infile, $!\n aborts!\n"; while (my $line = <$fh>) { my $x = 60; my $y = 760; my ($first, $last, $street, $zipCode, $city, $country) = split(/,/, $line); last unless $country; prPage() if $n++ > 1 ; prText($x, $y, "$first $last"); $y -= $incr; prText($x, $y, $street); $y -= $incr; prText($x, $y, $zipCode); prText(($x + 40), $y, $city); $y -= $incr; prText($x, $y, $country); prText(60, 600, "Dear $first $last,"); prText(400, 630, "Berlin, $date"); } prEnd(); close $fh; После открытия файла с помощью prFile, вызов prCompress(1) включает PDF сжатие. prFont задает шрифт в файле. Всегда доступные опции: Times-Roman, Times-Bold, Times-Italic, Times-BoldItalic, Courier, Courier-Bold, Courier-Oblique, Courier-BoldOblique, Helvetica, Helvetica-Bold, Helvetica-Oblique и Helvetica-BoldOblique. Установка размера шрифта осуществляется с помощью prFontSize. Шрифтом по умолчанию является Helvetica, размером 12 пикселей. Дальнейший код представляет собой простой цикл, проходящий по файлу, содержащему данные клиентов и заполняющий шаблон с помощью prText. Добавление номеров страниц. Иногда вам нужно сделать лишь небольшое изменение в документе, как, например, добавление пропущенных номеров страниц. # file: examples/sample-numbers.pl use PDF::Reuse; use strict; my $n = 1; prFile('examples/sample-numbers.pdf'); while (1) { prText(550, 40, $n++); last unless prSinglePage('sample-letters.pdf'); } prEnd(); prSinglePage берет одну страницу за другой из существующего PDF документа и возвращает количество остающихся страниц после каждого вызова. Низкоуровневые PDF команды. Если вы знаете низкоуровневые PDF инструкции, вы можете добавить их с помощью функции prAdd(string). PDF::Reuse не предоставляет проверки синтаксиса для инструкций, так что обратитесь к руководству по PDF. Вот пример вывода цветных прямоугольников с помощью функции prAdd. # file: examples/sample-rectangle.pl use PDF::Reuse; use strict; prFile('examples/sample-rectangle.pdf'); my $x = 40; my $y = 50; my @colors; foreach my $r (0..5) { foreach my $g (0..5) { foreach my $b (0..5) { push @colors, sprintf("%1.1f %1.1f %1.1f rg\n", $r * 0.2, $g * 0.2, $b * 0.2); } } } while (1) { if ($x > 500) { $x = 40; $y += 40; last unless @colors; } # a rectangle my $string = "$x $y 30 30 re\n"; $string .= shift @colors; # fill and stroke $string .= "b\n"; prAdd($string); $x += 40; } prEnd(); Добавление закладок. Работа с PDF файлами удобнее, если документ содержит закладки с таблицей структуры содержимого. Некоторые приложения не могут предоставить PDF документ с закладками, либо же поддержка этой возможности ограничена, либо закладки некорректны. PDF::Reuse может заполнить этот пробел с помощью функции prBookmark($reference). Закладка в виде ссылки представляет собой хеш или массив хешей, который выглядит так: { text => 'Document-Text', act => 'this.pageNum = 0; this.scroll(40, 500);', kids => [ { text => 'Chapter 1', act => '1, 40, 600' }, { text => 'Chapter 2', act => '10, 40, 600' } ] } ...где act это JavaScript действие для перехода, когда кто-либо кликает на закладку. По скольку эти JavaScript действия работают только в Acrobat Reader, а в других приложениях для просмотра PDF — нет, позднее я покажу улучшение PDF::Reuse, которое исправляет это. Другие примеры использования PDF::Reuse, включая встраивание изображений, доступны в PDF::Reuse:Tutorial. Консольное приложение для соединения PDF документов. Чтобы избежать правки Perl кода каждый раз при соединении PDF документов, я написал консольное приложение, которое принимает имена входных файлов и интервалы страница для каждого файла в качестве аргументов. Для того, чтобы это было легко использовать повторно в графическом приложении, используя Perl/Tk, я прописал этот код в отдельный Perl модуль, который я назвал CombinePDFs. Консольное приложение будет обращаться к этому модулю вместо того, чтобы работать с PDF::Reuse напрямую. Следующая схема показывает связи между пакетами, примером и приложениями: Examples | Packages | applications ------------------------------------------------------------------- combine.pdfs app-combine-console-pdfs.pl \ / PDF::Reuse -- CombinePDFs / \ create.pdfs app-combine-tk-pdfs.pl Приложение app-combine-console-pdfs.pl не работает напрямую с PDF::Reuse, но парсит аргументы из командной строки с помощью Getopt::Long, написанным Йоханом Вромансом. Это стандартный пакет для таких задач. В данном случае он парсит имена входных файлов и интервалы страниц в два массива одинаковой длины. Пользователь также должен указать имя выходного файла и может, опционально, имя файла закладок. Главная подпрограмма, которая парсит аргументы из командной строки и выполняет CombinePDFs::createPDF выглядит следующим образом: sub main { GetOptions("infile=s" => \@infiles, "outfile=s" => \$outfile, "pages=s", => \@pages, 'overwrite' => \$overwrite, 'bookmarks:s' => \$bookmarks, 'help' => \&help); help unless ((@infiles and $outfile and @pages) and @pages == @infiles); checkPages(); checkFiles(); checkBookmarks(); CombinePDFs::createPDF(\@infiles, \@pages, $outfile, $bookmarks); } Если пользователь указывает неполное число аргументов, неправильные имена файлов или некорректные интервалы номеров страниц, код вызовет вспомогательную подпрограмму. Она также будет вызвана, если пользователь укажет -help в командной строке. Любое хорошее консольное приложение должно работать именно так. Getopt::Long может различать обязательные аргументы, с = как символом после именти аргумента (входной файл, страницы), необязательные аргументы, с : (закладки), или флаги (перезаписать, использование), без символа. Он может хранить эти аргументы как массивы (входной файл, страницы), хеши или переменные. Он также поддерживает проверку типов. Пакет CombinePDFs. Приложение самостоятельно производит проверки на ошибки. Если все в порядке, оно вызывает подпрограмму CombinePDFs::createPDF, обрабатывает массив входных файлов, массив интервалов номеров страниц и информацию о закладках. Переменная для закладок необязательна. Интервалы номеров страниц могут быть разделены запятыми (1-11,14,17-23), отдельными страницами или в виде слова all — все страницы. Вы можете включать одни и те же страницы несколько раз в одном документе. Код проверки файла смотрит права на чтение и проверяет, является ли файл PDF документом с помощью подпрограммы CombinePDFs::isPDF($filename). Несмотря на то, что PDF от Антонио Росселы также предоставляет такой метод, но он написан без использования прагмы use strict; и выдает много предупреждений. Кроме того, этот пакет не поддерживается активно, так что, похоже, нет возможности исправить это в ближайшем будущем. Реализация подпрограммы isPDF довольно-таки проста: она считывает первую строчку PDF файла и проверяет наличие волшебной строки %PDF-1.[0-9] в первой строке документа. Пожалуйста, учтите, что PDF::Reuse не объектно-ориентированный модуль. Поэтому пакет CombinePDFs тоже не объектно-ориентированный. Пользователь этого пакета может создать несколько экземпляров, но все они будут работать с тем же PDF файлом. Прописывать все структуры данных через командную строку нелегко, поэтому я решил, что закладки будут прописаны в текстовом файле. Этот файл легко разметить таким образом, чтобы отразить древовидную структуру, где каждая строка имеет такой вид: <уровень> "текст закладки" <страница> Уровень начинается с 0 для корневых закладок. Дети корневых закладок имеют уровень 1, их дети — уровень 2, и так далее. В настоящий момент система поддерживает закладки до третьего уровня: 0 "Folder File 1 - Page 1" 1 1 "File 1 - Page 2" 2 1 "Subfolder File 1 - Page 3" 3 2 "File 1 - Page 4" 4 0 "Folder File 2 - Page 7 " 7 1 "File 2 - Page 7" 7 1 "File 2 - Page 9" 9 Подпрограмма CombinePDFs::addBookmarks($filename), которая парсит файл с закладками должна быть простой для понимания, поэтому нет необходимости в создании сложной структуры данных внутри подпрограммы. Закладки хранятся в массиве хешей. addBookmarks() использует несколько атрибутов. text это заголовок каждой записи панели закладок. act это действие, которое совершается, когда кто-либо кликает на запись. kids содержит ссылку на ребенка данной закладки. Во время цикла по содержимому файла, код ищет последнюю запись каждого уровня в переменной и продвигает его ребенка к тем последним записям. Корневые закладки хранятся как массив, и цикл добавляет детей как ссылку на массив и это продолжается до последних детей. Результатом является вложенная сложная структура данных, которая хранит всех детей в атрибуте kids родительского хеша закладок, — массив хешей, содержащий другие массивы хешей и так далее. Подпрограмма CombinePDFs::addBookmarks($filename), которая парсит файл с закладками собирает закладки в массив хешей. В конце, она добавляет закладки в документ с помощью prBookmarks($reference). Все это значит, что вы можете использовать файл закладок вметсе с PDF файлом с помощью следующей команды: $ perl bin/app-combine-pdfs.pl \ --infile out/file-1.pdf --pages 1-6 \ --infile out/file-2.pdf --pages 1-4,7,9-10 \ --bookmarks out/bookmarks.cnt \ --outfile file-all.pdf --overwrite В настоящий момент, вы должны открывать навигационную панель документа вручную, так как PDF::Reuse пока что не позволяет вам показать общий вид, так же, как и вид на весь экран или вид панели. Это легко исправить, и автор модуля, Ларс Лунберг, пообещал мне сделать это в следующем релизе PDF::Reuse. Пока новый релиз не вышел, я добавил исправленную версию PDF::Reuse в архив с примерами. Более того, закладки используют функции JavaScript. Для использования закладок в программах для просмотра PDF, отличных от Acrobat Reader, мой патч заменяет JavaScript закладки на закладки по спецификации PDF. Чтобы это использовать, замените ключ act на page с соответствующим номером страницы и опций скроллинга: $bookmarks = { text => 'Document', page => '0,40,50;', kids => [ { text => 'Chapter 1', page => '1, 40, 600' }, { text => 'Chapter 2', page => '10, 40, 600' } ] } Затем напечатайте закладки в PDF документ как обычно, с помощью prBookmark($bookmarks);. Tk приложение для соединения PDF документов. Консольное приложение подходит для опытных пользователей, но мы не можем отнести всех пользователей к этой категории. Вот почему я решил написать GUI для соединения PDF документов. Perl/Tk, основанный на старых Tix виджетах для Tcl/Tk не очень современный, поэтому его можно заменить на релиз Tcl/Tk 8.5 и виджеты Tile — они легко переносимы. Вот почему я использовал их для примера GUI. Так как я сделал уровень между пакетом PDF::Reuse и консольным приложением с пакетом CombinePDFs, теперь будет просто использовать ещё раз эти части в Tk-приложении app-combine-tk-pdfs.pl. С помощью Tk-приложения, пользователь визуально выбирает PDF файлы, упорядочивает их в виджете Tk::Tree и меняет интервалы номеров страниц в полях Tk::Entry. Кроме того, приложение может хранить результирующую древовидную структуру в файле сессии и восстанавливать её позже. Так же возможно копировать и вставлять записи внутри дерева, что делает проще создание панели закладок для одиночных файлов без использования файлов закладок. Вы можете найти и скачать Tk-приложение в конце этой статьи. Помимо результирующего PDF файла, приложение создает файл с таким же именем и расширением .cnt. Этот файл содержит закладки для PDF. Это также может быть полезно для обработки собранного PDF файла вместо пересоставления всех исходных файлов снова. Осуществляется это с помощью File->Load Bookmarks-File. При загрузке файла закладок, имеет место быть такое же расширение. Другие PDF пакеты на CPAN. Мне нравится PDF::Reuse, но существует несколько других возможностей для создания и работы с PDF на CPAN. • PDF::API2, от Альфреда Рейбенщуха, активно поддерживается. Этот пакет можно выбрать при создании новых PDF документов из временной памяти. • PDF::API2::Simple, от Red Tree Systems, надстройка над модулем PDF::API2 для пользователей, которые нашли модуль PDF::API2 слишком сложным для изучения. • Text::PDF, от Мартина Хоскена, может работать сразу с несколькими PDF файлами одновременно и поддерживает Truetype шрифты. • CAM::PDF, от Clotho Advanced Media, похож на PDF::Reuse, но больше сфокусирован на чтение и обработку существующих PDF документов. Тем не менее, он может работать с несколькими файлами одновременно. Используйте его если вам нужно больше возможностей, чем предоставляет PDF::Reuse. Заключение PDF::Reuse хорошо написанный и хорошо задокументированный модуль, который делает простым создание, объединение и изменение существующих PDF документов. Два приложения, написанных в качестве примера, показывают некоторые из его возможностей. Тем не менее, надо указать два ограничения: PDF::Reuse не может повторно использовать существующие закладки, и после объединения разных PDF документов некоторые ссылки в документе могут перестать работать правильно. Исходный код примеров прилагается. |