HotPDF Component може да търси и заменя текст в съществуващ PDF от Delphi и C++Builder; SearchLoadedPageText и SearchLoadedDocumentText намират всяко срещане на низ с точност до ниво глифове, а ReplaceLoadedPageText и ReplaceLoadedDocumentText пренаписват съвпадащите байтове на място — при условие че всеки заместващ символ може да бъде кодиран обратно чрез оригиналния шрифт, физическо ограничение, което тази статия разглежда честно, вместо да го крие в бележка под линия
Искането зад тази функция винаги е прозаично; Компания променя името си и три хиляди архивирани фактури все още носят старото име; Шаблон за договор е доставен с изтекъл срок на годност от миналата година; Продуктов код е спрян от употреба и всеки информационен лист, който го споменава, се нуждае вместо това от кода на наследника; В текстов редактор всяко от тези е задача за тридесет секунди; В PDF това е наистина труден проблем и разбирането на причината определя разликата между това да използвате API добре и да изпратите отчет за грешка, който всъщност е цитат от спецификацията
Защо подмяната на текст в PDF е толкова трудна?
Замяната на текст в PDF е трудна, тъй като PDF страницата не съдържа редактируем текст — тя съдържа позиционирани глифове; Съгласно модела за показване на текст в ISO 32000-1 §9.4, потокът от съдържание управлява оператори как Tj и TJ, които изчертават последователности от кодове на символи при координати, установени от текстовата матрица; Тези кодове не са Unicode; те са индекси в кодирането, което шрифтът на страницата декларира, а съпоставянето обратно към четими знаци може да живее в /ToUnicode CMap, масив с разлики в кодирането или верига за съпоставяне на CID; Няма обект за абзац, няма текстов поток и няма гаранция, че една визуална дума е съхранена като един низ
Замяната добавя втори слой трудност над декодирането: трябва да знаете точно кои байтове от оригиналния поток са произвели всеки глиф, за да можете да вмъкнете нови байтове точно в този диапазон и никъде другаде; Екстрактор на текст може да си позволи да изхвърли байтовите позиции, след като извлече Unicode; Модул за замяна обаче не може; Ето защо HotPDF раздели работата в две версии — v2.251.0 изгради слоя за проследяване на отместването и търсене, а v2.252.0 изгради слоя за пренаписване над него
Намиране на текст: търсене на ниво глифове с проследяване на байтовото отместване
Методът SearchLoadedDocumentText на HotPDF намира всяко срещане на низ чрез съпоставяне с декодираната Unicode последователност от глифове на всяка страница, а не с необработените байтове на потока, така че съвпадението е сигурно, независимо от начина, по който шрифтът го е кодирал; Инфраструктурата под него беше въведена във v2.251.0: токенизаторът на потоци от съдържание записва диапазон от байтове StartOfs/EndOfs за всеки операнд на низ — включително неговите разделители ( ) или < > — и всеки декодиран глиф носи тройка TokenIndex/ItemIndex/ByteOffset, сочеща обратно към точния операнд, елемент от масива TJ и кодова единица, които са го произвели; Същият интерпретатор на глифове захранва API за извличане, описан в извличане на текст от зареден PDF в Delphi; търсенето просто запазва произхода, който извличането отхвърля
Всяко съвпадение се връща като запис THPDFTextMatch, носещ индекса на страницата, включителния диапазон от глифове, началото по X/Y в потребителското пространство и ширината на съвпадението, изходния маркер и индекса на елемента, както и самия съвпаднал текст; Това е достатъчно за управление на наслагване за маркиране, потребителски интерфейс за преглед или стъпка за замяна; Търсене, което не намира нищо, връща празен масив, вместо да се срине, така че моделът на извикване остава прост
var
Pdf: THotPDF;
Matches: THPDFTextMatchArray;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('invoices-2025.pdf') > 0 then
begin
if Pdf.SearchLoadedDocumentText('Acme Corp', False, Matches) then
for I := 0 to Length(Matches) - 1 do
WriteLn(Format('page %d at (%.1f, %.1f): "%s"',
[Matches[I].PageIndex, Matches[I].X, Matches[I].Y,
Matches[I].Text]));
end;
finally
Pdf.Free;
end;
end;
Един съзнателен избор при дизайна заслужава отбелязване; Когато CaseSensitive е False, сравнението променя регистъра само за ASCII символи, по дизайн: пълното преобразуване на регистъра в Unicode се държи различно в различните компилатори от Delphi 5 до XE, които HotPDF поддържа, а API за търсене, което намира различни съвпадения в зависимост от това кой компилатор е изградил вашето приложение, е по-лошо от такова с документирано и предвидимо ограничение; За латински бизнес текстове — имена, кодове, дати — ASCII преобразуването покрива практическите случаи
Замяна на текст: обратно кодиране и хирургично снаждане
ReplaceLoadedDocumentText, добавен в HotPDF v2.252.0, пренаписва всяко срещане на низ чрез стартиране на механизмите за декодиране в обратна посока; Функцията HPDFEncodeUnicode е обратната на декодера за кодове на знаци: тя обхожда същата верига от стратегии в обратен ред — търсене в /ToUnicode bfchar и bfrange, CID съпоставяне в кодиращия поток, Type0 identity съпоставяния и предефинираните таблици WinAnsi и MacRoman — за да превърне всеки заместващ символ обратно в байтовете за код на знака, които оригиналният шрифт очаква; След това кодираните байтове се сериализират в добре оформен литерал на низ или шестнадесетичен низ, отразявайки собствените правила за екраниране на токенизатора, така че цикълът анализ → повторно сериализиране да бъде стабилен
Самото снаждане е хирургично, а не мащабно; Само диапазонът от кодови байтове, покрит от съвпадението, се заменя в операнда на низа; несъвпадащите байтове в същия операнд, интервалите между маркерите и всеки съпътстващ оператор се запазват буквално, байт по байт; Замяната на bca в abcabc дава a + замяна + bc, а не развален операнд; Замените могат да бъдат по-къси или по-дълги от търсения низ — литералът се сериализира отново и стойността /Length на потока се обновява — и всеки поток /Contents на страница с множество потоци се обработва изолирано, така че страницата остава правилно оформена
var
Pdf: THotPDF;
ReplaceCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('contract-draft.pdf') > 0 then
begin
if Pdf.ReplaceLoadedDocumentText('2025-12-31', '2026-12-31',
True, ReplaceCount) then
WriteLn(Format('%d operand rewrites performed', [ReplaceCount]));
Pdf.SaveLoadedDocument('contract-final.pdf');
end;
finally
Pdf.Free;
end;
end;
Обърнете внимание какво не прави този API: той не преформатира страницата; PDF няма автоматично пренареждане (reflow), така че замяна, която е визуално по-широка от оригинала, просто ще заеме повече хоризонтално пространство и може да застъпи това, което е нарисувано вдясно от нея; Замени със същата или близка дължина — дати, версии, номера на части, корекции на имена — са идеалният случай; Цялостното пренаписване на текста е задача за изходния документ, а не за PDF
Защо не можете да замените текст със символи, които подмножеството на шрифта никога не е включвало?
Не можете да замените текст със символ, който вграденото подмножество на шрифта никога не е включвало, тъй като последователността от байтове, която би избрала този символ, просто не съществува в таблиците за съпоставяне на шрифта; Когато PDF създател вгражда подмножество от шрифт, неговите /ToUnicode CMap... и кодиращи структури обхващат само глифовете, които оригиналният документ действително е използвал; HPDFEncodeUnicode може да обърне само съпоставяне, което присъства: ако документът никога не е съдържал буквата E в този шрифт, няма код на символ за E, към който да се върне; Това е физическо свойство на файла, а не ограничение на конкретна библиотека — нито един инструмент не може да извлече съпоставяне на глиф, което никога не е било вградено
HotPDF обработва неуспеха консервативно; Ако дори един символ от замяната не може да бъде кодиран обратно, цялото срещане се пропуска — без изключение, без частичен нечетлив текст, а срещането просто не се отчита в ReplaceCount; Практическата последица: проверете ReplaceCount спрямо броя на съвпаденията от предходно търсене и разглеждайте разликата като сигнал; В примера с датата по-горе, цифрата 6 трябва да се появява някъде в текста на документа със същия шрифт, за да успее пренаписването — това е много вероятно в дадена фактура, но никога не е гарантирано като цяло; Когато нужните символи просто не са налични и целта е да премахнете чувствителна информация, а не да пренапишете текста, истинското премахване на съдържание е по-добрият инструмент; вижте редактиране и преструктуриране на заредени PDF файлове в Delphi за този път
var
Matches: THPDFTextMatchArray;
Expected, Replaced: Integer;
begin
Pdf.SearchLoadedDocumentText('Acme Corp', True, Matches);
Expected := Length(Matches);
Pdf.ReplaceLoadedDocumentText('Acme Corp', 'Apex Corp', True, Replaced);
if Replaced < Expected then
WriteLn(Format('%d occurrence(s) skipped: characters missing ' +
'from the font subset, or match spans multiple operands',
[Expected - Replaced]));
end;
Второто условие за пропускане в това съобщение е другата документирана граница: търсен низ, който обхваща няколко операнда за низ — например Hello, разделен между елементите на [(He)(llo)] TJ — се намира от търсенето, тъй като то съпоставя декодираната последователност от глифове, но се пропуска при замяната, тъй като пренаписването през границите на операндите би изисквало обединяване на съседни диапазони от байтове; Търсенето, последвано от проверка, прави и двете ограничения видими, вместо тихи
Какво се променя във файла при запазване?
Замененият поток /Contents се запазва некомпресиран; Потоците, компресирани с FlateDecode, се декомпресират за редактиране и когато HotPDF записва възстановените байтове, той премахва записа /Filter на потока и обновява /Length, вместо да компресира отново; Полученият PDF е напълно валиден и се изобразява нормално в масовите визуализатори; компромисът е по-голям файл за всеки редактиран поток; За конвейер за групова обработка, който обработва хиляди документи, планирайте този растеж или стартирайте отделна стъпка за компресиране по веригата; Как пренаписаните обекти взаимодействат със структурата на кръстосаните препратки на документа при запазване е отделна тема, разгледана в обектови потоци и инкрементални актуализации в HotPDF
Всичко останало във файла се оставя недокоснато; Непроменените потоци запазват своята компресия, шрифтовете и изображенията не се пренаписват, а снаждането на ниво операнди означава, че даже редактираните потоци се различават от оригинала само там, където е попаднало съвпадение; Този консерватизъм е съзнателен: колкото повече от заредения документ пренаписва една библиотека, толкова повече възможности има да повреди някоя особеност на създателя, която не е предвидила
Търсенето и замяната на текст се присъединяват към извличането, редактирането и рендерирането на страници в инструментариума на HotPDF за заредени документи, всички управлявани от един и същ интерпретатор на потоци от съдържание и налични от Delphi 5 до настоящите версии на RAD Studio без външни зависимости; Пълната справочна информация за API и пробната версия за изтегляне са на страницата на продукта HotPDF Component