Коротка відповідь на той тікет підтримки — так, з обмеженнями. 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 постачає типи VCL з Delphi, для яких у Free Pascal немає еквівалента: відображає TMetafile і TMetafileCanvas на класи бітмапів і канвасів LCL, а TRichEdit аліасить на TMemo; HPDFDoc натомість аліасить TPNGObject на Graphics.TPortableNetworkGraphic. Ставтеся до цього як до шимів часу компіляції, а не як до паритету можливостей: клас метафайла на основі бітмапа тримає модуль компільованим, але не змушує метафайлові шляхи поводитися так, як вони поводяться в Delphi. Навіть не-GUI смоук-тест тягне 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 є заглушками. Спершу перевірте базову обʼєктну модель зі стисненням вимкненим, а потім вирішуйте, що вам потрібно іще. Саме такий порядок використовує комплектний смоук-тест: створити односторінковий документ без стиснення, перечитати його і переконатися, що кількість сторінок повернулася одиницею. Вивід без стиснення більший, і це все одно цілком валідний 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, щоб розмірити прогрес-бар або бюджет памʼяті, продовжує працювати і читає правду, а не припущення. Якщо ваш план пропускної здатності спирається на конвеєр паралельного рендерингу та його модель backpressure, цей план — план для 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