Artykuł techniczny

Otagowane figury PDF z obrazów Excela w HotXLS

Gdy HotXLS eksportuje arkusz do PDF z włączonym automatycznym tagowaniem, obrazki arkusza niosące tekst alternatywny emitują teraz jako niezależne elementy struktury /Figure z unikodowym wpisem /Alt, gęstymi lokalnymi dla strony identyfikatorami oznaczonej zawartości i dokładnymi wpisami drzewa rodziców. Obrazki bez tekstu alternatywnego pozostają dekoracyjnymi artefaktami, a wykresy też pozostają artefaktami. Ten precyzyjny zakres się liczy: czyni obrazy informacyjne osiągalnymi dla czytnika ekranu i nie jest tym samym, co pełna zgodność z PDF/UA

Mechanika stojąca za tym jest ciekawsza niż opis funkcji, bo dwa z jej elementów to ten rodzaj szczegółu, który po cichu produkuje strukturalnie poprawny PDF, którego struktura wskazuje na złą zawartość

Co liczy się jako obraz informacyjny?

Tylko niepusty AltText. Własność TXLSXImage.AltText przechodzi w obie strony atrybut OOXML descr niewizualnych własności obrazka, gdzie Excel trzyma tekst, który użytkownik wpisał w panel tekstu alternatywnego. To jedyny sygnał w pliku, że autor uznał obrazek za niosący informację, a nie dekorację, więc to jedyny sygnał, któremu eksporter ufa

Dwa niemal trafienia są celowo nieakceptowane. Pole tytułu, przechowywane osobno od opisu, nie jest zamiennikiem: tytuł to nazwa obiektu, nie jego tekstowy odpowiednik, i awansowanie go do /Alt wyprodukowałoby dokument przechodzący automatyczną kontrolę, a ogłaszający czytnikowi ekranu „Obraz 3”. Pusty opis też nie jest luką do wypełnienia placeholderem; znaczy, że obraz pozostaje artefaktem, co jest poprawnym wynikiem dla logo albo linii rozdzielającej. Wykresy również na razie pozostają artefaktami, bo tekstowym odpowiednikiem wykresu są jego dane, a synteza ich z serii byłaby wymyślaniem, a nie ekstrakcją

Obrazki z niepustym AltText eksportują jako elementy struktury Figure PDF z własnym MCID; puste opisy i wykresy pozostają artefaktami
Tylko opis autora w AltText sygnalizuje obraz informacyjny; sam tytuł nigdy nie staje się tekstem alternatywnym
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];

    // Audyt przed eksportem: obraz bez opisu zostanie
    // wyeksportowany jako dekoracyjny artefakt
    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;

Czemu strona potrzebuje jednego alokatora MCID?

Bo drzewo rodziców jest tablicą indeksowaną identyfikatorem oznaczonej zawartości, a dwa alokatory produkują dwa wpisy roszczące sobie to samo gniazdo. Otagowany PDF łączy zawartość ze strukturą w obu kierunkach. Po stronie zawartości wycinek strumienia zawartości strony jest owinięty operatorami BDC i EMC niosącymi numer /MCID unikalny w obrębie tej strony. Po stronie struktury słownik strony niesie klucz /StructParents nazywający wiersz dokumentowego /ParentTree, a ten wiersz jest tablicą, której element pod indeksem n to element struktury posiadający MCID n

Strona arkusza zawiera komórki tabeli i, teraz, figury. Jeśli tagger komórek liczy swoje identyfikatory od zera i tagger figur też liczy od zera, pierwsza figura roszczy sobie gniazdo, które pierwsza komórka już posiada. Nic w powstałym pliku nie jest zdeformowane na tyle, by parser go odrzucił: drzewo struktury jest nietknięte, oznaczona zawartość jest zbalansowana, a walidator widzi dokument z drzewem rodziców. To, co dostaje czytnik ekranu, to komórka tabeli ogłaszana jako obraz albo obraz ogłaszany z tekstem komórki. Eksporter alokuje więc z jednego licznika poziomu strony współdzielonego przez oba taggery i mrozi rekord strony dopiero, gdy numer obiektu strony jest znany, bo wiersza drzewa rodziców nie da się zapisać, zanim strona, do której się odnosi, zdobędzie tożsamość

Niezależne taggery komórek i figur kolidują się na gnieździe zero drzewa rodziców; jeden licznik MCID poziomu strony trzyma każdy znacznik zmapowany na jednego właściciela
Kolidujący plik wciąż przechodzi walidator strukturalny; tylko ogłoszenie czytnika ekranu jest złe

Figure musi owijać całą widoczną instancję

Naiwne umiejscowienie to owinięcie operatora Do, który wywołuje XObject obrazu, bo to operator rysujący obrazek. To za mało. Obrazek arkusza jest często rysowany z cieniem za sobą i ścieżką przycinającą wokół, a te znaczniki są częścią widocznego obiektu. Pozostawione poza zakresem /Figure stają się zawartością nieoznaczoną, czyli dokładnie tym stanem, który audyt struktury flaguje

Zakres oznaczonej zawartości otwiera się więc przed cieniem i zamyka po narysowaniu obrazu, obejmując również przycięcie. Współdzielenie jest zachowane tam, gdzie współdzielenie jest poprawne: dwie komórki pokazujące ten sam ładunek obrazka wciąż odwołują jeden XObject obrazu, bo to optymalizacja poziomu zasobów i nie ma nic do rzeczy z semantyką. To, co dostaje każda widoczna instancja, to własny MCID i własny element struktury, bo dwa wystąpienia tego samego logo w różnych miejscach to dwie rzeczy, które czytelnik napotyka. Umiejscowienie obrazów i geometria EMU pozycjonująca te obiekty jest omówiona w artykule o geometrii obrazów

Znacznik BDC otwiera zakres Figure przed cieniem i przycięciem, a EMC zamyka po narysowaniu obrazu Do, obejmując całą widoczną instancję
Owinięcie tylko operatora obrazu zostawiłoby cień i przycięcie jako zawartość nieoznaczoną; współdzielenie zasobów między komórkami jest zachowane

Kolejność czytania na stronie arkusza

Kolejność czytania to decyzja, którą eksporter musi podjąć, bo arkusz kalkulacyjny nie ma autorskiego przepływu tak, jak dokument. Przyjęta reguła jest stabilna i łatwa do wytłumaczenia: dla każdej strony najpierw tabela, potem figury w kolejności rysowania. Czytelnik słyszy więc tabelaryczną zawartość strony, a potem jej obrazy, zamiast mieć obrazy przeplatane w dowolnej pozycji, jaką obiekty rysunkowe akurat zajęły w pliku

Ta kolejność jest per strona, a nie per dokument, co ma znaczenie w skoroszycie paginującym się na dziesiątki stron: gałąź struktury każdej strony jest samowystarczalna, więc czytelnik przechodzący między stronami nie skacze z powrotem do wcześniejszej tabeli. Jeśli potrzebujesz kontroli nad tym, jak arkusz się paginuje w ogóle, interakcja ustawień strony i obszaru wydruku jest opisana w artykule o ochronie i ustawieniach strony

Co to certyfikuje, a czego nie

Certyfikuje, że obrazy informacyjne docierają do technologii wspomagającej z opisem dostarczonym przez autora i że mapowanie zawartości na strukturę jest poprawne, a nie jedynie obecne. Nie czyni wyjścia zgodnym z PDF/UA i opisywanie go w ten sposób byłoby twierdzeniem, którego implementacja nie udźwignie: wykresy wciąż są artefaktami, a pełne oświadczenie zgodności wymaga audytu każdego typu struktury, każdego fontu i metadanych dokumentu jako całości

Jeśli twoim wymaganiem jest profil archiwalny albo zgodnościowy, a nie poprawa dostępności, to inna konfiguracja eksportu i inny zestaw kontroli, opisane w artykule o archiwalnym eksporcie PDF/A. Te dwie się łączą, ale odpowiadają różnym audytorom

Jedna praktyczna sugestia dla potoku raportowania: audytuj tekst alternatywny w punkcie, gdzie skoroszyt jest generowany, nie w chwili eksportu. Generator wie, co przedstawia każdy obraz wykresu albo osadzony diagram, i może wpisać prawdziwy opis do AltText; przebieg w chwili eksportu potrafi ci tylko powiedzieć, że opisu brakuje. HotXLS czyta i pisze XLS, XLSX, ODS i CSV natywnie z Delphi i C++Builder bez zależności od Excela, a jego opcje konfiguracji eksportu są wypisane na stronie produktu HotXLS Delphi spreadsheet component