A resposta curta para aquele ticket de suporte é sim, com limites. O HotPDF 2.730.0 compila no Free Pascal 3.2.2 e Lazarus 4.6 para Win64, e os caminhos centrais de create, load e save funcionam. O que não vem junto é qualquer coisa que dependa de um objeto codec nativo com link estático ou dos anonymous methods do Delphi
A pergunta costuma chegar sempre do mesmo jeito: uma equipe padroniza no Lazarus para uma ferramenta multiplataforma, ou herda uma base de código Free Pascal, e quer o mesmo componente PDF que já licencia para Delphi. Portar uma biblioteca Delphi madura raramente é questão de sintaxe. A parte interessante é o que o port expõe sobre onde a biblioteca estava silenciosamente acoplada a uma única toolchain, e neste caso o acoplamento mora em dois lugares muito específicos: o ABI de arquivos de objeto dos codecs embutidos e os recursos do compilador escondidos atrás de um símbolo de versão
O que o Free Pascal 3.2.2 precisa antes de o HPDFDoc compilar
O HotPDF compila sob Free Pascal apenas no modo Delphi, e apenas quando os diretórios de units da LCL do Lazarus estão no search path. Nada disso é negociável. O HotPDF.inc alterna o compilador com {$MODE DELPHI} e {$H+} dentro do bloco {$IFDEF FPC} e recusa qualquer coisa mais antiga com um {$FATAL} quando FPC_FULLVERSION está abaixo de 30202, então uma instalação 3.0.x falha de forma ruidosa em vez de produzir uma unit quebrada. O pacote de runtime do Lazarus HotPDFLaz.lpk codifica o resto: LCL como pacote obrigatório e -Mdelphi como opção customizada
A exigência da LCL surpreende quem só quer saída de console, mas ela é estrutural. O HPDFFPCCompat fornece os tipos VCL do Delphi para os quais o Free Pascal não tem equivalente, mapeando TMetafile e TMetafileCanvas para classes de bitmap e canvas da LCL e apelidando TRichEdit de TMemo, enquanto o HPDFDoc apelida TPNGObject de Graphics.TPortableNetworkGraphic. Trate esses como shims de compilação, não paridade de recursos: uma classe metafile sustentada por um bitmap mantém a unit compilando, não faz os caminhos de metafile se comportarem como no Delphi. Até o smoke test sem GUI puxa Interfaces, e o script de build passa -Fu para lcl\units\x86_64-win64 e o diretório de saída do lazutils
Por que D2009+ não pode servir de version gate
É tentador tratar o build Free Pascal como um compilador moderno e simplesmente definir o símbolo de recursos mais novo do Delphi. O HotPDF não o faz, e o motivo vale ser dito com clareza: D2009+ não significa apenas strings Unicode, ele também filtra units cuja API pública é expressa com anonymous methods. O Free Pascal 3.2.2 não suporta nem os anonymous methods do Delphi nem essas APIs, então pegar o símbolo emprestado arrastaria código que não compila. A cláusula uses do HPDFDoc por isso carrega duas caudas condicionais separadas, e a sobreposição entre elas é deliberada, não acidental
uses
// ...
HPDFJavaScript,
HPDFFormCalcGraph
{$IFDEF FPC}
, HPDFFPCCodecStubs,
HPDFCMS,
HPDFWinCertSigner
{$ENDIF}
{$IFDEF D2009+}
, HPDFXFARuntime,
HPDFCMS,
HPDFWinCertSigner,
HPDFSignVerify,
HPDFSignatureBatch
{$ENDIF};
Por que os codecs nativos param no linker?
Porque são objetos COFF Win64 emitidos por uma toolchain específica, e nenhum dos linkers do Free Pascal no Win64 os consome: nem o linker interno, nem o caminho externo GNU ld. Isso é um problema de ABI de arquivo de objeto, não um problema Pascal, e nenhuma quantidade de source condicional conserta isso. A biblioteca toma a única rota honesta disponível. Cada diretiva {$L} que puxa um objeto codec estático está embrulhada em {$IFNDEF FPC}, então o build Free Pascal simplesmente as omite, e o HPDFFPCCodecStubs então fornece cada símbolo externo faltante como um stub que levanta exceção em vez de retornar
// 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;
Essa tabela de stubs é longa, e lê-la diz exatamente quais capacidades são exclusivas do Delphi hoje: os pontos de entrada deflate do zlib-ng e zopfli, compressão e descompressão libjpeg, o codec JPEG 2000 OpenJPEG, o libtiff e seus inicializadores por compressão, encode e decode JBIG2, os pontos de entrada de transformação de cor do Little-CMS e as primitivas AES. A escolha de design por trás dos stubs importa mais que a lista. Um símbolo faltante em tempo de link te dá uma parede de referências indefinidas vinda de uma unit que você nunca tocou; um stub que levanta ENotSupportedException te dá um build que roda, uma mensagem nomeando o motivo e um stack trace apontando para o call site. Isso também significa que um build Free Pascal nunca produz silenciosamente bytes errados onde um build Delphi produziria os corretos. Note também o efeito de segunda ordem: rodar codecs de imagem não confiáveis em um processo isolado é uma decisão que só surge no build Delphi, porque um build Free Pascal não tem decodificador nativo in-process para isolar em primeiro lugar
Compressão: a primeira linha a mudar é cmNone
Antes de portar qualquer outra coisa, defina Compression como cmNone. O THPDFCompressionMethod oferece exatamente dois valores, cmNone e cmFlateDecode, e o segundo vai direto para os pontos de entrada deflate que são stubs em um build Free Pascal. Verifique primeiro o modelo central de objetos com a compressão desligada, depois decida o que mais você precisa. Essa é a ordem que o smoke test embarcado usa: criar um documento de uma página sem compressão, recarregá-lo e verificar que a contagem de páginas voltou como um. A saída sem compressão é maior, e ainda é um PDF perfeitamente válido
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 chega a um símbolo stubado
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.
O que acontece com a renderização paralela de páginas?
Ainda compila, ainda retorna bitmaps corretos e deixa de ser paralela. O THotPDF.RenderLoadedPagesParallel e o THotPDF.RenderLoadedPagesParallelOrdered são construídos sobre TThread.CreateAnonymousThread com uma closure inline procedure, que o Free Pascal 3.2.2 não consegue expressar, então o ramo Free Pascal roda um fallback serial determinístico: percorre os índices de página em ordem, chama RenderLoadedPageToBitmap para cada um e conta os sucessos. O formato da API, o valor de retorno e o array de saída não mudam, e é isso que permite que uma única base de código compile dos dois jeitos
var
Bitmaps: THPDFBitmapArray;
Info: THPDFParallelRenderPipelineInfo;
Rendered: Integer;
begin
Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
Bitmaps, Info);
// Delphi: Info.WorkerCount é o que o orçamento de memória permitiu
// Free Pascal: Info.WorkerCount é sempre 1, páginas em ordem de índice
if Info.WorkerCount = 1 then
LogSerialFallback(Rendered, Info.RequestedWorkerCount);
O fallback não é silencioso, e é essa a parte que vale projetar em volta. Ele preenche o THPDFParallelRenderPipelineInfo com honestidade: PageCount do pedido, RequestedWorkerCount ecoando o que você pediu, WorkerCount definido como 1 e as contagens completed e delivered correspondendo ao que realmente voltou. Código que já inspeciona Info para dimensionar uma barra de progresso ou um orçamento de memória continua funcionando e lê a verdade em vez de uma suposição. Se seu plano de throughput depende de do pipeline de renderização paralela e seu modelo de backpressure, esse plano é um plano Delphi; no Free Pascal, orce o custo single-threaded de renderizar uma página para bitmap multiplicado pela contagem de páginas
Qual build você realmente deve distribuir?
Escolha por capacidade, não por preferência. Se seu fluxo de trabalho é montagem de documentos, desenho de texto e vetores, preenchimento de formulários, load e save, o build Free Pascal no Win64 cobre, e você deve validar com a compressão desligada antes de ligar qualquer coisa. Se envolve imagens JPEG ou JPEG 2000 ou TIFF ou JBIG2, transformações de cor ICC, saída comprimida ou throughput que depende de muitos cores, fique no Delphi ou C++Builder por enquanto. A fronteira é desenhada por um ABI de arquivo de objeto e um recurso de linguagem ausente, ambos visíveis no source em vez de enterrados em uma matriz de suporte, e ambos falham com um erro nomeado em vez de um resultado errado
O pacote Free Pascal e Lazarus vem na mesma distribuição que as units Delphi e C++Builder, então uma licença cobre os dois e você pode testar o caminho Lazarus contra seus próprios documentos antes de se comprometer com ele; a página do produto HotPDF Delphi PDF Component carrega a matriz atual de suporte a compiladores e a referência completa da API