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 a través de tres llamadas: PrintLoadedDocument controla un TPrinter de VCL, RenderLoadedPageToDC pinta una página directamente en cualquier HDC, y RenderLoadedPageToClipboard copia una página como un mapa de bits o un metarchivo vectorial. HotPDF es un componente PDF nativo de VCL para Delphi y C++Builder, y agregó esta ruta de impresión de primer nivel en la v2.331.0, refinándola en la v2.348.0, de modo que usted ya no tiene que implementar manualmente un bucle de TPrinter del mapa de bits. Las tres llamadas dibujan el contenido vectorial de la página directamente donde corresponde, por lo que la impresión se mantiene tan nítida como la impresora pueda renderizarla

Una página PDF es un programa de operadores de dibujo vectorial (rutas, texto e imágenes ejecutados contra el modelo gráfico ISO 32000-1), no una cuadrícula de píxeles. La forma obvia de imprimirla es rasterizar la página a un mapa de bits y transferir (blit) ese mapa de bits al lienzo de la impresora; esto funciona, pero descarta lo único en lo que una 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 ajustarse llega difuminado, con bordes de texto dentados e interpolación visible. RenderLoadedPageToDC toma un mejor camino: reproduce el flujo de contenido de la página como llamadas GDI emitidas directamente en el contexto de dispositivo de destino, por lo que el texto vectorial y el arte lineal llegan a la impresora como vectores y se rasterizan una sola vez, por la impresora, a su propia resolución

¿Cómo se imprime un PDF cargado en una impresora física en Delphi?

HotPDF imprime un documento cargado con PrintLoadedDocument(Printer, Options), que toma un TPrinter de VCL asignado y un registro THPDFPrintOptions, imprime las páginas seleccionadas y devuelve el conteo de páginas enviadas. Configure primero Printer.PrinterIndex para seleccionar un dispositivo, llene un registro de opciones (la función de clase THPDFPrintOptions.Default le proporciona valores lógicos) y llámela una vez. La biblioteca abre el trabajo con Printer.BeginDoc, recorre el rango de páginas emitiendo un Printer.NewPage entre ellas y lo cierra con Printer.EndDoc, por lo que usted nunca tiene que gestionar el ciclo de vida del trabajo de impresión de forma manual

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 selects the default printer
      Opts := THPDFPrintOptions.Default;  // all pages, 300 DPI, FitPage, colour
      Opts.PageRange := '1,3-5';          // one-based; empty prints every page
      Opts.Copies := 2;
      Pdf.PrintLoadedDocument(Printer, Opts);
    end;
  finally
    Pdf.Free;
  end;
end;

La cadena de caracteres PageRange se basa en 1 y se lee de la misma manera que 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 completo. Copies se mapea directamente a Printer.Copies. Sin embargo, sea honesto consigo mismo sobre Collate: debido a que VCL expone la intercalación de manera inconsistente en las distintas versiones de Delphi, HotPDF establece el conteo de copias y deja la intercalación al valor predeterminado del controlador de la impresora, por lo que si necesita un orden de intercalación garantizado, configúrelo en el controlador a través de TPrinterSetupDialog en lugar de confiar en que la bandera lo asigne

¿Cómo determinan la escala de impresión DPI y FitPage?

FitPage y DPI interactúan de una manera que vale la pena entender antes de realizar la entrega. 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 renderiza a la escala que hace que la página llene el papel. En ese modo, Options.DPI queda anulado efectivamente por la escala de ajuste, ya que la salida tiene que coincidir con las dimensiones físicas de la página de la impresora. Con FitPage en False, HotPDF renderiza cada página 1:1 a exactamente el valor de Options.DPI, mapeando 72 unidades PDF a píxeles de dispositivo DPI, que es lo que usted desea cuando la página de origen y el papel ya tienen el mismo tamaño y requiere una resolución exacta. Recurra a FitPage cuando el tamaño de la página de origen y el tamaño de la bandeja difieran; recurra a 1:1 cuando coincidan y desee una escala determinista

Dos opciones añadidas en la v2.348.0 corrigen las impresiones que se ven casi bien. Cada impresora física tiene un margen físico (un borde no imprimible al que el hardware no puede llegar) y, por defecto, el origen de la página queda dentro de él, por lo que una página con sangrado completo pierde una franja en dos bordes. Establezca IgnoreHardMargin en True y HotPDF consultará los valores PHYSICALOFFSETX y PHYSICALOFFSETY de la impresora, y luego desplazará el origen del dispositivo para cancelar ese desfase de modo que el origen de la página se asocie con la esquina real del papel. La segunda opción, AutoRotate, está activada de forma predeterminada: lee el MediaBox de cada página y, cuando una página es más ancha que alta, cambia la impresora a orientación horizontal para que la página ancha se imprima sin recortes en lugar de recortarse a vertical. AutoRotate se desactiva en el momento en que usted configura Orientation de manera explícita, por lo que una elección de orientación deliberada siempre prevalece

¿Cómo se renderiza una página PDF en cualquier contexto de dispositivo?

RenderLoadedPageToDC(PageIndex, DC, DPI, W, H) es la primitiva debajo del bucle de impresión, y es ú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 en memoria fuera de pantalla, el lienzo de un control en un visor personalizado o una superficie de vista previa de impresión. PageIndex se basa en cero, W and 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 a su DC. Vale la pena conocer un comportamiento deliberado: no dibuja ningún relleno de fondo. El llamador es propietario de la superficie, por lo que si desea papel blanco detrás de la página, debe llenar el DC usted mismo antes de renderizar, que es exactamente lo que le permite componer una página sobre un lienzo existente cuando así lo desee

// Draw page 1 into an off-screen 24-bit surface at 150 DPI
var
  Surface: TBitmap;
begin
  Surface := TBitmap.Create;
  try
    Surface.PixelFormat := pf24bit;
    Surface.SetSize(1275, 1650);           // 8.5 x 11 in at 150 DPI
    Surface.Canvas.Brush.Color := clWhite;
    Surface.Canvas.FillRect(Surface.Canvas.ClipRect);  // caller paints the background
    Pdf.RenderLoadedPageToDC(0, Surface.Canvas.Handle, 150, Surface.Width, Surface.Height);
    // Surface now holds the composited page, ready to blit or save
  finally
    Surface.Free;
  end;
end;

Cuando solo necesite un TBitmap terminado (una miniatura, una exportación de imagen o una vista previa para almacenar en caché), recurra en su lugar a RenderLoadedPageToBitmap, cubierto en el artículo complementario sobre renderización de páginas PDF a un mapa de bits. La diferencia es quién posee la superficie: la llamada de mapa de bits asigna y devuelve un búfer de píxeles, mientras que RenderLoadedPageToDC dibuja en un contexto de dispositivo que usted ya posee, que es exactamente lo que le entrega el lienzo de la impresora o un controlador de dibujo. Ambos comparten el mismo intérprete de flujo de contenido y motor de glifos incrustados, por lo que el texto resulta con los contornos incrustados exactos en cualquiera de los dos casos

¿Cómo se pega una página PDF en Word o en un editor de imágenes?

RenderLoadedPageToClipboard(PageIndex, DPI, AsMetafile) coloca una página en el portapapeles de Windows, lista para pegarse en Word, PowerPoint, un correo electrónico o un editor de imágenes. La bandera AsMetafile elige el formato, y la elección importa más de lo que parece. Si pasa False, HotPDF coloca un mapa de bits de dispositivo de 24 bits (CF_BITMAP) renderizado a su DPI: una cuadrícula fija de píxeles que se pega en cualquier lugar pero que se pixela cuando el destinatario la escala hacia arriba. Si pasa True, coloca un metarchivo mejorado (CF_ENHMETAFILE) en el cual el texto de la página y el arte vectorial viajan como comandos de dibujo GDI, por lo que el resultado pegado se mantiene nítido en cualquier nivel de zoom y se adapta a cualquier tamaño que le asigne el documento de destino

// Copy page 2 as a scalable vector metafile for pasting into Word
Pdf.RenderLoadedPageToClipboard(1, 300, True);

// Copy the same page as a fixed 300-DPI bitmap instead
Pdf.RenderLoadedPageToClipboard(1, 300, False);

La regla general es simple. Si una persona va a pegar la página en un documento y podría cambiar su tamaño (una figura en un reporte, una cláusula agregada en el borrador de un contrato), use the metafile, porque el texto vectorial se imprime y se amplía de manera limpia. Si el destino solo entiende datos rasterizados, o si desea específicamente una instantánea fija a una resolución conocida, use el mapa de bits. Para extraer las palabras de una página en lugar de una imagen de ellas, ese es un trabajo completamente diferente, gestionado por la API de extracción de texto, que devuelve los caracteres y sus posiciones en lugar de una imagen renderizada

Dónde se detiene la ruta de impresión

Todo en esta página es exclusivo para Windows, y vale la pena decirlo claramente. PrintLoadedDocument, RenderLoadedPageToDC y RenderLoadedPageToClipboard están basados en TPrinter, el modelo de contexto de dispositivo GDI, el portapapeles de Windows y los metarchivos mejorados; todas ellas son estructuras de Win32 y Win64. En otras plataformas de destino, el resto de HotPDF sigue creando, editando y leyendo PDF, pero este entorno de portapapeles e impresión respaldado por GDI no está disponible. El renderizador también se dirige al subconjunto común de documentos de texto, rutas e imágenes, por lo que las pruebas de diseño con muchos degradados y la impresión en color gestionada por ICC se imprimen como una vista previa fiel en lugar de una prueba colorimétrica, el mismo límite que tiene el renderizador de mapas de bits

Para la impresión por lotes de muchos archivos, cargue e imprima cada documento a su vez y observe el consumo de memoria: PrintLoadedDocument renderiza una página a la vez, pero el grafo del documento cargado en sí reside en la memoria durante todo el trabajo. Cuando controle una cola de impresión de cientos de archivos grandes, la API de archivos directos (Direct File API) orientada a flujos para flujos de trabajo de PDF grandes se complementa bien con la ruta de impresión (divida, fusione o seleccione las páginas que necesita en el disco primero, luego cargue solo el resultado liviano para imprimir). Así es como PrintLoadedDocument, RenderLoadedPageToDC y RenderLoadedPageToClipboard se distribuyen como parte de HotPDF Component para Delphi y C++Builder: una biblioteca nativa de VCL sin dependencias de DLL externas que cubre la creación, edición, extracción de texto y renderización de PDF, y ahora la impresión directa a TPrinter, a cualquier contexto de dispositivo o al portapapeles