Artículo técnico

Renderizado PDF multimotores en Delphi: guía PDF Library for Delphi

Tres rasterizadores pueden leer el mismo PDF y discrepar sobre lo que dice. El motor integrado en PDF Library for Delphi es el que se envía sin archivos adicionales y renderiza todo competentemente, que es por lo que se gana el espacio por defecto. Cairo trae una canalización diferente de transparencia y antialiasing, y tiende a ser al que la gente recurre cuando las máscaras suaves o los modos de mezcla salen mal en otra parte. PDFium carga el código de renderizado de Chrome, así que una página que se ve bien en un navegador normalmente se ve bien también bajo PDFium, al costo de un DLL considerable y un tamaño de bits que insiste en emparejar. Ninguno de los tres es correcto en lo abstracto. La corrección es por documento, y la única forma honesta de aprender qué motor maneja un corpus dado es correr ese corpus por cada uno de ellos

Ese es el caso para tratar al motor como una elección en tiempo de ejecución en lugar de una de tiempo de compilación. PDF Library for Delphi, la biblioteca PDF de Delphi y C++Builder de losLab, pone los tres detrás de una sola superficie de renderizado para que la decisión cueste un entero en lugar de una rama de código. El resto se reduce a seleccionar entre ellos de forma segura, confirmar qué motores carga realmente un binario desplegado, y evitar que el estado de renderizado envenene silenciosamente el siguiente trabajo

Tres rasterizadores detrás de una superficie de llamada

La biblioteca numera sus motores. El motor 1 es el renderizador integrado, el predeterminado, con opciones de suavizado GDI+ en Windows. El motor 2 es Cairo y el motor 3 es PDFium, ambos seleccionados en tiempo de ejecución a través de SelectRenderer. Los dos motores externos se cargan desde DLL cuyas rutas usted suministra con SetCairoFileName y SetPDFiumFileName antes de seleccionarlos. Sea cual sea el motor activo, el trabajo pasa por las mismas llamadas: RenderPageToFile, RenderPageToStream, RenderDocumentToFile. Cambiar de motor mueve un número; el resto de su código de renderizado nunca lo nota

El modelo de destino se extiende más allá de los mapas de bits. La clase renderizadora también apunta a metaarchivos (WMF, EMF, EMF+), EPS, contextos de dispositivo directos, impresoras y HTML5, con Cairo y PDFium apareciendo como destinos adicionales solo cuando se compilaron. La salida de trama es donde los tres motores divergen más visiblemente, así que es lo que los ejemplos aquí usan

Tres motores de renderizado PDF detrás de una sola superficie de llamada: SelectRenderer cambia entre el motor integrado, Cairo y PDFium mientras el código de la aplicación sigue llamando a las mismas funciones de renderizado
SelectRenderer cambia un entero para mover el trabajo entre los motores integrado, Cairo y PDFium. El código de la aplicación sigue llamando a RenderPageToFile y sus compañeros sin importar qué motor produjo los píxeles

Nunca asuma que un motor existe: pruebe al inicio

Cairo y PDFium son características de compilación condicional, lo que significa que un binario se puede construir completamente sin ellos. Cuando eso pasa, pedir el motor 2 o 3 no lanza nada. SelectRenderer simplemente devuelve un valor distinto al ID que pidió, y el código que ignora el valor de retorno sigue renderizando con el motor que ya estuviera activo. La defensa es una prueba de inicio que pide a cada motor identificarse y registra la respuesta:

function ProbeEngines(PDF: TPDFlib): string;
begin
  Result := 'built-in';                        // el motor 1 siempre está presente
  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);                       // restaura el predeterminado antes del trabajo real
end;

Corra esa prueba una vez al inicio y escriba su resultado en el registro junto a cada trabajo de renderizado. La pregunta más común cuando un cliente reporta una diferencia de renderizado es qué motores tiene realmente su instalación, y una respuesta de una línea sentada en el registro la resuelve sin una sesión de escritorio remoto. Un efecto secundario útil: si SetPDFiumFileName mismo devuelve 0, ya sabe que el DLL es el problema (ruta equivocada, tamaño de bits equivocado, una dependencia faltante) en lugar de un binario compilado sin soporte PDFium, porque la llamada de ruta no resolvió nada antes de que SelectRenderer se ejecutara

Diez formatos de salida detrás de un entero Options

El parámetro Options en 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 de archivo. JPEG (1), emparejado con SetJPEGQuality, es la mejor elección para escaneos fotográficos donde el tamaño del archivo importa más que los bordes nítidos

Un formato esconde un requisito sobre el flujo de destino. La ruta BMP escribe primero los datos de imagen, luego busca de vuelta al desplazamiento 0x26 para parchar los campos de resolución en el encabezado. Apunte eso a un flujo solo-hacia-adelante, un envoltorio de compresión o un socket de red, y la llamada falla de una forma que se lee como una falla del motor pero no lo es. Cuando un destino no buscable es inevitable, renderice PNG en su lugar, o haga pasar el BMP por un flujo de memoria y cópielo hacia adelante una vez completo

El DPI que pasa no es el DPI que obtiene

Cada llamada de renderizado toma un argumento DPI, pero la resolución que realmente obtiene es ese valor multiplicado por la escala de renderizado global. SetRenderScale comienza en 1.0, y una vez que la cambia el nuevo factor se aplica silenciosamente a cada renderizado posterior en esa instancia:

PDF.SetRenderScale(2.0);                    // cada renderizado posterior se duplica
PDF.RenderPageToFile(150, 1, 5, 'p1.png');  // efectivamente 300 DPI
PDF.SetRenderScale(1.0);                    // reinicie, o sus miniaturas llegarán enormes

La misma adherencia aplica a SetRenderCropType y al ajuste de calidad JPEG. En un servicio que produce miniaturas, vistas previas e imágenes de resolución de impresión desde una instancia compartida, estos ajustes sobrantes son lo que realmente está detrás del ticket ocasional de "las miniaturas de repente pesan 40 MB". Dos formas limpias de salir: reinicie el estado relevante en la parte superior de cada operación, o dedique una instancia separada a cada perfil de salida para que nada fugue entre ellos

PDF Library for Delphi: diagrama de flujo de la sonda de motores al inicio: cada renderizador confirma su ruta de DLL y su respuesta de SelectRenderer antes de que se registre un resumen de disponibilidad junto a cada trabajo de renderizado
Una llamada de ruta fallida acusa a la DLL, mientras que un resultado de SelectRenderer sin coincidencia significa que el binario nunca compiló el motor. La sonda se ejecuta una vez y su resumen de una línea resuelve la mayoría de las preguntas de renderizado de los clientes

Afinar el motor predeterminado antes de recurrir a otro

Una parte sorprendente de las solicitudes de "necesitamos un motor diferente" resulta ser problemas de configuración disfrazados. El renderizador integrado expone su comportamiento de suavizado a través de SetGDIPlusOptions y la familia más amplia SetRenderOptions, y SetGDIPlusFileName le permite apuntarlo a un runtime GDI+ específico cuando un entorno de despliegue envía uno inusual. Arte lineal dentado a DPI bajo, texto borroso en miniaturas, bandas a lo largo de degradados: todos responden a esas perillas, y girarlas no cuesta nada en el instalador. Añadir Cairo o PDFium, por el contrario, significa enviar más DLL, rastrear una segunda o tercera variante de tamaño de bits, y asumir la obligación de actualizarlos

Así que una queja de calidad tiene un orden natural de operaciones. Reprodúzcala primero al DPI y escala exactos del cliente, porque la mitad del tiempo la diferencia se evapora una vez que esos emparejan. Pruebe las opciones de suavizado del motor integrado a continuación. Solo entonces ponga la página lado a lado entre motores con cualquier otra variable sostenida constante: renderícela a PNG por los motores 1, 2 y 3 a DPI idéntico y adjunte los tres. Usualmente dos de los tres concuerdan, y esa mayoría le dice si el valor atípico es el documento interpretándose de forma diferente o su propia expectativa de línea base equivocada. Tres imágenes concretas resuelven una disputa de "se renderiza mal" mucho más rápido que un párrafo de adjetivos

Una cadena de respaldo que se explica sola

Una vez que las pruebas y la disciplina de estado están en su lugar, la cadena de respaldo en sí es corta. La detección de una falla se apoya en LastRenderError, que carga el texto del mensaje propio del motor para el renderizado más reciente y está vacío cuando el renderizado tuvo éxito:

procedure RenderPageWithFallback(PDF: TPDFlib; Page: Integer; const OutFile: string);
begin
  PDF.SelectRenderer(1);                            // integrado primero
  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 como respaldo pesado
  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 puntos de diseño cargan peso aquí. La cadena registra por qué pasó cada cambio, porque una línea de registro que lee "esta página cayó a PDFium desde el release 3.7" es una señal de regresión que quiere con tendencia en monitoreo en lugar de perdida. El orden de respaldo mismo es una política que vale la pena elegir por carga de trabajo. El motor integrado se despliega sin DLL adicionales, lo que lo hace el primer intento correcto en la mayoría de las instalaciones, mientras que los documentos pesados con grupos de transparencia o sombreado inusual son la razón habitual por la que un equipo cablea un motor alternativo. Ningún motor es el más rápido en general, que es todo el punto de elegir por llamada: haga benchmark de cada uno contra una muestra de sus documentos reales a su DPI real, y revise esa medición siempre que los DLL del motor o la mezcla de documentos cambien. El corpus gana el argumento cada vez

Cadena de alternativas de renderizado PDF: el motor integrado intenta primero, los fallos se registran, PDFium reintenta, y una excepción lanzada reporta cuando todos los motores disponibles fallan una página
Cada intento verifica LastRenderError y registra el motivo antes de cambiar de motor. Solo cuando todos los motores instalados han fallado la cadena lanza la excepción, con las causas recopiladas ya asentadas en el registro

Más allá de páginas individuales: lotes TIFF y contextos de dispositivo en vivo

Dos vecinos de las llamadas por página completan el toolkit. RenderAsMultipageTIFFToFile renderiza una expresión de rango de páginas directamente en un TIFF de múltiples páginas, la forma natural para entregas de archivo a sistemas de gestión documental que son anteriores al PDF. RenderPageToDC pinta directamente sobre un contexto de dispositivo de Windows para controles de vista previa, gobernado por su propio trío de ajustes adherentes (SetRenderDCOffset, SetRenderDCErasePage, más el tipo de recorte) que necesitan la misma disciplina de reinicio que el factor de escala. La vista previa en pantalla y el renderizado de ruta de impresión cargan suficientes trampas propias para merecer un artículo dedicado, enlazado abajo

Hacia dónde ir después

Un hábito que vale la pena llevar hacia adelante: como SelectRenderer entra en vigor para cada llamada posterior en la instancia, una sola página terca se puede reintentar en otro motor mientras el resto del documento se queda en el predeterminado. Para pintura de vista previa, selección de impresora y manejo de DevMode, continúe con el artículo de vista previa de impresión y contexto de dispositivo. Cuando los renderizados alimentan un pipeline de alto volumen sobre archivos muy grandes, el enfoque basado en identificadores de la guía de acceso directo se empareja naturalmente con el renderizado por página a través de DARenderPageToFile

El empaquetado de motores, los formatos soportados y las compilaciones de prueba se detallan en la página del producto PDF Library for Delphi