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

Вилучення тексту PDF у порядку структури в Delphi з HotPDF

Кожен геометричний витягувач тексту вгадує. Він читає гліфи, які малює сторінка, сортує їх за базовою лінією та горизонтальною позицією і сподівається, що візуальне розташування збігається з порядком, у якому читала б людина. На одностовпчиковому звіті та вгадка права. На статті журналу у два стовпчики, на формі з бічною панеллю чи на таблиці, чиї клітинки виводилися стовпчик за стовпчиком, вона неправильна так, що це важко помітити і дорого виявити внизу за течією. HotPDF відповідає на це ExtractLoadedPageStructureText, що взагалі ігнорує геометрію: вона проходить дерево структури документа в порядку авторства, визначеному ISO 32000-1 §14.8.4, а тоді перезбирає гліфи сторінки за їхнім ідентифікатором позначеного вмісту. Для тегованого PDF це не евристика, а порядок, який оголосив застосунок-творець

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

Чому геометричне вилучення плутає порядок читання?

Бо потік вмісту PDF не несе жодного порядку читання. Це послідовність операторів малювання, і творець вільний виводити їх у тій послідовності, яка личить його власному движку компонування. Текстові процесори зазвичай виводять у порядку потоку, і геометричне сортування виглядає добре. Інструменти компонування, конструктори форм та генератори звітів часто ні: нижній колонтитул може бути виведений до тіла, таблиця може наповнюватися по стовпчиках, а сторінка з двома стовпчиками може переплітати рядки з обох стовпчиків, бо верстальник розв'язав їх разом

Порівняння сторінки PDF у два стовпчики: геометричне вилучення з сортуванням за базовою лінією склеює стовпчики проти вилучення за порядком структури за MCID у HotPDF
Сортування гліфів за базовою лінією переплітає два стовпчики в нісенітницю, тоді як дерево структури повторює порядок, який оголосив творець

Режим збою тихий. Геометричний витягувач ніколи не повідомляє помилки — він просто повертає прозу, чиї речення склеєні з двох стовпчиків. Все, що споживає той текст, — пошуковий індекс, мапер полів електронного рахунка, конвеєр пошуку, що живить мовну модель, — успадковує пошкодження без попередження. HotPDF також постачає геометричні витягувачі для завантажених документів, і вони лишаються правильним інструментом для нетегованих файлів; суть шляху порядку структури — перестати вгадувати, коли документ уже несе відповідь

Що дерево структури справді зберігає

Тегований PDF тримає другий, паралельний опис сторінки. Каталог вказує на /StructTreeRoot, чиї діти /K творять дерево елементів структури: /Document, /Sect, /P, /Table, /TR, /TD тощо. Листя того дерева — посилання на позначений вміст, цілі числа, що називають ділянку потоку вмісту сторінки. Зі сторони вмісту ті ділянки відкриваються оператором BDC з /MCID і закриваються EMC. Кожен елемент структури також несе запис /Pg, що називає сторінку, до якої він належить, — саме це робить можливим прохід по сторінках у документі, чиє дерево структури розкинулося на сотні сторінок

Анатомія дерева структури PDF, що зв'язує елементи StructTreeRoot, як-от Sect, Table, TR і TD, з ділянками BDC MCID у потоці вмісту сторінки HotPDF
Листя дерева — посилання на позначений вміст, і кожен елемент несе запис Pg, який дозволяє проходові фільтрувати до поточної сторінки

HotPDF проходить те дерево зі стелею глибини 128 рівнів і фільтрує за /Pg, тож лише поточна сторінка робить внесок. Результат проходу — не текст, а впорядкований список значень MCID: порядок авторства ділянок позначеного вмісту на цій сторінці. Перезбирання тексту тоді справа повторення гліфів у тому порядку

MCID записується під час вилучення гліфів, а не шукається потім

Це деталь реалізації, що робить можливість дешевою. HotPDF уже записує активний ідентифікатор позначеного вмісту на кожному гліфі, який вилучає, у полі MCID запису THPDFGlyphRecord, бо інтерпретатор потоку вмісту знає, яка сфера BDC відкрита в момент обробки кожного оператора Tj чи TJ. Вилученню за порядком структури тому не потрібен другий прохід по потоці вмісту. Воно збирає послідовність MCID з дерева структури, тоді розсортовує вже вилучені гліфи за MCID і виводить їх у тій послідовності

var
  Pdf: THotPDF;
  PageCount, I, Untagged: Integer;
  PageText, AllText: UnicodeString;
  Report: TStrings;   // сховище діагностики, що належить викликачу
begin
  Pdf := THotPDF.Create(nil);
  try
    PageCount := Pdf.LoadFromFile('accessible-form.pdf');
    AllText := '';
    for I := 0 to PageCount - 1 do
    begin
      if Pdf.ExtractLoadedPageStructureText(I, PageText, Untagged) then
      begin
        // Порядок авторства прямо з дерева структури
        if Untagged > 0 then
          Report.Add(Format('page %d: %d glyphs outside the structure tree',
            [I, Untagged]));
      end
      else
        // Немає придатного дерева структури на цій сторінці: геометричний резерв
        Pdf.ExtractLoadedPageText(I, PageText);
      AllText := AllText + PageText + #13#10;
    end;
  finally
    Pdf.Free;
  end;
end;

Нетеговані гліфи рахуються, а не мовчки відкидаються

Сторінка може бути частково тегованою. Творці додають декоративну лінійку, номер сторінки чи пізній водяний знак поза будь-якою сферою BDC, і ті гліфи не належать жодному MCID. Відкинути їх — охайна реалізація, але неправильна, бо та сама прогалина з'являється і тоді, коли творець тегує тіло, але забуває таблицю, і ви втратили б таблицю, не помітивши

HotPDF додає непривласнені гліфи геометричним хвостом після тексту в порядку структури і повідомляє їх число через вихідний параметр UntaggedGlyphCount. Те число — сигнал якості, на який можна реагувати. Жменька гліфів на сторінці з двох тисяч — це сторінкове вбрання, і його можна ігнорувати. Сорок відсотків сторінки поза деревом структури означає, що тегування декоративне, і геометричний витягувач — чесніша відповідь для того файла

Схема рішень вилучення структурованого тексту HotPDF із геометричним резервом, коли сторінка не має придатного дерева структури чи має декоративне тегування
True означає порядок структури з доданим нетегованим хвостом, а False спрямовує сторінку до геометричного витягувача замість відмови
function ExtractPageBestEffort(Pdf: THotPDF; PageIndex: Integer;
  out AText: UnicodeString; out UsedStructure: Boolean): Boolean;
var
  Untagged, TotalGlyphs: Integer;
  Glyphs: THPDFGlyphArray;
begin
  UsedStructure := False;
  if Pdf.ExtractLoadedPageStructureText(PageIndex, AText, Untagged) then
  begin
    TotalGlyphs := 0;
    if Pdf.ExtractLoadedPageGlyphs(PageIndex, Glyphs) then
      TotalGlyphs := Length(Glyphs);
    // Довіряйте дереву структури лише коли воно заявляє більшу
    // частину сторінки
    if (TotalGlyphs = 0) or (Untagged * 4 <= TotalGlyphs) then
    begin
      UsedStructure := True;
      Result := True;
      Exit;
    end;
  end;
  Result := Pdf.ExtractLoadedPageText(PageIndex, AText);
end;

Що змушує функцію повернути False

Три випадки, і їх варто розрізняти, бо лише один із них — вада документа. Перший — звичайний нетегований PDF: без /StructTreeRoot, нема чого проходити, і False — просто правда. Другий — сканована сторінка, чий текст походить із шару OCR, який ніколи не тегували. Третій — цікавий: вміст, що несе оператори BDC зі значеннями /MCID, але чия сторінка не має запису /StructParents, а дерево структури ніколи не посилається на ті ідентифікатори. Позначений вміст існує, сторона структури — ні, і немає порядку, який можна відновити. HotPDF повідомляє False, а не вигадує його

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

Де порядок структури окупає себе

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

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

HotPDF — власний компонент VCL для Delphi та C++Builder, тож прохід дерева структури і повторення гліфів працюють у процесі над завантаженим документом без жодного зовнішнього рендерера. Повні деталі API родини вилучення з завантажених документів — на сторінці продукту HotPDF Delphi PDF component