Технічна стаття

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

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

Механіка за цим цікавіша за опис можливості, бо дві з них — це той сорт деталей, що мовчки творить структурно чинний PDF, чия структура вказує на неправильний вміст

Що рахується інформативним зображенням?

Лише непорожній AltText. Властивість TXLSXImage.AltText проводить туди й назад атрибут descr OOXML з невізуальних властивостей картинки, — саме там Excel зберігає текст, який користувач набирає в панелі альтернативного тексту. Це єдиний сигнал у файлі, що автор вважав картину носієм інформації, а не прикраси, тож це єдиний сигнал, якому довіряє експортер

Дві майже-влучання свідомо не приймаються. Поле назви, що зберігається окремо від опису, — не заміна: назва — це ім'я об'єкта, а не текстовий еквівалент його, і просування її в /Alt дало б документ, що проходить автоматичну перевірку, оголошуючи «Picture 3» читачеві з екрана. Порожній опис — також не прогалина, яку заповнити заповнювачем; він означає, що зображення лишається артефактом, що і є правильний результат для логотипа чи розділювальної лінійки. Діаграми також лишаються артефактами поки що, бо текстовий еквівалент діаграми — її дані, і синтезувати один із серій — винахід, а не витяг

Картинки з непорожнім AltText експортуються як структурні елементи фігур 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 рівня сторінки тримає кожну позначку зіставленою з одним власником
Файл, що конфліктує, досі проходить структурного валідатора; неправильне лише оголошення читача з екрана

Фігура мусить огортати весь видимий примірник

Наївне розміщення — огорнути оператор Do, що викликає зображення XObject, бо саме той оператор малює картину. Цього недостатньо. Картинку аркуша часто малюють з тінню позаду та шляхом відсікання навколо, і ті позначки — частина видимого об'єкта. Залишені поза сферою /Figure, вони стають непозначеним вмістом — точно той стан, який прапорить аудит структури

Тож сфера позначеного вмісту відкривається до тіні і закривається після малювання зображення, покриваючи також відсікання. Поширення зберігається там, де поширення правильне: дві клітинки, що показують те саме навантаження картинки, досі посилаються на одне зображення XObject, бо це оптимізація рівня ресурсів і не має нічого спільного з семантикою. Кожен видимий примірник отримує власний MCID і власний структурний елемент, бо два входження того самого логотипа в різних місцях — дві речі, з якими стикається читач. Розміщення зображень і геометрія EMU, що позиціонує ці об'єкти, охоплені в статті про геометрію зображень

Позначка BDC відкриває сферу фігури до тіні та відсікання, а EMC закриває після малювання зображення Do, покриваючи весь видимий примірник
Огорнути лише оператор зображення — значить лишити тінь і відсікання непозначеним вмістом; поширення ресурсів між клітинками зберігається

Порядок читання на сторінці аркуша

Порядок читання — рішення, яке має прийняти експортер, бо електронна таблиця не має авторського потоку, як документ. Прийняте правило стабільне і легке для пояснення: для кожної сторінки спершу таблиця, потім фігури в порядку малювання. Читач тому чує табличний вміст сторінки, а потім її зображення, замість того щоб мати зображення, переплетені на тих позиціях, які випадково зайняли об'єкти малювання в файлі

Той порядок на сторінку, а не на документ, що має значення на книзі, яка розбивається на десятки сторінок: структурна гілка кожної сторінки самодостатня, тож читач, що рухається між сторінками, не стрибає назад у попередню таблицю. Якщо вам потрібен контроль над тим, як аркуш розбивається на сторінки в першу чергу, взаємодія налаштування сторінки та області друку описана в статті про захист і налаштування сторінки

Що це сертифікує, а що ні

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

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

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