El ticket tenía tres líneas: "La vista previa muestra el formulario centrado. La página impresa sale desplazada hacia arriba y a la izquierda, y el borde se recorta en dos lados". No había nada mal en el código de renderizado. La vista previa dibujaba la página relativa a la hoja de papel, mientras que el origen del device context de la impresora está en la esquina del área imprimible, y la impresora láser en cuestión no podía alcanzar los 4.2 mm exteriores de la hoja. Toda característica de impresión en Delphi acaba chocando con esta diferencia, y el momento barato para manejarla es antes de que el primer cliente imprima un formulario con borde. losLab PDF Library (PDFlibPas) cubre todo el camino con llamadas de renderizado a device context, una capa de configuración de impresora virtual y bitmaps de vista previa generados desde las métricas propias de la impresora.
La geometría del papel no es la geometría imprimible
Tres rectángulos describen cualquier destino de impresión, y confundirlos produce exactamente el ticket anterior. El rectángulo de papel es la hoja física. El rectángulo imprimible es la región que el motor de impresión puede alcanzar. El offset entre sus orígenes es el margen de hardware, y cambia por modelo de impresora y a veces por bandeja. La capa de impresión de la biblioteca modela los tres: la clase subyacente TPLPrinter expone PageWidth y PageHeight para el área imprimible, FullPageWidth y FullPageHeight para la hoja, y PrintOffsetX y PrintOffsetY para la brecha, todo en píxeles de dispositivo a la resolución reportada por GetDPI. Una vista previa honesta escala los mismos tres rectángulos a resolución de pantalla en lugar de pintar la página en cualquier rectángulo que tenga el control.
Vista previa en pantalla mediante RenderPageToDC
Para un control de vista previa en pantalla, RenderPageToDC(DPI, Page, DC) dibuja una página del documento cargado directamente sobre cualquier GDI device context — el canvas de un TPaintBox, un bitmap fuera de pantalla o un metafile DC. El argumento DPI establece el zoom: 96 aproxima una vista al 100% en una pantalla clásica, y duplicarlo duplica el tamaño renderizado.
procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
// these three are sticky library state, not per-call parameters:
FPdf.SetRenderDCOffset(FOffsetX, FOffsetY);
FPdf.SetRenderDCErasePage(1);
FPdf.SetRenderCropType(0);
FPdf.RenderPageToDC(FPreviewDpi, FCurrentPage, PreviewBox.Canvas.Handle);
end;
La trampa es que la ruta de renderizado DC se dirige con estado persistente de la biblioteca, no con parámetros por llamada. SetRenderDCOffset, SetRenderDCErasePage y SetRenderCropType persisten hasta cambiarse, de modo que un ciclo de miniaturas que corre después de que el usuario ajustó la vista ampliada hereda cualquier offset o recorte que dejó el camino de código anterior — y el síntoma es una vista previa que deriva solo en secuencias específicas de navegación, algo muy difícil de reproducir. Configurar todo el estado relevante al inicio del paint handler, como arriba, no cuesta nada y elimina toda esa clase de bug. Un segundo multiplicador se esconde cerca: la resolución efectiva de salida es la escala de renderizado por el argumento DPI, y SetRenderScale tiene 1.0 como valor predeterminado pero persiste una vez cambiado, así que una función de exportación que lo ajustó reescala silenciosamente toda vista previa posterior.
Los visores con desplazamiento y repintados parciales tienen una variante dedicada: RenderPageToDCClip toma una especificación de clip junto con el device context, de modo que invalidar una banda de la ventana repinta solo esa banda en lugar de rasterizar de nuevo la página completa. A altos niveles de zoom en páginas de gran formato, esa diferencia es la línea entre un visor que sigue la barra de desplazamiento y uno que deja rastros detrás.
Un trabajo de impresión que coincide con la vista previa
El lado de impresión trabaja mediante una impresora virtual: NewCustomPrinter clona una impresora del sistema en una configuración privada de la biblioteca, y SetupPrinter ajusta ese clon — papel con la configuración 1 (una constante DMPAPER_*) y orientación con la configuración 11 — sin tocar el DevMode de toda la máquina. Un servicio puede imprimir etiquetas A4 mientras la impresora predeterminada del host permanece en Letter, y no hay nada que restaurar después.
var
Pdf: TPDFlib;
Virt: WideString;
Opt: Integer;
begin
Pdf := TPDFlib.Create;
try
if Pdf.LoadFromFile('report.pdf', '') <> 1 then
raise Exception.Create('load failed');
Virt := Pdf.NewCustomPrinter(Pdf.GetDefaultPrinterName);
Pdf.SetupPrinter(Virt, 1, 9); // setting 1 = paper, DMPAPER_A4
Pdf.SetupPrinter(Virt, 11, 1); // setting 11 = orientation, 1 = portrait
Opt := Pdf.PrintOptions(1, 1, 'Monthly Report'); // fit to paper, auto-rotate + center
Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
finally
Pdf.Free;
end;
end;
PrintOptions merece una lectura cuidadosa: devuelve un handle de opciones que debe pasarse a PrintDocument o PrintPages. No es estado ambiental. Crear opciones y olvidar pasar el handle es un fallo silencioso — el trabajo imprime con valores predeterminados, y nadie lo nota hasta que se esperaba una política fit-to-paper y salió una página sobredimensionada recortada. El argumento de escalado de página lleva la política: sin escalado conserva exactitud dimensional para formularios que se miden con reglas, fit-to-paper reescala todo, y shrink-large-pages interviene solo cuando una página supera el área imprimible — normalmente el valor predeterminado correcto para conjuntos mixtos de documentos. La bandera auto-rotate-and-center maneja páginas horizontales sin una ruta de código separada.
Las aplicaciones que ya administran un TPrinter mediante el flujo de diálogo VCL pueden entregarlo directamente: PrintDocumentToPrinterObject y PrintPagesToPrinterObject aceptan la instancia TPrinter configurada, lo que mantiene el diálogo estándar de impresión como superficie de configuración para el usuario mientras la biblioteca maneja el renderizado de páginas. Los dos enfoques no se mezclan bien en una misma ruta de código — elijan la ruta de impresora virtual para servicios desatendidos y la ruta TPrinter para aplicaciones interactivas, y el contrato de geometría permanece único.
Los entornos desatendidos también obtienen salida sin un dispositivo físico: las llamadas de impresión por rango de páginas tienen variantes print-to-file, la respuesta práctica para probar regresiones de geometría de impresión en un build server sin cola de driver. Rendericen el mismo documento con las mismas opciones hacia un artefacto de archivo en cada build, y una regresión de geometría se vuelve un diff en lugar de un reporte de cliente.
Bitmaps de vista previa con las métricas propias de la impresora
Una vista previa renderizada a 96 DPI contra un tamaño de página asumido responde la pregunta equivocada. GetPrintPreviewBitmapToString construye la vista previa usando la misma impresora personalizada y el mismo handle de opciones que el trabajo final, de modo que tamaño de papel, orientación, política de escalado, rotación y offset de hardware participan todos — lo que vuelve es lo que mostrará la hoja.
procedure ShowPrinterTruePreview(Pdf: TPDFlib; const Virt: WideString; Opt: Integer);
var
Data: AnsiString;
Strm: TMemoryStream;
Bmp: TBitmap;
begin
Data := Pdf.GetPrintPreviewBitmapToString(Virt, 1, Opt, 1200, 0);
Strm := TMemoryStream.Create;
try
Strm.WriteBuffer(PAnsiChar(Data)^, Length(Data));
Strm.Position := 0;
Bmp := TBitmap.Create;
try
Bmp.LoadFromStream(Strm);
PreviewImage.Picture.Assign(Bmp);
finally
Bmp.Free;
end;
finally
Strm.Free;
end;
end;
El argumento MaxDimension limita el borde largo del bitmap: 1200 píxeles es cómodamente nítido para un diálogo de vista previa y mantiene modesta la memoria incluso con planos de ingeniería tamaño E, donde un renderizado a resolución completa con los 600 DPI de la impresora llegaría a gigabytes.
Recordar las elecciones de impresora del usuario
Los diálogos de impresión que olvidan sus configuraciones entre sesiones generan sus propios tickets de soporte. El par DevMode — GetPrinterDevModeToString y SetPrinterDevModeFromString — serializa toda la configuración del driver de una impresora a una cadena opaca que pueden almacenar en preferencias de usuario y restaurar en la siguiente sesión, incluidas opciones específicas del driver que ninguna API genérica modela. Persistan la impresora por nombre desde GetPrinterNames, nunca por índice de lista: el orden de índices cambia cada vez que se agrega o quita una impresora, y GetDefaultPrinterName cubre el fallback cuando el dispositivo recordado desapareció.
La selección de bandeja completa la historia de persistencia: GetPrinterBins informa las fuentes de papel que expone un driver, algo importante para flujos con membrete donde la página uno sale de la bandeja de membrete y el resto de papel normal — una política que los usuarios esperan que la aplicación recuerde junto con todo lo demás.
Preguntas que aparecen en proyectos de impresión
¿Por qué la página impresa está desplazada respecto de mi vista previa?
Casi siempre por el margen de hardware: el origen del DC de impresora es la esquina del área imprimible, no la esquina del papel. Modelen el offset explícitamente en la vista previa, o generen vistas previas con GetPrintPreviewBitmapToString para que la geometría de la impresora quede incorporada.
¿Cómo imprimo una selección de páginas como 2-5 y 12?
PrintPages acepta una cadena de rango: pasen el nombre de la impresora virtual, '2-5,12' y el handle de opciones. La misma sintaxis de rangos impulsa las variantes print-to-file.
¿Pueden la vista previa y el trabajo de impresión usar distintos motores de renderizado?
Pueden, pero no deberían: la selección de motor aplica tanto a destinos de pantalla como de impresora, y mezclar motores reintroduce exactamente la deriva de fidelidad que elimina una vista previa fiel a la impresora. Los intercambios entre los motores integrado, Cairo y PDFium se evalúan en renderizado PDF multi-engine en Delphi.
Los documentos demasiado grandes para cargarse cómodamente antes de imprimir pueden abrirse por la ruta de acceso directo descrita en fusión, división y acceso directo de PDFs grandes, que renderiza páginas hacia un device context desde un handle de archivo sin construir el árbol de documento. La referencia completa de la API de impresión está en la página del producto losLab PDF Library for Delphi.