Às vezes o entregável não é um documento, é uma imagem de uma tabela. Um bloco de resumo em um e-mail de status, um painel de KPI renderizado em um dashboard, uma miniatura ao lado de um resultado de busca: todos querem as células e nenhum quer papel. O TXLSCellImageExporter no HotXLS recebe um retângulo de células clássico ou XLSX e produz um PNG ou JPEG compacto sem tamanho de página, sem margens, sem cabeçalhos ou 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 grade e bordas de células têm chaves independentes, o fundo pode ser uma cor ou transparente, e a gravação do arquivo passa por uma substituição atômica na mesma pasta que deixa um alvo existente intocado se algo falhar
A razão de isso precisar de um exportador próprio em vez de um flag no caminho de impressão é que a paginação não é uma camada opcional que se possa desligar. É a razão de existir do pipeline de páginas
Por que não renderizar o intervalo pelo pipeline de impressão?
Porque o pipeline de impressão insere uma página entre você e as células. O tamanho de papel decide quanto cabe, as margens empurram o conteúdo para dentro, cabeçalhos e rodapés ocupam faixas que você não pediu, títulos de impressão repetem linhas que você já tem, e quebras de página dividem o intervalo. Um bloco de resumo que por acaso cruza uma quebra sai como duas imagens com a linha interessante cortada ao meio. Você pode compensar tudo isso configurando um tamanho de página customizado que bata exatamente com o intervalo, e gente faz, mas isso significa recalcular a geometria do papel toda vez que o intervalo muda e ainda deixa a lógica da faixa de cabeçalho e de títulos de impressão no caminho
O exportador de células mede o retângulo, aloca um bitmap de exatamente esse tamanho, desenha as células nele e codifica. Não há página, então não há nada para configurar fora. Para os casos em que você quer papel de verdade, o caminho de exportação PDF é a ferramenta certa e é coberto em o artigo de exportação de planilha para PDF
Meça antes de renderizar
O Measure retorna as dimensões em pixels que as configurações atuais produziriam sem codificar nada. Isso importa por duas razões. Um template HTML ou de e-mail geralmente precisa das dimensões da imagem antes de ela existir, para poder reservar a caixa e evitar layout shift. E um serviço que renderiza intervalos escolhidos por usuários precisa de um jeito de rejeitar 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 traços finos nítidos
Exporter.DPI := 96;
Exporter.Scale := 2.0; // saída em 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 agora são 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 enfeite defensivo. A contagem de pixels cresce com o quadrado do fator de escala e com o quadrado da razão de resolução, então um intervalo que é um razoável 1200 por 800 a 96 DPI vira aproximadamente 47 megapixels a 600 DPI, e um usuário que seleciona toda a área usada em vez de um bloco de resumo adiciona outra ordem de magnitude por cima. Sem um teto o modo de falha é uma alocação que o processo não consegue satisfazer, o que derruba o que quer que aquele processo estivesse fazendo
Com um teto 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 decoder de metafile descrito em o artigo do decoder limitado de EMF e WMF
Substituição atômica, e por que a pasta importa
O Save para um nome de arquivo não escreve no alvo. Ele escreve um arquivo temporário na mesma pasta, codifica nele e só então substitui o alvo. Se a codificação falhar, se o orçamento for excedido no meio, ou se o processo for morto, a imagem anterior continua lá e ainda válida. Um dashboard que regenera seus tiles em um cronograma portanto nunca mostra um PNG truncado, que é o sintoma usual de uma gravação ingênua que abre o destino e começa a streamar
O detalhe da mesma pasta não é incidental. Um replace atômico só é atômico dentro de um volume, porque entre volumes o sistema operacional precisa copiar e depois apagar, o que reintroduz a janela que você estava tentando fechar. Qualquer implementação deste padrão que ponha seu arquivo temporário no diretório temp do sistema não é atômica em uma máquina em que a saída vive em outra unidade
Eventos de pintura desenham no canvas real
Tanto o exportador de intervalos quanto o exportador de páginas expõem eventos de pintura iniciais e finais, e eles recebem um contexto completo somente leitura em vez de apenas um handle de canvas. O TXLSPagePaintContext carrega o canvas vivo, os limites em pixels, o tamanho de página em pontos, a resolução e a escala realmente em uso, o número de página do documento, o número de página dentro da planilha, a contagem total de páginas, o nome da aba e a planilha de origem nos sabores clássico e XLSX. Isso basta para desenhar uma marca d'á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 de escala, então o carimbo parece o mesmo em 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 confiança. Os eventos disparam exatamente uma vez por frame renderizado, incluindo cada frame de um TIFF multipágina, então um contador incrementado no handler é confiável. Eles ficam em silêncio durante a medição, então um handler com efeito colateral não roda duas vezes para uma saída. E se o evento inicial levanta exceção, o evento final não dispara e nenhum byte parcial de imagem é escrito, então uma exceção no seu próprio código de desenho não pode produzir um arquivo meio carimbado
Escolhendo o formato
PNG para qualquer coisa rica em texto. O JPEG aplica uma transformação em blocos que produz ringing visível ao redor de traços finos de alto contraste, que é exatamente o que bordas de células e texto pequeno são, e os artefatos sobrevivem em configurações de qualidade em que uma fotografia parece perfeita. O JPEG ganha seu lugar quando o intervalo é dominado por fotografias embutidas e o tamanho do arquivo importa mais que a fidelidade de bordas. Fundos transparentes exigem PNG, já que o JPEG não tem canal alfa, então um tile destinado a ficar sobre uma superfície colorida já fez a escolha por você
Se seu intervalo contém células mescladas, confira a saída contra a aba: regiões mescladas interagem com larguras de coluna de jeitos que surpreendem as pessoas, e as regras de layout são cobertas em o artigo de células mescladas e templates de relatório. O HotXLS lê e escreve XLS, XLSX, ODS e CSV de Delphi e C++Builder sem dependência de Excel, e a superfície completa do exportador está documentada na página de produto do HotXLS Delphi spreadsheet component