Articolo tecnico

Esportare celle Excel come immagine unica con HotXLS

A volte il deliverable non è un documento, è un'immagine di una tabella. Un blocco riepilogo in una email di stato, un pannello KPI renderizzato in una dashboard, una miniatura accanto a un risultato di ricerca: tutti vogliono le celle e nessuno vuole carta. TXLSCellImageExporter in HotXLS prende un rettangolo di celle classico o XLSX e produce un solo PNG o JPEG compatto senza formato pagina, senza margini, senza intestazioni o piè di pagina, senza titoli di stampa e senza interruzioni di pagina. Risoluzione, scala, formato e qualità JPEG sono configurabili, oggetti, linee griglia e bordi cella hanno interruttori indipendenti, lo sfondo può essere un colore o trasparente, e la scrittura del file passa attraverso una sostituzione atomica nella stessa cartella che lascia intatto un target esistente se qualcosa fallisce

La ragione per cui questo richiede un proprio esportatore anziché un flag sulla via di stampa è che la paginazione non è uno strato opzionale che si può disattivare. È la cosa per cui esiste la pipeline di pagina

Perché non renderizzare l'intervallo attraverso la pipeline di stampa?

Perché la pipeline di stampa inserisce una pagina tra Lei e le celle. Il formato carta decide quanta parte ci sta, i margini spingono il contenuto verso l'interno, intestazioni e piè di pagina occupano fasce che non ha chiesto, i titoli di stampa ripetono righe che ha già, e le interruzioni di pagina dividono l'intervallo. Un blocco riepilogo che capita di cavalcare un'interruzione esce come due immagini con la riga interessante tagliata a metà. Si può compensare tutto ciò impostando un formato pagina personalizzato che corrisponda esattamente all'intervallo, e la gente lo fa, ma significa ricalcolare la geometria della carta ogni volta che l'intervallo cambia e lascia comunque la fascia di intestazione e la logica dei titoli di stampa nel percorso

L'esportatore di celle misura il rettangolo, alloca una bitmap di esattamente quella dimensione, disegna le celle dentro ed encoda. Non c'è pagina, quindi non c'è nulla da configurare via. Per i casi in cui la carta è voluta, la via di esportazione PDF è lo strumento giusto ed è trattata nell'articolo sull'esportazione PDF del foglio

TXLSCellImageExporter misura, disegna ed encoda una sola immagine per intervallo di celle mentre la pipeline di stampa divide l'intervallo alle interruzioni di pagina
La pipeline di pagina inserisce geometria di carta tra Lei e le celle; l'esportatore di celle non ha alcuna pagina da nessuna parte nel percorso

Misurare prima di renderizzare

Measure restituisce le dimensioni in pixel che le impostazioni correnti produrrebbero senza encodare nulla. Conta per due ragioni. Un template HTML o email di solito ha bisogno delle dimensioni dell'immagine prima che l'immagine esista, così può riservare il riquadro ed evitare spostamenti di layout. E un servizio che renderizza intervalli selezionati dagli utenti ha bisogno di un modo per rifiutare una richiesta assurda prima di allocare per essa

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 i tratti sottili nitidi
      Exporter.DPI := 96;
      Exporter.Scale := 2.0;             // output a densità 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 e H sono ora noti; riservare il riquadro di layout prima di encodare
      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;

I budget, perché la scala moltiplica

MaxPixels e MaxBytes non sono decorazione difensiva. Il numero di pixel cresce con il quadrato del fattore di scala e con il quadrato del rapporto di risoluzione, così un intervallo ragionevole di 1200 per 800 a 96 DPI diventa circa 47 megapixel a 600 DPI, e un utente che seleziona un intero intervallo usato anziché un blocco riepilogo ci aggiunge sopra un altro ordine di grandezza. Senza tetto la modalità di guasto è un'allocazione che il processo non può soddisfare, il che butta giù qualsiasi altra cosa quel processo stesse facendo

Con un tetto la richiesta fallisce e il chiamante può scegliere: rifiutare, ridurre la scala o restringere l'intervallo. È una posizione molto migliore per un server di report, ed è lo stesso ragionamento dietro i budget espliciti nel decoder di metafile descritto nell'articolo sul decoder limitato EMF e WMF

Flusso di budget per TXLSCellImageExporter in HotXLS: Measure restituisce prima la dimensione in pixel, poi MaxPixels e MaxBytes vincolano allocazione e dimensione dell'output
Il rifiuto avviene prima dell'allocazione, e un fallimento del budget di byte lascia l'immagine precedente intatta per il chiamante

Sostituzione atomica, e perché la cartella conta

Save verso un nome file non scrive nel target. Scrive un file temporaneo nella stessa cartella, encoda dentro di esso, e solo allora sostituisce il target. Se l'encodaggio fallisce, se il budget è superato a metà strada, o se il processo viene ucciso, l'immagine precedente è ancora lì e ancora valida. Una dashboard che rigenera le proprie tessere a intervalli quindi non mostra mai un PNG troncato, che è il sintomo usuale di una scrittura ingenua che apre la destinazione e comincia a streammare

Il dettaglio stessa-cartella non è incidentale. Una sostituzione atomica è atomica solo dentro un volume, perché tra volumi il sistema operativo deve coprire e poi cancellare, il che reintroduce la finestra che si cercava di chiudere. Qualsiasi implementazione di questo schema che mette il proprio file temporaneo nella directory temp di sistema non è atomica su una macchina dove l'output vive su un'unità diversa

Il Save di TXLSCellImageExporter encoda in un file temporaneo nella stessa cartella, poi sostituisce il target atomicamente; i fallimenti lasciano l'immagine precedente valida
Il file temporaneo deve vivere accanto al target perché una sostituzione atomica funziona solo dentro un volume

Gli eventi di paint disegnano sulla vera canvas

Sia l'esportatore di intervalli sia l'esportatore di pagine espongono eventi di paint iniziali e finali, e ricevono un contesto completo di sola lettura anziché solo un handle di canvas. TXLSPagePaintContext trasporta la canvas viva, i limiti in pixel, il formato pagina in punti, la risoluzione e la scala effettivamente in uso, il numero di pagina del documento, il numero di pagina nel foglio, il numero totale di pagine, il nome del foglio e il foglio di lavoro d'origine in versione sia classica sia XLSX. È abbastanza per disegnare un watermark che scala correttamente, o un timbro di pagina che sa dov'è nella sequenza

procedure TReportJob.StampDraft(Sender: TObject;
  const AContext: TXLSPagePaintContext);
begin
  // Consapevole della scala, così il timbro appare uguale a 1x e 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;

Tre comportamenti su cui contare. Gli eventi scattano esattamente una volta per frame renderizzato, compreso ogni frame di un TIFF multipagina, così un contatore incrementato nel gestore è affidabile. Restano silenziosi durante la misurazione, così un gestore con un effetto collaterale non gira due volte per un solo output. E se l'evento iniziale genera eccezioni, l'evento finale non scatta e non vengono scritti byte parziali dell'immagine, così un'eccezione nel codice di disegno proprio non può produrre un file timbrato a metà

Scegliere il formato

PNG per qualsiasi cosa ricca di testo. JPEG applica una trasformazione a blocchi che produce ringing visibile attorno ai tratti sottili ad alto contrasto, che è esattamente ciò che sono i bordi cella e il testo piccolo, e gli artefatti sopravvivono a impostazioni di qualità dove una fotografia sembra perfetta. JPEG si guadagna il suo posto quando l'intervallo è dominato da fotografie incorporate e la dimensione del file conta più della fedeltà dei bordi. Gli sfondi trasparenti richiedono PNG, poiché JPEG non ha canale alfa, così una tessera destinata a sedere su una superficie colorata ha fatto la scelta al posto di Lei

Se l'intervallo contiene celle unite, controllare l'output contro il foglio: le regioni unite interagiscono con le larghezze di colonna in modi che sorprendono la gente, e le regole di layout sono trattate nell'articolo su celle unite e template di report. HotXLS legge e scrive XLS, XLSX, ODS e CSV da Delphi e C++Builder senza dipendenza da Excel, e l'intera superficie dell'esportatore è documentata sulla pagina di prodotto di HotXLS Delphi spreadsheet component