Для экспорта TDataSet или отчета в PDF в Delphi библиотека losLab PDF Library предлагает два пути. PDFlibTableExport адаптирует любой TDataSet — запрос FireDAC, ClientDataSet, таблицу в памяти — в постраничную таблицу PDF, а три моста в разделе Addons передают подготовленный отчет FastReport, QuickReport или ReportBuilder тому же обработчику PDF. Оба варианта создают настоящий PDF-документ без использования драйвера принтера и без отображения окон
Эти две задачи кажутся похожими, но это не так. Компонент DBGrid или необработанный результат запроса не имеют собственного макета, поэтому их экспорт означает создание структуры с нуля: колонок, заголовков и разрывов страниц. Документ FastReport или ReportBuilder уже содержит разработанный макет, поэтому его экспорт означает точное воспроизведение чужих команд рисования в пространстве PDF. Библиотека losLab PDF Library разделяет эти задачи по разным модулям именно потому, что их типы сбоев различаются, и в оставшейся части этой статьи мы подробно рассмотрим каждый сценарий и его ограничения
Как экспортировать TDataSet в PDF в Delphi?
PDFlibTableExport преобразует набор данных в таблицу за один вызов. Экспортер обходит список полей один раз, автоматически пропуская двоичные поля blob (ftBlob, ftGraphic, ftBytes и им подобные не содержат полезного текста ячеек), выравнивает числовые колонки по правому краю и накладывает дополнительную зебру поверх стилизованного заголовка. Под капотом он строит сетку с помощью CreateTable, заполняет ячейки с помощью SetTableCellContent и отрисовывает их с помощью DrawTableRows, используя то же публичное API таблиц, которое вы могли бы использовать вручную. Удобная обертка PDFlibExportDataSet настраивает миллиметры и начало координат в левом верхнем углу для вас, поэтому вызывающей стороне нужно предоставить только страницу
uses
Data.DB, FireDAC.Comp.Client, FireDAC.Stan.StorageBin,
PDFlibrary, PDFlibTableExport;
var
MemTable: TFDMemTable;
PDF: TPDFlib;
Options: TPDFlibTableExportOptions;
begin
MemTable := TFDMemTable.Create(nil);
PDF := TPDFlib.Create;
try
MemTable.LoadFromFile('customer.FDS'); // здесь подходит любой TDataSet
PDF.SetOrigin(1);
PDF.SetMeasurementUnits(1); // миллиметры
PDF.SetPageSize('A4');
PDF.AddStandardFont(4);
Options := DefaultTableExportOptions;
Options.Title := 'Customers';
Options.ColumnWidth := 32; // 11 полей помещаются на A4 при 32 мм
Options.RepeatHeader := True; // перерисовывать заголовок на каждой странице
PDFlibExportDataSet(PDF, MemTable, 'customers.pdf', Options);
finally
PDF.Free;
MemTable.Free;
end;
end;
Автоматический пропуск полей blob — это поведение по умолчанию, а не жесткое правило. Если вам нужен контроль для каждого поля (пользовательская метка колонки, меньшая ширина или исключение внутреннего ключевого поля, не являющегося blob), создайте TPDFlibTableExporter напрямую и подключитесь к его событию OnFieldFilter, которое срабатывает один раз для каждого поля и передает вам изменяемую спецификацию. Установите Include в значение False, чтобы исключить поле, или задайте ColumnWidth и DisplayLabel, чтобы переопределить настройки по умолчанию. Этот обработчик также является местом, где вы можете исключить широкую колонку с примечаниями (memo), чтобы она не раздувала страницу
Разбивка на страницы: передача состояния отрисовки между страницами
Метод DrawTableRows хранит состояние разбивки на страницы, и понимание его принципа работы — это основа всей задачи. Вы вызываете его с указанием первой и последней строки; передача последней строки меньше 1 означает отрисовку до конца таблицы, ограниченной указанной вами высотой. Функция возвращает высоту, которую она фактически отрисовала, а GetTableLastDrawnRow сообщает последнюю строку, которая поместилась. Эта пара значений является состоянием, которое вы передаете с одной страницы на другую: если последняя отрисованная строка меньше общего количества, вы открываете новую страницу и продолжаете со следующей за ней строки. Ничего не измеряется повторно — продолжение начинается ровно с того места, где завершился предыдущий вызов
// Как экспортер продолжает длинную таблицу на нескольких страницах
PageHeight := PDF.PageHeight;
Y := PageHeight - Options.Top;
Row := 1;
while Row <= TotalRows do
begin
DrawHeight := Y - Options.BottomMargin;
// Last row = 0 означает "рисовать до конца", ограниченного DrawHeight
PDF.DrawTableRows(TableID, Options.Left, Y, DrawHeight, Row, 0);
LastDrawn := PDF.GetTableLastDrawnRow(TableID);
if LastDrawn >= TotalRows then
Break; // вся таблица поместилась на этой странице
if LastDrawn < Row then
Break; // защита: нет движения вперед, выход
PDF.NewPage;
Row := LastDrawn + 1; // продолжить с первой неотрисованной строки
Y := PageHeight - Options.Top;
end;
В этом цикле также реализуется повторяющийся заголовок. На каждой последующей странице экспортер при необходимости снова рисует строку 1 (заголовок) перед строками данных, и он использует высоту, возвращенную первым вызовом DrawTableRows, чтобы сдвинуть тело таблицы ниже. Вот почему DrawTableRows возвращает высоту, а не координату следующей строки: возвращаемое значение позволяет разместить перерисованный заголовок и продолжающееся тело таблицы без жесткого кодирования их размеров
Как экспортировать отчет FastReport или ReportBuilder в PDF?
Мосты для отчетов используют противоположный подход: они никогда не создают макет заново, они воспроизводят его. Каждый мост подключается к своему генератору отчетов в стандартной точке экспорта. PDFlibFRExport является наследником TfrxCustomExportFilter и принимает объекты заметок, изображений, фигур и линий FastReport, преобразуя каждый из них в примитив PDFlibPas. PDFlibRBDevice наследуется от TppFileDevice и обходит список DrawCommand ReportBuilder. PDFlibQRExport использует совершенно другой, третий путь, который рассматривается в следующем разделе. Все три моста работают в автономном (headless) режиме, что является главной причиной их использования на сервере
Автономный режим имеет свои особенности, и FastReport здесь является ярким примером. Метод TfrxReport.Export проходит через страницы предварительного просмотра, которые проверяют свойство фильтра ShowDialog, имеющее по умолчанию значение True. На машине без интерактивного рабочего стола модальное диалоговое окно возвращает результат отмены, и экспорт тихо завершается ошибкой. Установите ShowDialog в значение False перед вызовом Export, и отчет срендерится без лишних диалогов. У ReportBuilder есть аналогичные переключатели — AllowPrintToFile and ShowPrintDialog, а QuickReport, который выполняет все операции через Prepare, вообще не требует подавления диалоговых окон
var
Exporter: TPDFlibFRExport;
begin
Report.PrepareReport; // сначала постройте страницы
Exporter := TPDFlibFRExport.Create(nil);
try
Exporter.FileName := 'invoice.pdf';
Exporter.ShowDialog := False; // автономный режим: пропустить модальный диалог, без отмены
Report.Export(Exporter);
finally
Exporter.Free;
end;
end;
Три движка, три системы координат
Каждый мост выполняет свои собственные вычисления координат, потому что каждый движок измеряет мир по-разному. PDFlibFRExport рассматривает положение объектов FastReport как пиксели при 96 PPI и масштабирует их на 96/25.4 для перевода в миллиметры. PDFlibRBDevice считывает команды рисования ReportBuilder в тысячных долях миллиметра и делит на 1000, а также регистрирует себя через ppRegisterDevice, поэтому установки ppReport.DeviceType в значение 'PDFlibPas' и вызова Print достаточно для перенаправления вывода через него. PDFlibQRExport вообще не производит вычислений координат: подготовленная страница QuickReport уже является метафайлом EMF, поэтому мост сохраняет каждую страницу в поток и передает его в ImportEMFFromStream, позволяя встроенному в библиотеку механизму рендеринга контекста устройства и метафайлов разместить каждый глиф и линию. Напротив, мосты FastReport и ReportBuilder генерируют собственные векторные графические примитивы и текст
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
PDF.SetOrigin(1);
// Подготавливает отчет и добавляет каждую страницу за один вызов; мост
// преобразует EMF каждой страницы в PDF через ImportEMFFromStream.
PDFlibQRExportReport(PDF, QuickRep1, 'ledger.pdf');
finally
PDF.Free;
end;
end;
Где заканчивается точность воспроизведения
Изучите ограничения до того, как внедрить эти мосты в рабочий процесс. Экспортер наборов данных автоматически пропускает двоичные колонки, поэтому отчету, который должен содержать встроенное изображение, требуется иной подход, нежели простой таблице. Метод QuickReport из-за использования EMF превращает текст в векторные глифы: страница выглядит правильно, но не содержит выделяемого или доступного для поиска текста, а также структуры tagged PDF для обеспечения доступности. FastReport и ReportBuilder сохраняют настоящий текст, но их типизированные обработчики поддерживают только основные элементы (memo, picture, shape, line) и возвращаются к ограничивающей рамке или пропускают более сложные объекты, поэтому отчет, использующий форматированный текст (rich text), штрихкоды или градиентные заливки, потеряет детализацию. Ничто из этого не является дефектом — это лишь естественные границы трансляционного слоя
Одно эксплуатационное предостережение важнее остальных. Ни один из трех движков не поставляется в составе библиотеки losLab PDF Library, и мосты намеренно исключены из основной сборки — они компилируются только в проектах, которые уже имеют FastReport VCL 6.x, QuickReport 8 или ReportBuilder 20 на пути поиска. Интерфейсы метафайлов и DrawCommand, на которые они ориентируются, остаются стабильными на протяжении нескольких мажорных версий движков, но соответствие версий лежит на вашей ответственности. Если это соблюдено, оба пути покрывают весь спектр задач: от ситуативного дампа DBGrid до спроектированного счета-фактуры. Экспортер таблиц и мосты отчетов поставляются вместе с библиотекой losLab PDF Library для Delphi и C++Builder