Artículo técnico

Imprimir un PDF cargado en Delphi con HotPDF y TPrinter

HotPDF imprime un PDF cargado en una impresora física, en cualquier contexto de dispositivo de Windows o en el portapapeles mediante tres llamadas: PrintLoadedDocument maneja un TPrinter de la VCL, RenderLoadedPageToDC pinta una página directamente en cualquier HDC, y RenderLoadedPageToClipboard copia una página como mapa de bits o como metarchivo vectorial. HotPDF es un componente PDF nativo en VCL para Delphi y C++Builder, y agregó esta ruta de impresión de primera clase en la v2.331.0, refinándola en la v2.348.0, así que usted ya no tiene que cablear a mano un bucle de TPrinter alrededor de un renderizador de mapas de bits. Las tres llamadas dibujan el contenido vectorial de la página justo donde importa, así que la impresión queda tan nítida como la impresora pueda representarla

Por qué dibujar una página PDF directo en la impresora y no como mapa de bits

Una página PDF es un programa de operadores vectoriales de dibujo — rutas, texto e imágenes ejecutados contra el modelo gráfico de ISO 32000-1 — y no una grilla de píxeles. La forma obvia de imprimir una es rasterizarla a un mapa de bits y volcarlo sobre el lienzo de la impresora, y funciona, pero desperdicia justo aquello en lo que la impresora es buena. Una impresora láser moderna resuelve 600 o 1200 DPI; un mapa de bits rasterizado a 150 o 300 DPI y luego estirado para encajar llega blando, con bordes de texto dentados e interpolación visible. RenderLoadedPageToDC toma el mejor camino: reproduce el flujo de contenido de la página como llamadas GDI emitidas directamente al contexto de dispositivo de destino, así que el texto y el arte lineal vectoriales llegan a la impresora como vectores y se rasterizan una sola vez, en la impresora, a la resolución propia de la impresora

Diagrama que compara dos rutas de impresión de PDF en Delphi: rasterizar una página de HotPDF a un mapa de bits blando frente a reproducir llamadas GDI vectoriales en el contexto de dispositivo de la impresora para una sola rasterización limpia
RenderLoadedPageToDC reproduce el flujo de contenido como llamadas GDI para que la impresora rasterice el texto y el arte lineal una sola vez a su resolución nativa

Cómo imprimir un PDF cargado en una impresora física desde Delphi

HotPDF imprime un documento cargado con PrintLoadedDocument(Printer, Options), que recibe un TPrinter de la VCL ya asignado y un registro THPDFPrintOptions, imprime las páginas seleccionadas y devuelve la cantidad de páginas enviadas. Establezca primero Printer.PrinterIndex para que haya un dispositivo elegido, llene un registro de opciones — la función de clase THPDFPrintOptions.Default le da valores sensatos — y llámela una vez. La biblioteca abre el trabajo con Printer.BeginDoc, recorre el rango de páginas emitiendo un Printer.NewPage entre páginas y lo cierra con Printer.EndDoc, así que usted nunca administra a mano el ciclo de vida del trabajo de impresión

uses Printers;

var
  Pdf: THotPDF;
  Opts: THPDFPrintOptions;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('invoice.pdf') > 0 then
    begin
      Printer.PrinterIndex := -1;         // -1 selecciona la impresora predeterminada
      Opts := THPDFPrintOptions.Default;  // todas las páginas, 300 DPI, FitPage, color
      Opts.PageRange := '1,3-5';          // base 1; vacío imprime todas las páginas
      Opts.Copies := 2;
      Pdf.PrintLoadedDocument(Printer, Opts);
    end;
  finally
    Pdf.Free;
  end;
end;

La cadena PageRange arranca en 1 y se lee como en un cuadro de diálogo de impresión: '1,3-5' imprime la página 1 y las páginas 3 a 5, y una cadena vacía imprime el documento entero. Copies se corresponde directamente con Printer.Copies. Sea honesto consigo mismo respecto de Collate, eso sí: como la VCL expone el intercalado de forma inconsistente entre versiones de Delphi, HotPDF fija la cantidad de copias y deja el intercalado en el valor predeterminado del controlador de impresora, así que cuando necesite un orden de intercalado garantizado, establézcalo en el controlador mediante TPrinterSetupDialog en lugar de confiar en que el indicador lo lleve

Cómo deciden DPI y FitPage la escala de impresión

FitPage y DPI interactúan de una manera que conviene entender antes de publicar. Con FitPage en True — el valor predeterminado — HotPDF lee el MediaBox de cada página, lo escala a Printer.PageWidth y Printer.PageHeight conservando la relación de aspecto, y representa a la escala que hace que la página llene el papel. En ese modo, la escala de ajuste anula de hecho a Options.DPI, porque la salida tiene que coincidir con las dimensiones físicas de página de la impresora. Con FitPage en False, HotPDF representa cada página 1:1 exactamente a Options.DPI, mapeando 72 unidades PDF a DPI píxeles de dispositivo, que es lo que usted quiere cuando la página de origen y el papel ya tienen el mismo tamaño y le importa una resolución exacta. Recurra a FitPage cuando el tamaño de la página de origen y el de la bandeja difieran; recurra al 1:1 cuando coincidan y quiera un escalado determinista

Dos opciones agregadas en la v2.348.0 arreglan las impresiones que se ven casi bien. Toda impresora física tiene un margen duro — un borde no imprimible al que el hardware no llega — y de forma predeterminada el origen de página cae dentro de él, así que una página a sangre pierde una franja en dos bordes. Ponga IgnoreHardMargin en True y HotPDF consulta los valores PHYSICALOFFSETX y PHYSICALOFFSETY de la impresora, y luego desplaza el origen del dispositivo para cancelar ese desfase, de modo que el origen de página quede en la esquina real del papel. La segunda opción, AutoRotate, viene activada: lee el MediaBox de cada página y, cuando una página es más ancha que alta, pasa la impresora a horizontal para que una página ancha se imprima sin recortes en vez de quedar cortada en vertical. AutoRotate se hace a un lado en cuanto usted establece Orientation de forma explícita, así que una elección deliberada de orientación siempre gana

Diagrama del escalado de impresión de HotPDF en Delphi: la lectura de cada MediaBox, una comprobación de orientación con AutoRotate, el escalado FitPage frente al de DPI fijo, e IgnoreHardMargin cancelando el desfase de hardware de la impresora
FitPage llena la hoja mientras el DPI fijo conserva el escalado uno a uno, e IgnoreHardMargin corre el origen más allá del borde de hardware no imprimible

Cómo representar una página PDF en cualquier contexto de dispositivo

RenderLoadedPageToDC(PageIndex, DC, DPI, W, H) es la primitiva que hay debajo del bucle de impresión, y resulta útil por sí sola. PrintLoadedDocument la llama una vez por página contra el lienzo de la impresora, pero el destino puede ser cualquier HDC de Windows: un DC de memoria fuera de pantalla, el lienzo de un control en un visor propio o una superficie de vista previa de impresión. PageIndex arranca en cero, W y H son las unidades de dispositivo de la superficie de destino, y el renderizador recorre el /Contents de la página y emite llamadas GDI en su DC. Vale la pena conocer un comportamiento deliberado: no dibuja ningún relleno de fondo. La superficie es del llamador, así que si usted quiere papel blanco detrás de la página, llena el DC usted mismo antes de representar, que es justo lo que le permite componer una página sobre un lienzo existente cuando así lo desea

// Dibuja la página 1 en una superficie de 24 bits fuera de pantalla a 150 DPI
var
  Surface: TBitmap;
begin
  Surface := TBitmap.Create;
  try
    Surface.PixelFormat := pf24bit;
    Surface.SetSize(1275, 1650);           // 8.5 x 11 pulgadas a 150 DPI
    Surface.Canvas.Brush.Color := clWhite;
    Surface.Canvas.FillRect(Surface.Canvas.ClipRect);  // el llamador pinta el fondo
    Pdf.RenderLoadedPageToDC(0, Surface.Canvas.Handle, 150, Surface.Width, Surface.Height);
    // Surface ya contiene la página compuesta, lista para volcar o guardar
  finally
    Surface.Free;
  end;
end;

Cuando lo único que necesita es un TBitmap terminado — una miniatura, una exportación de imagen, una vista previa que va a guardar en caché — recurra en cambio a RenderLoadedPageToBitmap, tratado en el artículo complementario sobre representar páginas PDF en un mapa de bits. La diferencia está en quién es dueño de la superficie: la llamada de mapa de bits reserva y devuelve un búfer de píxeles, mientras que RenderLoadedPageToDC dibuja en un contexto de dispositivo que usted ya tiene, que es exactamente lo que le entrega un lienzo de impresora o un manejador de pintado. Ambas comparten el mismo intérprete de flujo de contenido y el mismo motor de glifos incrustados, así que el texto sale con los contornos incrustados exactos en cualquiera de los dos casos

Cómo pegar una página PDF en Word o en un editor de imágenes

RenderLoadedPageToClipboard(PageIndex, DPI, AsMetafile) pone una página en el portapapeles de Windows, lista para pegar en Word, PowerPoint, un correo o un editor de imágenes. El indicador AsMetafile elige el formato, y la elección importa más de lo que parece. Pásele False y HotPDF coloca un mapa de bits de dispositivo de 24 bits (CF_BITMAP) representado a su DPI: una grilla fija de píxeles que se pega en cualquier parte pero se pixela cuando el destinatario la agranda. Pásele True y coloca un metarchivo mejorado (CF_ENHMETAFILE) en el que el texto y el arte vectorial de la página viajan como comandos de dibujo GDI, así que el resultado pegado se mantiene nítido a cualquier zoom y se adapta al tamaño que le dé el documento de destino

// Copia la página 2 como metarchivo vectorial escalable para pegar en Word
Pdf.RenderLoadedPageToClipboard(1, 300, True);

// Copia la misma página como mapa de bits fijo de 300 DPI
Pdf.RenderLoadedPageToClipboard(1, 300, False);

La regla práctica es simple. Si una persona va a pegar la página en un documento y quizá la redimensione — una figura en un informe, una cláusula puesta en el borrador de un contrato — use el metarchivo, porque el texto vectorial se imprime y se amplía con limpieza. Si el destino solo entiende datos ráster, o si usted quiere específicamente una instantánea congelada a una resolución conocida, use el mapa de bits. Sacar las palabras de una página en lugar de una imagen de ellas es otro trabajo por completo, del que se encarga la API de extracción de texto, que devuelve los caracteres y sus posiciones en vez de una imagen representada

Dónde se detiene la ruta de impresión

Todo lo de esta página funciona solo en Windows, y vale la pena decirlo sin rodeos. PrintLoadedDocument, RenderLoadedPageToDC y RenderLoadedPageToClipboard están construidos sobre TPrinter, el modelo de contextos de dispositivo de GDI, el portapapeles de Windows y los metarchivos mejorados — todos constructos de Win32 y Win64. En otros destinos, el resto de HotPDF sigue creando, editando y leyendo PDF, pero esta superficie de impresión y portapapeles respaldada por GDI no está disponible. El renderizador además apunta al subconjunto común de documentos con texto, rutas e imágenes, así que las pruebas de diseño cargadas de degradados y el color gestionado por ICC se imprimen como una vista previa fiel y no como una prueba colorimétrica, el mismo límite que arrastra el renderizador de mapas de bits

Para imprimir por lotes muchos archivos, cargue e imprima cada documento por turno y vigile la factura de memoria: PrintLoadedDocument representa una página por vez, pero el grafo del documento cargado vive en memoria durante todo el trabajo. Cuando maneje una cola de impresión sobre cientos de archivos grandes, la Direct File API en streaming para flujos con PDF grandes combina bien con la ruta de impresión: divida, una o seleccione en disco las páginas que necesita y cargue después solo el resultado liviano para imprimir. Así es como PrintLoadedDocument, RenderLoadedPageToDC y RenderLoadedPageToClipboard vienen incluidos en el HotPDF Delphi Component para Delphi y C++Builder — una biblioteca VCL nativa sin dependencias de DLL externas que cubre la creación, edición, extracción de texto, representación y ahora la impresión de PDF directo a TPrinter, a cualquier contexto de dispositivo o al portapapeles