08 февраля 2021

Создание PDF файла в Windows и Linux

    Различные операционные системы все больше теснят MS Windows в технических заданиях. Часто все усугубляется пугающим в недалеком прошлом словом "кроссплатформенность". Многие задачи могут потребовать серьезной доработки для портирования под другую операционную систему. Повезет тем, кто изначально заложил в проект использование кроссплатформенных библиотек. Им портирование можно будет реализовать в несколько строк кода или добавлением директив компилятора. Одним из самых востребованных кроссплатформенных форматов электронных документов является формат PDF. Сегодня я рассмотрю несколько вариантов создания файлов в этом формате под Windows и Linux. Но они подойдут и для других операционных систем.
    Первый вариант – это использование библиотеки Foxit Quick PDF Library (бывшая Debenu Quick PDF Library). Foxit Quick PDF Library предоставляет разработчику полный спектр функций для создания и манипулирования файлами формата PDF под Windows, Mac, Linux, iOS и Android. Сразу скажу, что это удовольствие не из дешевых. Библиотека лицензируется отдельно под каждую операционную систему, и лицензия на одного разработчика стоит $499. Я не рассматриваю бесплатную Quick PDF Library Lite по двум причинам: во-первых, у нее нет версий под Linux и Android, а во-вторых, она не обновлялась с начала июня 2015-года и содержит давно устаревшие версии (11.14 под Windows и 11.15 под Mac/iOS). У Foxit Software есть еще Foxit PDF SDK, который существовал до покупки ими компании Debenu. Но его я тоже не буду рассматривать, так как на это тоже есть две причины: во-первых, у него нет версии под Delphi, а во-вторых, его цена на одного разработчика $3 000.
    Quick PDF Library for Windows включает в себя:
  • DCU для Delphi 4-10.4 (32/64 бита);
  • DLL для C, Visual C++, C++ Builder, C#, Delphi, Visual Basic, Python... (32/64 бита);
  • ActiveX для C#, Visual C++, C++Builder, Delphi, Visual Basic, PHP, Python, Java, PowerBASIC... (32/64 бита);
  • LIB для 32-битных C/C++.
Желающие могут за $999 купить исходный код Quick PDF Library Delphi Edition. Он предоставит возможность самим компилировать библиотеку под Windows. Судя по директивам компилятора в исходном коде возможно, еще и под Mac. Но это не точно. Я далек от техники Apple, поэтому не пробовал. Для меня было большим разочарованием то, что исходные коды библиотеки Delphi Edition не компилируются под Linux. Оказалось, что ее версии под другие операционные системы написаны на С++. Поэтому для использования Quick PDF Library под Linux нужно купить еще и Linux Developer License. То есть поддержка в проекте экспорта в файл PDF-формата под Windows и Linux обойдется в $998.
    Quick PDF Library for Linux поставляется в скомпилированном виде и поддерживает 64-разрядные версии Ubuntu, CentOS и Debian. Не повторяйте мою ошибку и не пытайтесь сами ее прикрутить к проекту на Delphi. Для экономии вашего времени сразу требуйте у разработчиков pascal'евский модуль с классом, который является оберткой для вызова функций библиотеки. Судя по комментарию в тексте, он "macOS Edition".
    Теперь приведу пример небольшой тестовой программы. Она создает простой PDF файл содержащий текст, написанный разными шрифтами, линию, картинку и гиперссылки.
program fqPdf;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  {$IFDEF MSWINDOWS}
  DebenuPDFLibrary
  {$ELSE}
  FoxitQPLDylib
  {$ENDIF};

const
  {$IFDEF MSWINDOWS}
  csKey = 'key for Windows';
  {$ELSE}
  csKey = 'key for Linux';
  {$ENDIF}

procedure Test;
var
  {$IFDEF MSWINDOWS}
  pdf: TDebenuPDFLibrary;
  {$ELSE}
  pdf: TFoxitQPLDylib1811;
  {$ENDIF}
  FontArial: Integer;
begin
  {$IFDEF MSWINDOWS}
  pdf := TDebenuPDFLibrary.Create;
  {$ELSE}
  pdf := TFoxitQPLDylib1811.Create('libFoxitQPL1811-linux-x64.so');
  {$ENDIF}
  try
    if pdf.UnlockKey(csKey) = 1
      then begin
        pdf.NewDocument;
        pdf.SetPageSize('A4');
        pdf.SetOrigin(1);

        // добавляем шрифт для дальнейшего использования
        FontArial := pdf.AddTrueTypeFont('Arial', 0);

        // зеленая линия
        pdf.SetLineWidth(0.86);
        pdf.SetLineColor(0, 0.5, 0);
        pdf.DrawLine(20, 10, 355, 10);

        // черный текст Arial 14
        pdf.SelectFont(FontArial);
        pdf.SetTextSize(14);
        pdf.SetTextColor(0, 0, 0);
        pdf.DrawText(95, 30, 'BlackCat');

        // синий подчеркнутый текст Times New Roman 15.5 курсив
//        pdf.AddTrueTypeFont('Times New Roman [Italic]', 0);
        pdf.AddStandardFont(10); // TimesItalic
        pdf.SetTextSize(15.5);
        pdf.SetTextColor(0, 0, 1);
        pdf.SetTextUnderline(1); // Single
        pdf.DrawText(155, 30, 'https://it-blackcat.blogspot.com');

        // добавляем изображение и делаем его ссылкой
        var imgID: Integer := pdf.AddImageFromFile('BlackCat.jpg', 0);
        pdf.SelectImage(imgID);
        pdf.DrawImage(20, 15, 70, 64);
        pdf.AddLinkToWeb(20, 15, 70, 64,
                         'https://it-blackcat.blogspot.com', 0);

        pdf.CompressContent;
        pdf.SaveToFile('test.pdf');
      end
      else WriteLn('The license key is invalid or has expired')
  finally
    pdf.Free;
  end;
end;

begin
  ReportMemoryLeaksOnShutdown := True;
  try
    Test;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
Как видите, несколько директив компилятора и программа стала кроссплатформенной. Для большего сходства с версией под Windows в FoxitQPLDylib я добавил класс TDebenuPDFLibrary, который унаследовал от TFoxitQPLDylib1811. Класс содержит только конструктор, который инициализирует библиотеку без указания ее имени. Что бы в будущем не менять модуль я переименовал libFoxitQPL1811-linux-x64.so в libFoxitQPL-linux-x64.so. Никогда не мог понять, зачем авторы Quick PDF Library всегда в название классов вставляют номер ее версии.
type
  TDebenuPDFLibrary = class (TFoxitQPLDylib1811)
    constructor Create;
  end;

implementation

constructor TDebenuPDFLibrary.Create;
begin
  inherited Create('libFoxitQPL-linux-x64.so');
end;
Теперь программе понадобилось меньше директив:
program fqPdf;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  {$IFDEF MSWINDOWS}
  DebenuPDFLibrary
  {$ELSE}
  FoxitQPLDylib
  {$ENDIF};

const
  {$IFDEF MSWINDOWS}
  csKey = 'key for Windows';
  {$ELSE}
  csKey = 'key for Linux';
  {$ENDIF}

procedure Test;
var
  pdf: TDebenuPDFLibrary;
  FontArial: Integer;
begin
  pdf := TDebenuPDFLibrary.Create;
  ...
Если сравнить результат работы программы под Linux
Quick PDF Library for Linux
и результат работы программы под Windows
Quick PDF Library for Windows
то вы не сможете найти различия. Ссылки на обеих картинках — это всплывающая подсказка в Acrobat Reader. Они оставлены на картинках для того, чтобы продемонстрировать, что программа создала ссылки.
    Второй вариант, для тех, у кого бюджет проекта не позволяет заложить $1000 на дополнительные библиотеки. Посмотрите внимательно на библиотеки, которые уже есть в вашем распоряжении. Например, мне пришла мысль воспользоваться имеющейся у меня библиотекой TMS FlexCel. Про TMS FlexCel тоже можно сказать "предоставляет разработчику полный спектр функций для создания и манипулирования файлами под Windows, Mac, Linux, iOS и Android". Только формат этих файлов не PDF, как нам надо, а MS Excel. Но библиотека TMS FlexCel включает в себя модуль для конвертации файлов из формата MS Excel в PDF. Нас не интересует его основной класс TFlexCelPdfExport, который на "высоком уровне" может преобразовывать файлы. Нас интересует класс TPdfWriter, который представляет собой API для создания документов в формате PDF. Как работать с TMS FlexCel под Linux я уже описывал раньше, поэтому сразу перейду к примеру программы:
program fcPdf;

{$APPTYPE CONSOLE}

uses
  System.Classes, System.SysUtils,
  {$IFDEF MSWINDOWS}
  VCL.FlexCel.Core
  {$ELSE}
  SKIA.FlexCel.Core
  {$ENDIF},
  FlexCel.Pdf;

procedure Test;
var
  pdf: TPdfWriter;
  fs: TFileStream;
  FontArial: TUIFont;
  BrushBlack: TUISolidBrush;
  Underline: TUITextDecoration;
  Brush: TUISolidBrush;
begin
  pdf := TPdfWriter.Create;
  try
    fs := TFileStream.Create('test.pdf', fmCreate);
    try
      // добавляем шрифт для дальнейшего использования
      FontArial := TUIFont.CreateNew('Arial', 14, []);
      try
        BrushBlack := TUISolidBrush.CreateNew(Colors.Black);
        try
          Underline := TUITextDecoration.Create(TUIUnderline.Single);
          pdf.Compress := True;
          pdf.YAxisGrowsDown := True;

          pdf.BeginDoc(fs);
          try
            // зеленая линия
            var pen: TUIPen := TUIPen.CreateNew(Colors.Green);
            try
              pdf.DrawLine(pen, 20, 10, 355, 10);
            finally
              pen.Free;
            end;
            // черный текст Arial 14
            pdf.DrawString('BlackCat', FontArial, BrushBlack, 95, 30);

            // синий подчеркнутый текст Times New Roman 15.5 курсив
            Brush:= TUISolidBrush.CreateNew(Colors.Blue);
            try
              var FontTimes: TUIFont := 
                  TUIFont.CreateNew('Times New Roman', 15.5, 
                                    [TUIFontStyle.fsItalic]);
              try
                pdf.DrawString('https://it-blackcat.blogspot.com', 
                               FontTimes, Underline, Brush, 155, 30);
              finally
                FontTimes.Free
              end;
            finally
              Brush.Free;
            end;

            // добавляем изображение и делаем его ссылкой
            pdf.Hyperlink(20, 15, 70, 64,
                          'https://it-blackcat.blogspot.com');
            var img: TUIImage := TUIImage.FromFile('BlackCat.jpg');
            try
              pdf.DrawImage(img, TUIRectangle.Create(20, 15, 70, 64), nil);
            finally
              img.Free;
            end;

          finally
            pdf.EndDoc;
          end;
        finally
          BrushBlack.Free
        end;
      finally
        FontArial.Free
      end;
    finally
      fs.Free
    end;
  finally
    pdf.Free
  end;
end;

begin
  ReportMemoryLeaksOnShutdown := True;
  try
    Test
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
Приведу одну картинку с результатами, т.к. они опять под Windows и под Linux совпадают между собой:
TMS FlexCel - TPdfWriter
Как вы видите результат работы TPdfWriter из TMS FlexCel очень похож на результат Quick PDF Library. Единственное, еле заметное отличие – это то, что у надписи курсивом местами немного не совпадают расстояния между буквами.
    Размер скомпилированной тестовой программы (в мегабайтах):
Библиотека Windows Linux
Программа Дополнительные файлы * Суммарно
Quick PDF Library 7.73 1.11 20.00 21.11
TMS FlexCel 3.17 8.01 5.41 13.42
* Дополнительные файлы: для Quick PDF Library – libFoxitQPL1811-linux-x64.so (сама библиотека), а для TMS FlexCel – libflexskia.so.4 (open-source библиотека Skia для работы с 2D-графикой).
    Функциональные возможности TPdfWriter из TMS FlexCel, по сравнению с Quick PDF Library, небольшие, но их вполне хватает для создания простых документов в формате PDF. Стоимость TMS FlexCel на сегодняшний день €175 или $264.52 (в пересчете сайта TMS). Таким образом, поддержка экспорта в формат PDF для Windows и Linux с ее использованием будет стоить в четыре раза дешевле, чем с использованием Foxit Quick PDF Library.
    Многие библиотеки имеют свои классы для экспорта в PDF формат. Изучая, в процессе написания этой статьи, сайт компании TMS Software, я нашел, что TMS VCL UI Pack (бывший TMS Component Pack) включат в себя библиотеку для создания документов в формате PDF – TAdvPDFLib. Я не уверен, что TAdvPDFLib можно скомпилировать под Linux, но в TMS FMX UI Pack есть кроссплатформенный TTMSFMXPDFLib. Таким образом не спешите покупать новые библиотеки, а хорошо изучите те, что уже имеете. Возможно их функций будет достаточно для вашей новой задачи.

9 комментариев:

  1. Для генерации PDF использую FastReport. Под Linux правда приходится использовать Lazarus, но возможности полноценного генератора отчётов перевешивают все сложности первоначального вхождения.

    ОтветитьУдалить
  2. Тоже пользуюсь FastReport для генерации PDF. Delphi - Windows.

    ОтветитьУдалить
  3. Про использование FastReport для генерации PDF, я тоже думал написать. У меня был такой опыт :) Кстати, я об этом писал ровно 10 лет тому назад - https://it-blackcat.blogspot.com/2011/02/fastreport-pdf.html
    Но не написал в этой статье по трем причинам:
    1. Я писал про Windows и Linux, а тут информация о Linux на сайте производителя противоречивая. На странице FastReport FMX 2 написано "Multi platform Report Generator for Apple Mac OS X and Microsoft Windows", хотя в анонсе FastReport FMX 2.8 написано "added FmxLinux support". Поддержка Linux добавлена меньше месяца тому назад, значит, как она работает еще не известно. К тому же черновик статьи я написал еще в прошлом году.
    2. FastReport зажат рамками шаблонов - это не подходило под мою задачу. Хотя я не изучал исходный текст экспорта FastReport, не исключено, что им можно воспользоваться, как TPdfWriter у FlexCel.
    3. В статье я пишу "не спешите покупать новые библиотеки, а хорошо изучите те, что уже имеете". Т.е. я рассмотрел то, что я имею и использую давно: Debenu Quick PDF Library и TMS FlexCel. Для исследования темы экспорта в PDF у меня нет FastReport FMX. Да и лицензия на FastReport VCL закончилась на какой-то из Delphi XE (все как-то не судьба опять поработать с FastReport).

    ОтветитьУдалить
    Ответы
    1. 1. FMX только Windows и OS X. Для чистого Linux либо VCL, либо Mono. Но в Mono особо смысла не вижу, так как под Linux вполне уже работает майкрософтовский .CORE 3.1.
      2. Шаблоны да, правда. Но в особо извращённых случаях их можно рисовать из кода "на лету". "Чистого" TPDFWrite'а тут конечно нет, но т.к. есть исходный код, всё в наших руках.
      3. Я ни в коем разе не призываю "все на FR" :), просто пользуюсь им очень давно (ещё с версии 2.0). Он покрывает 100% моих потребностей формирования форматированных страниц. И когда в нём появился экспорт в PDF это закрыло вообще все остававшиеся дыры.
      Последние годы, так сошлись звёзды, мне приходится активно работать с системой написанной на Perl (Debian). И "внезапно" там потребовался экспорт в PDF всевозможных документов, генерируемых системой. Потратив немного времени, скрестил ежа с ужом и теперь у меня есть FastReport под Perl ;). Это я к тому, что при желании FR можно прикрутить почти куда угодно.

      Удалить
  4. А бесплатный mORMot с задачей не справился бы?

    ОтветитьУдалить
    Ответы
    1. Может и справился бы. О mORMot и SynPDF я даже не знал :) Спасибо за информацию. Освятить вариант с бесплатной библиотекой я не подумал (я больше доверяю проприетарному софту).
      Объясню, чем обусловлен выбор библиотек в статье - я как акын "что вижу, о том и пою". Проект был реализован под Windows с использованием Quick PDF и FlexCel. При переводе его на Linux отсутствие Quick PDF Delphi Edition под Linux сильно опечалило заказчика. TPdfWriter у FlexCel - это как пример того, что прямо под носом есть доступные кроссплатформенные альтернативы Quick PDF, которые заточены под Delphi. В реальности же в том проекте уйти с Quick PDF малой кровью не получится. Quick PDF прикручен к TMS Scripter и при смене генератора PDF нужно кому то будет переписывать скрипты, которые генерируют PDF-файлы. Плюс используются функции, которых у TPdfWriter нет. Например, экспорт созданного PDF в картинку. Поэтому вариант "libFoxitQPL-linux-x64.so + TFoxitQPLDylib1811" - это выход.
      P.S. Если говорить не только об создании PDF, но и о его парсинге, то, как мне кажется, Quick PDF - это хороший (а может и лучший) выбор.

      Удалить
  5. Спасибо! Пользуюсь FlexCel много лет, но не думал, что его можно использовать для генерации PDF.

    ОтветитьУдалить
  6. Вы написали, что "предоставляет разработчику полный спектр функций ... и Android". На сайте https://www.quickpdflibrary.com/products/quickpdf/index.php нет "Android". В Delphi под Windows компилится, а для android требует Unit DebenuPDFLibrary1811, есть только *.dcu. Заработает с исходниками?

    ОтветитьУдалить
    Ответы
    1. Под Android собирать QuickPDF из исходников Delphi Edition нельзя. Foxit Quick PDF Library for Android - это библиотеки на С++ и юнита FoxitQPLAndroid1811.pas, которая содержит класс TFoxitQPLAndroid1811 с вызовами функций из библиотеки.

      Удалить