Komponent HotPDF Component dokáže vyhľadávať a nahrádzať text vo vnútri existujúceho PDF z prostredia Delphi a C++Builder. Metódy SearchLoadedPageText a SearchLoadedDocumentText vyhľadajú každý výskyt reťazca s presnosťou na úrovni glyfov, a ReplaceLoadedPageText a ReplaceLoadedDocumentText prepíšu zodpovedajúce bajty priamo na mieste — za predpokladu, že každý znak nahradenia sa dá spätne zakódovať prostredníctvom pôvodného písma, čo je fyzické obmedzenie, ku ktorému sa tento článok stavia úprimne a neskrýva ho v poznámke pod čiarou
Požiadavka na túto funkciu býva zvyčajne prozaická. Spoločnosť zmení svoj názov a tri tisícky archivovaných faktúr stále nesú starý názov. Šablóna zmluvy bola odoslaná s minuloročným dátumom expirácie. Kód produktu bol vyradený a každý katalógový list, ktorý ho uvádza, potrebuje namiesto neho kód nástupcu. V textovom procesore je každá z týchto úloh prácou na tridsať sekúnd. V PDF je to však skutočne zložitý problém a pochopenie toho, prečo je to tak, predstavuje rozdiel medzi správnym používaním tohto API a nahlasovaním chyby, ktorá je v skutočnosti vlastnosťou špecifikácie
Prečo je nahrádzanie textu v PDF také zložité?
Nahrádzanie textu v PDF je zložité, pretože stránka PDF neobsahuje upraviteľný text — obsahuje umiestnené glyfy. Podľa modelu zobrazovania textu normy ISO 32000-1 §9.4, tok obsahu (content stream) riadi operátory ako Tj a TJ, ktoré vykresľujú sekvencie kódov znakov na súradniciach určených maticou textu. Tieto kódy nie sú Unicode; sú to indexy do akéhokoľvek kódovania, ktoré deklaruje písmo stránky, a mapovanie späť na čitateľné znaky sa môže nachádzať v tabuľke /ToUnicode CMap, poli rozdielov kódovania alebo reťazci mapovania CID. Neexistuje tu objekt odseku, žiadny tok textu a žiadna záruka, že jedno vizuálne slovo je vôbec uložené ako jeden reťazec
Nahrádzanie pridáva k dekódovaniu druhú úroveň obtiažnosti: musíte presne vedieť, ktoré bajty pôvodného toku vygenerovali každý glyf, aby ste mohli nové bajty vložiť presne do tohto rozsahu a nikam inam. Extrakčnému nástroju stačí vyhodiť pozície bajtov po získaní Unicode textu. Nástroj na nahrádzanie to však urobiť nemôže. Preto HotPDF rozdelil prácu do dvoch vydaní — verzia v2.251.0 vybudovala vrstvu na sledovanie posunu a vyhľadávanie, a verzia v2.252.0 na nich postavila prepisovaciu vrstvu
Vyhľadávanie textu: vyhľadávanie na úrovni glyfov so sledovaním posunu bajtov
Metóda SearchLoadedDocumentText v HotPDF nájde každý výskyt hľadaného reťazca porovnaním s dekódovanou postupnosťou Unicode glyfov každej stránky, a nie so surovými bajtami toku, takže zásah je platný bez ohľadu na to, ako ho písmo zakódovalo. Infraštruktúra pod tým bola predstavená vo verzii v2.251.0: tokenizer toku obsahu zaznamenáva bajtový rozsah StartOfs/EndOfs for every string operand — including its ( ) or < > delimiters — a každý dekódovaný glyf nesie trojicu TokenIndex/ItemIndex/ByteOffset ukazujúcu späť na presný operand, položku poľa TJ a jednotku kódu, ktoré ho vytvorili. Rovnaký interpretátor glyfov poháňa API pre extrakciu popísané v extracii textu z načítaného PDF v Delphi; vyhľadávanie si jednoducho ponecháva informácie o pôvode, ktoré extrakcia zahadzuje
Každá zhoda sa vracia ako záznam THPDFTextMatch obsahujúci index stránky, inkluzívny rozsah glyfov, súradnice počiatku X/Y v používateľskom priestore, šírku zásahu, indexy zdrojového tokenu a položky a samotný zhodný text. To stačí na vykreslenie zvýraznenia, používateľského rozhrania pre kontrolu alebo krok nahradenia. Vyhľadávanie, ktoré nič nenájde, vracia prázdne pole namiesto zlyhania, takže vzor volania zostáva jednoduchý
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;
Jedno zámerné rozhodnutie pri návrhu si zaslúži poznámku. Ak je parameter CaseSensitive nastavený na False, porovnávanie prevádza veľkosť písma iba pre znaky ASCII, a to zámerne: úplný prevod veľkosti písma Unicode sa chová odlišne v rôznych nástrojoch od Delphi 5 po XE, ktoré HotPDF podporuje, a vyhľadávacie API, ktoré nachádza rôzne zhody v závislosti od toho, ktorý kompilátor zostavil vašu aplikáciu, je horšie ako to s dokumentovaným, predvídateľným limitom. Pre bežný latinský obchodný text — názvy, kódy, dátumy — prevod ASCII pokrýva väčšinu praktických prípadov
Nahrádzanie textu: reverzné kódovanie a chirurgické spájanie
Metóda ReplaceLoadedDocumentText, pridaná v HotPDF v2.252.0, prepisuje každý výskyt hľadaného reťazca tak, že spúšťa dekódovací aparát spätne. Funkcia HPDFEncodeUnicode je inverznou funkciou dekodéra kódov znakov: prechádza rovnakým reťazcom stratégií v opačnom poradí — vyhľadávanie bfchar a bfrange v /ToUnicode, mapovanie CID v toku kódovania, mapovania identity Type0 a vopred definované tabuľky WinAnsi a MacRoman — aby premenila každý nahradzovaný znak späť na bajty kódov znakov, ktoré pôvodné písmo očakáva. Spätne zakódované bajty sa potom serializujú do správne naformátovaného string literálu alebo hexadecimálneho reťazca, pričom odzrkadľujú pravidlá escaping samotného tokenizeru, takže cyklus analýzy a opätovnej serializácie je stabilný
Samotné spojenie (splice) je skôr chirurgické ako plošné. Nahradí sa iba rozsah kódových bajtov pokrytý zhodou vo vnútri string operandu; nezhodné bajty v rovnakom operande, biele znaky medzi tokenmi a každý okolitý operátor zostávajú zachované presne tak, ako boli, bajt po bajte. Nahradenie bca vo vnútri abcabc prinesie a + nahradenie + bc, nie poškodený operand. Nahradenia môžu byť kratšie alebo dlhšie ako hľadaný reťazec — literál sa znova serializuje a obnoví sa hodnota /Length toku — a každý tok /Contents na viacprúdovej stránke sa spracováva izolovane, aby stránka zostala správne naformátovaná
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;
Všimnite si, čo toto API nerobí: nevykonáva novú sadzbu stránky. Formát PDF nemá funkciu plynulého pretekania textu (reflow), takže nahrada, ktorá je vizuálne širšia ako originál, jednoducho zaberie viac horizontálneho priestoru a môže prekryť čokoľvek, čo bolo vykreslené napravo od nej. Substitúcie rovnakej alebo blízkej dĺžky — dátumy, verzie, čísla dielov, opravy mien — sú ideálnym prípadom. Kompletné preformulovanie textu patrí do zdrojového dokumentu, nie do PDF
Prečo nemožno nahradiť text znakmi, ktoré podmnožina písma nikdy neobsahovala?
Nemôžete nahradiť text znakom, ktorý vložená podmnožina písma nikdy neobsahovala, pretože postupnosť bajtov, ktorá by tento znak vybrala, v mapovacích tabuľkách písma jednoducho neexistuje. Keď tvorca PDF vloží písmo s podmnožinou, jeho tabuľka /ToUnicode CMap a štruktúry kódovania pokrývajú iba glyfy, ktoré pôvodný dokument skutočne používal. Metóda HPDFEncodeUnicode môže zvrátiť iba také mapovanie, ktoré je prítomné: ak dokument nikdy neobsahoval písmeno E v tomto písme, neexistuje žiadny kód znaku pre E, na ktorý by sa dalo spätne odkázať. Toto je fyzická vlastnosť súboru a nie obmedzenie konkrétnej knižnice — žiadny nástroj nedokáže vyčariť mapovanie glyfu, ktoré nebolo nikdy vložené
HotPDF rieši toto zlyhanie konzervatívne. Ak sa nedá spätne zakódovať čo i len jediný znak nahradenia, preskočí sa celý výskyt hľadaného reťazca — žiadna výnimka, žiadny neúplný poškodený text a výskyt sa jednoducho nezapočíta do hodnoty ReplaceCount. Praktický dôsledok: porovnajte ReplaceCount s počtom zhôd z predchádzajúceho vyhľadávania a akýkoľvek rozdiel berte ako varovanie. V príklade s dátumom vyššie sa musí číslica 6 niekde v texte dokumentu v rovnakom písme nachádzať, aby prepis uspel — čo je pravdepodobné pri faktúre, ale všeobecne to zaručené nie je. Keď potrebné znaky jednoducho nie sú k dispozícii a cieľom je odstrániť citlivý text namiesto jeho zmeny, lepším nástroju je aj tak skutočné odstránenie obsahu; pozrite si redigovanie a reštrukturalizáciu načítaných PDF v Delphi pre túto cestu
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;
Druhou podmienkou preskočenia v tejto správe je ďalšia dokumentovaná hranica: hľadaný reťazec, ktorý presahuje viacero reťazcových operandov — napríklad Hello rozdelené medzi položky [(He)(llo)] TJ — sa vyhľadávaním nájde, pretože vyhľadávanie porovnáva dekódovanú sekvenciu glyfov, ale pri nahrádzaní sa preskočí, pretože prepísanie cez hranice operandov by vyžadovalo zlúčenie susediacich bajtových rozsahov. Postup vyhľadať-potom-overiť zviditeľní oba tieto limity
Čo sa v súbore zmení pri uložení?
Nahradený tok /Contents sa ukladá nekomprimovaný. Toky s kompresiou FlateDecode se dekomprimujú kvôli úpravám, a keď HotPDF zapisuje prestavané bajty, odstráni záznam /Filter toku a obnoví /Length namiesto opätovnej kompresie. Výsledné PDF je plne platné a vykresľuje sa normálne v bežných prehliadačoch; kompromisom je väčší súbor pre každý upravený tok. Pre hromadné spracovanie tisícok dokumentov počítajte s týmto nárastom veľkosti, alebo spustite samostatný krok kompresie neskôr. Ako upravené objekty interagujú so štruktúrou krížových odkazov dokumentu pri uložení je samostatnou témou, o ktorej sa píše v tokoch objektov a inkrementálnych aktualizáciách v HotPDF
Všetko ostatné v súbore zostáva nezmenené. Nedotknuté toky si zachovávajú svoju kompresiu, písma a obrázky sa neprepisujú a spojenie na úrovni operandov znamená, že aj upravené toky sa od originálu líšia len v miestach, kde došlo k zhode. Tento konzervatívny prístup je zámerný: čím viac z načítaného dokumentu knižnica prepisuje, tým viac príležitostí má na porušenie nejakého špecifického správania tvorcu súboru, ktoré nepredvídala
Vyhľadávanie a nahrádzanie textu sa spája s extrakciou, redigovaním a vykresľovaním stránok v sade nástrojov pre načítané dokumenty v HotPDF, pričom všetko je riadené rovnakým interpretátorom toku obsahu a dostupné od Delphi 5 po súčasné verzie RAD Studio bez externých závislostí. Úplná referenčná príručka API a skúšobná verzia na stiahnutie sú na stránke produktu HotPDF Component