[Перевод] Генерация PDF документов в Lazarus IDE
Для разработки различных заглушек, используемых для тестирования сервиса, пока не готова ответная часть, я иногда использую отрисовку нужной информации на Canvas PaintBox`а, и последующую генерацию PDF с отрисованной картинкой (сохраняю BMP в поток, затем загрузка из потока для размещения изображения в PDF) и дополнением документа текстовой информацией. Для реализации такого подхода я использую модуль fpPDF, который, на мой взгляд, является достаточно простым и удобным. Для ознакомления с возможностями модуля ниже привожу вольный перевод статьи разработчика данного модуля — Michaël Van Canneyt Creating PDF files in Lazarus and Free Pascal.
Введение
В Lazarus уже давно есть компонент для создания PDF: PowerPDF. Он был перенесен из Delphi и используется в LazReport для создания PDF-файлов из отчетов. Однако у него есть несколько недостатков: он требует подключения LCL и графического интерфейса и не поддерживает встраивание шрифтов и шрифты юникода.
Чтобы исправить это, недавно во Free Pascal была добавлена новая реализация: fpPDF. Этот новый компонент имеет следующие особенности:
Чистый код на Object Pascal.
Отсутствие зависимостей от графического интерфейса, внешних библиотек или вызовов ОС.
Множество примитивов для рисования :- Изображения (любой формат, поддерживаемый FPC)
Линии (стили пера)
Круги
Прямоугольники (опционально закругленные)
Полилинии, т.е. рисование нескольких сегментов линии одной командой.
Многоугольники, с двумя режимами заливки: ненулевое правило намотки и четно-нечетное правило заливки.
Поддержка кривых Безье.
Полная поддержка TTF.
Поддержка Юникода.
Опциональное встраивание шрифтов (частичное встраивание находится в стадии разработки).
Поддержка преобразования координат (вращение, масштабирование и перевод).
Поддержка сжатия текста и изображений.
Встраивание HTML-ссылок
Поскольку этот компонент не требует графического интерфейса, он очень удобен для использования, например, в веб-приложениях, где графического интерфейса и нет. Так как компонент не имеет внешних зависимостей и самостоятельно обрабатывает шрифты, его можно использовать на любой платформе, которую поддерживают Free Pascal и Lazarus, включая различные вариации Raspberry Pi. Новый Lazarus lazReport PDF export уже был создан с помощью данного компонента.
Использовать этот компонент достаточно просто, что и будет продемонстрированно далее. Код достаточно прост, чтобы без особых усилий перенести компонент на Delphi, если это понадобится.
2. Установка компонента
В оригинале статьи описывается вариант установки компонента через скачивание и установку пакета lazpdf.lpk, но в последних версиях Lazarus fpPDF уже входит в дистрибутив IDE. Для использования компонента достаточно добавить в список используемых модулей fpPDF (uses fppdf;).
Основной класс из модуля pfPDF — это TPDFDocument, который имеет несколько опубликованных свойств:
LineStyles — коллекция стилей линий, которые можно использовать при рисовании. Стиль линии определяет цвет, толщину линии и стиль пера.
PageLayout — это свойство определяет, как программа просмотра PDF должна открывать файл: отображать одну страницу, две страницы или всё в непрерывном режиме.
Infos — это свойство содержит список свойств документа, которые можно задать (разработчик, автор и т. д.). Они будут отображаться в свойствах документа в средстве просмотра PDF-файлов.
DefaultPaperType — тип бумаги по умолчанию, используемый при создании новой страницы. По умолчанию установлено значение A4.
DefaultOrientation — тип ориентации по умолчанию для новой страницы альбомная или портретная.
DefaultUnitOfMeasure — это свойство определяет единицу измерения по умолчанию, используемую на страницах PDF-документа. Это может быть одно из следующих значений: дюймы, миллиметры (по умолчанию), сантиметры или пиксели (дополнительная информация по этому вопросу будет приведена ниже).
FontDirectory — папка расположения всех шрифтов, которые необходимо внедрить в документ.
Options — набор параметров, которые могут быть заданы и могут влиять на тип генерируемого PDF-кода. В настоящее время доступны следующие варианты опций:
poOutLine — создать структуру документа, которая будет отображаться в средстве просмотра PDF.
poCompressText — сжать все строки, записанные в документ.
poCompressFonts — сжать встроенные шрифты.
poCompressImages — сжать изображения, включенные в документ.
poUseRawJPEG — при добавлении изображения JPEG изображение включается как есть. По умолчанию читать изображение и преобразовывать его в собственный формат PDF.
poNoEmbeddedFonts — не встраивать шрифты в документ. По умолчанию все шрифты в документ встраиваются.
poPageOriginAtTop — перевернуть систему координат так, чтобы начало координат находилось в верхнем левом углу, а не в нижнем левом углу (подробнее о системе координат будет рассказано ниже).
3. Рисование: система координат
Стандарт PDF является производным от стандарта PostScript. Это означает, что начало холста рисования находится в нижнем левом углу страницы, как показано на рисунке 1. Большее значение координаты Y означает более высокую позицию на странице.
Рис. 1. Система координат PDF
Это отличается от системы координат, используемой в Canvas приложений LCL или VCL, где начало координат находится в верхнем левом углу, и где более высокие значения координаты Y означают более низкие значения на экране. Систему координат можно сделать совместимой со значением LCL или VCL, указав опцию poPageOriginAtTop в наборе параметров документа. После чего новая страница будет с новой системой координат, начинающейся в верхнем левом углу. Система координат текущей страницы не будет изменена.
Система координат использует значение с плавающей точкой и использует 2 цифры для точности. Она может использовать различные единицы, которые можно задать для каждой страницы отдельно в свойстве UnitOfMeasure для объекта страницы. Опция имеет одно из следующих значений:
uomInches — 1 дюйм равен 25,4 миллиметра.
uomMillimeters — миллиметры, единица по умолчанию.
uomCentimeters — сантиметры.
uomPixels — пиксели. Количество пикселей на дюйм рассчитывается с использованием DPI равным 72.
Единицу измерения по умолчанию можно указать на уровне документа. Все команды рисования сначала преобразуют координаты с помощью матрицы преобразования. Когда команда рисования имеет координаты (X, Y), то X и Y масштабируются и преобразуются следующим образом:
X := XScale * X + XTranslation;
Y := YScale * Y + YTranslation;
Обратите внимание, что это не позволяет выполнять поворот, для которого требуется немного более сложная матрица. Вместо этого поворот должен быть указан отдельно при рисовании объектов.
4. Рисование: начало работы
Прежде чем начать рисовать, необходимо вызвать метод StartDocument
, который выполнит несколько вспомогательных задач и подготовит всё к процессу рисования. После этого в документ необходимо добавить раздел. Раздел необходим для создания структуры документа: каждый раздел может содержать одну или несколько страниц. Допустимо иметь только один раздел в документе, содержащий все страницы.
TPDFDocument является «странично-ориентированным» компонентом. Это означает, что перед тем, как приступить к рисованию, необходимо инициировать запуск страницы. Страницу запускают вызовом метода Pages.AddPage
, который возвращает объект TPDFPage
. Затем эту страницу необходимо добавить в раздел.
Пример кода, который начинает документ и добавляет в него раздел и страницу:
var
D: TPDFDocument;
S: TPDFSection;
P :TPDFPage;
begin
D:=TPDFDocument.Create(Nil); // Создание документа
D.StartDocument; // Начало работы с документом
S:=D.Sections.AddSection; // Добавление раздела. Помните, необходим хотя бы один раздел!
P:=P.Pages.AddPage;
S.AddPage(P);// Добавление страницы в раздел
// Установка свойств страницы:
P.PaperType := ptA4;
P.UnitOfMeasure := uomMillimeters;
end;
В TPDFPage есть два способа указать размер страницы:
Использование
PaperType
иOrientation
, что установит размеры в свойствеPaper
.Установка
PaperType
в значениеptCustom
и явная установка размеров в свойствеPaper
.
Свойство Paper
содержит размер бумаги в точках PDF (пикселях). Чтобы преобразовать размеры PDF в миллиметры, сантиметры или дюймы, можно использовать процедуры преобразования mmToPDF
, PDFToMM
и т.д. названия процедур говорят сами за себя.
5. Рисование: цвета, ширина линий и стили
Команды рисования используют определенный цвет, ширину линии и стиль пера. Их можно задать на объекте TPDFPage с помощью очевидных методов:
procedure SetColor(AColor : TARGBColor; AStroke : Boolean = True);
procedure SetPenStyle(AStyle : TPDFPenStyle; ALineWidth: TPDFFloat = 1.0);
Как только одно из этих свойств установлено, оно остается действительным для всех последующих команд рисования.
Цвета, используемые в документе PDF, указываются с использованием нотации RGB. Модуль fpPDF содержит некоторые константы для общих цветов. При установке цвета необходимо указать, является ли это цветом обводки (или рисования линии) (AStroke=True
) или цветом заливки (AStroke=False
). Стиль пера определяет, как будут нарисованы линии, и может быть одним из следующих:
TPDFPenStyle = (ppsSolid,ppsDash,ppsDot,ppsDashDot,ppsDashDotDot);
Значение этих вариантов прямо отражено в названии. Чтобы упростить управление цветами и стилями линий, компонент TPDFDocument может поддерживать набор стилей линий в свойстве коллекции LineStyles. Каждый элемент в коллекции определяет цвет, ширину и стиль пера.
Для установки цвета, ширины и стиля пера в одной команде, можно использовать следующие методы:
procedure SetLineStyle(AIndex: Integer; AStroke: Boolean = True);
procedure SetLineStyle(S: TPDFLineStyleDef; AStroke: Boolean = True);
Стиль можно задать используя AIndex
элемента в свойстве документа LineStyles
, или элемент можно указать напрямую.
6. Рисование: команды рисования линий
Класс TPDFPage во многом похож на класс TCanvas в LCL или VCL: он предлагает множество команд для фактического рисования чего-либо на странице. Существует 4 команды рисования линий:
procedure DrawLine(X1, Y1, X2, Y2, ALineWidth : TPDFFloat;
const AStroke: Boolean = True);
procedure DrawLine(APos1, APos2: TPDFCoord; ALineWidth: TPDFFloat;
const AStroke: Boolean = True);
procedure DrawLineStyle(X1, Y1, X2, Y2: TPDFFloat; AStyle: Integer);
procedure DrawLineStyle(APos1, APos2: TPDFCoord; AStyle: Integer);
TPDFCoord — это запись, описывающая положение X и Y с использованием типа TPDFFloat. Все команды предоставляются в двух перегруженных формах: одна с явными координатами X и Y, другая с координатами, указанными через структуру TPDFCoord. В этом документе мы представим только первую форму.
Команда DrawLine нарисует линию в текущем цвете, стиле и ширине линии. Команда DrawLineStyle нарисует линию в цвете, стиле и ширине линии, заданных в указанном элементе свойства LineStyles документа TPDFDocument. Обратите внимание, что стиль останется установленным для последующих команд.
Опция AStroke, установленная в значение True, указывает PDF-движку на необходимость создания линии. Если необходимо нарисовать много сегментов линий, то линии могут быть нарисованы не кусками (каждая линия по отдельности), а как бы целиком (это называется рисованием пути), в этом случае, когда последняя линия будет задано, можно выполнить отрисовку контура (будет фактически нарисован заданный путь).
Пример кода, который рисует прямоугольник на странице:
procedure TMainForm.GenerateLines(P : TPDFPage);
var
W,M,T,R : TPDFFloat;
begin
W:=1.0; // Ширина страницы
M:=10; // Поле
T:=PDFToMM(P.Paper.H)-M; // Верх
R:=PDFToMM(P.Paper.W)-M; // Право
// Поля страницы
P.DrawLine(M,M,M,T,W,True);
P.DrawLine(M,M,R,M,W,True);
P.DrawLine(M,T,R,T,W,True);
P.DrawLine(R,M,R,T,W,True);
end;
Четыре линии рисуются последовательно и генерируются одновременно. Обратите внимание, что линии не рисуются в логическом порядке: слева, снизу, сверху, справа. Если проводить аналогию, то при рисовании этого прямоугольника вручную на бумаге, нужно каждый раз поднимать руку, чтобы перейти к начальной позиции следующей линии.
Код второго примера достигает того же результата, как и приведенный выше, но рисует линии в логическом порядке: слева с низу — наверх, далее направо, далее вниз и затем назад налево. Рисуя этот прямоугольник от руки на бумаге, вы как будто рисуете 4 сегмента одним плавным движением (одной непрерывной линией).
Второй пример кода, рисующий прямоугольник на странице:
procedure TMainForm.GenerateLinesNoStroke(P : TPDFPage);
var
W,M,T,R : TPDFFloat;
begin
W:=1.0; // Ширина страницы
M:=10; // Поле
T:=PDFToMM(P.Paper.H)-M; // Верх
R:=PDFToMM(P.Paper.W)-M; // Право
// Поля страницы
P.MoveTo(M,M);
P.DrawLine(M,M,M,T,W,False);
P.DrawLine(M,T,R,T,W,False);
P.DrawLine(R,T,R,M,W,False);
P.DrawLine(R,M,M,M,W,False);
P.StrokePath;
end;
Первая координата DrawLine игнорируется, когда параметр Stroke равен False, рисование начинается с текущей позиции. Из-за этого необходима команда MoveTo, которая задает начальную позицию пера. Последняя команда StrokePath сообщает движку, что рисование завершено, и что линию можно уже нарисовать.
Для одновременного создания нескольких линий можно использовать команду DrawPolyLine:
procedure DrawPolyLine(const APoints: array of TPDFCoord;const ALineWidth: TPDFFloat);
Процедура принимает массив точек и рисует линии между N
и N+1
-й точками. Используя эту команду, прямоугольник страницы теперь можно нарисовать с помощью следующего кода:
Пример кода для отрисовки прямоугольника:
procedure TMainForm.GeneratePolyLines(P : TPDFPage);
var
W,M,T,R : TPDFFloat;
A : Array[1..5] of TPDFCoord;
begin
W:=1.0; // Ширина страницы
M:=10; // Поле
T:=PDFToMM(P.Paper.H)-M; // Верх
R:=PDFToMM(P.Paper.W)-M; // Право
// Поля страницы
A[1].X:=M; A[1].Y:=M;
A[2].X:=M; A[2].Y:=T;
A[3].X:=R; A[3].Y:=T;
A[4].X:=R; A[4].Y:=M;
A[5].X:=M; A[5].Y:=M;
P.DrawPolyLine(A,1);
P.StrokePath;
end;
Помните о необходимости вызова команды StrokePath, поскольку команда DrawPolyline сама не будет рисовать путь. Хотя в нарисованном прямоугольнике углов всего четыре, но массиву нужно указать 5 координат: последняя позиция в массиве — это координаты первой точки. Для замкнутых фигур, можно использовать функцию DrawPolygon: она автоматически замкнет линию, нарисовав сегмент от последней до первой точки.
procedure DrawPolygon(const APoints: array of TPDFCoord; const ALineWidth: TPDFFloat);
Опять же помните о том, что StrokePath должен быть вызван после вызова процедуры DrawPolygon. При использовании DrawPolygon (или при рисовании любого замкнутого контура) внутренняя часть нарисованного контура может быть заштрихована. Это можно сделать с помощью двух команд, каждая из которых использует разный алгоритм:
Подробные описания этих правил можно прочитать, по ссылкам на страницы в Википедии. Сами правила могут давать разные результаты, в зависимости от сложности пути контура.
Цвет заливки должен быть установлен с помощью SetColor, с параметром AStroke, установленным в значении False.
Пример кода, который нарисует прямоугольник страницы и заполнит его желтым цветом:
procedure TMainForm.GeneratePolygonsFill(P: TPDFPage);
var
W,M,T,R : TPDFFloat;
L : Integer;
A : Array[1..4] of TPDFCoord;
begin
W:=1.0; // Ширина страницы
M:=10; // Поле
T:=PDFToMM(P.Paper.H)-M; // Верх
R:=PDFToMM(P.Paper.W)-M; // Право
// Поля страницы
A[1].X:=M; A[1].Y:=M;
A[2].X:=M; A[2].Y:=T;
A[3].X:=R; A[3].Y:=T;
A[4].X:=R; A[4].Y:=M;
P.DrawPolygon(A,1);
P.SetColor(clYellow,False);
P.FillStrokePath;
end;
7. Рисование: фигуры — прямоугольники, круги и эллипсы.
Прямоугольник можно нарисовать с помощью полилинии, но это достаточно трудоемко. PDF определяет команду прямоугольника, что упрощает его рисование (и, при необходимости, заливку). Для команды нужны координаты нижнего левого угла, а также ширина с высотой.
Дополнительно можно указать заливку и обводку, а также поворот. Имейте ввиду, что поворот (в градусах) выполняется не вокруг центра прямоугольника, а вокруг его нижнего, левого угла прямоугольника.
Рисование эллипса (или круга, который является просто эллипсом с равными радиусами) выполняется почти так же, как и прямоугольника: указывается ограничивающий прямоугольник эллипса, и эллипс будет нарисован внутри прямоугольника.
Ниже приведены определения различных команд рисования:
procedure DrawRect(const X, Y, W, H, ALineWidth: TPDFFloat; const AFill,
AStroke : Boolean; const ADegrees: single = 0.0);
procedure DrawRoundedRect(const X, Y, W, H, ARadius, ALineWidth: TPDFFloat;
const AFill, AStroke : Boolean; const ADegrees: single = 0.0);
procedure DrawEllipse(const APosX, APosY, AWidth, AHeight, ALineWidth: TPDFFloat;
const AFill: Boolean = True; AStroke: Boolean = True;
const ADegrees: single = 0.0);
Приведенный ниже код нарисует 2 эллипса с одинаковым (нижним, левым) углом, но второй будет повернут на 45 градусов. Для каждого эллипса будут нарисованы ограничивающий прямоугольник вокруг него.
Пример кода, рисующего эллипсы:
procedure TMainForm.GenerateEllipses(P: TPDFPage);
var
W,M,T,R : TPDFFloat;
L : Integer;
CX,CY : TPDFFloat;
begin
W:=1.0; // Ширина страницы
M:=10; // Поле
T:=PDFToMM(P.Paper.H)-M; // Верх
R:=PDFToMM(P.Paper.W)-M; // Право
CX:=R/4;
CY:=T/2;
P.DrawEllipse(CX-10,CY-10,40,20,W,False,True);
// Вращение вокруг нижнего левого угла
P.SetColor(clRed);
P.DrawEllipse(CX-10,CY-10,40,20,W,False,True,45);
P.SetColor(clDkGray);
P.SetPenStyle(ppsDash);
P.DrawRect(CX-10,CY-10,40,20,W,False,True);
P.DrawRect(CX-10,CY-10,40,20,W,False,True,45);
end;
Ниже на рисунке 2 можно увидеть результат этого кода и еще несколько примеров.
Рис. 2. Примеры рисования примитивов.
В спецификации PDF на самом деле нет команд для рисования круга или эллипса. Вместо этого рисуются кривые Безье. Круги и эллипсы на самом деле рисуются с аппроксимацией с использованием кривых Безье. В спецификации PDF есть 3 команды рисования кривых Безье (C, V и Y), они отличаются только тем, как указываются контрольные точки для кривой.
Форма C явно указывает 2 контрольные точки.
procedure CubicCurveTo(ACtrl1, ACtrl2, ATo: TPDFCoord; const ALineWidth: TPDFFloat;
AStroke: Boolean = True); overload;
Для формы V первая контрольная точка совпадает с началом кривой, для формы Y вторая контрольная точка совпадает с конечной точкой кривой.
procedure CubicCurveToV(ACtrl2, ATo: TPDFCoord; const ALineWidth: TPDFFloat;
AStroke: Boolean = True); overload;
procedure CubicCurveToY(ACtrl1, ATo: TPDFCoord; const ALineWidth: TPDFFloat;
AStroke: Boolean = True); overload;
Во всех случаях первая точка кривой Безье является текущим положением.
8. Изображения
Возможности PDF-движка не будут полностью понятны без описания работы с изображениями. Рисование изображения в PDF-документе — это двухэтапный процесс:
Добавьте изображение в документ. Это приведет к получению числового идентификатора изображения.
Нарисуйте изображение на странице, используя идентификатор, полученный на gthdjv шаге.
PDF-документ поддерживает список изображений в свойстве Images. Эта коллекция хранит список изображений. А само изображение можно добавить одним из следующих способов:
function AddJPEGStream(Const AStream : TStream; Width,Height : Integer): Integer;
function AddFromStream(Const AStream : TStream; Handler : TFPCustomImageReaderClass;
KeepImage : Boolean = False): Integer;
function AddFromFile(Const AFileName : String;
KeepImage : Boolean = False): Integer
Имена методов говорят сами за себя. Вы можете добавить в документ любой формат изображения, который поддерживает Free Pascal; компонент выполнит все необходимые преобразования. Поскольку PDF изначально поддерживает сжатые потоки JPEG, вы можете добавить необработанные файлы JPEG в документ без изменений. После добавления изображения в список, вы сможете использовать его одним из двух методов:
procedure DrawImageRawSize(const X, Y: TPDFFloat;
const APixelWidth, APixelHeight,
ANumber: integer;
const ADegrees: single = 0.0);
procedure DrawImage(const X, Y: TPDFFloat;
const AWidth, AHeight: TPDFFloat;
const ANumber: integer;
const ADegrees: single = 0.0);
Первый метод (DrawImageRawSize) рисует изображения, используя их размеры в пикселях, а метод DrawImage — в текущих единицах страницы. Как и все остальные элементы, изображение можно вращать. Приведенный ниже код рисует одно и то же изображение четыре раза на странице, по одному в каждом углу, поворачивая его для каждого угла. Изображения в нижнем правом и верхнем левом углах изменяются в размере, причем нижнее правое изображение сохраняет соотношение сторон.
Пример кода вставки изображений:
procedure TMainForm.GenerateImages(P: TPDFPage);
var
M,T,R : TPDFFloat;
I : Integer;
W,H : TPDFFloat;
FN : String;
begin
M:=10; // Поля
T:=PDFToMM(P.Paper.H)-M; // Верхняя граница
R:=PDFToMM(P.Paper.W)-M; // Правая граница
DrawPageMargin(P);
FN:=ExtractFilePath(ParamStr(0))+'poppy.jpg';
I:=D.Images.AddFromFile(FN,False);
W:=PDFtoMM(D.Images[i].Width);
H:=PDFtoMM(D.Images[i].Height);
// Нижний левый угол
P.DrawImage(2*M,2*M,W,H,I);
// Верхний правый угол, поворот на 180 градусов
P.DrawImage(R-M,T-M,W,H,I,180);
// Нижний правый угол, сохранение пропорций.
P.DrawImage(R-M,2*M,4*M,4*M * H/W,I,90);
// Верхний левый угол, поворот на 270 градусов. Искажение.
P.DrawImage(M,T-M,4*M,4*M,I,270);
end;
Ниже на рисунке 3 можно увидеть результат работы этого кода.
Рис. 3. Отрисовка изображений.
9. Поддержка текста
Вероятно одна из самых сложных задач при работе с PDF — это написание текста на странице. Текст должен быть размещён с использованием шрифта. В стандарте PDF определены несколько встроенных шрифтов, которые будут эмулироваться, если они недоступны на устройстве, где просматривается PDF-документ.
Кроме того, PDF также поддерживает использование шрифтов TrueType (TTF). Если шрифт доступен на устройстве, где просматривается PDF-документ, его не нужно включать в файл. В противном случае, если шрифт недоступен, PDF не будет отображаться корректно. В таких случаях шрифт может быть встроен в сам PDF-документ.
Как и в случае с изображениями, написание текста на странице PDF-документа также состоит из двух этапов:
Добавьте определение шрифта в документ. Это создаст уникальный числовой идентификатор шрифта.
Напишите текст на странице, используя идентификатор шрифта, полученный на первом этапе.
Добавление шрифта можно выполнить с помощью метода AddFont:
function AddFont(AName : String) : Integer;
function AddFont(AFontFile: String; AName : String) : Integer;
Первый вариант функции добавляет определение шрифта в документ и предназначена для стандартных шрифтов. Второй вариант функции добавляет файл шрифта в список доступных шрифтов, метрики которых будут считываться из файла шрифта. Чтобы не указывать полный путь к шрифту, имя файла может быть задано относительно каталога шрифта, который указывается в свойстве FontDirectory.
Когда приходит время фактически отобразить текст на экране, шрифт должен быть задан с использованием его индекса в списке шрифтов и указан размер шрифта (в пунктах):
procedure SetFont(AFontIndex : Integer; AFontSize : Integer);
А затем текст можно нарисовать с помощью WriteText:
procedure WriteText(X,Y: TPDFFloat; AText: UTF8String;
const ADegrees: single = 0.0);
Текст будет размещён по координатам X, Y, при этом базовая линия текста совпадает с координатой Y. Это означает, что текст может простираться как выше, так и ниже координаты Y. Текст может быть в формате Unicode с кодировкой UTF-8. Как и все другие команды рисования, текст можно вращать. Он рисуется с использованием цвета заливки.
Пример, который рисует текст в четырёх углах страницы:
procedure TMainForm.GenerateText(P: TPDFPage);
var
M,T,R : TPDFFloat;
I : Integer;
begin
M:=10; // Поля
T:=PDFToMM(P.Paper.H)-M; // Верх
R:=PDFToMM(P.Paper.W)-M; // Право
I:=D.AddFont('Helvetica');
// Установить шрифт, Helvetica, 12 пт
P.SetFont(I,12);
// Нижний левый угол
P.WriteText(2*M,2*M,'(Bottom,left)');
// Верхний левый угол, повернутый на 270 градусов
P.WriteText(2*M,T-M,'(Top,Left)',270);
// Верхний правый угол, повернутый на 180 градусов
P.WriteText(R-M,T-M,'(Top,Right)',180);
// Нижний правый угол, повернутый на 90 градусов
P.WriteText(R-M,2*M,'(Bottom,Right)',90);
end;
10. Больше текста и HTML
Код рисования текста, приведенный выше, обошелся без вычислений ширины и высоты, текст был просто выведен в определённой точке. Это не всегда возможно, в случае вывода большого текста необходимо знать его ширину (например, для переноса слов) и высоту, чтобы определить, где разместить следующую строку текста.
Часто текст помещают в прямоугольник (рамку) для его выделения. Чтобы нарисовать рамку вокруг текста, необходимо рассчитать его ширину и высоту.
Кроме того, PDF поддерживает встраивание гиперссылок. Это реализуется путём обозначения прямоугольника на странице как активной области. Это можно сделать с помощью метода AddExternalLink класса TPDFPage:
procedure AddExternalLink(const APosX, APosY, AWidth, AHeight: TPDFFloat;
const AURI: string;
ABorder: boolean = false);
Чтобы пометить фрагмент текста как гиперссылку, сначала нужно узнать объем текста. Для того, чтобы иметь возможность вычислять размеры текста и встраивать шрифты, в модуле fpttf реализован менеджер шрифтов. Менеджер шрифтов будет читать файл шрифта и искать метрики шрифта, т. е. размеры различных символов в шрифте. Используя эти метрики, менеджер шрифтов может затем вычислить высоту и ширину текста. Эти метрики также необходимы при встраивании определения шрифта в PDF.
Менеджер шрифтов должен быть инициализирован. Самый простой способ — использовать метод BuildFontCache, который создаст кэш шрифтов, найденных в одном или нескольких каталогах. Для примера программы мы предположим, что все шрифты находятся в каталоге с именем fonts
:
procedure TMainForm.FormCreate(Sender: TObject);
var
FontDir : String;
begin
FontDir:=ExtractFilePath(ParamStr(0))+’fonts’;
D.FontDirectory:=FontDir;
gTTFontCache.SearchPath.Add(FontDir);
gTTFontCache.BuildFontCache;
end;
После инициализации менеджера шрифтов, для вычисления ширины и высоты заданного текста (в пикселях) можно использовать методы TextWidth и TextHeight:
function TextWidth(const AStr: utf8string; const APointSize: single): single;
function TextHeight(const AText: utf8string; const APointSize: single;
out ADescender: single): single;
Высота текста — это расстояние от базовой линии до верхней границы текста, а выносной элемент — это количество пикселей, на которое текст выступает ниже базовой линии. Количество пикселей определяется настройкой DPI в кэше шрифта (по умолчанию 72).
Собрав все это вместе, мы теперь можем разместить текст на странице, нарисовать вокруг него прямоугольник и пометить его как гиперссылку.
Пример кода для добавления текста и гиперссылки на страницу:
procedure TMainForm.GenerateHTML(P : TPDFPage);
const
aText1 = 'Hello, Free Pascal!';
aURL = 'http://www.freepascal.org/';
FontName = 'FreeSans';
FontSize = 12;
var
DH,H,W,M,T,AY,AX : TPDFFloat;
I : integer;
lFC: TFPFontCacheItem;
begin
M:=10; // Поля
T:=PDFToMM(P.Paper.H)-M; // Верх
// Добавить шрифт в документ.
I:=D.AddFont('FreeSans.ttf', FontName);
P.SetFont(I, FontSize);
P.SetColor(clBlack, false);
AX:=2*M;
AY:=T-T/4-M;
P.WriteText(AX,AY, aText1);
// Найти метрики шрифта:
lFC := gTTFontCache.Find(FontName, False, False);
// Рассчитать высоту в пикселях согласно DPI в кэше шрифта
H:=lFC.TextHeight(aText1, FontSize, DH);
W := lFC.TextWidth(aText1, FontSize);
// Преобразовать в мм
H:=(H * cInchToMM)/gTTFontCache.DPI ;
DH:=(DH * cInchToMM)/gTTFontCache.DPI ;
W:=(W * cInchToMM)/gTTFontCache.DPI;
// Нарисовать рамку вокруг текста
P.SetColor(cldkGray, true);
P.DrawRect(AX,AY-DH,W,H+DH,1,false,true);
// Теперь создать гиперссылку на этом месте:
P.AddExternalLink(AX,AY-DH,W,H+DH,aURL,False);
end;
11. Заключение
Любой, кто умеет рисовать что-либо в LCL или VCL, может создать базовый PDF. Создание PDF очень похоже на рисование на холсте. Методы немного отличаются, но идеи остаются практически одинаковыми. В настоящее время PDF API Free Pascal не поддерживает расширения PDF, такие как формы, видео и звук, но предоставляет все необходимые методы для создания базовых PDF документов.