Короткий ответ на тот тикет поддержки — да, с оговорками. HotPDF 2.730.0 собирается на Free Pascal 3.2.2 и Lazarus 4.6 для Win64, и базовые пути создания, загрузки и сохранения работают. Что не следует автоматически — всё, что опирается на статически слинкованный нативный кодек-объект или на анонимные методы Delphi
Вопрос обычно приходит одинаково: команда стандартизуется на Lazarus для кроссплатформенного инструмента или наследует кодовую базу Free Pascal и хочет тот же PDF-компонент, который уже лицензирован для Delphi. Перенос зрелой библиотеки Delphi редко сводится к синтаксису. Интересна часть, которую перенос обнажает: где библиотека тихо срослась с одним тулчейном. Здесь сращение сидит в двух вполне конкретных местах — ABI объектных файлов кодеков из комплекта и особенности компилятора, прячущиеся за символом версии
Что нужно Free Pascal 3.2.2, прежде чем HPDFDoc скомпилируется
HotPDF компилируется под Free Pascal только в режиме Delphi и только когда каталоги модулей LCL Lazarus лежат на пути поиска. И то и другое не обсуждается. HotPDF.inc переключает компилятор директивами {$MODE DELPHI} и {$H+} внутри блока {$IFDEF FPC} и отказывает всему более старому через {$FATAL}, когда FPC_FULLVERSION ниже 30202, поэтому установка 3.0.x падает громко, а не выдаёт сломанный модуль. Пакет времени выполнения Lazarus HotPDFLaz.lpk кодирует остальное: LCL как обязательный пакет и -Mdelphi как пользовательскую опцию
Требование LCL удивляет тех, кому нужен лишь консольный вывод, но оно структурное. HPDFFPCCompat поставляет типы Delphi VCL, для которых у Free Pascal нет эквивалента, отображая TMetafile и TMetafileCanvas на классы битмапов и канвасов LCL и алиася TRichEdit на TMemo, а HPDFDoc алиасит TPNGObject на Graphics.TPortableNetworkGraphic. Считайте их шимами времени компиляции, а не паритетом возможностей: класс метафайла на базе битмапа держит модуль компилируемым, но не заставляет пути метафайла вести себя как на Delphi. Даже не-GUI smoke test подтягивает Interfaces, а скрипт сборки передаёт -Fu для lcl\units\x86_64-win64 и выходной каталог lazutils
Почему D2009+ не годится на роль врата версии
Соблазнительно считать сборку Free Pascal современным компилятором и просто определить самый свежий символ возможностей Delphi. HotPDF этого не делает, и причину стоит сформулировать прямо: D2009+ означает не только юникодные строки, он ещё и врата для модулей, чей публичный API выражен анонимными методами. Free Pascal 3.2.2 не поддерживает ни анонимные методы Delphi, ни эти API, так что заимствование символа втянуло бы код, который не может скомпилироваться. Поэтому секция uses HPDFDoc несёт два отдельных условных хвоста, и перекрытие между ними намеренно, а не случайно
uses
// ...
HPDFJavaScript,
HPDFFormCalcGraph
{$IFDEF FPC}
, HPDFFPCCodecStubs,
HPDFCMS,
HPDFWinCertSigner
{$ENDIF}
{$IFDEF D2009+}
, HPDFXFARuntime,
HPDFCMS,
HPDFWinCertSigner,
HPDFSignVerify,
HPDFSignatureBatch
{$ENDIF};
Почему нативные кодеки останавливаются на линкере?
Потому что это Win64 COFF-объекты, выпущенные одним конкретным тулчейном, и ни один линкер Free Pascal на Win64 их не примет: ни внутренний линкер, ни внешний путь GNU ld. Это проблема ABI объектных файлов, а не проблема Pascal, и условная компиляция исходников её не чинит в каком угодно объёме. Библиотека идёт единственным честным доступным маршрутом. Каждая директива {$L}, подтягивающая статический кодек-объект, обёрнута в {$IFNDEF FPC}, поэтому сборка Free Pascal их просто опускает, а HPDFFPCCodecStubs затем поставляет каждый недостающий внешний символ заглушкой, которая выбрасывает исключение вместо возврата
// HPDFFPCCodecStubs.pas
function HPDFFPCNativeCodecUnavailable: PtrUInt;
begin
raise ENotSupportedException.Create(
'This native codec is not available in the Free Pascal build');
end;
function HPDFFPCStub_deflate: PtrUInt; cdecl;
public name 'deflate';
begin
Result := HPDFFPCNativeCodecUnavailable;
end;
Таблица заглушек длинна, и её чтение точно говорит, какие возможности сегодня существуют только для Delphi: точки входа deflate zlib-ng и zopfli, сжатие и распаковка libjpeg, кодек JPEG 2000 OpenJPEG, libtiff и его инициализаторы по каждому сжатию, кодирование и декодирование JBIG2, точки входа цветового преобразования Little-CMS и примитивы AES. Дизайн-выбор за заглушками важнее списка. Недостающий символ на этапе линковки даёт стену неопределённых ссылок из модуля, которого вы никогда не трогали; заглушка, выбрасывающая ENotSupportedException, даёт сборку, которая работает, сообщение с названной причиной и трассировку стека, указывающую на место вызова. Это также означает, что сборка Free Pascal никогда молча не выдаёт неверные байты там, где сборка Delphi выдала бы верные. Заметим и эффект второго порядка: запуск недоверенных кодеков изображений в изолированном процессе — решение, возникающее только на сборке Delphi, ведь сборке Free Pascal попросту нечего изолировать: нативного декодера в процессе нет
Сжатие: первая строка, которую надо поменять, — cmNone
Прежде чем переносить что-либо ещё, поставьте Compression в cmNone. THPDFCompressionMethod предлагает ровно два значения, cmNone и cmFlateDecode, и второе уходит прямиком в точки входа deflate, являющиеся заглушками в сборке Free Pascal. Сначала проверьте базовую объектную модель без сжатия, затем решайте, что ещё нужно. Именно в таком порядке идёт поставляемый smoke test: создать одностраничный несжатый документ, перечитать его и убедиться, что число страниц вернулось равным единице. Несжатый вывод крупнее, и это по-прежнему совершенно валидный PDF
program HotPDFLazarusSmoke;
{$mode delphi}
{$H+}
uses
Interfaces, SysUtils, HPDFDoc;
var
Pdf, Reloaded: THotPDF;
OutputFile: string;
PageCount: Integer;
begin
OutputFile := IncludeTrailingPathDelimiter(GetTempDir) +
'HotPDF-FPC-Smoke.pdf';
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := OutputFile;
Pdf.Compression := cmNone; // cmFlateDecode упирается в заглушенный символ
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(72, 72, 0, 'HotPDF Free Pascal smoke test');
Pdf.EndDoc;
finally
Pdf.Free;
end;
Reloaded := THotPDF.Create(nil);
try
PageCount := Reloaded.LoadFromFile(OutputFile);
if PageCount <> 1 then
raise Exception.CreateFmt('Expected one page, got %d', [PageCount]);
finally
Reloaded.Free;
end;
end.
Что происходит с параллельным рендерингом страниц?
Он по-прежнему компилируется, по-прежнему возвращает корректные битмапы и перестаёт быть параллельным. THotPDF.RenderLoadedPagesParallel и THotPDF.RenderLoadedPagesParallelOrdered построены на TThread.CreateAnonymousThread с встроенным замыканием procedure, которое Free Pascal 3.2.2 выразить не может, поэтому ветка Free Pascal выполняет детерминированный последовательный запасной путь: идёт по индексам страниц по порядку, вызывает RenderLoadedPageToBitmap для каждой и считает успехи. Форма API, возвращаемое значение и выходной массив не меняются — именно это позволяет одной кодовой базе собираться в обе стороны
var
Bitmaps: THPDFBitmapArray;
Info: THPDFParallelRenderPipelineInfo;
Rendered: Integer;
begin
Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
Bitmaps, Info);
// Delphi: Info.WorkerCount — что позволил бюджет памяти
// Free Pascal: Info.WorkerCount всегда 1, страницы в порядке индексов
if Info.WorkerCount = 1 then
LogSerialFallback(Rendered, Info.RequestedWorkerCount);
Запасной путь не молчалив, и именно это стоит закладывать в дизайн. Он честно заполняет THPDFParallelRenderPipelineInfo: PageCount из запроса, RequestedWorkerCount, повторяющий то, что вы просили, WorkerCount, равный 1, а счётчики завершённого и доставленного совпадают с тем, что реально вернулось. Код, уже инспектирующий Info для калибровки прогресс-бара или бюджета памяти, продолжает работать и читает правду, а не допущение. Если ваш план пропускной способности опирается на пайплайн параллельного рендеринга и его модель противодавления, этот план — план для Delphi; на Free Pascal закладывайте однопоточную стоимость рендеринга страницы в битмап, умноженную на число страниц
Какую сборку в действительности поставлять?
Выбирайте по возможностям, а не по симпатиям. Если ваш поток работы — сборка документов, текст и векторная отрисовка, заполнение форм, загрузка и сохранение, сборка Free Pascal на Win64 это покрывает, и вам стоит проверить всё без сжатия, прежде чем что-то включать. Если же в деле изображения JPEG, JPEG 2000, TIFF или JBIG2, цветовые преобразования ICC, сжатый вывод или пропускная способность, зависящая от многих ядер, оставайтесь пока на Delphi или C++Builder. Границу прочерчивают ABI объектных файлов и недостающая возможность языка: оба видны в исходнике, а не зарыты в матрице поддержки, и оба отказывают именованной ошибкой, а не неверным результатом
Пакет Free Pascal и Lazarus поставляется в том же дистрибутиве, что модули Delphi и C++Builder, поэтому лицензия покрывает оба, и вы можете испытать путь Lazarus на собственных документах, прежде чем связываться; на странице продукта HotPDF Delphi PDF Component приведены актуальная матрица поддержки компиляторов и полный справочник API