PDF-файлы являются одним из наиболее распространенных способов обмена документами в Интернете. Нужно ли нам передавать документы наших клиентов сторонним поставщикам услуг, таким как банки или страховые компании, или просто отправлять резюме работодателю, использование документа PDF часто является первым вариантом.
Файлы PDF могут передавать простой / форматированный текст, изображения, гиперссылки и даже заполняемые формы. В этом уроке мы увидим, как мы можем заполнять PDF-формы, используя PHP и отличный инструмент для работы с PDF- файлами, называемый PDFtk Server .
Для простоты мы будем ссылаться на PDFtk Server
как PDFtk
в оставшейся части статьи.
Установка
Как обычно, мы будем использовать Homestead Improved для нашей среды разработки.
После того, как виртуальная машина загружена, и нам удалось выполнить ssh в систему с помощью vagrant ssh
, мы можем начать установку PDFtk
с помощью apt-get
:
sudo apt - get install pdftk
Чтобы проверить, работает ли он, мы можем запустить следующую команду:
pdftk -- version
Вывод должен быть похож на:
Copyright ( c ) 2003 - 13 Steward and Lee , LLC - Please Visit : www . pdftk . com . This is free software ; see the source code for copying conditions . There is NO warranty , not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
Как это устроено
PDFtk предоставляет широкий спектр функций для управления PDF-документами, от слияния и разделения страниц до заполнения PDF-форм или даже применения водяных знаков. Эта статья посвящена использованию PDFtk для заполнения стандартной формы PDF с использованием PHP.
PDFtk использует файлы FDF для работы с PDF-формами, но что такое файл FDF?
FDF или файл данных формы — это простой текстовый файл, который может хранить данные формы в гораздо более простой структуре, чем файлы PDF.
Проще говоря, нам нужно сгенерировать файл FDF из предоставленных пользователем данных и объединить его с исходным файлом PDF с помощью команд PDFtk.
Что находится внутри файла FDF
Структура файла FDF состоит из трех частей: заголовка, содержимого и нижнего колонтитула:
заголовок
% FDF - 1.2
1 0 obj << /FDF<< / Fields [
Нам не нужно беспокоиться об этой части, так как это то, что мы собираемся использовать для всех файлов FDF.
содержание
Пример содержания FDF выглядит следующим образом:
<< /T (first_name) /V (John) << /T (last_name) /V (Smith) << /T (occupation) /V (Teacher)>> << /T (age) /V (45)>> << /T (gender) /V (male)>>
Поначалу раздел контента может показаться запутанным, но не волнуйтесь, мы скоро к этому вернемся.
нижний колонтитул
] >> >> endobj trailer <</ Root 1 0 R >>
%% EOF
Этот раздел также одинаков для всех наших файлов FDF.
Раздел содержимого содержит записи данных формы, каждая из которых следует стандартному шаблону. Каждая строка представляет одно поле в форме. Они начинаются с имени элемента формы с префиксом /T
, который указывает заголовок. Вторая часть — это значение элемента с префиксом /V
указывающее значение:
<< /T(FIELD_NAME)/V(FIELD_VALUE) >>
Чтобы создать файл FDF, нам нужно знать имена полей в PDF-форме. Если у нас есть доступ к компьютеру Mac или Windows, мы можем открыть форму в Adobe Acrobat Pro и просмотреть свойства полей.
В качестве альтернативы, мы можем использовать команду dumpt_data_fields из dump_data_fields
чтобы извлечь информацию о полях из файла:
pdftk path / to / the / form . pdf dump_data_fields > field_names . txt
В результате PDFtk сохранит результат в файле field_names.txt
. Ниже приведен пример извлеченных данных:
--
FieldType : Text
FieldName : first_name FieldFlags : 0
FieldJustification : Left
---
FieldType : Text
FieldName : last_name FieldFlags : 0
FieldJustification : Left
---
FieldType : Text
FieldName : occupation FieldFlags : 0
FieldJustification : Center
---
FieldType : Button
FieldName : gender FieldFlags : 0
FieldJustification : Center
Есть несколько свойств для каждого поля в форме. Мы можем изменить эти свойства в Adobe Acrobat Pro. Например, мы можем изменить выравнивание текста, размеры шрифта или даже цвет текста.
PDFtk и PHP
Мы можем использовать функцию exec()
PHP, чтобы перенести PDFtk в среду PHP. Предположим, у нас есть простая PDF-форма с четырьмя текстовыми полями и группой из двух переключателей:
Давайте напишем простой скрипт для заполнения этой формы:
<? php // Form data: $fname = 'John' ; $lname = 'Smith' ; $occupation = 'Teacher' ; $age = '45' ; $gender = 'male' ;
// FDF header section $fdf_header = <<< FDF % FDF - 1.2
%,, oe " 1 0 obj << /FDF << /Fields [ FDF; // FDF footer section $fdf_footer = <<<FDF " ] >> >> endobj trailer <</ Root 1 0 R >>
%% EOF ; FDF ;
// FDF content section $fdf_content = "<</T(first_name)/V({$fname})>>" ; $fdf_content .= "<</T(last_name)/V({$lname})>>" ; $fdf_content .= "<</T(occupation)/V({$occupation})>>" ; $fdf_content .= "<</T(age)/V({$age})>>" ; $fdf_content .= "<</T(gender)/V({$gender})>>" ; $content = $fdf_header . $fdf_content , $fdf_footer ;
// Creating a temporary file for our FDF file. $FDFfile = tempnam ( sys_get_temp_dir (), gethostname ()); file_put_contents ( $FDFfile , $content );
// Merging the FDF file with the raw PDF form
exec ( "pdftk form.pdf fill_form $FDFfile output.pdf" );
// Removing the FDF file as we don't need it anymore unlink ( $FDFfile );
Хорошо, давайте разберем сценарий. Сначала мы определяем значения, которые мы собираемся записать в форму. Мы можем получить эти значения из таблицы базы данных, ответа JSON API или даже жестко закодировать их внутри скрипта.
Далее мы создаем файл FDF на основе шаблона, который мы обсуждали ранее. Мы использовали функцию tempnam в PHP для создания временного файла для хранения содержимого FDF. Причина в том, что PDFtk полагается только на физические файлы для выполнения операций, особенно при заполнении форм.
Наконец, мы вызвали команду filltform в PDFtk, используя PHP-функцию exec
. fill_form
объединяет файл FDF с необработанной PDF-формой. Согласно сценарию наш PDF-файл должен находиться в том же каталоге, что и наш PHP-сценарий.
Сохраните указанный выше файл PHP в корневом веб-каталоге как pdftk.php
. Результатом будет новый PDF-файл со всеми полями, заполненными нашими данными.
Это так просто!
Сглаживание выходного файла
Мы также можем сгладить выходной файл, чтобы предотвратить будущие изменения. Это возможно, передавая flatten
в качестве параметра команде fill_form
.
<? php exec ( "pdftk path/to/form.pdf fill_form $FDFfile output path/to/output.pdf flatten" );
Загрузка выходного файла
Вместо сохранения файла на диске мы можем принудительно загрузить выходной файл, отправив содержимое файла вместе с необходимыми заголовками в выходной буфер:
<? php // ...
exec ( "pdftk path/to/form.pdf fill_form $FDFfile output output.pdf flatten" );
// Force Download the output file header ( 'Content-Description: File Transfer' ); header ( 'Content-Type: application/octet-stream' ); header ( 'Content-Disposition: attachment; filename=' . 'path/to/output.pdf' ); header ( 'Expires: 0' ); header ( 'Cache-Control: must-revalidate' ); header ( 'Pragma: public' ); header ( 'Content-Length: ' . filesize ( 'output.pdf' )); readfile ( 'output.pdf' );
exit ;
Если мы запустим скрипт в браузере, выходной файл будет загружен на наш компьютер.
Теперь, когда у нас есть общее представление о том, как работает PDFtk, мы можем начать строить PHP-класс вокруг него, чтобы сделать наш сервис более пригодным для повторного использования.
Создание класса Wrapper вокруг PDFtk
Использование нашего конечного продукта должно быть таким простым, как следующий код:
<? php // Data to be written to the PDF form $data = [
'first_name' => 'John' ,
'last_name' => 'Smith' ,
'occupation' => 'Teacher' ,
'age' => '45' ,
'gender' => 'male'
]; $pdf = new pdfForm ( 'form.pdf' , $data ); $pdf -> flatten ()
-> save ( 'outputs/form-filled.pdf' )
-> download ();
Мы создадим новый файл в корневом веб-каталоге и PdfForm.php
его PdfForm.php
. Назовем также класс PdfForm
.
Начиная с свойств класса
Прежде всего, нам нужно объявить некоторые частные свойства для класса:
<? php class PdfForm
{
/* * Path to raw PDF form * @var string */
private $pdfurl ;
/* * Form data * @var array */
private $data ;
/* * Path to filled PDF form * @var string */
private $output ;
/* * Flag for flattening the file * @var string */
private $flatten ;
// ...
}
Конструктор
Давайте напишем конструктор:
<? php // ...
public function __construct ( $pdfurl , $data )
{ $this -> pdfurl = $pdfurl ; $this -> data = $data ;
}
Конструктор не делает ничего сложного. Он назначает путь PDF и данные формы их соответствующим свойствам.
Обработка временных файлов
Поскольку PDFtk использует физические файлы для выполнения своих задач, нам обычно нужно создавать временные файлы во время процесса. Чтобы сохранить код чистым и многократно используемым, давайте напишем метод для создания временных файлов:
<? php // ...
private function tmpfile ()
{
return tempnam ( sys_get_temp_dir (), gethostname ());
}
Этот метод создает файл, используя PHP-функцию tempnum . Мы передали в функцию два параметра, первый из которых — путь к каталогу tmp
извлеченному с помощью функции sys_get_temp_dir , а второй — префикс для имени файла, просто для того, чтобы имя файла было как можно более уникальным на разных хостах. Это будет префикс имени файла с нашим именем хоста. Наконец, метод возвращает путь к файлу для вызывающей стороны.
Извлечение информации о форме
Как уже говорилось ранее, для создания файла FDF нам нужно заранее знать имя элементов формы. Это возможно либо путем открытия формы в Adobe Acrobat Pro, либо с помощью команды dumpt_data_fields в dump_data_fields
.
Чтобы упростить задачу разработчику, давайте напишем метод, который выводит информацию о полях на экран. Хотя мы не будем использовать этот метод в процессе создания PDF, он может быть полезен, когда нам неизвестны имена полей. Другим вариантом использования будет анализ метаданных полей, чтобы сделать процесс записи более динамичным.
<? php // ...
public function fields ( $pretty = false )
{ $tmp = $this -> tmpfile ();
exec ( "pdftk {$this->pdfurl} dump_data_fields > {$tmp}" ); $con = file_get_contents ( $tmp ); unlink ( $tmp );
return $pretty == true ? nl2br ( $con ) : $con ;
}
Вышеуказанный метод запускает команду dumpt_data_fields в dump_data_fields
, записывает вывод в файл и возвращает его содержимое.
Мы также устанавливаем необязательный аргумент для украшения вывода. В результате мы сможем получить дружественный к человеку результат, передав значение true
методу. Если нам нужно проанализировать вывод или запустить с ним регулярное выражение, мы должны вызвать его без аргументов.
Создание файла FDF
На следующем шаге мы напишем метод для создания файла FDF:
<? php // ...
public function makeFdf ( $data )
{ $fdf = '%FDF-1.2 1 0 obj<</FDF<< /Fields[' ;
foreach ( $data as $key => $value ) { $fdf .= '<</T(' . $key . ')/V(' . $value . ')>>' ;
} $fdf .= "] >> >> endobj trailer <</Root 1 0 R>> %%EOF" ; $fdf_file = $this -> tmpfile (); file_put_contents ( $fdf_file , $fdf );
return $fdf_file ;
}
Метод makeFdf()
перебирает элементы массива $data
для генерации записей на основе стандартного шаблона FDF. Наконец, он помещает содержимое во временный файл с file_put_contents
функции file_put_contents
и возвращает путь к файлу вызывающей стороне.
Сглаживание файла
Давайте напишем метод для установки атрибута $flatten
на flatten
. Это значение используется методом generate()
:
<? php // ...
public function flatten ()
{ $this -> flatten = ' flatten' ;
return $this ;
}
Заполнение формы
Теперь, когда мы можем создать файл FDF, мы можем заполнить форму с помощью команды fill_form
:
// ...
private function generate ()
{ $fdf = $this -> makeFdf ( $this -> data ); $this -> output = $this -> tmpfile ();
exec ( "pdftk {$this->pdfurl} fill_form {$fdf} output {$this->output}{$this->flatten}" ); unlink ( $fdf );
}
generate()
вызывает метод makeFdf()
для генерации файла FDF, затем запускает команду fill_form
чтобы объединить его с необработанной PDF-формой. Наконец, он сохранит вывод во временный файл, который создается с помощью метода tempfile()
.
Сохранение файла
Когда файл сгенерирован, мы можем захотеть сохранить или загрузить его, или сделать оба одновременно.
Во-первых, давайте создадим метод сохранения:
// ...
public function save ( $path = null )
{
if ( is_null ( $path )) {
return $this ;
}
if (! $this -> output ) { $this -> generate ();
} $dest = pathinfo ( $path , PATHINFO_DIRNAME );
if (! file_exists ( $dest )) { mkdir ( $dest , 0775 , true );
} copy ( $this -> output , $path ); unlink ( $this -> output ); $this -> output = $path ;
return $this ;
}
Метод сначала проверяет, существует ли какой-либо путь для пункта назначения. Если путь назначения равен нулю, он просто возвращается без сохранения файла, в противном случае он перейдет к следующей части.
Затем он проверяет, был ли файл уже сгенерирован; если нет, он вызовет метод generate()
для его генерации.
Убедившись в том, что выходной файл создан, он проверяет, существует ли на диске целевой путь. Если путь не существует, он создаст каталоги и установит соответствующие разрешения.
В конце он копирует файл (из каталога tmp
) в постоянное местоположение и обновляет значение $this->output
в постоянный путь.
Принудительно загрузить файл
Для принудительной загрузки файла нам нужно отправить содержимое файла вместе с необходимыми заголовками в выходной буфер.
// ...
public function download ()
{
if (! $this -> output ) { $this -> generate ();
} $filepath = $this -> output ;
if ( file_exists ( $filepath )) { header ( 'Content-Description: File Transfer' ); header ( 'Content-Type: application/pdf' ); header ( 'Content-Disposition: attachment; filename=' . uniqid ( gethostname ()) . '.pdf' ); header ( 'Expires: 0' ); header ( 'Cache-Control: must-revalidate' ); header ( 'Pragma: public' ); header ( 'Content-Length: ' . filesize ( $filepath )); readfile ( $filepath );
exit ;
}
}
В этом методе сначала нам нужно проверить, был ли файл сгенерирован, потому что нам может потребоваться загрузить файл без его сохранения. Убедившись, что все установлено, мы можем отправить содержимое файла в выходной буфер с помощью функции PHP readfile()
.
Наш класс PdfForm готов к использованию. Полный код на GitHub .
Ввод в действие класса
<? php require 'PdfForm.php' ; $data = [
'first_name' => 'John' ,
'last_name' => 'Smith' ,
'occupation' => 'Teacher' ,
'age' => '45' ,
'gender' => 'male'
]; $pdf = new PdfForm ( 'form.pdf' , $data ); $pdf -> flatten ()
-> save ( 'output.pdf' )
-> download ();
Создание файла FDF
Если нам просто нужно создать файл FDF без заполнения формы, мы можем использовать метод makeFdf()
.
<? php require 'PdfForm.php' ; $data = [
'first_name' => 'John' ,
'last_name' => 'Smith' ,
'occupation' => 'Teacher' ,
'age' => '45' ,
'gender' => 'male'
]; $pdf = new PdfForm ( 'form.pdf' , $data ); $fdf = $pdf -> makeFdf ();
Возвращаемое значение makeFdf()
— это путь к сгенерированному файлу FDF в каталоге tmp
. Мы можем либо получить содержимое файла, либо сохранить его в постоянном месте.
Извлечение PDF Полевой информации
Если нам просто нужно увидеть, какие поля и типы полей существуют в форме, мы можем вызвать метод fields()
:
<? php require 'PdfForm.php' ; $fields = new PdfForm ( 'form.pdf' )-> fields (); echo $fields ;
Если нет необходимости анализировать вывод, мы можем передать true
в метод fields()
, чтобы получить читабельный вывод:
<? php require 'PdfForm.php' ; $pdf = new PdfForm ( 'pdf-test.pdf' )-> fields ( true ); echo $pdf ;
Завершение
Мы установили PDFtk
и узнали некоторые из его полезных команд, таких как dump_data_fields
и fill_form
. Затем мы создали базовый класс, чтобы показать, как мы можем использовать возможности PDFtk в наших PHP-приложениях.
Обратите внимание, что эта реализация является базовой, и мы постарались сделать ее как можно более простой. Мы можем пойти дальше и поместить функцию создания FDF в отдельный класс, который даст нам больше места при работе с файлами FDF. Например, мы могли бы применить сцепленные фильтры к каждой записи данных формы, например, в верхнем, нижнем регистре или даже в формате даты, просто чтобы назвать несколько. Мы также можем реализовать методы download()
и save()
для класса FDF.
Вопросов? Комментарии? Оставьте их ниже, и мы сделаем все возможное, чтобы ответить своевременно!