À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
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
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
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