Tehnički članak

Pretraživanje i zamjena teksta u postojećem PDF-u s Delphijem

HotPDF Component može pretraživati i zamijeniti tekst unutar postojećeg PDF-a iz Delphija i C++Buildera. Pozivi SearchLoadedPageText i SearchLoadedDocumentText pronalaze svako pojavljivanje niza s preciznošću na razini glifa, a ReplaceLoadedPageText i ReplaceLoadedDocumentText prepisuju podudarne bajtove na licu mjesta — pod uvjetom da se svaki zamjenski znak može ponovno kodirati putem izvornog fonta, što je fizičko ograničenje koje ovaj članak tretira pošteno, umjesto da ga skriva u bilješci

Zahtjev iza ove značajke uvijek je svakodnevan. Tvrtka mijenja naziv, a tri tisuće arhiviranih računa i dalje nosi stari naziv. Predložak ugovora isporučen je s prošlogodišnjim datumom isteka. Šifra proizvoda je povučena iz upotrebe i svaka podatkovna tablica koja je spominje treba novu šifru nasljednika. U programu za obradu teksta svaki od ovih zadataka traje trideset sekundi. U PDF-u je to uistinu težak problem, a razumijevanje zašto čini razliku između dobrog korištenja API-ja i podnošenja izvješća o pogrešci koje je zapravo citat iz specifikacije

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

Zamjena 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 deklarira font stranice, a mapiranje natrag u čitljive znakove može živjeti u CMap-u /ToUnicode, nizu razlika kodiranja (encoding difference array) ili CID lancu mapiranja. Ne postoji objekt odlomka, nema toka teksta i nema jamstva da je jedna vizualna riječ uopće pohranjena kao jedan niz

Zamjena dodaje drugi sloj težine na dekodiranje: morate znati točno koji su bajtovi izvornog toka proizveli svaki glif, kako biste mogli spojiti nove bajtove točno u taj raspon i ništa drugo. Alat za izdvajanje teksta može si priuštiti odbacivanje pozicija bajtova nakon što izvuče Unicode. Alat za zamjenu ne može. Zato je HotPDF podijelio 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 razini glifa s praćenjem pomaka bajtova

HotPDF-ov SearchLoadedDocumentText pronalazi svako pojavljivanje traženog pojma podudaranjem s 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 biljež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 natrag na toč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 podrijetlo (provenance) koje izdvajanje odbacuje

Svaki pogodak dolazi kao zapis THPDFTextMatch koji nosi indeks stranice, uključivi raspon glifova, korisnički prostor X/Y ishodišta i širine pogotka, izvorni token i indeks stavke te sam podudarni tekst. To je dovoljno za pokretanje preklapanja isticanja, korisničkog sučelja za pregled ili koraka zamjene. Pretraživanje koje ne pronađe ništa vraća prazno polje radije nego da ne uspije, 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 namjerni izbor dizajna zaslužuje bilješku. Kada je CaseSensitive postavljeno na False, usporedba 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 prevoditelj izgradio vašu aplikaciju gori je od onog s dokumentiranim, predvidljivim ograničenjem. Za latinični poslovni tekst — imena, šifre, datume — ASCII preklapanje pokriva praktične slučajeve

Zamjena teksta: obrnuto kodiranje i kirurško spajanje

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

Samo spajanje je kirurško, a ne skupno. Zamjenjuje 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. Zamjena bca unutar abcabc daje a + zamjena + bc, a ne uništeni operand. Zamjene mogu biti kraće ili duže od traženog pojma — literal se ponovno serijalizira i osvjež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 ponovno stranicu (no re-typeset). PDF nema automatsko prelamanje teksta (no reflow), pa će zamjena koja je vizualno šira od izvornika jednostavno zauzeti više vodoravnog prostora i može stisnuti sve što je iscrtano desno od nje. Zamjene iste ili slične duljine — datumi, verzije, brojevi dijelova, ispravci imena — idealan su slučaj upotrebe. Cjelovito preoblikovanje pripada izvornom dokumentu, a ne PDF-u

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

Ne možete zamijeniti 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 knjižnice — nijedan alat ne može stvoriti mapiranje glifa koje nikada nije ugrađeno

HotPDF rješava neuspjeh konzervativno. Ako se bilo koji pojedinačni znak zamjene ne može ponovno kodirati, to cijelo pojavljivanje traženog pojma se preskače — bez iznimke, bez djelomično neupotrebljivog teksta, a pojavljivanje se jednostavno ne broji u ReplaceCount. Praktična posljedica: provjerite ReplaceCount u odnosu na broj podudaranja iz prethodnog pretraživanja i tretirajte odstupanje kao signal. U gornjem primjeru datuma, znamenka 6 mora se pojaviti negdje u tekstu dokumenta u tom istom fontu kako bi ponovno pisanje uspjelo — što je vjerojatno u računu, ali nikada općenito zajamčeno. Kada znakovi koji su vam potrebni jednostavno nisu dostupni, a cilj je ukloniti osjetljivi tekst umjesto da ga preformulirate, 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 uvjet preskakanja u toj poruci je druga dokumentirana granica: traženi pojam koji se proteže kroz više operanda niza — na primjer, Hello podijeljen na stavke [(He)(llo)] TJ — pronalazi se pretraživanjem, jer pretraživanje podudara dekodiranu sekvencu glifova, ali se preskače pri zamjeni, jer bi ponovno pisanje preko granica operanda zahtijevalo spajanje susjednih raspona bajtova. Korak pretraži-pa-provjeri čini oba ograničenja vidljivima umjesto tihima

Što se mijenja u datoteci prilikom spremanja?

Zamijenjeni tok /Contents sprema se nekomprimiran. Tokovi komprimirani s FlateDecode dekomprimiraju se radi uređivanja, a kada HotPDF zapisuje obnovljene bajtove, odbacuje unos /Filter toka i osvježava /Length umjesto ponovnog komprimiranja. Dobiveni PDF je potpuno valjan i renderira se normalno u uobičajenim preglednicima; kompromis je veća datoteka za svaki uređeni tok. Za serijski cjevovod koji obrađuje tisuće dokumenata, planirajte taj rast ili pokrenite zasebni prolaz kompresije nizvodno. Kako prepisani objekti stupaju u interakciju s unakrsnom referentnom strukturom dokumenta pri spremanju 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 razini operanda znači da se čak i uređeni tokovi razlikuju od izvornika samo tamo gdje je podudaranje sletjelo. Ta je konzervativnost namjerna: što više učitanog dokumenta knjižnica ponovno ispiše, to ima više prilika da pokvari neku neobičnost proizvođača koju nije predvidjela

Pretraživanje i zamjena teksta pridružuju se izdvajanju, redakciji i renderiranju stranica u HotPDF-ovom alatu za učitane dokumente, a sve ih pokreće isti interpretator toka sadržaja i dostupni su od Delphija 5 do trenutnih izdanja RAD Studija bez vanjskih ovisnosti. Cjelovita referenca API-ja i probno preuzimanje nalaze se na stranici proizvoda HotPDF Component