[Перевод] Генерация 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 означает более высокую позицию на странице.

Habr-4-1.png

Рис. 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 есть два способа указать размер страницы:

  1. Использование PaperType и Orientation, что установит размеры в свойстве Paper.

  2. Установка 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 можно увидеть результат этого кода и еще несколько примеров.

Habr-4_2.jpg

Рис. 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-документе — это двухэтапный процесс:

  1. Добавьте изображение в документ. Это приведет к получению числового идентификатора изображения.

  2. Нарисуйте изображение на странице, используя идентификатор, полученный на 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 можно увидеть результат работы этого кода.

Habr-4_3.jpg

Рис. 3. Отрисовка изображений.

9. Поддержка текста

Вероятно одна из самых сложных задач при работе с PDF — это написание текста на странице. Текст должен быть размещён с использованием шрифта. В стандарте PDF определены несколько встроенных шрифтов, которые будут эмулироваться, если они недоступны на устройстве, где просматривается PDF-документ.

Кроме того, PDF также поддерживает использование шрифтов TrueType (TTF). Если шрифт доступен на устройстве, где просматривается PDF-документ, его не нужно включать в файл. В противном случае, если шрифт недоступен, PDF не будет отображаться корректно. В таких случаях шрифт может быть встроен в сам PDF-документ.

Как и в случае с изображениями, написание текста на странице PDF-документа также состоит из двух этапов:

  1. Добавьте определение шрифта в документ. Это создаст уникальный числовой идентификатор шрифта.

  2. Напишите текст на странице, используя идентификатор шрифта, полученный на первом этапе.

Добавление шрифта можно выполнить с помощью метода 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 документов.

Habrahabr.ru прочитано 1921 раз