Извличането на текст от PDF изглежда лесно, докато не се сблъскате с документ, при който текстовият слой липсва, повреден е или е разделен на десетки малки части без ясна последователност. PDFium Component предоставя две възможности: масива Character[] за директен достъп по индекс до всеки символ на страницата, и метода ReadablePageContent за структуриран изглед, който пресъздава абзаци и заглавия от структурата на таговете в PDF файла или чрез евристичен анализ. Нито един от тях не е универсален избор, затова е важно да се знае какво предлага всеки
Отваряне на документа и капанът с безшумен отказ
Компонентът TPdf отваря файл чрез задаване на FileName и промяна на Active := True. Важна подробност: задаването Active := True никога не генерира изключение. Ако файлът липсва, защитен е с парола или е повреден, PDFium обработва грешката вътрешно и Active остава на стойност False. Това означава, че всеки процес на извличане трябва да съдържа следната проверка:
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True;
if not Pdf.Active then
begin
ShowMessage('Could not open PDF (damaged or wrong password)');
Exit;
end;
// extraction follows here
finally
Pdf.Active := False;
Pdf.Free;
end;
Защитените с парола файлове изискват задаване на Pdf.Password := '...' преди Active := True. Няма втори шанс: ако Active върне грешка, трябва да затворите файла и да го отворите отново с правилната парола
Извличане страница по страница чрез Character[]
Най-ниско ниво на достъп се осигурява чрез обхождане на всеки символ на страницата. Задайте Pdf.PageNumber, за да заредите текстовия слой за тази страница, след което обходете елементите до CharacterCount чрез свойството Character[]. Струва си да се проверяват два флага за всеки елемент: CharacterGenerated[i] маркира генерирани символи, въведени от рендиращия модул (например тирета за пренасяне), които нямат реална Unicode стойност, а CharacterMapError[i] показва, че PDFium не е успял да напасне символа към Unicode код, което се случва при кодиране на шрифтове без ToUnicode таблица
procedure ExtractAllText(Pdf: TPdf; Output: TStrings);
var
Page, I: Integer;
Line: string;
Ch: WideChar;
begin
for Page := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := Page;
Line := '';
for I := 0 to Pdf.CharacterCount - 1 do
begin
if Pdf.CharacterGenerated[I] or Pdf.CharacterMapError[I] then
Continue;
Ch := Pdf.Character[I];
if Ch = #13 then
Ch := #10; // normalize CR to LF
Line := Line + Ch;
end;
Output.Add(Line);
end;
end;
Резултатът е обикновен низ от Unicode символи в реда, в който PDFium ги извлича (реда на появата им в потока от съдържание), което невинаги съвпада с реда за четене отляво надясно. За повечето документи на латиница, създадени със стандартни офис пакети, това е напълно достатъчно. При сканирани PDF файлове с разпознаване на текст (OCR) или при текст отдясно наляво, редът може да бъде грешен. В тези случаи методът ReadablePageContent е по-полезен
Структурирано извличане с ReadablePageContent
Методът ReadablePageContent работи на по-високо ниво: той връща запис TPdfReadableContent, чийто масив Fragments съдържа фрагменти с определен тип Kind, който идентифицира абзаци, заглавия, елементи от списъци, клетки на таблици и други. Когато PDF файлът съдържа структурирано дърво (проверете Pdf.IsTagged), източникът е rosStructure и редът на четене е гарантиран. При неструктурирани файлове PDFium се връща към rosHeuristic – евристичен анализ, който групира символите в логически единици въз основа на техните размери, но не гарантира пълна точност
procedure ExtractStructured(Pdf: TPdf; Output: TStrings);
var
Page: Integer;
Content: TPdfReadableContent;
Fragment: TPdfContentFragment;
begin
for Page := 1 to Pdf.PageCount do
begin
Content := Pdf.ReadablePageContent(Page);
for Fragment in Content.Fragments do
begin
case Fragment.Kind of
cfHeading : Output.Add('# ' + Fragment.Text);
cfParagraph : Output.Add(Fragment.Text);
cfListItem : Output.Add('- ' + Fragment.Text);
else
Output.Add(Fragment.Text);
end;
end;
end;
end;
Ако Content.Source = rosHeuristic и резултатът изглежда разбъркан, текстовият слой на документа вероятно не е записан с оглед на реда за четене. В този случай единственото сигурно решение е нов експорт от изходното приложение с коректни тагове или допълнителна обработка, която сортира координатите на символите първо по Y, а след това по X
Какво предоставят CharacterOrigin и CharacterRectangle
И двете свойства връщат позицията на символа в координатите на страницата (в точки, като началото е в долния ляв ъгъл, а Y нараства нагоре). CharacterOrigin[i] е базовата точка на символа; CharacterRectangle[i] е неговият пълен обхващащ правоъгълник. Това са градивните елементи за всяка обработка отвъд чистия текст: откриване на колони, групиране на символи в редове по Y координати или изграждане на селекция за четец. Ако трябва да разберете кой символ се намира под курсора на мишката, методът CharacterIndexAtPos(X, Y, ToleranceX, ToleranceY) извършва това търсене директно, без да е необходимо да обхождате правоъгълниците ръчно
Инсталиране на DLL библиотеката
Компонентът PDFium Component прехвърля целия анализ на PDF към нативна DLL библиотека – pdfium32.dll или pdfium64.dll, в зависимост от целевата платформа. Комплектът включва скрипт CopyDlls.bat, който копира правилния файл в системната директория на Windows. Изпълнението му като администратор веднъж на компютъра за разработка е достатъчно; за разпространение копирайте DLL файла в папката на самото приложение. Версиите с поддръжка на V8 (pdfium32v8.dll, pdfium64v8.dll) са много по-големи и са необходими само ако вашите PDF документи съдържат JavaScript код. За просто извличане на текст стандартната версия е най-добрият избор
Ако DLL файлът липсва по време на работа, задаването на Active := True ще приключи с безшумен отказ, точно както при липсващ файл, тъй като компонентът улавя грешката при зареждане вътрешно. Винаги тествайте на чиста машина преди разпространение
Използване на FontSize[] заедно с Character[] за анализ на оформлението
Отвъд чистия текст, програмният интерфейс предоставя свойството FontSize[i], което връща размера на всеки символ в точки. Комбинирано с CharacterOrigin[i] и CharacterRectangle[i], това ви позволява да разграничавате основния текст от заглавията, без да разчитате на структурираното дърво. Поредица от символи, при която размерът на шрифта се увеличава над определен праг, почти сигурно представлява заглавие. Същият метод може да се използва за откриване на подписи под картинки (малък текст под обхвата на изображението) или бележки под линия (малък текст в долната част на страницата). Нищо от това не изисква рендиране; и трите свойства четат данни директно от текстовия слой, който PDFium изгражда при Active := True
Един нюанс: FontSize[i] отразява размера след прилагане на матрицата на трансформация на страницата (CTM), така че документ, чиито страници са били мащабарини при създаването, ще върне съответно променени размери. Ако сравнявате размери между страници с различни размери, ги нормализирайте спрямо височината на MediaBox на страницата, преди да вземате решения въз основа на прагови стойности
Записване на резултата във файл
Класът TStringList в Delphi поддържа UTF-8 изходни данни коректно от XE насам. Задайте WriteBOM := False, ако имате нужда от файл без BOM маркер (много софтуерни инструменти не поддържат водещ BOM маркер):
var
Lines: TStringList;
begin
Lines := TStringList.Create;
try
ExtractAllText(Pdf, Lines);
Lines.WriteBOM := False;
Lines.SaveToFile('output.txt', TEncoding.UTF8);
finally
Lines.Free;
end;
end;
За много големи документи, където паметта е ограничена, пишете директно в TStreamWriter с TEncoding.UTF8 в рамките на цикъла за страници, вместо първо да натрупвате целия текст в списък
Програмните интерфейси Character[], CharacterCount, CharacterOrigin[], CharacterRectangle[], ReadablePageContent и CharacterIndexAtPos, показани тук, са част от PDFium Component за Delphi и C++Builder