Todo rasterizador PDF tiene un archivo que lo humilla. En un caso de soporte fue una corrida de facturas de servicios donde los logos con soft mask salían como rectángulos negros — pero solo en el sitio del cliente, porque su compilación renderizaba con un motor distinto al del banco de pruebas. Bugs así rara vez se arreglan en código de aplicación; la página es válida, el motor simplemente discrepa con ella. Lo que sí se puede arreglar es la arquitectura: hagan que el motor de renderizado sea una decisión en runtime, verifiquen qué motores contiene realmente el binario desplegado y registren qué motor produjo cada imagen. PDFlibPas, la biblioteca PDF de losLab para Delphi y C++Builder, está hecha exactamente para ese patrón — una sola superficie de llamadas de renderizado, tres motores seleccionables.
Tres rasterizadores detrás de una sola superficie de llamadas
La biblioteca numera sus motores: 1 es el renderizador integrado (el predeterminado, con opciones de suavizado GDI+ en Windows), 2 es Cairo y 3 es PDFium, seleccionado en runtime con SelectRenderer. Los motores externos se cargan desde DLLs cuyas rutas suministran mediante SetCairoFileName y SetPDFiumFileName. Sea cual sea el motor, las mismas llamadas hacen el trabajo — RenderPageToFile, RenderPageToStream, RenderDocumentToFile — así que una estrategia fallback cambia un entero, no el código de renderizado.
Por debajo, el modelo de destino es más amplio que bitmaps: la clase de renderizado soporta metafiles (WMF, EMF, EMF+), EPS, device contexts directos, impresoras y salida HTML5, con Cairo y PDFium apareciendo como destinos adicionales cuando están compilados. En este artículo el foco es la salida raster, donde las diferencias entre motores son más visibles.
Nunca supongan que un motor existe: sondeen al inicio
El soporte de Cairo y PDFium son características de compilación condicional. Un binario creado sin ellas no lanzará una excepción cuando seleccionen el motor 2 o 3 — SelectRenderer simplemente devuelve algo distinto al ID solicitado, y si ignoran el valor de retorno seguirán renderizando con lo que estaba activo antes. El patrón confiable es una sonda de arranque:
function ProbeEngines(PDF: TPDFlib): string;
begin
Result := 'built-in'; // engine 1 is always present
if (PDF.SetCairoFileName('cairo.dll') = 1) and (PDF.SelectRenderer(2) = 2) then
Result := Result + ', cairo';
if (PDF.SetPDFiumFileName('pdfium.dll') = 1) and (PDF.SelectRenderer(3) = 3) then
Result := Result + ', pdfium';
PDF.SelectRenderer(1); // restore the default before real work
end;
Registren el resultado de la sonda con cada trabajo. Cuando un cliente reporta una diferencia de renderizado, la primera pregunta diagnóstica es "qué motores tiene su instalación", y una respuesta de una línea en el log supera a una sesión remota.
Diez formatos de salida detrás de un entero Options
El parámetro Options de las llamadas de renderizado selecciona la codificación de salida: 0 es BMP, 1 JPEG, 2 WMF, 3 EMF, 4 EPS, 5 PNG, 6 GIF, 7 TIFF, 8 EMF+ y 9 HTML5. PNG (5) es el valor predeterminado sensato para vistas previas e imágenes de página archivables; JPEG (1) combinado con SetJPEGQuality gana para escaneos fotográficos donde importa el tamaño.
Un formato esconde un requisito de stream. La ruta BMP parchea los campos de resolución del encabezado después de escribir los datos de imagen, buscando de regreso al offset 0x26 en la salida. Rendericen BMP hacia un stream que solo avanza — un wrapper de compresión, un stream de red — y la llamada falla de una forma que parece relacionada con el motor, pero no lo está. Si no pueden evitar un destino no seekable, rendericen PNG en su lugar, o preparen la salida BMP mediante un memory stream.
El DPI que pasan no es el DPI que reciben
Todas las llamadas de renderizado aceptan un argumento DPI, pero la resolución efectiva es ese valor multiplicado por la escala global de renderizado. SetRenderScale tiene 1.0 como valor predeterminado; una vez cambiado, se aplica silenciosamente a todos los renderizados posteriores de esa instancia:
PDF.SetRenderScale(2.0); // every later render is doubled
PDF.RenderPageToFile(150, 1, 5, 'p1.png'); // effectively 300 DPI
PDF.SetRenderScale(1.0); // reset, or your thumbnails arrive huge
La misma persistencia aplica a SetRenderCropType y a la configuración de calidad JPEG. En un servicio que renderiza miniaturas, vistas previas e imágenes a resolución de impresión desde una instancia compartida, esas configuraciones persistentes son la causa raíz detrás de tickets como "las miniaturas de pronto pesan 40 MB". Restablezcan estado al inicio de cada operación o dediquen una instancia por perfil de salida.
Ajustar el motor predeterminado antes de buscar otro
Una parte sorprendente de las solicitudes de "necesitamos otro motor" son en realidad solicitudes de configuración de calidad disfrazadas. El renderizador integrado expone su comportamiento de suavizado mediante SetGDIPlusOptions y la familia más amplia SetRenderOptions, y apuntar el motor a un runtime GDI+ específico es posible con SetGDIPlusFileName cuando un entorno de despliegue trae uno inusual. Arte lineal dentado a bajo DPI, texto borroso en miniaturas y bandas en degradados responden a estos controles — y ajustarlos no cuesta nada en despliegue, mientras que agregar Cairo o PDFium a un instalador añade DLLs, variantes por bitness y una obligación de actualización.
La secuencia práctica ante una queja de calidad es por lo tanto: primero reproduzcan con el DPI exacto y la configuración de escala del cliente, luego prueben opciones de suavizado en el motor integrado, y solo entonces comparen la página entre motores con todo lo demás constante. Rendericen la misma página a PNG mediante los motores 1, 2 y 3 con DPI idéntico, y adjunten las tres imágenes al issue. La mayoría de las veces dos de tres coinciden, lo que indica si el valor atípico es la interpretación del documento o la expectativa base — evidencia que resuelve disputas de "renderiza mal" mucho más rápido que reportes basados en adjetivos.
Una cadena fallback que se explica sola
Con sondeo y disciplina de estado en su lugar, la cadena fallback en sí es corta. La detección de fallos usa LastRenderError, que contiene el texto de mensaje del motor para el renderizado más reciente:
procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
PDF.SelectRenderer(1); // built-in first
PDF.RenderPageToFile(200, Page, 5, OutFile); // 5 = PNG
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('built-in', Page, PDF.LastRenderError);
if PDF.SelectRenderer(3) = 3 then // PDFium as the heavy fallback
begin
PDF.RenderPageToFile(200, Page, 5, OutFile);
if PDF.LastRenderError = '' then Exit;
LogEngineFailure('pdfium', Page, PDF.LastRenderError);
end;
raise Exception.CreateFmt('Page %d failed on all available engines', [Page]);
end;
Dos decisiones de diseño son deliberadas. La cadena registra la razón de cada cambio, porque "esta página cae a PDFium desde la versión 3.7" es una señal de regresión que conviene ver como tendencia en monitoreo, no enterrada. Y el orden fallback es una política que deben elegir por carga de trabajo: el motor integrado se despliega sin DLLs extra, lo que lo hace el primer intento correcto en la mayoría de instalaciones, mientras que documentos cargados de grupos de transparencia o patrones de sombreado inusuales son la razón clásica para conectar un motor alternativo. Midan con su propio corpus; el corpus siempre gana la discusión.
Más allá de páginas individuales: lotes TIFF y device contexts en vivo
Dos vecinos de las llamadas por página completan la caja de herramientas. RenderAsMultipageTIFFToFile renderiza una expresión de rango de páginas directamente a un TIFF multipágina — la forma natural para entregas archivísticas a sistemas de gestión documental anteriores a PDF. Y RenderPageToDC pinta directamente sobre un Windows device context para controles de vista previa, gobernado por su propio trío de configuraciones persistentes (SetRenderDCOffset, SetRenderDCErasePage, más el tipo de recorte), que merecen la misma disciplina de restablecimiento que el factor de escala. La vista previa en pantalla y la ruta de impresión tienen suficientes trampas propias para merecer un artículo dedicado, enlazado abajo.
Preguntas de renderizado, respondidas
¿Por qué SelectRenderer(3) devuelve 0 en mi máquina? O el binario desplegado fue compilado sin soporte PDFium, o SetPDFiumFileName no resolvió a una DLL cargable — ruta incorrecta, bitness incorrecto o dependencias faltantes. El patrón de sonda al inicio distingue ambas: si la llamada de nombre de archivo ya devolvió 0, el problema es la DLL.
¿Qué motor es el más rápido? No hay una respuesta estable entre tipos de documento, justo por eso la biblioteca permite elegir por llamada. Comparen cada motor contra una muestra de sus documentos reales con su DPI real, y vuelvan a revisar cuando cambien las DLLs del motor o la mezcla de documentos.
¿Pueden distintas páginas de un documento usar motores distintos? Sí. SelectRenderer afecta llamadas posteriores en la instancia, de modo que una cadena fallback puede reintentar una página difícil en otro motor mientras el resto del documento permanece en el predeterminado.
Sigan explorando
Para pintura de vista previa, selección de impresora y manejo de DevMode, continúen con el artículo de vista previa de impresión y device context. Si sus renderizados alimentan una canalización de alto volumen sobre archivos muy grandes, el enfoque basado en handles de la guía de Direct Access combina bien con renderizado por página mediante DARenderPageToFile.
El empaquetado de motores, los formatos soportados y las compilaciones de prueba se detallan en la página del producto PDFlibPas.