Las coordenadas de PDF están en puntos, las coordenadas de la impresora están en unidades de dispositivo, y ambas no tienen nada que ver entre sí hasta que se convierten deliberadamente. Esa discrepancia es la raíz de la mayoría de los malos resultados de impresión en las aplicaciones de Delphi: el código envía el archivo correcto, pero la página sale recortada, estirada o en blanco. PDFium Component maneja limpiamente el lado del renderizado; la infraestructura de la impresora es VCL estándar. Ambos encajan con una cantidad modesta de código una vez que se comprende qué espera cada lado
Cómo funciona la canalización de renderizar y luego imprimir
PDFium Component no se comunica directamente con las impresoras. El patrón es: renderice una página a un TBitmap con la resolución que desee, luego transfiera ese mapa de bits al lienzo de la impresora con StretchDIBits. TPdf.RenderPage devuelve un mapa de bits propiedad del llamador, por lo que usted controla las dimensiones de los píxeles. Pase [rePrinting] en el conjunto de opciones y PDFium cambiará su ruta de renderizado a una que omita los efectos que son solo para pantalla, como el hinting de subpíxeles LCD, y manejará correctamente el MediaBox de la página para la salida de impresión. Si omite rePrinting, lo que envíe a la impresora será un renderizado de pantalla, el cual se ve bien en un monitor, pero tiende a producir una salida más suave en impresoras de alto DPI porque las decisiones de hinting tomadas para pantallas de 96 DPI no se adaptan a la impresión de 300 o 600 DPI
TPdf.Active es la única puerta que debe verificar antes de tocar cualquier propiedad de la página. El componente absorbe los errores de carga silenciosamente: establecer Active := True en un archivo dañado o protegido con contraseña no genera una excepción; simplemente deja Active como False. Siempre verifíquelo después de la asignación. Leer PageCount o PageWidth en un documento inactivo devuelve cero, lo que produce operaciones nulas que son muy difíciles de diagnosticar una vez que llegan al spooler
Un bucle de impresión mínimo
El caso de uso funcional más simple carga un archivo, abre un trabajo de impresión, itera las páginas y se cierra. El único detalle complicado es que Printer.NewPage no debe llamarse antes de la primera página, de ahí la bandera FirstPage. La transferencia de StretchDIBits pasa por GetDIBSizes y GetDIB para extraer los bits independientes del dispositivo desde el identificador del mapa de bits, y luego los pinta en el lienzo de la impresora al tamaño completo de la página:
procedure PrintPdfFile(const FileName: string);
var
Pdf: TPdf;
I: Integer;
Bitmap: TBitmap;
InfoHeaderSize, ImageSize: DWORD;
InfoHeader: PBitmapInfo;
Image: Pointer;
FirstPage: Boolean;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True;
if not Pdf.Active then
Exit; // la carga falló silenciosamente; salir
Printer.Title := Pdf.Title;
Printer.BeginDoc;
try
FirstPage := True;
for I := 1 to Pdf.PageCount do
begin
if FirstPage then
FirstPage := False
else
Printer.NewPage;
Pdf.PageNumber := I;
// Renderizar a la resolución de la impresora; rePrinting ajusta la ruta de renderizado
Bitmap := Pdf.RenderPage(
0, 0,
Printer.PageWidth,
Printer.PageHeight,
ro0,
[rePrinting]
);
try
GetDIBSizes(Bitmap.Handle, InfoHeaderSize, ImageSize);
InfoHeader := AllocMem(InfoHeaderSize);
try
Image := AllocMem(ImageSize);
try
GetDIB(Bitmap.Handle, 0, InfoHeader^, Image^);
StretchDIBits(
Printer.Canvas.Handle,
0, 0, Printer.PageWidth, Printer.PageHeight,
0, 0, Bitmap.Width, Bitmap.Height,
Image, InfoHeader^, DIB_RGB_COLORS, SRCCOPY
);
finally
FreeMem(Image);
end;
finally
FreeMem(InfoHeader);
end;
finally
Bitmap.Free;
end;
end;
finally
Printer.EndDoc;
end;
finally
Pdf.Active := False;
Pdf.Free;
end;
end;
Pasar Printer.PageWidth y Printer.PageHeight como las dimensiones del mapa de bits significa que renderiza al tamaño de píxel nativo de la impresora, lo que ya tiene en cuenta el DPI del dispositivo. La llamada a StretchDIBits luego mapea esos píxeles 1:1 en la página. Esto le brinda la mejor fidelidad posible sin ninguna aritmética explícita de DPI, pero solo funciona cuando la página del PDF y el papel físico resultan tener el mismo tamaño. Cuando difieren, necesita un escalado explícito
Escalado cuando los tamaños de la página y el papel difieren
Una página PDF en A4 vertical no se ajusta automáticamente a una impresora de tamaño Carta estadounidense (US Letter), y una página horizontal que se envía a una impresora con orientación vertical se recortará. El enfoque estándar es calcular un factor de escala uniforme a partir de la relación entre los píxeles de la impresora y los puntos del PDF, para luego aplicarlo a ambas dimensiones y así preservar la relación de aspecto. Pdf.PageWidth y Pdf.PageHeight exponen las dimensiones actuales de la página en puntos, donde un punto equivale a 1/72 de pulgada. Multiplicar por un DPI objetivo y dividir por 72 lo convierte a píxeles en esa resolución. Tome el Min de las relaciones X e Y para obtener la escala más grande que todavía quepa dentro del área imprimible:
// Ajustar la página del PDF al área imprimible, preservando la relación de aspecto
var
ScaleX, ScaleY, Scale: Double;
DestWidth, DestHeight: Integer;
Dpi: Integer;
begin
Dpi := 300; // resolución de renderizado objetivo
Pdf.PageNumber := PageIndex;
ScaleX := Printer.PageWidth / (Pdf.PageWidth * Dpi / 72);
ScaleY := Printer.PageHeight / (Pdf.PageHeight * Dpi / 72);
Scale := Min(ScaleX, ScaleY);
// Limitar a 1.0 solo para encoger y ajustar (sin ampliación)
if Scale > 1.0 then Scale := 1.0;
DestWidth := Round(Pdf.PageWidth * Dpi / 72 * Scale);
DestHeight := Round(Pdf.PageHeight * Dpi / 72 * Scale);
Bitmap := Pdf.RenderPage(0, 0, DestWidth, DestHeight, ro0,
[rePrinting, reAnnotations]);
// ... transferir con StretchDIBits como se mostró anteriormente
end;
Renderizar a Dpi = 300 es adecuado para la mayoría de las impresoras de oficina. A 600 DPI, el mapa de bits para una sola página A4 llega a aproximadamente 34 megapíxeles, lo que equivale a unos 100 MB como un mapa de bits de 32 bits; la ganancia en calidad para documentos de texto ordinarios es mínima y el costo de memoria por página es significativo. Reserve los 600 DPI para tiendas de impresión o dibujos técnicos con muchos vectores donde realmente importe
La bandera reAnnotations en el segundo bloque de código es independiente de rePrinting. Inclúyala cuando el usuario espere que los sellos, resaltados y cuadros de comentarios aparezcan en el papel. Omítala para una salida de solo contenido. Ambas banderas se pueden combinar libremente
Rotación de página
PDFium almacena la rotación de la página en el PDF como una entrada /Rotate, accesible a través de Pdf.PageRotation, que devuelve un valor TRotation (ro0, ro90, ro180, ro270). El sistema de coordenadas de la impresora invierte las rotaciones de 90 y 270 grados con respecto a la pantalla. Si pasa el valor en bruto de PageRotation directamente a RenderPage sin ningún ajuste, las páginas horizontales incrustadas en un documento vertical se imprimirán al revés en la mayoría de los controladores de impresora de Windows. La solución es un simple intercambio antes de la llamada de renderizado: asigne ro90 a ro270 y ro270 de vuelta a ro90, dejando ro0 y ro180 sin cambios
Verifique este comportamiento en su impresora objetivo específica antes del envío. El comportamiento del controlador con respecto a la rotación no es uniforme entre los proveedores, y algunos controladores aplican su propia corrección de rotación a nivel de GDI. Si observa una doble rotación, elimine el intercambio; si no observa ninguna corrección en absoluto, agréguelo. Un documento de orientación mixta con páginas que alternan entre vertical y horizontal es la forma más rápida de detectar cualquiera de los modos de fallo durante las pruebas
Manejo de memoria a lo largo de un trabajo de impresión extenso
Cada llamada a RenderPage asigna un nuevo TBitmap del cual el llamador es propietario y que debe liberar. En el bucle anterior, el bloque try/finally Bitmap.Free maneja esto correctamente para una página a la vez. No acumule mapas de bits a través de las páginas: un renderizado a 300 DPI de un documento de 200 páginas consumiría gigabytes antes de que la primera página llegue al spooler. Libere cada mapa de bits antes de avanzar a la siguiente página
El par AllocMem / FreeMem dentro del bloque de transferencia sigue la misma regla. GetDIBSizes le indica cuánta memoria necesitan el encabezado DIB y los datos de píxeles; usted asigna, llena, pinta y libera, todo dentro del alcance de una página. Permitir que cualquiera de los bloques tenga fugas provocará que el trabajo de impresión agote el montón del proceso en documentos de más de unas pocas docenas de páginas
Si necesita ejecutar trabajos de impresión en un hilo de fondo, mantenga TPdf y todas las llamadas de impresora de VCL en el mismo hilo. TPdf en sí no es seguro para hilos entre instancias que comparten el estado global del DLL de PDFium; el modelo más seguro es un TPdf por hilo, donde cada uno carga su propia copia del archivo
La API de renderizado y documentos que se muestra aquí es parte de PDFium Component para Delphi y C++Builder