Artículo técnico

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

La respuesta corta a ese ticket de soporte es sí, con límites. HotPDF 2.730.0 compila en Free Pascal 3.2.2 y Lazarus 4.6 para Win64, y los caminos núcleo de crear, cargar y guardar funcionan. Lo que no sigue son las cosas que descansen sobre un objeto de códec nativo enlazado estáticamente o sobre los métodos anónimos de Delphi

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

Una matriz de capacidades que compara la compilación Delphi de HotPDF con la compilación Free Pascal 3.2.2 y Lazarus 4.6 para Win64, mostrando qué caminos 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 excepción
Los caminos núcleo de crear, cargar y guardar son idénticos en ambas compilaciones, y la brecha queda por completo en los códecs enlazados estáticamente y 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. Ninguna de las dos cosas es negociable. HotPDF.inc conmuta 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 es menor que 30202, así que una instalación 3.0.x falla con estruendo en lugar de producir una unidad rota. El paquete runtime de Lazarus HotPDFLaz.lpk codifica el resto: LCL como paquete requerido y -Mdelphi como opción personalizada

El requisito de LCL sorprende a quienes solo quieren 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 poniendo TRichEdit como alias de TMemo, mientras que HPDFDoc pone TPNGObject como alias de Graphics.TPortableNetworkGraphic. Trátenlos como shims de compilación, no como paridad de características: una clase metafile respaldada por un bitmap mantiene la unidad compilando, no hace que los caminos de metafile se comporten como en Delphi. Incluso la prueba de humo sin GUI incorpora 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 de compuerta de versión

Es tentador tratar la compilación Free Pascal como un compilador moderno y simplemente definir el símbolo de características Delphi más nuevo. HotPDF no lo hace, y la razón vale enunciarla sin rodeos: D2009+ no significa solo cadenas Unicode, también da paso a 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 tomar prestado el símbolo arrastraría código que no puede compilar. Por eso la cláusula uses de HPDFDoc lleva dos colas condicionales separadas, y el traslape 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 linker?

Porque son objetos COFF Win64 emitidos por una cadena de herramientas en particular, y ninguno de los dos linkers de Free Pascal en Win64 los consumirá: ni el linker interno, ni el camino externo de GNU ld. Este es un problema de ABI de archivos objeto, no un problema de Pascal, y ninguna cantidad de código condicional lo arregla. La biblioteca toma el único camino honesto disponible. Cada directiva {$L} que incorpora un objeto de códec estático va envuelta en {$IFNDEF FPC}, así que la compilación Free Pascal simplemente los omite, y HPDFFPCCodecStubs luego suministra cada símbolo externo faltante 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 les dice exactamente qué capacidades son solo de Delphi hoy: 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 faltante en tiempo de enlace les da una pared de referencias indefinidas desde una unidad que nunca tocaron; un stub que lanza ENotSupportedException les da una compilación que corre, un mensaje que nombra la razón y un stack trace que apunta al sitio de la llamada. También significa que una compilación Free Pascal nunca produce bytes equivocados en silencio donde una compilación Delphi produciría los correctos. Fíjense 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 compilación Delphi, porque una compilación Free Pascal no tiene decodificador nativo en proceso que aislar en primer lugar

En Delphi los objetos de códecs estáticos de HotPDF se enlazan y corren nativamente, mientras que la compilación Free Pascal Win64 salta las directivas de enlace y enruta cada símbolo externo faltante a un stub que lanza una excepción con nombre en el sitio de la llamada
Saltar las directivas de enlace y poner stubs en cada símbolo externo convierte una pared de referencias indefinidas en una compilación que corre y nombra sus propios límites

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

Antes de portar cualquier otra cosa, pongan 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 compilación Free Pascal. Verifiquen primero el modelo de objetos núcleo con la compresión apagada, y luego decidan qué más necesitan. Ese es el orden que usa la prueba de humo incluida: crear un documento de una página sin comprimir, recargarlo y verificar que el conteo de páginas volvió 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 de páginas en paralelo?

Sigue compilando, sigue devolviendo bitmaps correctos y deja de ser paralelo. THotPDF.RenderLoadedPagesParallel y THotPDF.RenderLoadedPagesParallelOrdered están construidos sobre TThread.CreateAnonymousThread con un closure procedure en línea, que Free Pascal 3.2.2 no puede expresar, así que la rama Free Pascal corre 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 arreglo de salida no cambian, que es lo que permite que una sola base de código compile de ambas maneras

La misma llamada de renderizado paralelo de HotPDF corre en hilos de trabajo traslapados bajo Delphi y recorre los índices de página en serial bajo Free Pascal, con el record de info del pipeline reportando un conteo de workers de uno en lugar de esconder el fallback
La rama Free Pascal conserva la forma de la API y el arreglo de salida mientras reporta un conteo de workers de uno, así que el código que ya lee el record de info 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 el presupuesto de memoria permitió
  // Free Pascal: Info.WorkerCount siempre es 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 vale la pena tener en cuenta al diseñar. Llena THPDFParallelRenderPipelineInfo con honestidad: PageCount de la petición, RequestedWorkerCount reflejando lo que pidieron, WorkerCount en 1, y los conteos de completadas y entregadas coincidiendo con lo que realmente 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 su plan de throughput depende de el pipeline de renderizado paralelo y su modelo de backpressure, ese plan es un plan Delphi; en Free Pascal, presupuesten el costo de un solo hilo de renderizar una página a un bitmap multiplicado por el conteo de páginas

¿Qué compilación debería enviar realmente?

Elijan por capacidad, no por preferencia. Si su flujo de trabajo es ensamblaje de documentos, dibujo de texto y vectores, llenado de formularios, carga y guardado, la compilación Free Pascal en Win64 lo cubre, y deberían validar con la compresión apagada antes de activar cualquier cosa. Si involucra imágenes JPEG o JPEG 2000 o TIFF o JBIG2, transformaciones de color ICC, salida comprimida o throughput que depende de muchos núcleos, quédense 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 código fuente en lugar de enterrados en una matriz de soporte, y ambos fallan con un error con nombre en lugar de 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 pueden probar el camino Lazarus contra sus propios documentos antes de comprometerse con él; la página de producto de HotPDF Delphi PDF Component lleva la matriz de soporte de compiladores actual y la referencia completa de la API