Delphi y Lazarus compilan el mismo Object Pascal, y esa semejanza superficial es precisamente lo que hace engañosa la migración de un visor entre ambos. Las dos cadenas de herramientas divergen en tres aspectos importantes para el trabajo con PDF: el tipo nativo string es UTF-16 en Delphi y UTF-8 en una aplicación LCL; VCL y LCL son marcos visuales distintos con sus propios controles, diálogos y formatos de streaming de formularios; y un binario Delphi se dirige a Windows mientras que un binario FPC puede estar destinado a Linux o macOS. Ninguna de esas diferencias se aprecia en tiempo de compilación. Un visor basado en PDFium Component, que distribuye ediciones VCL y LCL desde un único árbol de código fuente, compilará correctamente en Lazarus tras unos cuantos cambios de nombre de unidades y algunos bloques {$IFDEF FPC}. Los fallos llegan después, cuando datos reales y un despliegue real revelan las suposiciones que la compilación Delphi estaba haciendo en silencio
Cuatro de esas suposiciones explican la mayor parte del tiempo perdido: la codificación de texto en el límite de la interfaz, la tentación de mantener dos copias del formulario, la forma en que se resuelve un binario de motor nativo en tiempo de ejecución y el momento en que la conversión de texto a voz deja de ser multiplataforma al desaparecer SAPI. Cada una es barata de resolver si se sabe que va a aparecer y costosa de perseguir si no se sabe
El mismo Pascal, distintas cargas de cadena
El tipo nativo string de Delphi es UTF-16 desde 2009. Lazarus y Free Pascal usan UTF-8 de forma predeterminada en aplicaciones LCL. Las API del componente que tratan texto hablan UTF-16 mediante el tipo WString, que la compilación FPC convierte en un alias de WideString, de modo que cada límite donde el texto pasa entre la interfaz LCL y el motor PDF es un punto de conversión
Las conversiones se producen automáticamente en asignaciones sencillas, y la mayoría del código nunca necesita pensar en ellas. Dos hábitos mantienen alejados los errores de codificación. Pase el texto directamente, sin manipulación a nivel de bytes: el código que corta un término de búsqueda mediante desplazamientos de bytes funciona en Delphi, donde un Char es una unidad UTF-16, y corrompe UTF-8 multibyte en LCL. Y pruebe con datos no ASCII desde la primera ejecución. Un nombre de archivo alemán, un término de búsqueda cirílico, un nombre de autor con acento en los metadatos del documento: los datos de prueba exclusivamente ASCII ocultan todos los defectos de codificación, porque ASCII es el único intervalo en que UTF-8 y UTF-16 coinciden byte a carácter. El error está presente todo el tiempo; ASCII solo lo mantiene invisible hasta que un cliente de Múnich abre un archivo que nunca se probó
Un bloque condicional, no una bifurcación por IDE
Después de la primera docena de IFDEF, la base de código comienza a parecer dos proyectos con un único repositorio, y dividirla por IDE parece tentador. Es una mala decisión. Las diferencias reales se reducen a un bloque de declaraciones compartido, y una bifurcación duplica desde entonces el coste de cada corrección. Mantenga la capa condicional así de pequeña:
{$IFDEF FPC}
uses
LCLType, Forms, Graphics, Controls;
type
WString = WideString; // las API de texto del componente son UTF-16
TBytes = array of Byte;
{$ELSE}
uses
Winapi.Windows, Vcl.Forms, Vcl.Graphics, Vcl.Controls;
{$ENDIF}
Todo lo que está por debajo de ese bloque compila de forma idéntica en ambos IDE. La gestión de documentos, la navegación por páginas y las llamadas de renderizado: TPdf y TPdfView exponen la misma superficie en las ediciones VCL y LCL, de manera que la mayor parte del visor nunca encuentra una condición del compilador. Mantenerlo así es una disciplina estructural, no un truco ingenioso. La lógica PDF compartida vive en unidades que no importan diálogos ni paneles específicos de un marco. Las pocas cosas que realmente difieren, como los diálogos de impresión y los selectores de archivos con sus convenciones de plataforma, se ocultan tras una interfaz fina implementada una vez por marco. El bloque IFDEF se convierte en el único lugar donde puede recaer la futura divergencia de plataforma, en vez de dejar que las directivas del compilador se filtren por cuarenta unidades
Construir el formulario con código, no en dos diseñadores
El streaming de formularios es donde los proyectos de doble IDE se deterioran en silencio. Un .dfm y un .lfm que afirman describir el mismo formulario se separan propiedad a propiedad hasta que las dos compilaciones se comportan de forma distinta por razones que nadie puede comparar, porque los dos archivos ni siquiera tienen el mismo formato. Construir el visor en tiempo de ejecución evita todo el problema. Hay una secuencia de constructor, controlada por versiones como código ordinario, que se interpreta igual en ambas plataformas:
procedure TViewerForm.FormCreate(Sender: TObject);
begin
Pdf := TPdf.Create(Self);
PdfView := TPdfView.Create(Self);
PdfView.Parent := Self;
PdfView.Align := alClient;
PdfView.Pdf := Pdf;
PdfView.FitMode := pfmFitWidth;
if ParamCount > 0 then
begin
Pdf.FileName := ParamStr(1);
Pdf.Active := True; // abre el documento; PageCount es válido después de esto
end;
end;
El orden exacto de esas asignaciones importa menos que la línea que realiza el trabajo real. PdfView.Pdf := Pdf vincula el control visual al componente de documento y, desde ese punto, la navegación de páginas mediante PageNumber y el comportamiento de ajuste mediante FitMode responden de forma idéntica en VCL y LCL. Hay una peculiaridad entre marcos que conviene conocer antes de que un usuario la informe como error: asignar Zoom manualmente devuelve FitMode a pfmNone en ambos marcos. Por tanto, si la barra de herramientas trata «ajustar al ancho» como una preferencia persistente, es necesario volver a asignar el modo de ajuste después de cualquier zoom programático, o la preferencia deja de mantenerse silenciosamente la primera vez que el código modifica el nivel de zoom
El binario sobre el que el IDE nunca avisó
El componente envuelve el motor PDFium, que se distribuye como binario nativo de plataforma, y ese binario es el origen de casi todos los informes de «funciona en el IDE, falla desde el acceso directo instalado». Tres reglas explican la mayoría. La arquitectura de bits debe coincidir exactamente. Un ejecutable de 32 bits no puede cargar una biblioteca pdfium de 64 bits, y el mensaje que devuelve el sistema operativo («módulo no encontrado» en algunas versiones de Windows) engaña de forma activa, porque el archivo está allí mismo junto al ejecutable. Resuelva la ruta de la biblioteca respecto al ejecutable, nunca respecto al directorio de trabajo; un inicio desde el IDE y uno desde una consola difieren justo en ese punto, que es por lo que el error queda oculto durante el desarrollo. Y capture una carga fallida antes de abrir el primer documento e informe de ella indicando claramente la ruta y la arquitectura esperadas. Un ticket de soporte que diga «Falta el binario PDFium de 64 bits en <path>» se cierra en minutos. Uno que diga «el visor se bloquea al iniciar» se convierte en una semana de idas y venidas
Versione también el binario del motor junto con el ejecutable. PDFium avanza rápido, y un instalador que actualiza la aplicación pero deja una biblioteca obsoleta en disco provoca bloqueos que nadie en la oficina puede reproducir, por la sencilla razón de que todas las máquinas de la oficina tienen el par correcto. Trate la biblioteca como parte del artefacto de compilación, con el mismo instalador, la misma marca de versión y la misma vía de reversión que el ejecutable que la carga
Registrar componentes en el IDE de Lazarus
La construcción en tiempo de ejecución no necesita ningún registro de diseño, lo que constituye la configuración más limpia para un visor que crea su propia interfaz con código. Cuando se quieran los componentes en la paleta de Lazarus para trabajo de diseño, instale el paquete y deje que su unidad de registro dedicada, PDFiumLazReg en Lib/FPC/PDFiumLaz.lpk, se encargue de ello. Esa unidad se marca como de diseño deliberadamente: hace referencia a interfaces de editores de propiedades del IDE que nunca deben enlazarse con el ejecutable distribuido
Si se hace mal, el síntoma es una aplicación que depende inexplicablemente de paquetes del IDE, lo que aparece como un fallo de despliegue en la primera máquina de cliente que nunca ha tenido Lazarus instalado
Voz y lectores de pantalla fuera de Windows
La conversión de texto a voz es la única función donde se rompe la historia multiplataforma, y se rompe en el sistema operativo, no en el componente. SAPI, el backend TTS habitual en Windows, solo existe en Windows. Una compilación de Lazarus que siga dirigida a Windows conserva toda la salida SAPI y el mismo comportamiento compatible con NVDA que tenía el original de Delphi, así que una migración de Windows a Windows no pierde nada en este aspecto y un usuario de NVDA no puede distinguir las dos compilaciones
Un destino Linux o macOS es otra cuestión. No hay SAPI que llamar, de modo que la salida de audio debe reconectarse a un servicio de voz nativo mientras que las API de lectura que están por encima permanecen sin cambios. Esa división es el argumento para ocultar la voz tras una interfaz desde el primer commit: el análisis del orden de lectura y el cursor de seguimiento de palabras son independientes de la plataforma y se trasladan intactos, y solo debe cambiar por plataforma la capa fina que realmente produce sonido. El artículo sobre lector accesible cubre en profundidad esa maquinaria de lectura
Una lista de comprobación de paridad antes de dar por terminada la migración
El siguiente pase ha detectado regresiones reales, enumeradas aproximadamente en el orden en que suelen aparecer los fallos. Abra un documento cuya ruta contenga caracteres no ASCII. Busque un término con caracteres no ASCII y confirme que los resultados se resaltan donde corresponde. Pruebe el desplazamiento con rueda del ratón, la selección por arrastre y la navegación de páginas por teclado en cada conjunto de widgets distribuido, porque el manejo del foco y el comportamiento de la rueda son los rincones de LCL que más dependen del conjunto de widgets. Compruebe el renderizado con escalado de pantalla al 100 %, 150 % y 200 %. Por último, ejecute la compilación instalada, no la del IDE, en una máquina que nunca haya tenido el IDE, porque esa es la única prueba que ejercita de forma honesta la resolución de binarios. Todo lo demás puede funcionar mientras esa falla silenciosamente
El rendimiento de renderizado se mantiene igual entre ambas ediciones, por lo que el enfoque de caché del artículo sobre caché de renderizado y rendimiento del zoom se aplica al visor LCL exactamente como está escrito para el visor VCL
Nada de esto convierte a la edición LCL en una edición inferior. La superficie principal es idéntica en ambos lados: TPdf, TPdfView, renderizado, formularios, extracción de texto y las API de accesibilidad se comportan igual independientemente del IDE que los haya compilado. Todas las diferencias que merece la pena seguir están ligadas a la plataforma y no a la edición. La voz SAPI es exclusiva de Windows, los diálogos siguen las convenciones de cada marco y el binario debe coincidir con la arquitectura en la que se carga. Resuelva correctamente los límites de codificación, el formulario en tiempo de ejecución y la resolución de binarios, y el resto de la migración será el trabajo mecánico que el compilador ya ha hecho por usted
Las ediciones VCL y LCL descritas aquí se distribuyen juntas como PDFium Component, con código fuente y API públicas idénticas para Delphi, C++Builder y Lazarus/FPC