- Подробности
-
Категория: PHP. Файлы
В наше время формат документов PDF приобретает большую популярность. Он был разработан компанией Adobe Systems Incorporated. Как указано в документации, THE ADOBE PORTABLE DOCUMENT FORMAT (PDF) — переносимый формат документов, является "родным" для программных продуктов семейства Adobe Acrobat. Их цель — дать пользователю возможность легко обмениваться электронными документами и просматривать их независимо от той среды, в которой эти документы были созданы. PDF опирается на графическую модель, позволяющую отображать картинки и текст вне зависимости от установленных на компьютере устройств и разрешения. В документах этого формата присутствуют такие объекты, как гиперссылки и аннотации, что делает их интерактивными.
С другой стороны, web-приложения зачастую нуждаются в отображении динамически составленных документов, таких как отчеты, прайслисты, счета и многое другое. Это позволяет персонализировать приложение и сделать его более мощным по своим функциональным возможностям. Помимо PDF, существуют и другие решения, но этот формат можно назвать одним из самых удачных, так как PDF документ без потери форматирования можно вывести на принтер или конвертировать в HTML или текст.
PHP, как один из самых мощных и популярных современных средств разработки web-приложений, справляется с задачей генерации PDF документов "на лету". Для этого разработано несколько дополнительных инструментов. Не возьмусь перечислить их все, но назову одни из самых известных — библиотеку PDFLib, ClibPDF и PHP класс FPDF.
FPDF — только PHP
Названные в предыдущей главе PDFLib и ClibPDF требуют дополнительной настройки PHP, в то время как класс FPDF является чистым PHP кодом и легко подключается к скриптам командой include() и другими подобными. Скачать класс и ознакомиться с подробной документацией можно на сайте www.fpdf.org. Дополнительным (порой решающим) аргументом в пользу этого решения можно рассматривать его бесплатность для использования как в личных, так и коммерческих целях. Цитата из лицензионного соглашения:
"FPDF is Freeware (it is stated at the beginning of the source file). There is no usage restriction. You may embed it freely in your application (commercial or not), with or without modification".
Разрешается также видоизменять исходный код класса. Никаких ограничений.
Решение проблемы с кириллицей
При создании русскоязычных документов средствами иностранных программных продуктов (библиотек, приложений и прочего) часто возникает проблема правильного отображения кириллических шрифтов. Не всякий зарубежный продукт корректно работает (а то и вовсе не работает) с кириллицей. К счастью, класс FPDF не принадлежит к их числу и легко настраивается на работу с русским языком.
Если быть точным, то сам класс настраивать практически не придется. Проблема может возникнуть с файлами кириллических шрифтов. Оговорюсь, что тестирование класса я проводил на wintel платформе (впрочем, весь приведенный код работал и на коммерческом *nix хостинге). В windows одним из самых основных форматов шрифтов (наряду с PostScript) является TTF (True Type Font). Но для правильной работы наших скриптов необходим и еще один формат файлов — AFM (файл метрики шрифта). Как считается, AFM файлы поставляются вместе с TTF. В своей ОС я AFM файлов не обнаружил.
Здесь нам на помощь приходят полезные утилиты, в частности — ttf2pt1. Одна из задач данной утилиты — сгенерировать метрический файл для True Type или PFB. Другими словами, появляется возможность взять из директории /fonts (ОС Windows) любой .TTF файл шрифта с поддержкой кириллицы и получить для него метрику при помощи нашей волшебной утилиты. Скачать утилиту можно по следующим линкам: http://ttf2pt1.sourceforge.net и http://fpdf.org/fr/dl.php?id=22 (для Windows).
После того, как утилита скачана, ее необходимо запустить из командной строки (в windows Пуск->Выполнить команду cmd). Формат вызова утилиты для нужной нам цели выглядит следующим образом:
ttf2pt1 -A font.ttf font
К примеру, если вы положили скачанный экземпляр ttf2pt1 прямо на диск C: , а файл шрифта times.ttf в C:CyrFonts, то вам будет необходимо запустить следующую команду:
c:ttf2pt1 -A c:CyrFontstimes.ttf times
где c:ttf2pt1 — вызов программы, -A — ключ, указывающий на необходимость сформировать файл AFM, c:CyrFontstimes.ttf — это адрес файла True Type шрифта и, наконец, times — это имя будущего метрического файла. Итак, AFM файл готов.
Следующим шагом является генерация файла описания шрифта. Этот файл будет иметь знакомое нам расширение — PHP. Вместе с классом FPDF поставляется полезный скрипт для решения этой задачи. Его можно найти в директории font/makefont/ класса. Использовать его просто. Для этого создадим РНР файл (скажем, mf.php) и в нем укажем:
<?php
require('font/makefont/makefont.php');
MakeFont('times.ttf',times.afm','cp1251');
?>
Используя require, мы подключаем нужный скрипт. Понятно, что для этого рядом с нашим файлом должна быть папка font, содержащая в себе makefont/makefont.php. А вот функция MakeFont() уже является специфической и по определению имеет следующий формат:
MakeFont(string fontfile, string afmfile [, string enc [, array patch [, string type]]])
где fontfile — путь к TTF или PFB файлу, afmfile — путь к AFM файлу, enc — имя используемой кодировки (по умолчанию это cp1252), patch — опциональное изменение кодировки и type — тип шрифта (по умолчанию True Type). Для выбора кодировки можно воспользоваться следующим списком:
* cp1250 (Central Europe)
* cp1251 (Cyrillic)
* cp1252 (Western Europe)
* cp1253 (Greek)
* cp1257 (Baltic)
* ISO-8859-1 (Western Europe)
* ISO-8859-2 (Central Europe)
* ISO-8859-4 (Baltic)
* ISO-8859-4 (Cyrillic)
* ISO-8859-7 (Greek)
* ISO-8859-15 (Western Europe)
* ISO-8859-16 (Central Europe)
* KOI8-R (Cyrillic)
Кодировка определяет связь между кодом (от 0 до 255) и символом. Для выбора кириллической кодировки в Windows используйте cp1251. Обычно кодировки с префиксом cp используются в Windows, в то время как Linux системы используют ISO.
Составленный нами скрипт mf.php необходимо открыть в браузере. Он подготовит для наших нужд необходимый файл с расширением php. Итак, что мы имеем? У нас теперь есть комплект из трех файлов шрифта — times.ttf, times.afm и times.php. Важно два из них (times.ttf и times.php) положить в нужное место. Этим местом является директория font, находящаяся в папке класса. Впрочем, вы вольны сами указать место директории, которая будет хранить шрифты. Для этого нужно определить константу FPDF_FONTPATH обыкновенным для PHP способом:
define('FPDF_FONTPATH','font/');
Теперь наша система готова к разработке web приложения с динамической генерацией русскоязычного PDF документа.
Начинаем работу
Для наглядного иллюстрирования возможностей FPDF класса попробуем создать реальный документ — прайс-лист фирмы, занимающейся оптово-розничной продажей алкогольных напитков. Краткое техническое задание следующее:
* в шапке документа должны быть данные: логотип, наименование фирмы, заголовок документа;
* в теле документа должны быть приведены данные по товарным позициям, включающие наименование товара, розничную цену и оптовую цену;
Сразу разобьем нашу работу на три этапа. Первый этап — вывод статической информации. Для простоты примера к статической информации мы отнесем все, кроме данных по товарным позициям. На втором этапе уделим внимание табличному выводу товарных позиций в теле документа. Уточним, что в этой статье мы рассмотрим загрузку данных из файла CSV, где разделителем является точка с запятой. Я остановился на этом решении по одной причине — такой файл легко получить из формата xls и, одновременно, с ним легко работать из РНР приложения в других целях (например, организовать вывод в HTML). На третьем этапе мы рассмотрим доставку PDF файла конечному пользователю.
Приступим к практическому знакомству с классом FPDF. Для начала создадим файл price.php, который будет осуществлять вывод PDF документа прямо в браузер (остальные способы мы рассмотрим в главе 6). Рядом с этим рабочим файлом положим скачанный ранее fpdf.php (файл класса) и папку font с вложенными в нее файлами кириллических шрифтов (см. предыдущую главу). Теперь в файле price.php подключим класс FPDF и установим путь к папке шрифтов.
<?php
define('FPDF_FONTPATH','font/');
require('fpdf.php');
?>
Необходимо уточнить, что тэг <?php должен стоять на самой первой строке файла, так как в последствии мы будем самостоятельно отправлять заголовки (headers), и пустая строка, текст или разметка HTML в начале файла могут нам помешать.
Для вывода статической информации (логотип, название фирмы, название документа) мы будем использовать следующие методы класса FPDF: cell() и image(). Метод cell() выводит ячейку (прямоугольную фигуру) с опциональной установкой границы, цветовой заливкой и строкой текста. Формат записи метода следующий:
Cell(float w [, float h [, string txt [, mixed border [, int ln [, string align [, int fill [, mixed link]]]]]]])
Метод image() отвечает за вывод графического изображения JPG или PNG. Формат вызова метода следующий:
Image(string file, float x, float y, float w [, float h [, string type [, mixed link]]])
Конечно, перед тем, как воспользоваться методами класса, сначала необходимо создать экземпляр этого класса. Приведем наш файл к следующему виду:
<?php
define('FPDF_FONTPATH','font/');
require('fpdf.php');
//Создадим экземпляр класса
$price = new FPDF();
$price->Open();
?>
После создания экземпляра класса нам будет необходимо указать используемые шрифты. Так как кириллический Times New Roman (рассмотренный в предыдущей главе) не является в классе FPDF шрифтом, установленным по умолчанию, сначала придется его "показать" скрипту. Это мы сделаем при помощи метода AddFont().
<?php
define('FPDF_FONTPATH','font/');
require('fpdf.php');
//Создадим экземпляр класса
$price = new FPDF();
$price->Open();
//Подключаем кириллический шрифт
$price-> AddFont('TimesNewRomanPSMT','','times.php');
$price-> SetFont('TimesNewRomanPSMT','',12);
?>
Первым аргументом функции мы указываем наименование шрифта. Его можно посмотреть в сгенерированном РНР файле (значение переменной $name). Второй аргумент — форматирование текста (B — Bold, I — Italic и смешанный BI или IB). Если аргумент пустой, то шрифт обычный. Для использования B и I необходимо также подключить соответствующие типы шрифтов (для Times New Roman это могут быть файлы timesI.ttf и timesBd.ttf). Третий аргумент — РНР файл описания (его мы сгенерировали в предыдущей главе). Теперь шрифт можно применять в данном документе. Для использования на странице установим его размер методом SetFont(). Этот метод можно вызывать несколько раз в одном скрипте, в то время как добавление AddFont() делается один раз для каждого шрифта. Формат записи SetFont() следующий:
SetFont(string family [, string style [, float size]])
Наконец-то можно сформировать содержимое страницы. На этом этапе мы столкнемся с увеличением количества кода, и поэтому, во избежание путаницы, расширим класс FPDF своими методами. Конечно, нет необходимости вносить изменения в сам FPDF класс. Мы напишем свой price.class.php, наследуемый от FPDF.
Листинг 1
<?php
class Price extends FPDF {
function PrintTitle($title,$image,$company) {
//Выводим логотип
$this->Image($image,6,6,40,20);
//Устанавливаем шрифт для наименования компании
$this->SetFont('TimesNewRomanPSMT','',20);
//Выводим наименование компании
$this->Cell(210,4,$company,0,0,'C');
//Переходим на следующую строку
$this->Ln();
//Переходим на следующую строку
$this->Ln();
//Делаем отступ от левого края (рисуя прозрачную ячейку)
$this->Cell(37);
//Устанавливаем цвет заливки следующих ячеек (R,G,B)
$this->SetFillColor(209,204,244);
//Устанавливаем шрифт для наименования документа
$this->SetFont('TimesNewRomanPSMT','',12);
//Выводим наименование документа
$this->Cell(150,8,$title,0,0,'C',1);
//Переходим на следующую строку
$this->Ln();
}
}
?>
Листинг 2
<?php
define('FPDF_FONTPATH','font/');
require('fpdf.php');
require('price.class.php');
$price = new Price();
$price->Open();
$price->AddFont('TimesNewRomanPSMT','','times.php');
//Добавляем страничку в документ
$price->AddPage();
//Выводим заголовок, используя написанный нами метод в файле класса //price.class.php
$price->PrintTitle('Прайс-лист','logo.jpg','Компания "ALKO SELL"');
//Выводим документ в браузер
$price->Output();
?>
В этом файле мы столкнулись еще с несколькими методами класса FPDF. Необходимо заметить, что документ сначала создается в буфере и лишь потом, при вызове метода Output(), выводится в браузер. Поэтому общая схема работы с документом следующая: создаем в буфере документ методом Open(), затем для работы добавляем в этот документ страничку методом AddPage(), формируем содержимое документа различными методами типа Cell() и, наконец, выводим его из буфера в браузер. Если у вас установлен adobe acrobat версии не ниже 5.0, то при запуске price.php в браузере должен открыться документ price.pdf (в Internet Explorer) или сформируется документ doc.pdf для скачивания (в Opera).
Этот документ пока не представляет собой практической ценности, однако на его примере мы разобрали основы вывода статических данных в документ .PDF.
Подключаем источник данных
Перейдем к самому интересному — динамическому формированию содержимого документа. Именно этой возможностью и ценен для РНР разработчика класс FPDF. Я уже говорил выше, что здесь мы рассмотрим вариант получения данных из CSV файла.
Как указано в нашем коротком техническом задании, тело прайс-листа должно состоять из трех граф: наименование товара, розничная цена и оптовая цена. Соответственно, подготовим и файл данных следующего вида:
Пиво "Балтика";15.00;13.45
Пиво "Оболонь";14.00;12.30
Водка "Nemiroff", 1 л.;100.00;89.45
Сохраним его как price.csv. Конечно, в реальном прайс-листе товарных позиций и граф будет больше.
Возможны так же деления по группам товаров (например "пиво", "водка", "вино"), но в данном конкретном примере попробуем рассмотреть упрощенную версию (количество записей можно увеличить).
Перед нами встает необходимость получить данные динамически и представить их в виде обыкновенной таблицы в три колонки, с заголовками колонок (столбцов). Перед созданием экземпляра объекта Price добавим инициализацию массива заголовков (соберем все заголовки в массив):
$header = array("Наименование","Розн.","Опт.");
Далее нам понадобится еще больше расширить класс Price (унаследованный от класса FPDF) методами LoadData() и ImprovedTable(). Первый метод должен будет извлекать данные из CSV файла в массив $data, а второй метод пригодится для отрисовки таблицы с данными из этого массива. Рассмотрим метод LoadData() для класса Price.
function LoadData($file) {
//Получаем данные в массив строк
$lines=file($file);
$data=array();
//Разделяем столбцы каждой строки по точке с запятой.
foreach($lines as $line)
$data[]=explode(';',chop($line));
//Возвращаем массив с данными
return $data;
}
Вставим этот код в файл класса price.class.php. Теперь перейдем к рассмотрению метода ImprovedTable().
Вот его код:
function ImprovedTable($header,$data) {
//Указываем ширину столбцов
$w=array(100,40,40);
//Выводим заголовки столбцов
for($i=0;$i<count($header);$i++)
$this->Cell($w[$i],7,$header[$i],1,0,'C');
$this->Ln();
//Выводим данные
//Сначала установим шрифт для данных
$this-> SetFont('TimesNewRomanPSMT','',8);
foreach($data as $row)
{
/*Первый параметр Cell() — ширина столбца, указанная ранее в массиве $w, второй параметр — высота столбца, третий параметр — строка для вывода, LRBT — означает прорисовку границ со всех сторон ячейки (Left, Right, Bottom, Top). Можно также указать выравнивание в ячейке по правому краю ('R') */
//Рисуем ячейку наименования товара
$this->Cell($w[0],6,$row[0],'LRBT');
//Рисуем ячейку розничной цены
$this-> Cell($w[1],6,number_format($row[1]),'LRBT',0,'R');
//Рисуем ячейку оптовой цены
$this-> Cell($w[2],6,number_format($row[2]),'LRBT',0,'R');
//Переходим на следующую строку
$this->Ln();
}
//Closure line
$this->Cell(array_sum($w),0,'','T');
}
Вставим и этот метод в код класса Price. Теперь, когда мы обросли двумя новыми методами, попробуем их применить. Изменим код нашего главного файла (листинг 3).
Можно смотреть на промежуточный результат. Это практически все, чего мы хотели достичь.
Добавим только небольшую деталь — номера страниц с указанием их общего числа в нижней части каждой страницы. Ведь наш источник данных может состоять не из трех записей, а, например, из ста.
Для этого необходимо организовать вывод "подвала" (footer) на каждой странице. С этой целью напишем метод Footer() нашего класса Price. Следует отметить, что методы Footer() и Header() заранее прописаны в классе FPDF и вызываются автоматически при выполнении метода AddPage() и Close().
По умолчанию они пустые, но их можно определить в своих наследуемых классах, при этом ни Footer() ни Header() не потребуется явно вызывать из приложения. Добавим в класс Price() следующий код метода:
function Footer() {
//Позиционирование в 1.5 см от нижней границы
$this->SetY(-15);
//TimesNewRomanPSMT italic 8
$this->SetFont('TimesNewRomanPSMT','',8);
//Номер страницы
$this->Cell(0,10,'Страница '.$this->PageNo().'/{nb}',0,0,'C');
}
Листинг 3
<?php
define('FPDF_FONTPATH','font/');
require('fpdf.php');
require('price.class.php');
$header = array("Наименование товара","Розн. цена","Опт. цена");
$price=new Price();
$price->Open();
//Получим массив данных из файла в $data
$data = $price->LoadData("price.csv");
$price->AddFont('TimesNewRomanPSMT','','times.php');
$price->AddPage();
$price->SetFont('TimesNewRomanPSMT','',12);
$price->PrintTitle('Прайс-лист','logo.jpg','Компания "ALKO SELL"');
//Нарисуем таблицу. Аргументами метода являются массив наименований
// столбцов и массив данных из файла price.csv
$price->ImprovedTable($header,$data);
$price->Output();
?>
Теперь этот метод будет автоматически исполняться при вызове метода AddPage(). Для корректной работы необходим последний штрих — вызов из файла приложения (price.php) метода $price->AliasNbPages() перед $price->AddPage().
AliasNbPages([string alias])
При написании метода Footer() мы использовали также PageNo(), метод, возвращающий номер текущей страницы и параметр {nb}, который по умолчанию будет заменен цифрой общего количества страниц в текущем документе. Документ готов, и перед нами встает необходимость его вывода в браузер.
Вывод в браузер
По идее, существует всего два варианта вывода документа в браузер — открытие (если установлен adobe acrobat) и скачивание без непосредственного открытия. В классе FPDF выводом документа в браузер управляет метод Output(). В нашем примере мы использовали этот метод без дополнительных аргументов. Однако документация к FPDF приводит следующий формат его записи:
Output([string file [, boolean download]])
Метод предназначен для сохранения PDF документа в локальный файл или для непосредственного вывода в браузер (если установлена программа просмотра PDF файлов).
Аргумент file означает имя файла. Если таковое отсутствует, то производится попытка открыть документ в окне браузера. Если аргумент file определен, то аргумент download указывает, что файл должен быть сохранен на сервере (значение false) или у пользователя (при установке true выводится диалог "Сохранить как").
Соответственно, можно выделить три варианта написания метода Output() для нашего документа. Первый — Output() — пытается открыть документ в окне браузера. Второй вариант написания выведет у пользователя диалоговое окно "Сохранить как" и предложит скачать документ на его диск — Output("AlkoPrice.pdf", true). И, наконец, третий вариант просто сохранит документ на локальном для скрипта сервере — Output("AlkoPrice.pdf",false) или просто Output("AlkoPrice.pdf"), так как по умолчанию атрибут download всегда имеет значение false.
Стоит отдельно рассмотреть компрессию полученного файла. По умолчанию будет произведена попытка его "ужать" средствами Zlib. Это расширение (extension) должно быть установлено в системе. Если установка не была произведена, то документ получится не сжатым, и будет весить немного больше.
Для начала, пожалуй, все!