A resposta curta a esse ticket de suporte é sim, com limites. O HotPDF 2.730.0 compila em Free Pascal 3.2.2 e Lazarus 4.6 para Win64, e os caminhos centrais de criação, carregamento e gravação funcionam. O que não acompanha é tudo o que assente num objeto de codec nativo ligado estaticamente ou nos anonymous methods de Delphi
A pergunta chega quase sempre da mesma forma: uma equipa padroniza em 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 é uma questão de sintaxe. A parte interessante é o que o porting expõe sobre onde a biblioteca estava discretamente acoplada a uma única toolchain, e neste caso o acoplamento está em dois lugares muito específicos: o ABI de ficheiros de objeto dos codecs incluídos, e as funcionalidades do compilador escondidas 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 em Free Pascal apenas em modo Delphi, e apenas quando os diretórios de units LCL do Lazarus estão no caminho de pesquisa. Nada disto é negociável. O HotPDF.inc comuta o compilador com {$MODE DELPHI} e {$H+} dentro do seu bloco {$IFDEF FPC} e recusa qualquer versão mais antiga com um {$FATAL} quando FPC_FULLVERSION está abaixo de 30202, pelo que uma instalação 3.0.x falha de forma ruidosa em vez de produzir uma unit partida. O pacote de runtime Lazarus HotPDFLaz.lpk codifica o resto: LCL como pacote obrigatório e -Mdelphi como opção personalizada
O requisito da LCL surpreende quem só quer saída em consola, mas é estrutural. O HPDFFPCCompat fornece os tipos VCL de Delphi para que o Free Pascal não tem equivalente, mapeando TMetafile e TMetafileCanvas para classes de bitmap e canvas da LCL e aliasando TRichEdit a TMemo, enquanto o HPDFDoc faz alias de TPNGObject a Graphics.TPortableNetworkGraphic. Trate-os como shims de compilação, não como paridade de funcionalidades: uma classe de metafile suportada por um bitmap mantém a unit a compilar, não faz os caminhos de metafile comportarem-se como se comportam em 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 de lazutils
Porque o D2009+ não pode servir também de gate de versão
É tentador tratar a build de Free Pascal como um compilador moderno e simplesmente definir o símbolo de funcionalidades mais recente de Delphi. O HotPDF não o faz, e a razão merece ser dita sem rodeios: D2009+ não significa apenas strings Unicode, também controla units cuja API pública é expressa com anonymous methods. O Free Pascal 3.2.2 não suporta nem os anonymous methods de Delphi nem essas APIs, pelo que pedir emprestado o símbolo arrastaria código que não consegue compilar. A cláusula uses do HPDFDoc traz por isso duas caudas condicionais separadas, e a sobreposição entre elas é deliberada e não acidental
uses
// ...
HPDFJavaScript,
HPDFFormCalcGraph
{$IFDEF FPC}
, HPDFFPCCodecStubs,
HPDFCMS,
HPDFWinCertSigner
{$ENDIF}
{$IFDEF D2009+}
, HPDFXFARuntime,
HPDFCMS,
HPDFWinCertSigner,
HPDFSignVerify,
HPDFSignatureBatch
{$ENDIF};
Porque é que os codecs nativos param no linker?
Porque são objetos COFF Win64 emitidos por uma toolchain específica, e nenhum dos linkers de Free Pascal em Win64 os consome: nem o linker interno, nem o caminho externo com GNU ld. Isto é um problema de ABI de ficheiros de objeto, não um problema de Pascal, e nenhuma quantidade de código condicional o resolve. A biblioteca toma o único caminho honesto disponível. Cada diretiva {$L} que puxa um objeto de codec estático está embrulhada em {$IFNDEF FPC}, pelo que a build de Free Pascal simplesmente as omite, e o HPDFFPCCodecStubs fornece então cada símbolo externo em falta como um stub que levanta exceção em vez de devolver
// 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 é extensa, e lê-la diz-lhe exatamente que capacidades são hoje só de Delphi: os pontos de entrada deflate do zlib-ng e do zopfli, a compressão e descompressão libjpeg, o codec JPEG 2000 OpenJPEG, o libtiff e os seus inicializadores por compressão, o encode e decode JBIG2, os pontos de entrada de transformação de cor do Little-CMS, e as primitivas AES. A decisão de design por trás dos stubs importa mais do que a lista. Um símbolo em falta ao ligar dá-lhe uma parede de referências indefinidas vinda de uma unit que nunca tocou; um stub que levanta ENotSupportedException dá-lhe uma build que corre, uma mensagem que nomeia o motivo, e um stack trace que aponta para o local da chamada. Significa também que uma build de Free Pascal nunca produz em silêncio bytes errados onde uma build de Delphi produziria bytes corretos. Note ainda o efeito de segunda ordem: correr codecs de imagens não fidedignas num processo isolado é uma decisão que só surge na build de Delphi, porque uma build de Free Pascal não tem decoder nativo em processo para isolar em primeiro lugar
Compressão: a primeira linha a mudar é cmNone
Antes de portar qualquer outra coisa, ponha Compression em cmNone. O THPDFCompressionMethod oferece exatamente dois valores, cmNone e cmFlateDecode, e o segundo vai diretamente para os pontos de entrada deflate que são stubs numa build de Free Pascal. Verifique primeiro o modelo de objetos central com a compressão desligada, e depois decida o mais de que precisa. É essa a ordem que o smoke test incluído usa: criar um documento de uma página sem compressão, recarregá-lo, e verificar que a contagem de páginas devolveu um. A saída sem compressão é maior, e continua a ser 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 com stub
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 à renderização paralela de páginas?
Continua a compilar, continua a devolver bitmaps corretos, e deixa de ser paralelo. O THotPDF.RenderLoadedPagesParallel e o THotPDF.RenderLoadedPagesParallelOrdered assentam em TThread.CreateAnonymousThread com uma closure de procedure inline, que o Free Pascal 3.2.2 não sabe expressar, pelo que o ramo de Free Pascal corre um fallback serial determinista: percorre os índices de páginas por ordem, chama RenderLoadedPageToBitmap a cada um, e conta os sucessos. A forma da API, o valor de retorno e a matriz de saída ficam iguais, e é isso que permite a uma única base de código compilar das duas maneiras
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 pela ordem dos índices
if Info.WorkerCount = 1 then
LogSerialFallback(Rendered, Info.RequestedWorkerCount);
O fallback não é silencioso, e é essa a parte que vale a pena ter em conta ao desenhar. Preenche o THPDFParallelRenderPipelineInfo com honestidade: PageCount do pedido, RequestedWorkerCount a ecoar o que pediu, WorkerCount fixado em 1, e as contagens de concluídas e entregues a coincidir com o que realmente voltou. O código que já inspeciona Info para dimensionar uma barra de progresso ou um orçamento de memória continua a funcionar e lê a verdade em vez de uma suposição. Se o seu plano de débito depende do pipeline de renderização paralela e do seu modelo de backpressure, esse plano é um plano de Delphi; no Free Pascal, considere no orçamento o custo single-threaded de renderizar uma página para bitmap multiplicado pela contagem de páginas
Que build deve realmente distribuir?
Escolha por capacidade, não por preferência. Se o seu fluxo de trabalho é montagem de documentos, desenho de texto e vetores, preenchimento de formulários, carregamento e gravação, a build de Free Pascal em Win64 cobre isso, e 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 débito que dependa de muitos núcleos, fique em Delphi ou C++Builder por agora. A fronteira é traçada por um ABI de ficheiros de objeto e por uma funcionalidade de linguagem em falta, ambos visíveis no código-fonte em vez de enterrados numa matriz de suporte, e ambos a falhar com um erro nomeado em vez de um resultado errado
O pacote de Free Pascal e Lazarus vem na mesma distribuição que as units de Delphi e C++Builder, pelo que uma licença cobre ambos e pode testar o caminho Lazarus contra os seus próprios documentos antes de se comprometer; a página de produto do componente Delphi PDF HotPDF traz a matriz de suporte de compiladores atual e a referência completa da API