Техническая статья

Тегированные фигуры PDF из изображений Excel в HotXLS

Когда HotXLS экспортирует лист в PDF с включённым автоматическим тегированием, картинки листа, несущие альтернативный текст, теперь выдаются как самостоятельные структурные элементы /Figure с записью /Alt в Unicode, плотными локальными для страницы идентификаторами помеченного содержимого и точными записями родительского дерева. Картинки без альтернативного текста остаются декоративными артефактами, и диаграммы тоже остаются артефактами. Эта точная граница важна: она делает информативные изображения достижимыми для программы экранного доступа, и это не то же самое, что полная совместимость с PDF/UA

Механика за этим интереснее описания возможности, потому что две её детали — из тех, что молча производят структурно валидный PDF, чья структура указывает на не тот контент

Что считается информативным изображением?

Только непустой AltText. Свойство TXLSXImage.AltText прозрачно проводит атрибут descr OOXML из невизуальных свойств картинки — именно там Excel хранит текст, который пользователь ввёл в панель альтернативного текста. Это единственный сигнал в файле, что автор считал картинку несущей информацию, а не украшение, поэтому это единственный сигнал, которому экспортёр доверяет

Два близких варианта сознательно не принимаются. Поле заголовка, хранимое отдельно от описания, — не замена: заголовок — имя объекта, а не его текстовый эквивалент, и продвижение его в /Alt дало бы документ, проходящий автоматическую проверку, но объявляющий программе экранного доступа «Рисунок 3». Пустое описание — тоже не пробел, который стоит заполнить заполнителем; оно означает, что изображение остаётся артефактом, что и есть верный исход для логотипа или линейки-разделителя. Диаграммы также пока остаются артефактами, потому что текстовый эквивалент диаграммы — её данные, а синтезировать его из рядов — изобретение, а не извлечение

Картинки с непустым AltText экспортируются как структурные элементы Figure PDF со своим MCID; пустые описания и диаграммы остаются артефактами
Только авторское описание в AltText сигнализирует об информативном изображении; один заголовок никогда не становится альтернативным текстом
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];

    // Аудит перед экспортом: изображение без описания
    // будет экспортировано как декоративный артефакт
    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;

Почему странице нужен единственный распределитель MCID?

Потому что родительское дерево — массив, индексируемый идентификатором помеченного содержимого, а два распределителя дают две записи, претендующие на один слот. Тегированный PDF связывает содержимое со структурой в обоих направлениях. На стороне содержимого отрезок потока содержимого страницы обёрнут в операторы BDC и EMC, несущие номер /MCID, уникальный в пределах этой страницы. На стороне структуры словарь страницы несёт ключ /StructParents, называющий строку документа /ParentTree, и эта строка — массив, чей элемент с индексом n — структурный элемент, владеющий MCID n

Страница листа содержит ячейки таблицы и, теперь, фигуры. Если тегировщик ячеек считает свои идентификаторы с нуля и тегировщик фигур тоже считает с нуля, первая фигура занимает слот, которым уже владеет первая ячейка. В получившемся файле нет ничего, достаточно искажённого, чтобы синтаксический анализатор его отверг: дерево структуры цело, помеченное содержимое сбалансировано, а валидатор видит документ с родительским деревом. А программа экранного доступа получает ячейку таблицы, объявленную как изображение, или изображение, объявленное с текстом ячейки. Поэтому экспортёр выделяет из одного счётчика уровня страницы, общего для обоих тегировщиков, и замораживает запись страницы только после того, как известен номер объекта страницы, ведь строку родительского дерева нельзя записать раньше, чем страница, на которую она указывает, получит идентичность

Независимые тегировщики ячеек и фигур сталкиваются на слоте нуль родительского дерева; один счётчик MCID уровня страницы отображает каждую метку одному владельцу
Сталкивающийся файл всё же проходит структурный валидатор; ошибочно только объявление программы экранного доступа

Figure должен обёртывать весь видимый экземпляр

Наивное размещение — обернуть оператор Do, вызывающий XObject изображения, ведь это оператор, рисующий картинку. Этого недостаточно. Картинка листа часто рисуется с тенью позади и путём отсечения вокруг, и эти метки — часть видимого объекта. Оставленные вне области /Figure, они становятся непомеченным содержимым, а это ровно то состояние, которое помечает аудит структуры

Поэтому область помеченного содержимого открывается до тени и закрывается после отрисовки изображения, покрывая и отсечение. Разделение сохраняется там, где оно корректно: две ячейки, показывающие одну и ту же полезную нагрузку картинки, по-прежнему ссылаются на один XObject изображения, ведь это оптимизация уровня ресурсов и к семантике отношения не имеет. Что получает каждый видимый экземпляр, так это свой MCID и свой структурный элемент, потому что два вхождения одного логотипа в разных местах — две вещи, с которыми читатель встречается. Размещение изображений и геометрия EMU, позиционирующая эти объекты, описаны в статье о геометрии изображений

Метка BDC открывает область Figure до тени и отсечения, а EMC закрывает после отрисовки изображения оператором Do, покрывая весь видимый экземпляр
Обёртывание одного лишь оператора изображения оставило бы тень и отсечение непомеченным содержимым; разделение ресурсов между ячейками сохраняется

Порядок чтения на странице листа

Порядок чтения — решение, которое экспортёр обязан принять, потому что у электронной таблицы нет авторского потока, как у документа. Принятое правило стабильно и легко объяснимо: для каждой страницы сначала таблица, затем фигуры в порядке отрисовки. Читатель поэтому слышит табличное содержимое страницы, а затем её изображения, вместо изображений, перемежаемых на тех позициях, которые графические объекты случайно заняли в файле

Этот порядок задан на страницу, а не на документ, что важно для книги, разбивающейся на десятки страниц: структурная ветвь каждой страницы самодостаточна, поэтому читатель, переходящий между страницами, не прыгает обратно в более раннюю таблицу. Если вам нужен контроль над тем, как лист разбивается на страницы в первую очередь, взаимодействие параметров страницы и области печати описано в статье о защите и параметрах страницы

Что это сертифицирует, а что нет

Это сертифицирует, что информативные картинки достигают ассистивных технологий с авторским описанием и что отображение содержимого на структуру корректно, а не просто присутствует. Это не делает вывод PDF/UA-совместимым, и описывать его так значило бы делать утверждение, которого реализация не поддержит: диаграммы всё ещё артефакты, а полное заявление о совместимости требует аудита каждого типа структуры, каждого шрифта и метаданных документа в целом

Если ваше требование — архивный профиль или профиль совместимости, а не улучшение доступности, это другая конфигурация экспорта и другой набор проверок, описанных в статье об архивном экспорте PDF/A. Оба сочетаются, но отвечают разным аудиторам

Одно практическое предложение для конвейера отчётов: аудит альтернативного текста — в точке, где книга генерируется, а не во время экспорта. Генератор знает, что представляет каждое изображение диаграммы или встроенная схема, и может записать настоящее описание в AltText; проход во время экспорта способен лишь сообщить, что описания нет. HotXLS читает и пишет XLS, XLSX, ODS и CSV нативно из Delphi и C++Builder без зависимости от Excel, а его параметры конфигурации экспорта перечислены на странице продукта HotXLS Delphi spreadsheet component