La risposta breve a quel ticket di supporto è sì, con limiti. HotPDF 2.730.0 compila con Free Pascal 3.2.2 e Lazarus 4.6 per Win64, e i percorsi principali di creazione, caricamento e salvataggio funzionano. Ciò che non segue è tutto ciò che poggia su un oggetto codec nativo linkato staticamente o sui metodi anonimi Delphi
La domanda di solito arriva allo stesso modo: un team standardizza su Lazarus per uno strumento multipiattaforma, o eredita una codebase Free Pascal, e vuole lo stesso componente PDF per cui ha già una licenza Delphi. Portare una libreria Delphi matura raramente è una questione di sintassi. La parte interessante è ciò che il porting rivela su dove la libreria era silenziosamente accoppiata a una toolchain, e in questo caso l'accoppiamento sta in due posti molto precisi: l'ABI dei file oggetto dei codec inclusi, e le caratteristiche del compilatore nascoste dietro un simbolo di versione
Cosa richiede Free Pascal 3.2.2 prima che HPDFDoc compili
HotPDF compila sotto Free Pascal solo in modalità Delphi, e solo quando le directory delle unit LCL di Lazarus sono nel percorso di ricerca. Nessuna delle due è negoziabile. HotPDF.inc commuta il compilatore con {$MODE DELPHI} e {$H+} dentro il suo blocco {$IFDEF FPC} e rifiuta qualsiasi cosa più vecchia con un {$FATAL} quando FPC_FULLVERSION è sotto 30202, così un'installazione 3.0.x fallisce rumorosamente invece di produrre una unit rotta. Il pacchetto runtime Lazarus HotPDFLaz.lpk codifica il resto: LCL come pacchetto richiesto e -Mdelphi come opzione personalizzata
Il requisito LCL sorprende chi vuole solo output da console, ma è strutturale. HPDFFPCCompat fornisce i tipi VCL Delphi per cui Free Pascal non ha equivalenti, mappando TMetafile e TMetafileCanvas su classi bitmap e canvas LCL e aliasando TRichEdit a TMemo, mentre HPDFDoc aliasa TPNGObject a Graphics.TPortableNetworkGraphic. Trattali come shim di compilazione, non come parità di funzionalità: una classe metafile supportata da una bitmap tiene la unit in compilazione, non fa sì che i percorsi metafile si comportino come su Delphi. Perfino lo smoke test non GUI tira dentro Interfaces, e lo script di build passa -Fu per lcl\units\x86_64-win64 e la directory di output lazutils
Perché D2009+ non può fare anche da gate di versione
È allettante trattare la build Free Pascal come un compilatore moderno e definire semplicemente il simbolo della più recente caratteristica Delphi. HotPDF non lo fa, e il motivo merita di essere detto chiaramente: D2009+ non significa solo stringhe Unicode, fa anche da gate alle unit la cui API pubblica è espressa con metodi anonimi. Free Pascal 3.2.2 non supporta né i metodi anonimi Delphi né quelle API, quindi prendere in prestito il simbolo trascinerebbe dentro codice che non può compilare. La clausola uses di HPDFDoc porta quindi due code condizionali separate, e la sovrapposizione tra loro è deliberata anziché accidentale
uses
// ...
HPDFJavaScript,
HPDFFormCalcGraph
{$IFDEF FPC}
, HPDFFPCCodecStubs,
HPDFCMS,
HPDFWinCertSigner
{$ENDIF}
{$IFDEF D2009+}
, HPDFXFARuntime,
HPDFCMS,
HPDFWinCertSigner,
HPDFSignVerify,
HPDFSignatureBatch
{$ENDIF};
Perché i codec nativi si fermano al linker?
Perché sono oggetti COFF Win64 emessi da una toolchain particolare, e nessuno dei due linker Free Pascal su Win64 li consuma: né il linker interno, né il percorso GNU ld esterno. È un problema di ABI dei file oggetto, non un problema Pascal, e nessuna quantità di sorgente condizionale lo risolve. La libreria prende l'unica strada onesta disponibile. Ogni direttiva {$L} che tira dentro un oggetto codec statico è avvolta in {$IFNDEF FPC}, così la build Free Pascal semplicemente li omette, e HPDFFPCCodecStubs poi fornisce ogni simbolo esterno mancante come stub che solleva eccezioni invece di ritornare
// 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;
Quella tabella di stub è lunga, e leggerla ti dice esattamente quali capacità sono oggi solo Delphi: i punti di ingresso deflate di zlib-ng e zopfli, compressione e decompressione libjpeg, il codec JPEG 2000 OpenJPEG, libtiff e i suoi inizializzatori per compressione, codifica e decodifica JBIG2, i punti di ingresso di trasformazione colore Little-CMS, e le primitive AES. La scelta di progetto dietro gli stub conta più dell'elenco. Un simbolo mancante al link ti dà un muro di riferimenti indefiniti da una unit che non hai mai toccato; uno stub che solleva ENotSupportedException ti dà una build che gira, un messaggio che nomina il motivo, e uno stack trace che punta al sito della chiamata. Significa anche che una build Free Pascal non produce mai in silenzio byte sbagliati dove una build Delphi ne produrrebbe di corretti. Nota anche l'effetto di secondo ordine: eseguire codec di immagini non fidati in un processo isolato è una decisione che nasce solo sulla build Delphi, perché una build Free Pascal non ha per cominciare alcun decodificatore nativo in-process da sandboxare
Compressione: la prima riga da cambiare è cmNone
Prima di portare qualsiasi altra cosa, imposta Compression su cmNone. THPDFCompressionMethod offre esattamente due valori, cmNone e cmFlateDecode, e il secondo instrada dritto nei punti di ingresso deflate che sono stub in una build Free Pascal. Verifica prima il modello di oggetti principale con la compressione spenta, poi decidi che altro ti serve. È l'ordine che usa lo smoke test spedito: crea un documento di una pagina non compresso, ricaricalo, e asserisci che il numero di pagine è tornato uno. L'output non compresso è più grande, ed è comunque un PDF perfettamente valido
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 arriva a un simbolo 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.
Cosa succede al rendering parallelo delle pagine?
Compila ancora, ritorna bitmap corretti, e smette di essere parallelo. THotPDF.RenderLoadedPagesParallel e THotPDF.RenderLoadedPagesParallelOrdered sono costruiti su TThread.CreateAnonymousThread con una chiusura procedure inline, che Free Pascal 3.2.2 non sa esprimere, così il ramo Free Pascal esegue un fallback seriale deterministico: percorre gli indici di pagina in ordine, chiama RenderLoadedPageToBitmap per ciascuno, e conta i successi. La forma dell'API, il valore di ritorno e l'array di output sono invariati, ed è ciò che permette a una sola codebase di compilare in entrambi i modi
var
Bitmaps: THPDFBitmapArray;
Info: THPDFParallelRenderPipelineInfo;
Rendered: Integer;
begin
Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
Bitmaps, Info);
// Delphi: Info.WorkerCount è quanto il budget di memoria permetteva
// Free Pascal: Info.WorkerCount è sempre 1, pagine in ordine di indice
if Info.WorkerCount = 1 then
LogSerialFallback(Rendered, Info.RequestedWorkerCount);
Il fallback non è silenzioso, ed è la parte che vale la pena progettare attorno. Riempie THPDFParallelRenderPipelineInfo onestamente: PageCount dalla richiesta, RequestedWorkerCount che ripete ciò che hai chiesto, WorkerCount impostato a 1, e i conteggi di completati e consegnati che corrispondono a ciò che è davvero tornato. Il codice che già ispeziona Info per dimensionare una barra di avanzamento o un budget di memoria continua a funzionare e legge la verità anziché un'assunzione. Se il tuo piano di throughput dipende da la pipeline di render parallelo e il suo modello di backpressure, quel piano è un piano Delphi; su Free Pascal, prevedi il costo single-thread di renderizzare una pagina in bitmap moltiplicato per il numero di pagine
Quale build dovresti davvero spedire?
Scegli per capacità, non per preferenza. Se il tuo flusso di lavoro è assemblaggio documenti, disegno testo e vettoriale, compilazione di moduli, caricamento e salvataggio, la build Free Pascal su Win64 lo copre, e dovresti validare con la compressione spenta prima di accendere qualsiasi cosa. Se coinvolge immagini JPEG o JPEG 2000 o TIFF o JBIG2, trasformazioni colore ICC, output compresso, o throughput che dipende da molti core, resta su Delphi o C++Builder per ora. Il confine è tracciato da un ABI di file oggetto e da una caratteristica del linguaggio mancante, entrambi visibili nel sorgente anziché sepolti in una matrice di supporto, ed entrambi falliscono con un errore nominato anziché con un risultato sbagliato
Il pacchetto Free Pascal e Lazarus viene spedito nella stessa distribuzione delle unit Delphi e C++Builder, così una licenza copre entrambi e puoi testare il percorso Lazarus sui tuoi documenti prima di impegnarti; la pagina prodotto del componente HotPDF Delphi PDF porta l'attuale matrice di supporto dei compilatori e il riferimento API completo