Tehnički članak

Pretraživanje i zamena teksta u postojećem PDF-u sa Delphijem

HotPDF Component može pretraživati i zameniti tekst unutar postojećeg PDF-a iz Delphija i C++Buildera. Pozivi SearchLoadedPageText i SearchLoadedDocumentText pronalaze svako pojavljivanje niza sa preciznošću na nivou glifa, a ReplaceLoadedPageText i ReplaceLoadedDocumentText prepisuju podudarne bajtove na licu mesta — pod uslovom da se svaki zamenski znak može ponovno kodirati putem izbornog fonta, što je fizičko ograničenje koje ovaj članak tretira pošteno, umesto da ga skriva u belešci

Zahtev iza ove funkcije uvek je svakodnevan. Firma menja naziv, a tri hiljade arhiviranih računa i dalje nosi stari naziv. Šablon ugovora isporučen je sa prošlogodišnjim datumom isteka. Šifra proizvoda je povučena iz upotrebe i svaka podatkovna tablica koja je spominje treba novu šifru naslednika. U programu za obradu teksta svaki od ovih zadataka traje trideset sekundi. U PDF-u je to uistinu težak problem, a razumevanje zašto čini razliku između dobrog korišćenja API-ja i podnošenja izveštaja o grešci koje je zapravo citat iz specifikacije

Zašto je zamena teksta u PDF-u tako teška?

Zamena teksta u PDF-u je teška jer PDF stranica ne sadrži tekst koji se može uređivati — ona sadrži pozicionirane glifove. Prema modelu prikaza teksta iz ISO 32000-1 §9.4, tok sadržaja pokreće operatore poput Tj i TJ koji iscrtavaju sekvence kodova znakova na koordinatama uspostavljenim tekstualnom matricom. Ti kodovi nisu Unicode; oni su indeksi u bilo koje kodiranje koje deklarise font stranice, a mapiranje nazad u čitljive znakove može živeti u CMap-u /ToUnicode, nizu razlika kodiranja (encoding difference array) ili CID lancu mapiranja. Ne postoji objekat odlomka, nema toka teksta i nema garancije da je jedna vizuelna reč uopšte čuvana kao jedan niz

Zamena dodaje drugi sloj težine na dekodiranje: morate znati tačno koji su bajtovi izvornog toka proizveli svaki glif, kako biste mogli spojiti nove bajtove tačno u taj raspon i ništa drugo. Alat za izdvajanje teksta može sebi priuštiti odbacivanje pozicija bajtova nakon što izvuče Unicode. Alat za zamenu ne može. Zato je HotPDF podelio rad na dva izdanja — v2.251.0 je izgradio sloj za praćenje pomaka i pretraživanje, a v2.252.0 je izgradio sloj za ponovno pisanje na vrhu

Pronalaženje teksta: pretraživanje na nivou glifa sa praćenjem pomaka bajtova

HotPDF-ov SearchLoadedDocumentText pronalazi svako pojavljivanje traženog pojma podudaranjem sa dekodiranim Unicode nizom glifova svake stranice, a ne sa sirovim bajtovima toka, pa je pogodak pogodak bez obzira na to kako ga je font kodirao. Temeljna infrastruktura uvedena je u verziji v2.251.0: analizator toka sadržaja beleži raspon bajtova StartOfs/EndOfs za svaki operand niza — uključujući njegove graničnike ( ) ili < > — i svaki dekodirani glif nosi trojku TokenIndex/ItemIndex/ByteOffset koja upućuje nazad na tačan operand, stavku niza TJ i kodnu jedinicu koja ga je proizvela. Isti interpretator glifova pokreće API za izdvajanje opisan u izdvajanju teksta iz učitanog PDF-a u Delphiju; pretraživanje jednostavno zadržava poreklo (provenance) koje izdvajanje odbacuje

Svaki pogodak dolazi kao zapis THPDFTextMatch koji nosi indeks stranice, uključivi raspon glifova, korisnički prostor X/Y porekla i širine pogotka, izvorni token i indeks stavke te sam podudarni tekst. To je dovoljno za pokretanje preklapanja isticanja, korisničkog interfejsa za pregled ili koraka zamene. Pretraživanje koje ne pronađe ništa vraća prazno polje radije nego da ne uspe, pa pozivni obrazac ostaje jednostavan

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;

Jedan namerni izbor dizajna zaslužuje zabelešku. Kada je CaseSensitive postavljeno na False, poređenje preklapa velika i mala slova samo za ASCII znakove, po dizajnu: potpuno Unicode preklapanje velikih i malih slova ponaša se različito na alatima od Delphi 5 do XE koje HotPDF podržava, a API za pretraživanje koji pronalazi različite pogotke ovisno o tome koji je kompajler izgradio vašu aplikaciju gori je od onog sa dokumentovanim, predvidljivim ograničenjem. Za latinični poslovni tekst — imena, šifre, datume — ASCII preklapanje pokriva praktične slučajeve

Zamena teksta: obrnuto kodiranje i hirurško spajanje

ReplaceLoadedDocumentText, dodat u HotPDF v2.252.0, prepisuje svako pojavljivanje traženog pojma pokretanjem mehanizma za dekodiranje unazad. Funkcija HPDFEncodeUnicode je obrnuta funkcija od dekodera kodova znakova: prolazi kroz isti lanac strategija unazad — pretraživanje /ToUnicode bfchar and bfrange, CID mapiranje toka kodiranja, Type0 mapiranja identiteta te unapred definisane WinAnsi i MacRoman tablice — kako bi svaki zamenski znak pretvorila nazad u bajtove koda znaka koje izvorni font očekuje. Ponovno kodirani bajtovi se zatim serijalizuju u ispravno oblikovan literal niza ili heksadecimalni niz, zrcaleći sopstvena pravila analizatora (tokenizer) za izbegavanje znakova, tako da je ciklus analiza → ponovna serijalizacija stabilan

Samo spajanje je hirurško, a ne grupno. Zamenjuje se samo raspon bajtova koda pokriven podudaranjem unutar operanda niza; nepodudarni bajtovi u istom operandu, razmaci između tokena i svaki okolni operator čuvaju se doslovno, bajt po bajt. Zamena bca unutar abcabc daje a + zamena + bc, a ne uništeni operand. Zamene mogu biti kraće ili duže od traženog pojma — literal se ponovo serijalizuje i osvežava se /Length toka — a svaki tok /Contents višestruko tokne stranice obrađuje se u izolaciji kako bi stranica ostala ispravno oblikovana

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;

Imajte na umu što API ne radi: on ne slaže ponovo stranicu (no re-typeset). PDF nema automatsko prelamanje teksta (no reflow), pa će zamena koja je vizuelno šira od izvornika jednostavno zauzeti više vodoravnog prostora i može stisnuti sve što je iscrtano desno od nje. Zamene iste ili slične dužine — datumi, verzije, brojevi delova, ispravci imena — idealan su slučaj upotrebe. Celovito preoblikovanje pripada izvornom dokumentu, a ne PDF-u

Zašto ne možete zameniti tekst znakovima koje podskup fonta nikada nije uključivao?

Ne možete zameniti tekst znakom koji ugrađeni podskup fonta nikada nije uključivao, jer sekvenca bajtova koja bi odabrala taj znak jednostavno ne postoji u tablicama mapiranja fonta. Kada proizvođač PDF-a ugradi podskup fonta, njegove strukture /ToUnicode CMap i kodiranja pokrivaju samo glifove koje je izvorni dokument stvarno koristio. HPDFEncodeUnicode može obrnuti samo ono mapiranje koje je prisutno: ako dokument nikada nije sadržavao slovo E u tom fontu, ne postoji kod znaka u koji bi se E mogao obrnuto pretvoriti. To je fizičko svojstvo datoteke, a ne ograničenje bilo koje specifične biblioteke — no tool can conjure a glyph mapping that was never embedded

HotPDF rješava neuspeh konzervativno. Ako se bilo koji pojedinačni znak zamene ne može ponovno kodirati, to celo pojavljivanje traženog pojma se preskače — bez izuzetka, bez delimično neupotrebljivog teksta, a pojavljivanje se jednostavno ne broji u ReplaceCount. Praktična posledica: proverite ReplaceCount u odnosu na broj podudaranja iz prethodnog pretraživanja i tretirajte odstupanje kao signal. U gornjem primeru datuma, cifra 6 mora se pojaviti negde u tekstu dokumenta u tom istom fontu kako bi ponovno pisanje uspelo — što je verovatno u računu, ali nikada uopšte garantovano. Kada znakovi koji su vam potrebni jednostavno nisu dostupni, a cilj je ukloniti osetljivi tekst umesto da ga preformulišete, pravo uklanjanje sadržaja ionako je bolji alat; pogledajte redakciju i restrukturiranje učitanih PDF-ova u Delphiju za taj put

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;

Drugi uslov preskakanja u toj poruci je druga dokumentovana granica: traženi pojam koji se proteže kroz više operanda niza — na primer, Hello podeljen na stavke [(He)(llo)] TJ — pronalazi se pretraživanjem, jer pretraživanje podudara dekodiranu sekvencu glifova, ali se preskače pri zameni, jer bi ponovno pisanje preko granica operanda zahtevalo spajanje susednih raspona bajtova. Korak pretraži-pa-proveri čini oba ograničenja vidljivim umesto tihim

Šta se menja u datoteci prilikom čuvanja?

Zamijenjeni tok /Contents čuva se nekomprimovan. Tokovi komprimovani sa FlateDecode dekomprimuju se radi uređivanja, a kada HotPDF zapisuje obnovljene bajtove, odbacuje unos /Filter toka i osvežava /Length umesto ponovnog komprimovanja. Dobijeni PDF je potpuno valjan i renderuje se normalno u uobičajenim pregledačima; kompromis je veća datoteka za svaki uređeni tok. Za serijski pipeline koji obrađuje hiljade dokumenata, planirajte taj rast ili pokrenite zasebni prolaz kompresije nizvodno. Kako prepisani objekti stupaju u interakciju sa unakrsnom referentnom strukturom dokumenta pri čuvanju zasebna je tema, pokrivena u tokovima objekata i inkrementalnim ažuriranjima u HotPDF-u

Sve ostalo u datoteci ostaje netaknuto. Netaknuti tokovi zadržavaju svoju kompresiju, fontovi i slike se ne prepisuju, a spajanje na nivou operanda znači da se čak i uređeni tokovi razlikuju od izvornika samo tamo gde je podudaranje sletelo. Ta je konzervativnost namerna: što više učitanog dokumenta biblioteka ponovno ispiše, to ima više prilika da pokvari neku neobičnost proizvođača koju nije predvidela

Text search and replace joins extraction, redaction, and page rendering in HotPDF's loaded-document toolset, all driven by the same content-stream interpreter and available from Delphi 5 through the current RAD Studio releases without external dependencies. The full API reference and trial download are on the HotPDF Component product page