Artigo Técnico

Exportar um intervalo de células Excel como uma imagem

Às vezes o entregável não é um documento, é uma imagem de uma tabela. Um bloco de resumo num email de estado, um painel de KPI renderizado num painel de controlo, uma miniatura ao lado de um resultado de pesquisa: todos querem as células e nenhum quer papel. O TXLSCellImageExporter do HotXLS recebe um retângulo de células clássico ou XLSX e produz um único PNG ou JPEG compacto sem tamanho de página, sem margens, sem cabeçalhos nem rodapés, sem títulos de impressão e sem quebras de página. Resolução, escala, formato e qualidade JPEG são configuráveis, objetos, linhas de grelha e bordas de células têm interruptores independentes, o fundo pode ser uma cor ou transparente, e a gravação do ficheiro passa por uma substituição atómica na mesma pasta que deixa um alvo existente intacto se algo falhar

A razão pela qual isto precisa do seu próprio exportador em vez de uma flag no caminho de impressão é que a paginação não é uma camada opcional que se possa desligar. É a razão de existir da pipeline de páginas

Porque não renderizar o intervalo através da pipeline de impressão?

Porque a pipeline de impressão insere uma página entre si e as células. O tamanho do papel decide quanto cabe, as margens empurram o conteúdo para dentro, os cabeçalhos e rodapés ocupam faixas que não pediu, os títulos de impressão repetem linhas que já tem, e as quebras de página partem o intervalo. Um bloco de resumo que por acaso atravessa uma quebra sai como duas imagens com a linha interessante cortada ao meio. Pode compensar tudo isso configurando um tamanho de página personalizado que corresponda exatamente ao intervalo, e há quem o faça, mas isso significa recalcular a geometria do papel sempre que o intervalo muda e ainda deixa a faixa de cabeçalho e a lógica de títulos de impressão no caminho

O exportador de células mede o retângulo, aloca um bitmap exatamente desse tamanho, desenha as células nele, e codifica. Não há página, portanto não há nada para configurar à parte. Para os casos em que quer mesmo papel, o caminho de exportação PDF é a ferramenta certa e está coberto no artigo sobre exportação PDF de folhas de cálculo

O TXLSCellImageExporter mede, desenha e codifica uma imagem por intervalo de células enquanto a pipeline de impressão parte o intervalo nas quebras de página
A pipeline de páginas insere geometria de papel entre si e as células; o exportador de células não tem página nenhuma em lado nenhum do caminho

Medir antes de renderizar

Measure devolve as dimensões em píxeis que as definições atuais produziriam sem codificar nada. Isso importa por duas razões. Um template HTML ou de email normalmente precisa das dimensões da imagem antes de a imagem existir, para poder reservar a caixa e evitar deslocação de layout. E um serviço que renderiza intervalos selecionados por utilizadores precisa de uma forma de recusar um pedido absurdo antes de alocar para ele

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 mantém os traços finos nítidos
      Exporter.DPI := 96;
      Exporter.Scale := 2.0;             // saída de densidade 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 são agora conhecidos; reserve a caixa de layout 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;

Orçamentos, porque a escala multiplica

MaxPixels e MaxBytes não são decoração defensiva. A contagem de píxeis cresce com o quadrado do fator de escala e com o quadrado da razão de resolução, pelo que um intervalo razoável de 1200 por 800 a 96 DPI se torna em cerca de 47 megapíxeis a 600 DPI, e um utilizador que selecione todo o intervalo usado em vez de um bloco de resumo acrescenta outra ordem de grandeza por cima. Sem um limite o modo de falha é uma alocação que o processo não consegue satisfazer, o que derruba todo o resto que esse processo estava a fazer

Com um limite o pedido falha e o chamador pode escolher: recusar, reduzir a escala, ou estreitar o intervalo. Essa é uma posição muito melhor para um servidor de relatórios, e é o mesmo raciocínio por trás dos orçamentos explícitos no descodificador de metaficheiros descrito no artigo sobre o descodificador limitado de EMF e WMF

Fluxo de orçamento do TXLSCellImageExporter no HotXLS: Measure devolve primeiro o tamanho em píxeis, depois MaxPixels e MaxBytes limitam a alocação e o tamanho de saída
A recusa acontece antes da alocação, e uma falha de orçamento de bytes deixa a imagem anterior intacta para o chamador

Substituição atómica, e porque é que a pasta importa

Save para um nome de ficheiro não escreve no alvo. Escreve um ficheiro temporário na mesma pasta, codifica para ele, e só então substitui o alvo. Se a codificação falhar, se o orçamento for excedido a meio, ou se o processo for morto, a imagem anterior continua lá e continua válida. Um painel de controlo que regenera os seus mosaicos num horário portanto nunca mostra um PNG truncado, que é o sintoma habitual de uma gravação ingénua que abre o destino e começa a escrever em fluxo

O detalhe da mesma pasta não é incidental. Uma substituição atómica só é atómica dentro de um volume, porque entre volumes o sistema operativo tem de copiar e depois apagar, o que reintroduz a janela que se tentava fechar. Qualquer implementação deste padrão que ponha o seu ficheiro temporário no diretório temporário do sistema não é atómica numa máquina onde a saída vive numa unidade diferente

O Save do TXLSCellImageExporter codifica para um ficheiro temporário na mesma pasta, e depois substitui o alvo atomicamente; falhas deixam a imagem anterior válida
O ficheiro temporário tem de viver ao lado do alvo porque uma substituição atómica só funciona dentro de um volume

Eventos de pintura desenham no canvas real

Tanto o exportador de intervalos como o exportador de páginas expõem eventos de pintura iniciais e finais, e recebem um contexto completo só de leitura em vez de apenas um handle de canvas. O TXLSPagePaintContext transporta o canvas vivo, os limites em píxeis, o tamanho da página em pontos, a resolução e escala realmente em uso, o número da página no documento, o número da página na folha, a contagem total de páginas, o nome da folha e a folha de cálculo de origem nas versões clássica e XLSX. Isso chega para desenhar uma marca de água que escala corretamente, ou um carimbo de página que sabe onde está na tiragem

procedure TReportJob.StampDraft(Sender: TObject;
  const AContext: TXLSPagePaintContext);
begin
  // Consciente da escala, para o carimbo parecer o mesmo 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;

Três comportamentos valem a pena confiar. Os eventos disparam exatamente uma vez por quadro renderizado, incluindo cada quadro de um TIFF multipágina, pelo que um contador incrementado no tratador é de confiança. Mantêm-se em silêncio durante a medição, pelo que um tratador com efeito lateral não corre duas vezes por uma saída. E se o evento inicial lançar exceção, o evento final não dispara e nenhum byte parcial de imagem é escrito, pelo que uma exceção no seu próprio código de desenho não pode produzir um ficheiro meio carimbado

Escolher o formato

PNG para qualquer coisa com muito texto. O JPEG aplica uma transformação em blocos que produz um halo visível à volta de traços finos de alto contraste, que é exatamente o que as bordas de células e o texto pequeno são, e os artefactos sobrevivem a definições de qualidade em que uma fotografia parece perfeita. O JPEG ganha o seu lugar quando o intervalo é dominado por fotografias embutidas e o tamanho do ficheiro importa mais do que a fidelidade de bordas. Fundos transparentes exigem PNG, já que o JPEG não tem canal alfa, pelo que um mosaico destinado a assentar numa superfície colorida já tomou a decisão por si

Se o seu intervalo contém células unidas, verifique a saída contra a folha: as regiões unidas interagem com as larguras de colunas de formas que surpreendem as pessoas, e as regras de layout estão cobertas no artigo sobre células unidas e templates de relatórios. O HotXLS lê e escreve XLS, XLSX, ODS e CSV a partir de Delphi e C++Builder sem dependência de Excel, e toda a superfície do exportador está documentada na página de produto do HotXLS Delphi spreadsheet component