PDFlibPas convierte metafiles mejorados en contenido real de página PDF registro a registro, en lugar de rasterizarlos, que es lo que mantiene nítido un gráfico o dibujo CAD importado a cualquier zoom. Ese conversor tiene unas 6500 líneas y se escribió contra la VCL, así que cuando la biblioteca ganó un destino Free Pascal se clasificó como no portable y se sustituyó por un stub. Esa clasificación estaba equivocada, y la forma en que estaba equivocada deja una lección útil sobre cómo auditar una dependencia antes de decidir reescribir a su alrededor
La superficie VCL real de esas 6500 líneas resultó ser pequeña: una clase de bitmap usada por su formato de píxel, su guardado a stream, su handle, su canvas y sus scanlines; una clase de metarchivo usada por su anchura, su altura y su handle; y el tipo de color con dos constantes. Cada una de esas piezas ya la proporcionaba la unidad de gráficos de la propia biblioteca, que existe precisamente para que la compilación sin VCL tenga equivalentes. El conversor no estaba bloqueado por la VCL en absoluto. Estaba bloqueado por la unidad Windows de Free Pascal
Dividir por el eje del que el código realmente depende
Así que el cambio no fue una reimplementación. Fue un condicional: de «compilar el stub cuando se construye sin la VCL» a «compilar el stub cuando no se construye para Windows». Ese es el eje correcto, y exponer el porqué hace obvia la diferencia. Un metarchivo mejorado es un contenedor de Windows. El conversor es un parser de registros GDI de Windows de principio a fin. Que la aplicación anfitriona use la VCL, otro conjunto de widgets o ninguno no tiene nada que ver con que esos registros puedan interpretarse; que el destino sea Windows lo tiene todo que ver
Las consecuencias de elegir el eje correcto caen por su propio peso. Las compilaciones con C++Builder, que eliminan el símbolo de plataforma Windows en esta biblioteca, conservan el stub que lanza una excepción y se comportan exactamente igual que antes. macOS conserva el stub, correctamente, porque allí no hay registros GDI que interpretar. Las compilaciones Delphi con VCL quedan intactas. Y una compilación Windows con un conjunto de widgets ajeno a la VCL gana importación vectorial de EMF como efecto secundario, sin que nadie tuviera que implementarla. Un condicional alineado con la dependencia real convierte el trabajo de plataforma en un cambio de una línea; alineado con el equivocado, lo convierte en una reescritura que nunca llega a planificarse
La carencia de Free Pascal eran declaraciones, no lógica
Lo que faltaba de verdad eran las declaraciones Win32 que aporta la unidad Windows de Delphi y que la de Free Pascal no trae. Reunirlas en una única unidad de compatibilidad, en vez de dispersar condicionales por el conversor, mantuvo legible el parser. La lista es ilustrativa porque muestra lo desigual que es la cobertura de cabeceras entre ambos RTL: 113 constantes de tipos de registro de metarchivo, dos banderas de salida de texto extendida, tres constantes de modo de relleno con degradado, un tipo de puntero a tabla de handles, alias para los registros de vértices y primitivas de degradado, y tres tipos de registro que Free Pascal no declara en absoluto, correspondientes a la mezcla alfa, el blitting transparente y el modo de gestión de color
Nada de eso tiene interés por separado. Todo tiene que estar bien antes de que el parser compile, y una unidad de compatibilidad es su hogar natural porque puede compararse con la documentación de cabeceras como bloque
La que dibuja la imagen equivocada en silencio
Dos de esas declaraciones no solo están ausentes: están presentes y equivocadas para este propósito, y esta es la parte que merece recordarse aunque no toquéis nunca un metarchivo
Free Pascal declara el registro de creación de brushes con la estructura de brush en tiempo de ejecución incrustada, y el registro de pluma extendida con la estructura de pluma en tiempo de ejecución incrustada. Ambas estructuras de tiempo de ejecución declaran su miembro hatch como un entero del tamaño de un puntero, porque en una llamada GDI en vivo ese miembro puede transportar un handle. Un metarchivo, sin embargo, guarda siempre la forma de 32 bits, porque la disposición del registro es parte del formato de fichero serializado y no cambia con el bitness del proceso
En compilaciones de 32 bits ambas coinciden y no pasa nada. En Win64 el miembro del tamaño de un puntero son ocho bytes donde el fichero tiene cuatro, así que cada campo posterior al miembro hatch se lee desde un desplazamiento equivocado. No hay excepción, ni error de análisis, ni aviso. El metarchivo simplemente se dibuja mal: colores tomados de los bytes equivocados, anchuras de pluma tomadas de los bytes equivocados y una imagen que parece un fallo de renderizado y no un fallo de disposición de struct. Delphi distribuye variantes explícitamente de 32 bits de ambas estructuras precisamente por esto, y la unidad de compatibilidad las redeclara del mismo modo
// Equivocado en Win64: Hatch es del tamaño de un puntero, el fichero
// guarda 32 bits, y cada campo posterior se desplaza cuatro bytes sin error
type
TLogBrushRuntime = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: ULONG_PTR; // 8 bytes en un proceso de 64 bits
end;
// Correcto: la disposición serializada, anchura fija sin importar el bitness
type
TLogBrush32 = record
lbStyle: UINT;
lbColor: COLORREF;
lbHatch: DWORD; // siempre 4 bytes, como se guarda en el metarchivo
end;
La regla general: toda estructura que aparece tanto como argumento de API en tiempo de ejecución como disposición serializada de campos necesita dos declaraciones, y la serializada debe usar tipos de anchura fija de principio a fin. Los miembros del tamaño de un puntero en un formato de fichero son siempre un error esperando una compilación de 64 bits
Las diferencias de firma pertenecen a un wrapper, no a cada punto de llamada
Las diferencias restantes eran discrepancias de firma corrientes, y la forma de absorberlas es un wrapper de reenvío en lugar de un condicional en cada punto de llamada. La función de combinación de transformaciones recibe punteros bajo Free Pascal donde Delphi recibe parámetros por referencia, así que el wrapper toma referencias y pasa direcciones. También copia ambos argumentos de origen a locales primero, porque el conversor tiene puntos de llamada donde la matriz de destino es a la vez una de las fuentes, y pasar la misma dirección dos veces a una función que escribe mientras lee produce una transformación sutilmente errónea de un modo que solo se manifiesta en contenido rotado
function CombineTransformCompat(var Dest: TXForm;
const A, B: TXForm): BOOL;
var
SrcA, SrcB: TXForm;
begin
// Copiar primero: quien llama pasa legítimamente Dest como A o B
SrcA := A;
SrcB := B;
{$IFDEF FPC}
Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;
Los tipos de rectángulo y punto son el otro caso. Free Pascal trata los registros de rectángulo y punto del metarchivo como tipos distintos de los gráficos generales, así que ocho puntos de asignación necesitaron un cast explícito entre registros de disposición idéntica. Ambos compiladores aceptan la forma con cast, así que esos puntos no llevan condicional alguno, lo que merece un poco de fealdad
Qué cambia esto en un despliegue con Free Pascal
La importación vectorial de EMF funciona en Windows bajo Free Pascal y produce el mismo contenido de página que la compilación Delphi: paths como paths, degradados como contenido de patrón, texto como texto. Fuera de Windows la ruta rasterizada sigue siendo la respuesta, y eso es una limitación del formato, no del port. El estado de coordenadas y recortes que alimenta el conversor se describe en el artículo sobre el tracker de CTM y recortes del content stream, y las primitivas vectoriales que emite se cubren en gráficos vectoriales, shaders y degradados
Si estáis auditando vuestra propia base de código buscando la misma oportunidad, el ejercicio útil es el que dio origen a esto: enumerar los miembros que de verdad usáis del marco del que creéis depender. La respuesta suele ser mucho más corta de lo que sugiere la lista de imports, y la restricción real suele estar en otro sitio por completo. Las rutas de importación basadas en device context en general se describen en el artículo de vista previa de impresión y device context, y la cobertura de plataformas y toolchains está en la página de producto de la losLab PDF Developer Library