Artigo Técnico

Figuras PDF tagged de imagens do Excel com HotXLS

Quando o HotXLS exporta uma planilha para PDF com a tagação automática habilitada, imagens de planilha que carregam texto alternativo agora emitem como elementos de estrutura /Figure independentes com uma entrada /Alt Unicode, marked-content identifiers densos e locais de página e entradas exatas de parent tree. Imagens sem texto alternativo permanecem artifacts decorativos, e gráficos também permanecem artifacts. Esse escopo preciso importa: torna imagens informativas alcançáveis a um leitor de tela, e não é a mesma coisa que conformidade total PDF/UA

A mecânica por trás disso é mais interessante que a descrição do recurso, porque dois dos detalhes são do tipo que silenciosamente produz um PDF estruturalmente válido cuja estrutura aponta para o conteúdo errado

O que conta como uma imagem informativa?

Apenas um AltText não vazio. A propriedade TXLSXImage.AltText faz o round-trip do atributo OOXML descr das propriedades não visuais da imagem, que é onde o Excel guarda o texto que um usuário digita no painel de texto alternativo. Esse é o único sinal no arquivo de que o autor considerou que a imagem carrega informação em vez de decoração, então é o único sinal em que o exportador confia

Dois quase-acertos são deliberadamente não aceitos. O campo título, guardado separado da descrição, não é um substituto: um título é um nome para o objeto, não um equivalente textual dele, e promovê-lo a /Alt produziria um documento que passa em uma checagem automatizada enquanto anuncia "Imagem 3" a um leitor de tela. Uma descrição vazia também não é uma lacuna a ser preenchida com um placeholder; significa que a imagem permanece um artifact, que é o desfecho correto para um logo ou uma régua divisória. Gráficos também permanecem artifacts por ora, porque o equivalente textual de um gráfico são seus dados e sintetizar um a partir das séries seria invenção em vez de extração

Imagens com AltText não vazio exportam como elementos de estrutura PDF Figure com seu próprio MCID; descrições vazias e gráficos permanecem artifacts
Só a descrição do autor no AltText sinaliza uma imagem informativa; um título sozinho nunca vira o texto alternativo
uses
  lxHandleX, lxPDF;

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Exporter: TXLSPDFExport;
  I: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('regional-review.xlsx');
    Sheet := Book.Sheets.ByPos[0];

    // Audite antes de exportar: uma imagem sem descrição
    // será exportada como artifact decorativo
    for I := 0 to Sheet.Images.Count - 1 do
      if Sheet.Images[I].AltText = '' then
        Sheet.Images[I].AltText := DescribeImage(Sheet.Images[I].Name);

    Exporter := TXLSPDFExport.Create;
    try
      Exporter.TagMode := xlsPdfTagsAutomatic;
      Exporter.DocumentLanguage := 'en-US';
      Exporter.SaveAsPDF(Sheet, 'regional-review.pdf');
    finally
      Exporter.Free;
    end;
  finally
    Book.Free;
  end;
end;

Por que a página precisa de um único alocador de MCID?

Porque a parent tree é um array indexado por marked-content identifier, e dois alocadores produzem duas entradas reivindicando o mesmo slot. O PDF tagged conecta conteúdo a estrutura nos dois sentidos. No lado do conteúdo, um trecho do content stream da página é embrulhado em operadores BDC e EMC carregando um número /MCID que é único dentro dessa página. No lado da estrutura, o dicionário de página carrega uma chave /StructParents nomeando uma linha do /ParentTree do documento, e essa linha é um array cujo elemento no índice n é o elemento de estrutura que possui o MCID n

Uma página de planilha contém células de tabela e, agora, figuras. Se o tagger de células conta seus identificadores a partir de zero e o tagger de figuras também conta a partir de zero, a primeira figura reivindica o slot que a primeira célula já possui. Nada no arquivo resultante é malformado o suficiente para um parser rejeitar: a árvore de estrutura está intacta, o marked content está balanceado, e um validador vê um documento com uma parent tree. O que um leitor de tela recebe é uma célula de tabela anunciada como imagem, ou uma imagem anunciada com o texto de uma célula. O exportador portanto aloca de um contador de nível de página compartilhado pelos dois taggers, e congela o record da página só quando o número de objeto da página é conhecido, já que a linha da parent tree não pode ser escrita antes de a página a que se refere ter uma identidade

Taggers independentes de células e figuras colidem no slot zero da parent tree; um contador de MCID de nível de página mantém cada marca mapeada a um dono
O arquivo com colisão ainda passa em um validador estrutural; só o anúncio do leitor de tela está errado

A Figure deve embrulhar toda a instância visível

A colocação ingênua é embrulhar o operador Do que invoca o XObject da imagem, já que é o operador que desenha a imagem. Não basta. Uma imagem de planilha frequentemente é desenhada com uma sombra atrás e um caminho de recorte ao redor, e essas marcas são parte do objeto visível. Deixadas fora do escopo /Figure viram conteúdo não marcado, que é exatamente o estado que uma auditoria de estrutura sinaliza

Então o escopo de marked-content abre antes da sombra e fecha depois do desenho da imagem, cobrindo também o recorte. O compartilhamento é preservado onde o compartilhamento é correto: duas células mostrando o mesmo payload de imagem ainda referenciam um XObject de imagem, porque isso é uma otimização de nível de recurso e não tem nada a ver com semântica. O que cada instância visível recebe é seu próprio MCID e seu próprio elemento de estrutura, porque duas ocorrências do mesmo logo em lugares diferentes são duas coisas que um leitor encontra. A colocação de imagens e a geometria EMU que posiciona esses objetos é coberta em o artigo de geometria de imagens

A marca BDC abre o escopo da Figure antes da sombra e do recorte e o EMC fecha depois do desenho Do da imagem, cobrindo toda a instância visível
Embrulhar só o operador de imagem deixaria sombra e recorte como conteúdo não marcado; o compartilhamento de recursos entre células é preservado

Ordem de leitura em uma página de planilha

A ordem de leitura é uma decisão que o exportador precisa tomar, porque uma planilha não tem fluxo autoral do jeito que um documento tem. A regra adotada é estável e fácil de explicar: para cada página, a tabela vem primeiro, depois figuras em ordem de desenho. Um leitor portanto ouve o conteúdo tabular da página e depois suas imagens, em vez de ter imagens intercaladas na posição que os objetos de desenho por acaso ocupavam no arquivo

Essa ordem é por página em vez de por documento, o que importa em uma pasta de trabalho que pagina em dezenas de páginas: o ramo de estrutura de cada página é autossuficiente, então um leitor se movendo entre páginas não salta de volta a uma tabela anterior. Se você precisa controlar como a aba pagina em primeiro lugar, a interação de configuração de página e área de impressão é descrita em o artigo de proteção e configuração de página

O que isto certifica, e o que não

Certifica que imagens informativas chegam à tecnologia assistiva com sua descrição fornecida pelo autor, e que o mapeamento de conteúdo para estrutura está correto em vez de meramente presente. Não torna a saída PDF/UA conforme, e descrevê-la assim seria uma alegação que a implementação não pode sustentar: gráficos ainda são artifacts, e uma declaração de conformidade completa exige uma auditoria de todo tipo de estrutura, toda fonte e os metadados do documento como um todo

Se seu requisito é um perfil de arquivamento ou conformidade em vez de melhoria de acessibilidade, essa é uma configuração de exportação diferente e um conjunto diferente de checagens, descrito em o artigo de exportação de arquivamento PDF/A. Os dois se combinam, mas respondem a auditores diferentes

Uma sugestão prática para um pipeline de relatórios: audite o texto alternativo no ponto em que a pasta de trabalho é gerada, não no momento da exportação. O gerador sabe o que cada imagem de gráfico ou diagrama embutido representa, e pode escrever uma descrição real no AltText; uma passada em tempo de exportação só pode dizer que uma descrição está ausente. O HotXLS lê e escreve XLS, XLSX, ODS e CSV nativamente de Delphi e C++Builder sem dependência de Excel, e suas opções de configuração de exportação estão listadas na página de produto do HotXLS Delphi spreadsheet component