Artículo técnico

HotPDF en Free Pascal y Lazarus: límites en Win64

La respuesta corta a ese ticket de soporte es sí, con límites. HotPDF 2.730.0 compila con Free Pascal 3.2.2 y Lazarus 4.6 para Win64, y las rutas centrales de creación, carga y guardado funcionan. Lo que no viene de la mano es cualquier cosa que se apoye en un objeto de códec nativo enlazado estáticamente o en métodos anónimos de Delphi

La pregunta suele llegar siempre igual: un equipo estandariza en Lazarus para una herramienta multiplataforma, o hereda una base de código Free Pascal, y quiere el mismo componente PDF para el que ya paga licencia en Delphi. Portar una biblioteca Delphi madura rara vez es cuestión de sintaxis. Lo interesante es lo que el port revela sobre dónde estaba la biblioteca acoplada calladamente a una sola cadena de herramientas, y en este caso el acoplamiento se sienta en dos lugares muy concretos: el ABI de archivos objeto de los códecs incluidos y las características del compilador escondidas tras un símbolo de versión

Una matriz de capacidades que compara la build Delphi de HotPDF con la build Free Pascal 3.2.2 y Lazarus 4.6 para Win64, mostrando qué rutas de documentos se comparten y qué APIs de códec, compresión, renderizado paralelo y métodos anónimos llegan a un stub que lanza
Las rutas centrales de creación, carga y guardado son idénticas en ambas builds, y la brecha se sienta por completo en los códecs enlazados estáticamente y en las APIs de métodos anónimos

Qué necesita Free Pascal 3.2.2 antes de que HPDFDoc compile

HotPDF compila bajo Free Pascal solo en modo Delphi, y solo cuando los directorios de unidades LCL de Lazarus están en la ruta de búsqueda. Nada de eso es negociable. HotPDF.inc cambia el compilador con {$MODE DELPHI} y {$H+} dentro de su bloque {$IFDEF FPC} y rechaza cualquier versión anterior con un {$FATAL} cuando FPC_FULLVERSION está por debajo de 30202, de modo que una instalación 3.0.x falla a los gritos en lugar de producir una unidad rota. El paquete de runtime de Lazarus HotPDFLaz.lpk codifica el resto: LCL como paquete requerido y -Mdelphi como opción personalizada

El requisito de LCL sorprende a quien solo quiere salida de consola, pero es estructural. HPDFFPCCompat suministra los tipos VCL de Delphi para los que Free Pascal no tiene equivalente, mapeando TMetafile y TMetafileCanvas sobre clases de bitmap y canvas de LCL y aliasando TRichEdit a TMemo, mientras que HPDFDoc aliasa TPNGObject a Graphics.TPortableNetworkGraphic. Tómalos como cuñas de compilación, no como paridad de características: una clase metafile apoyada en un bitmap mantiene la unidad compilando, no hace que las rutas de metafile se comporten como en Delphi. Hasta la prueba de humo sin GUI tira de Interfaces, y el script de build pasa -Fu para lcl\units\x86_64-win64 y el directorio de salida de lazutils

Por qué D2009+ no puede hacer también de gate de versión

Es tentador tratar la build Free Pascal como un compilador moderno y definir sin más el símbolo de la característica Delphi más nueva. HotPDF no lo hace, y el motivo conviene decirlo sin adornos: D2009+ no significa solo cadenas Unicode, también hace de gate para unidades cuya API pública se expresa con métodos anónimos. Free Pascal 3.2.2 no soporta ni los métodos anónimos de Delphi ni esas APIs, así que pedir prestado el símbolo arrastraría código que no puede compilar. La cláusula uses de HPDFDoc lleva por eso dos colas condicionales separadas, y el solape entre ellas es deliberado y no accidental

uses
  // ...
  HPDFJavaScript,
  HPDFFormCalcGraph
{$IFDEF FPC}
  , HPDFFPCCodecStubs,
  HPDFCMS,
  HPDFWinCertSigner
{$ENDIF}
{$IFDEF D2009+}
  , HPDFXFARuntime,
  HPDFCMS,
  HPDFWinCertSigner,
  HPDFSignVerify,
  HPDFSignatureBatch
{$ENDIF};

¿Por qué los códecs nativos se detienen en el enlazador?

Porque son objetos COFF Win64 emitidos por una cadena de herramientas concreta, y ningún enlazador de Free Pascal en Win64 los consume: ni el enlazador interno, ni la ruta externa de GNU ld. Es un problema de ABI de archivos objeto, no un problema Pascal, y ninguna cantidad de código condicional lo arregla. La biblioteca toma la única ruta honesta disponible. Cada directiva {$L} que trae un objeto de códec estático va envuelta en {$IFNDEF FPC}, así que la build Free Pascal simplemente los omite, y HPDFFPCCodecStubs suministra entonces cada símbolo externo ausente como un stub que lanza en lugar 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;

Esa tabla de stubs es larga, y leerla te dice exactamente qué capacidades son hoy exclusivas de Delphi: los puntos de entrada deflate de zlib-ng y zopfli, la compresión y descompresión de libjpeg, el códec JPEG 2000 de OpenJPEG, libtiff y sus inicializadores por compresión, la codificación y decodificación JBIG2, los puntos de entrada de transformación de color de Little-CMS y las primitivas AES. La decisión de diseño detrás de los stubs importa más que la lista. Un símbolo ausente en tiempo de enlace te da un muro de referencias sin definir desde una unidad que jamás tocaste; un stub que lanza ENotSupportedException te da una build que corre, un mensaje que nombra el motivo y un stack trace que apunta al punto de llamada. También significa que una build Free Pascal nunca produce bytes equivocados en silencio donde una build Delphi produciría los correctos. Fíjate también en el efecto de segundo orden: ejecutar códecs de imágenes no confiables en un proceso aislado es una decisión que solo surge en la build Delphi, porque una build Free Pascal no tiene decodificador nativo en proceso que encerrar en un sandbox, para empezar

En Delphi los objetos de códec estáticos de HotPDF se enlazan y corren de forma nativa, mientras que la build Free Pascal Win64 se salta las directivas de enlace y encamina cada símbolo externo ausente a un stub que lanza una excepción con nombre en el punto de llamada
Saltarse las directivas de enlace y poner stubs a cada símbolo externo convierte un muro de referencias sin definir en una build que corre y nombra sus propios límites

Compresión: la primera línea que cambiar es cmNone

Antes de portar cualquier otra cosa, pon Compression en cmNone. THPDFCompressionMethod ofrece exactamente dos valores, cmNone y cmFlateDecode, y el segundo va directo a los puntos de entrada deflate que son stubs en una build Free Pascal. Verifica primero el modelo de objetos central con la compresión apagada y decide luego qué más necesitas. Ese es el orden que usa la prueba de humo incluida: crear un documento de una página sin comprimir, recargarlo y comprobar que el recuento de páginas vuelve como uno. La salida sin comprimir es más grande, y sigue siendo un PDF perfectamente 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 llega a un símbolo con 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.

¿Qué pasa con el renderizado paralelo de páginas?

Sigue compilando, sigue devolviendo bitmaps correctos y deja de ser paralelo. THotPDF.RenderLoadedPagesParallel y THotPDF.RenderLoadedPagesParallelOrdered se apoyan en TThread.CreateAnonymousThread con un closure procedure inline, que Free Pascal 3.2.2 no sabe expresar, así que la rama Free Pascal ejecuta un fallback serial determinista: recorre los índices de página en orden, llama a RenderLoadedPageToBitmap por cada uno y cuenta los éxitos. La forma de la API, el valor de retorno y el array de salida no cambian, que es lo que permite que una sola base de código compile por ambos caminos

La misma llamada de renderizado paralelo de HotPDF corre en hilos de trabajo solapados bajo Delphi y recorre los índices de página en serie bajo Free Pascal, con el registro de información del pipeline informando un recuento de workers de uno en lugar de esconder el fallback
La rama Free Pascal conserva la forma de la API y el array de salida mientras informa un recuento de workers de uno, así que el código que ya lee el registro de información ve la verdad
var
  Bitmaps: THPDFBitmapArray;
  Info: THPDFParallelRenderPipelineInfo;
  Rendered: Integer;
begin
  Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
    Bitmaps, Info);
  // Delphi: Info.WorkerCount es lo que dejó el presupuesto de memoria
  // Free Pascal: Info.WorkerCount es siempre 1, páginas en orden de índice
  if Info.WorkerCount = 1 then
    LogSerialFallback(Rendered, Info.RequestedWorkerCount);

El fallback no es silencioso, que es la parte que merece diseñar alrededor. Llena THPDFParallelRenderPipelineInfo con honestidad: PageCount de la petición, RequestedWorkerCount haciendo eco de lo que pediste, WorkerCount puesto en 1, y los recuentos de completadas y entregadas coincidiendo con lo que de verdad volvió. El código que ya inspecciona Info para dimensionar una barra de progreso o un presupuesto de memoria sigue funcionando y lee la verdad en lugar de una suposición. Si tu plan de rendimiento depende de el pipeline de renderizado paralelo y su modelo de contrapresión, ese plan es un plan Delphi; en Free Pascal, presupuesta el coste monothread de renderizar una página a bitmap multiplicado por el recuento de páginas

Qué build deberías enviar de verdad

Elige por capacidad, no por preferencia. Si tu flujo de trabajo es ensamblaje de documentos, dibujo de texto y vectores, relleno de formularios, carga y guardado, la build Free Pascal en Win64 lo cubre, y deberías validar con la compresión apagada antes de encender nada. Si implica imágenes JPEG o JPEG 2000 o TIFF o JBIG2, transformaciones de color ICC, salida comprimida o rendimiento que depende de muchos núcleos, quédate en Delphi o C++Builder por ahora. La frontera la trazan un ABI de archivos objeto y una característica del lenguaje ausente, ambos visibles en el fuente en lugar de enterrados en una matriz de soporte, y ambos fallan con un error con nombre y no con un resultado equivocado

El paquete Free Pascal y Lazarus se entrega en la misma distribución que las unidades Delphi y C++Builder, así que una licencia cubre ambas y puedes probar la ruta Lazarus contra tus propios documentos antes de comprometerte; la página de producto del HotPDF Delphi PDF Component lleva la matriz de soporte de compiladores actual y la referencia completa de la API