A veces el entregable no es un documento, es una imagen de una tabla. Un bloque de resumen en un correo de estado, un panel de KPI renderizado en un tablero, una miniatura junto a un resultado de búsqueda: todos quieren las celdas y ninguno quiere papel. TXLSCellImageExporter en HotXLS toma un rectángulo de celdas clásico o XLSX y produce un PNG o JPEG compacto sin tamaño de página, ni márgenes, ni encabezados ni pies, ni títulos de impresión ni saltos de página. La resolución, la escala, el formato y la calidad JPEG son configurables, los objetos, las líneas de cuadrícula y los bordes de celda tienen interruptores independientes, el fondo puede ser un color o transparente, y la escritura del archivo pasa por un reemplazo atómico en la misma carpeta que deja un destino existente intacto si algo falla
La razón de que esto necesite su propio exportador y no una bandera en la vía de impresión es que la paginación no es una capa opcional que puedan apagar. Es la cosa para la que existe el pipeline de páginas
Por qué no renderizar el rango por el pipeline de impresión
Porque el pipeline de impresión inserta una página entre ustedes y las celdas. El tamaño de papel decide cuánto cabe, los márgenes empujan el contenido hacia adentro, los encabezados y pies ocupan bandas que no pidieron, los títulos de impresión repiten filas que ya tienen, y los saltos de página parten el rango. Un bloque de resumen que casualmente cruza un salto sale como dos imágenes con la fila interesante cortada a la mitad. Pueden compensar todo eso configurando un tamaño de página personalizado que coincida exactamente con el rango, y la gente lo hace, pero eso significa recalcular la geometría del papel cada vez que el rango cambia, y aun así deja la banda de encabezado y la lógica de títulos de impresión en el camino
El exportador de celdas mide el rectángulo, asigna un bitmap de exactamente ese tamaño, dibuja las celdas dentro y codifica. No hay página, así que no hay nada que configurar fuera. Para los casos donde sí quieren papel, la vía de exportación PDF es la herramienta correcta y se cubre en el artículo de exportación PDF de hojas
Medir antes de renderizar
Measure devuelve las dimensiones en píxeles que produciría la configuración actual sin codificar nada. Eso importa por dos razones. Una plantilla de HTML o de correo normalmente necesita las dimensiones de la imagen antes de que exista, para poder reservar la caja y evitar desplazamientos de maquetación. Y un servicio que renderiza rangos elegidos por usuarios necesita una forma de rechazar una solicitud absurda antes de asignar para ella
uses
lxHandleX, lxPagination;
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Exporter: TXLSCellImageExporter;
Summary: TXLSXRange;
W, H, Bytes: Integer;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarter.xlsx');
Sheet := Book.Sheets.ByPos[0];
Summary := Sheet.Range['A1:F20'];
Exporter := TXLSCellImageExporter.Create;
try
Exporter.ImageFormat := xpifPng; // PNG mantiene nítidos los trazos finos
Exporter.DPI := 96;
Exporter.Scale := 2.0; // salida con densidad retina
Exporter.IncludeGridlines := False;
Exporter.IncludeCellBorders := True;
Exporter.TransparentBackground := True;
Exporter.MaxPixels := 40 * 1000 * 1000;
Exporter.MaxBytes := 8 * 1024 * 1024;
if not Exporter.Measure(Summary, W, H) then
raise Exception.Create('range exceeds the configured budget');
// W y H ya se conocen; reservar la caja de maquetación antes de codificar
Bytes := Exporter.Save(Summary, 'summary.png');
if Bytes <= 0 then
raise Exception.Create('image export failed, previous file kept');
finally
Exporter.Free;
end;
finally
Book.Free;
end;
end;
Presupuestos, porque la escala multiplica
MaxPixels y MaxBytes no son decoración defensiva. El recuento de píxeles crece con el cuadrado del factor de escala y con el cuadrado de la razón de resolución, así que un rango que es un razonable 1200 por 800 a 96 DPI se vuelve de unos 47 megapíxeles a 600 DPI, y un usuario que selecciona todo el rango usado en lugar de un bloque de resumen añade otro orden de magnitud encima. Sin tope el modo de fallo es una asignación que el proceso no puede satisfacer, lo que tumba todo lo demás que ese proceso estuviera haciendo
Con un tope la solicitud falla y quien llama elige: rechazar, reducir la escala o estrechar el rango. Es una posición mucho mejor para un servidor de reportes, y es el mismo razonamiento detrás de los presupuestos explícitos del decodificador de metarchivos descrito en el artículo del decodificador acotado de EMF y WMF
Reemplazo atómico y por qué importa la carpeta
Save a un nombre de archivo no escribe sobre el destino. Escribe un archivo temporal en la misma carpeta, codifica dentro de él y solo entonces reemplaza el destino. Si la codificación falla, si el presupuesto se excede a mitad de camino, o si el proceso muere, la imagen anterior sigue ahí y sigue siendo válida. Un tablero que regenera sus mosaicos en un horario por lo tanto nunca muestra un PNG truncado, que es el síntoma usual de una escritura ingenua que abre el destino y empieza a verter
El detalle de la misma carpeta no es casual. Un reemplazo atómico solo es atómico dentro de un volumen, porque entre volúmenes el sistema operativo tiene que copiar y después borrar, lo que reintroduce la ventana que intentaban cerrar. Cualquier implementación de este patrón que ponga su archivo temporal en el directorio temporal del sistema no es atómica en una máquina donde la salida vive en otra unidad
Los eventos de pintura dibujan sobre el canvas real
Tanto el exportador de rangos como el exportador de páginas exponen eventos de pintura al inicio y al final, y reciben un contexto completo de solo lectura y no solo un handle de canvas. TXLSPagePaintContext lleva el canvas vivo, los límites en píxeles, el tamaño de página en puntos, la resolución y la escala realmente en uso, el número de página del documento, el número de página dentro de la hoja, el recuento total de páginas, el nombre de la hoja y la hoja de origen en las variantes clásica y XLSX. Es suficiente para dibujar una marca de agua que escale correctamente, o un sello de página que sepa dónde está en la corrida
procedure TReportJob.StampDraft(Sender: TObject;
const AContext: TXLSPagePaintContext);
begin
// Consciente de la escala, así el sello se ve igual a 1x y a 3x
AContext.Canvas.Font.Height := Round(-48 * AContext.Scale);
AContext.Canvas.Font.Color := clSilver;
AContext.Canvas.Brush.Style := bsClear;
AContext.Canvas.TextOut(AContext.Bounds.Left + Round(24 * AContext.Scale),
AContext.Bounds.Top + Round(24 * AContext.Scale), 'DRAFT');
end;
Exporter.AfterPaint := Job.StampDraft;
Tres comportamientos valen la pena apalancar. Los eventos se disparan exactamente una vez por cuadro renderizado, incluido cada cuadro de un TIFF multipágina, así que un contador incrementado en el manejador es confiable. Se mantienen en silencio durante la medición, así que un manejador con un efecto secundario no corre dos veces por una salida. Y si el evento inicial lanza una excepción, el evento final no se dispara y no se escriben bytes parciales de imagen, así que una excepción en su propio código de dibujo no puede producir un archivo medio sellado
Elegir el formato
PNG para todo lo que tenga mucho texto. JPEG aplica una transformación por bloques que produce un halo visible alrededor de trazos finos de alto contraste, que es exactamente lo que son los bordes de celda y el texto pequeño, y los artefactos sobreviven en configuraciones de calidad donde una fotografía se ve perfecta. JPEG gana su lugar cuando el rango está dominado por fotografías incrustadas y el tamaño del archivo importa más que la fidelidad de los bordes. Los fondos transparentes exigen PNG, ya que JPEG no tiene canal alfa, así que un mosaico destinado a posarse sobre una superficie de color ya tomó la decisión por ustedes
Si su rango contiene celdas combinadas, cotejen la salida contra la hoja: las regiones combinadas interactúan con los anchos de columna de maneras que sorprenden a la gente, y las reglas de maquetación se cubren en el artículo de celdas combinadas y plantillas de reportes. HotXLS lee y escribe XLS, XLSX, ODS y CSV desde Delphi y C++Builder sin dependencia de Excel, y toda la superficie del exportador está documentada en la página del producto HotXLS Delphi spreadsheet component