HotPDF Component poate căuta și înlocui text într-un PDF existent din Delphi și C++Builder. SearchLoadedPageText și SearchLoadedDocumentText localizează fiecare apariție a unui șir de caractere cu precizie la nivel de glifă, iar ReplaceLoadedPageText și ReplaceLoadedDocumentText rescriu octeții potriviți pe loc — cu condiția ca fiecare caracter de înlocuire să poată fi recodificat prin fontul original, o constrângere fizică pe care acest articol o tratează în mod direct, nu ascunsă într-o notă de subsol
Solicitarea din spatele acestei caracteristici este întotdeauna una obișnuită. O companie își schimbă numele și trei mii de facturi arhivate încă mai conțin vechiul nume. Un șablon de contract a fost distribuit cu data de expirare de anul trecut. Un cod de produs a fost retras și fiecare fișă tehnică ce îl menționează are nevoie în schimb de codul succesor. Într-un procesator de text, fiecare dintre acestea este o sarcină de treizeci de secunde. Într-un PDF, este o problemă cu adevărat dificilă, iar înțelegerea motivului face diferența între utilizarea corectă a API-ului și trimiterea unui raport de eroare care este în realitate o trimitere la specificație
De ce este atât de dificilă înlocuirea textului într-un PDF?
Înlocuirea textului într-un PDF este dificilă deoarece o pagină PDF nu conține text editabil — ea conține glife poziționate. Conform modelului de afișare a textului din ISO 32000-1 §9.4, un flux de conținut rulează operatori precum Tj și TJ care desenează secvențe de coduri de caractere la coordonatele stabilite de matricea textului. Acele coduri nu sunt Unicode; sunt indecși în orice codificare declară fontul paginii, iar maparea înapoi la caractere lizibile poate fi stocată într-un CMap /ToUnicode, într-un tablou de diferențe de codificare sau într-un lanț de mapare CID. Nu există niciun obiect paragraf, niciun flux de text și nicio garanție că un cuvânt vizual este stocat ca un singur șir
Înlocuirea adăugă un al doilea nivel de dificultate peste decodificare: trebuie să știți exact ce octeți ai fluxului original au produs fiecare glifă, astfel încât să puteți introduce octeții noi exact în acea secțiune și nicăieri altundeva. Un extractor de text își poate permite să elimine pozițiile octeților odată ce a extras codul Unicode. Un înlocuitor nu poate face acest lucru. De aceea, HotPDF a împărțit activitatea în două versiuni — v2.251.0 a construit urmărirea decalajelor și stratul de căutare, iar v2.252.0 a construit stratul de rescriere peste acesta
Găsirea textului: căutare la nivel de glifă cu urmărirea decalajului de octeți
Funcția SearchLoadedDocumentText din HotPDF găsește fiecare apariție a unui șir prin compararea cu secvența de glife Unicode decodificată a fiecărei pagini, nu cu octeții bruts ai fluxului, astfel încât o potrivire este validă indiferent de modul în care fontul a codificat-o. Infrastructura subiacentă a fost introdusă în v2.251.0: tokenizer-ul fluxului de conținut înregistrează un interval de octeți StartOfs/EndOfs pentru fiecare operand șir — inclusiv delimitatorii săi ( ) sau < > — iar fiecare glifă decodificată conține un triplet TokenIndex/ItemIndex/ByteOffset care trimite înapoi la operandul exact, elementul din tabloul TJ și unitatea de cod care l-au produs. Același interpret de glife controlează API-ul de extragere descris în extragerea textului dintr-un PDF încărcat în Delphi; căutarea păstrează pur și simplu sursa pe care extragerea o elimină
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;
O decizie deliberată de proiectare merită menționată. Când parametrul CaseSensitive are valoarea False, compararea uniformizează literele mari și mici doar pentru caracterele ASCII, prin proiectare: uniformizarea completă a literelor Unicode funcționează diferit în seturile de instrumente Delphi de la versiunea 5 la XE acceptate de HotPDF, iar un API de căutare care oferă rezultate diferite în funcție de compilatorul care a construit aplicația este mai puțin util decât unul cu o limită documentată și predictibilă. Pentru textele comerciale latine — nume, coduri, date — uniformizarea ASCII acoperă cazurile practice
Înlocuirea textului: codificare inversă și îmbinare chirurgicală
ReplaceLoadedDocumentText, adăugat în HotPDF v2.252.0, rescrie fiecare apariție a unui șir prin rularea inversă a mecanismului de decodificare. Funcția HPDFEncodeUnicode este inversul decodorului de coduri de caractere: parcurge același lanț de strategii în sens invers — căutarea bfchar și bfrange în /ToUnicode, maparea CID a fluxului de codificare, mapările de identitate Type0 și tabelele predefinite WinAnsi și MacRoman — pentru a transforma fiecare caracter de înlocuire înapoi în octeții de cod de caracter pe care îi așteaptă fontul original. Octeții recodificați sunt apoi serializați într-un literal de șir corect format sau într-un șir hexazecimal, reflectând regulile proprii de escape ale tokenizer-ului, astfel încât un proces complet de analiză și reserializare să fie stabil
Îmbinarea în sine este una chirurgicală, nu una globală. Doar intervalul de octeți de cod acoperit de potrivire este înlocuit în interiorul operandului șir; octeții nepotriviți din același operand, spațiile dintre token-uri și toți operatorii din jur sunt păstrați neschimbați, octet cu octet. Înlocuirea bca în abcabc produce a + înlocuire + bc, nu un operand compromis. Înlocuirile pot fi mai scurte sau mai lungi decât șirul inițial — literalul este reserializat, iar lungimea /Length a fluxului este actualizată — iar fiecare flux /Contents al unei pagini cu mai multe fluxuri este procesat izolat, astfel încât pagina să rămână corect formată
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;
Rețineți ce nu face API-ul: acesta nu culege din nou textul paginii. PDF-ul nu are o rearanjare automată a textului (reflow), așa că o înlocuire care este mai lată vizual decât cea originală va ocupa pur și simplu mai mult spațiu orizontal și poate aglomera elementele desenate la dreapta sa. Înlocuirile cu aceeași lungime sau cu o lungime similară — date, șiruri de versiuni, coduri de piese, corecturi de nume — reprezintă scenariul ideal. Rearanjarea globală a textului trebuie realizată în documentul sursă, nu în PDF
De ce nu se poate înlocui textul cu caractere pe care subsetul de fonturi nu le-a inclus niciodată?
Nu puteți înlocui textul cu un caracter pe care subsetul de fonturi încorporat nu l-a inclus niciodată, deoarece secvența de octeți care ar selecta acel caracter pur și simplu nu există în tabelele de mapare ale fontului. Când un program de generare PDF încorporează un font subsetat, CMap-ul său /ToUnicode și structurile de codificare acoperă doar glifele pe care documentul original le-a utilizat de fapt. HPDFEncodeUnicode poate anula doar o mapare care este prezentă: dacă documentul nu a conținut niciodată litera E în acel font, nu există niciun cod de caracter la care E să poată fi asociat. Aceasta este o proprietate fizică a fișierului, nu o limitare a unei anumite biblioteci — niciun instrument nu poate crea o mapare de glife care nu a fost încorporată niciodată
HotPDF gestionează acest eșec în mod conservator. Dacă un singur caracter al înlocuirii nu poate fi recodificat, întreaga apariție a acelui șir este omisă — fără excepții, fără text parțial eronat, iar apariția respectivă nu este contorizată în ReplaceCount. Consecința practică: comparați ReplaceCount cu numărul de potriviri din căutarea anterioară și tratați o diferență ca pe un semnal. In exemplul cu data de mai sus, cifra 6 trebuie să apară undeva în textul documentului cu același font pentru ca rescrierea să reușească — probabil într-o factură, lucru care nu este garantat în general. Când caracterele de care aveți nevoie pur și simplu nu sunt disponibile, iar scopul este eliminarea textului sensibil în loc de rearanjarea sa, eliminarea efectivă a conținutului este oricum instrumentul mai bun; consultați redactarea și restructurarea PDF-urilor încărcate în Delphi pentru această variantă
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;
A doua condiție de omitere din acel mesaj este cealaltă limită documentată: un șir care acoperă mai mulți operanzi de tip șir — de exemplu, Hello divizat în elementele [(He)(llo)] TJ — este găsit de funcția de căutare, deoarece căutarea compară secvența de glife decodificată, dar este omis de înlocuire, deoarece rescrierea dincolo de limitele operanzilor ar necesita îmbinarea intervalelor de octeți adiacente. Procesul de căutare urmat de verificare face ca ambele limite să fie vizibile, nu silențioase
Ce se modifică în fișier când salvați?
Un flux /Contents înlocuit este salvat necomprimat. Fluxurile comprimate prin FlateDecode sunt decomprimate pentru editare, iar când HotPDF scrie octeții reconstruiți, elimină intrarea /Filter a fluxului și actualizează /Length în loc să recomprime. PDF-ul rezultat este complet valid și se afișează normal în vizualizatoarele populare; dezavantajul este un fișier mai mare pentru fiecare flux editat. Pentru o linie de procesare pe loturi care procesează mii de documente, luați în calcul această creștere sau rulați separat o etapă de compresie în aval. Modul în care obiectele rescrise interacționează cu structura de referințe încrucișate a documentului la salvare este un subiect distinct, tratat în fluxurile de obiecte și actualizările incrementale în HotPDF
Toate celelalte elemente din fișier sunt lăsate neschimbate. Fluxurile neatinse își păstrează compresia, fonturile și imaginile nu sunt rescrise, iar îmbinarea la nivel de operand înseamnă că până și fluxurile editate diferă de cel original doar în locurile în care s-a realizat o potrivire. Acest conservatorism este intenționat: cu cât o bibliotecă rescrie mai mult dintr-un document încărcat, cu atât are mai multe oportunități de a afecta un comportament specific al programului de generare pe care nu l-a prevăzut
Căutarea și înlocuirea textului se alătură extragerii, redactării și redării paginilor în setul de instrumente pentru documente încărcate al HotPDF, toate fiind controlate de același interpret de flux de conținut și fiind disponibile de la Delphi 5 până la versiunile actuale RAD Studio, fără dependențe externe. Referința completă a API-ului și versiunea de evaluare pentru descărcare se găsesc pe pagina produsului HotPDF Component