Representar una página PDF en un contexto de dispositivo de Windows para la vista previa de impresión pone tres sistemas de coordenadas en la misma línea de código, y rara vez coinciden. La página PDF se mide en puntos con el origen en la esquina inferior izquierda. El DC de pantalla se mide en píxeles con el origen en la esquina superior izquierda y un factor de zoom que usted elige. El DC de la impresora, el que se supone que debe predecir la vista previa, mide píxeles a la resolución del dispositivo pero coloca su origen en la esquina del área imprimible, no en la esquina de la hoja. Si alguno de ellos es incorrecto, la vista previa se ve bien mientras que la página impresa sale desplazada, escalada o recortada por un borde. El síntoma habitual es un formulario con borde que se previsualiza centrado y se imprime con las líneas superior e izquierda cortadas, porque la impresora láser no puede poner tinta en los milímetros exteriores y nadie se lo indicó a la vista previa. losLab PDF Library (PDF Library for Delphi) cubre toda la ruta con llamadas de representación de contexto de dispositivo, una capa de configuración de impresora virtual y mapas de bits de vista previa generados a partir de las métricas propias de la impresora, la parte que hace que la vista previa sea honesta respecto de ese margen
La geometría del papel no es la geometría imprimible
Dos rectángulos describen cualquier destino de impresión, y el desplazamiento entre ellos es donde viven la mayoría de los errores de vista previa. El rectángulo del papel es la hoja física. El rectángulo imprimible es la región más pequeña a la que realmente puede llegar el motor de impresión, insertada por un margen de hardware que difiere según el modelo de impresora y a veces según la bandeja. La capa de impresión de la biblioteca mide ambos. La clase subyacente TPLPrinter expone PageWidth y PageHeight para el área imprimible, FullPageWidth y FullPageHeight para la hoja completa, y PrintOffsetX con PrintOffsetY para la separación entre sus orígenes, todo en píxeles de dispositivo a la resolución que informa GetDPI. Una vista previa honesta reduce esos mismos valores a la resolución de pantalla en vez de pintar la página en cualquier rectángulo que tenga el control. Omitir ese paso hace que la vista previa suponga silenciosamente un margen cero, el único valor que ninguna impresora real utiliza
Vista previa de pantalla mediante RenderPageToDC
Para un control de vista previa en pantalla, RenderPageToDC(DPI, Page, DC) dibuja una página del documento cargado directamente en cualquier contexto de dispositivo GDI, ya sea un lienzo de TPaintBox, un mapa de bits fuera de pantalla o un DC de metarchivo. El argumento DPI establece el zoom. 96 aproxima una vista al 100 % en una pantalla clásica, y duplicarlo duplica el tamaño representado
procedure TPreviewForm.PreviewBoxPaint(Sender: TObject);
begin
// estos tres son estado sticky de la librería, no parámetros por llamada:
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 representación de DC se controla mediante estado persistente de la biblioteca, no mediante parámetros por llamada. SetRenderDCOffset, SetRenderDCErasePage y SetRenderCropType persisten hasta que algo los cambia, por lo que un bucle de miniaturas que se ejecuta después de que el usuario ajustó la vista con zoom hereda cualquier desplazamiento o recorte que haya dejado la ruta de código anterior. El síntoma es una vista previa que se desplaza solo en secuencias específicas de navegación, algo tan miserable de reproducir como puede ser un error. Establecer todo el estado pertinente al principio del controlador de pintura, como arriba, no cuesta nada y elimina toda la clase de problemas. Cerca se esconde un segundo multiplicador. La resolución de salida efectiva es la escala de representación multiplicada por el argumento DPI y, aunque SetRenderScale se establece de manera predeterminada en 1.0, también persiste una vez cambiado, así que una función de exportación que la aumentó reescala silenciosamente toda vista previa posterior hasta que algo la restablece
Los visores con desplazamiento y repintados parciales tienen una variante dedicada. RenderPageToDCClip acepta una especificación de recorte junto con el contexto de dispositivo, por lo que invalidar una franja de la ventana vuelve a pintar solo esa franja en lugar de volver a rasterizar la página completa. Con zoom alto en páginas de gran formato, esa es la diferencia entre un visor que sigue la barra de desplazamiento y uno que deja manchas detrás de ella
Un trabajo de impresión que coincide con la vista previa
El lado de impresión funciona mediante una impresora virtual. NewCustomPrinter clona una impresora del sistema en una configuración privada de la biblioteca y SetupPrinter ajusta ese clon sin tocar el DevMode de toda la máquina: el papel se indica como configuración 1 (una constante DMPAPER_*) y la orientación como configuración 11. La ventaja es el aislamiento. Un servicio puede imprimir etiquetas A4 mientras la impresora predeterminada del host permanece en Carta, y luego no hay nada que restaurar
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'); // ajusta al papel, autorrotar + centrar
Pdf.PrintDocument(Virt, 1, Pdf.PageCount, Opt);
finally
Pdf.Free;
end;
end;
PrintOptions merece una lectura cuidadosa. Devuelve un identificador de opciones que se debe pasar a PrintDocument o PrintPages; no es estado ambiental. Crear las opciones y luego olvidar pasar el identificador falla silenciosamente. El trabajo se imprime con los valores predeterminados y nadie lo nota hasta que se esperaba una política de ajustar al papel y una página demasiado grande sale recortada. El argumento de escalado de página es donde reside esa política. Sin escalado se conserva la exactitud dimensional, lo que importa para formularios que se miden con una regla. Ajustar al papel reescala todo a la hoja. Reducir páginas grandes deja intactas las páginas normales e interviene solo cuando una página supera el área imprimible, que suele ser el valor predeterminado correcto para un conjunto de documentos mixto. El indicador de rotación automática y centrado maneja las páginas horizontales sin una segunda ruta de código
Las aplicaciones que ya administran una TPrinter mediante el flujo de diálogo de VCL pueden entregarla directamente. PrintDocumentToPrinterObject y PrintPagesToPrinterObject aceptan la instancia de TPrinter configurada, lo que mantiene el diálogo de impresión estándar como superficie de configuración para el usuario mientras la biblioteca maneja la representación de las páginas. Mezclar ambos enfoques en una sola ruta de código suele reintroducir la deriva geométrica que el resto de este trabajo pretendía eliminar, así que elija uno. La ruta de impresora virtual conviene a servicios sin supervisión; la ruta de TPrinter conviene a aplicaciones interactivas
La salida selectiva funciona de la misma manera. PrintPages acepta una cadena de rango, por lo que pasar el nombre de la impresora virtual, '2-5,12' y el identificador de opciones imprime las páginas 2 a 5 y 12 con el contrato geométrico intacto, y la misma sintaxis controla las variantes de impresión a archivo. Esas variantes de archivo son la respuesta práctica para un entorno sin supervisión sin dispositivo físico conectado: probar por regresión la geometría de impresión en un servidor de compilación que no tiene ninguna cola de controlador. Represente el mismo documento mediante las mismas opciones en un artefacto de archivo en cada compilación, y una regresión de geometría se convierte en una diferencia en vez de un reporte de cliente tres semanas después
Mapas de bits de vista previa con las métricas de la propia impresora
Una vista previa representada a 96 DPI contra un tamaño de página supuesto responde la pregunta equivocada. Muestra cómo se ve la página, no lo que esta impresora pondrá en este papel. GetPrintPreviewBitmapToString cubre esa brecha construyendo la vista previa a partir de la misma impresora personalizada y el mismo identificador de opciones que el trabajo final, de modo que el tamaño de papel, la orientación, la política de escalado, la rotación y el desplazamiento de hardware alimentan el mapa de bits. 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 mapa de bits. 1200 píxeles se mantiene nítido para un cuadro de diálogo de vista previa y conserva un uso moderado de memoria incluso para dibujos de ingeniería de tamaño E, donde una representación de resolución completa a los 600 DPI de la impresora alcanzaría gigabytes
Recordar las elecciones de impresora del usuario
Los diálogos de impresión que olvidan su configuración entre sesiones generan sus propios tickets de soporte. El par DevMode, GetPrinterDevModeToString y SetPrinterDevModeFromString, serializa la configuración completa del controlador de una impresora en una cadena opaca que se puede guardar en las preferencias del usuario y restaurar en la siguiente sesión, incluidas las opciones específicas del controlador que ninguna API genérica se molesta en modelar. Persista la impresora por nombre desde GetPrinterNames, nunca por índice de lista. El orden de los índices cambia cada vez que se agrega o elimina una impresora, por lo que un índice guardado apunta silenciosamente al dispositivo equivocado la próxima vez que cambia la lista. GetDefaultPrinterName cubre la alternativa cuando el dispositivo recordado ha desaparecido por completo
La selección de bandeja completa la historia de persistencia. GetPrinterBins informa las fuentes de papel que expone un controlador, lo que importa para flujos de trabajo de papelería membretada en los que la página uno sale de la bandeja de membrete y el resto de papel común. Esa es una política que los usuarios esperan que la aplicación recuerde junto con todo lo demás, y un trabajo de impresión que llega al material equivocado se interpreta como un error aunque cada byte del PDF haya sido correcto
Mantenga un solo motor para vista previa e impresión
Una última decisión gobierna discretamente la fidelidad. La selección de motor de representación se aplica tanto a destinos de pantalla como de impresora, por lo que la tentación es previsualizar con un motor rápido e imprimir con uno preciso. Resístala. Manejar la vista previa y el trabajo mediante motores diferentes reintroduce exactamente la deriva de fidelidad que una vista previa fiel a la impresora se creó para eliminar, y lo hace de una forma que solo aparece en el papel. Las compensaciones entre los motores integrados, Cairo y PDFium se analizan en representación PDF con varios motores en Delphi; elija uno y úselo en ambos lados
Los documentos demasiado grandes para cargarse cómodamente antes de imprimir pueden abrirse mediante la ruta de acceso directo descrita en combinación, división y acceso directo de PDF grandes, que representa páginas en un contexto de dispositivo desde un identificador de archivo sin construir el árbol del documento. La referencia completa de la API de impresión se encuentra en la página de producto de losLab PDF Library for Delphi