„HotPDF Component“ gali ieškoti ir pakeisti tekstą esamame PDF faile iš Delphi ir C++Builder aplinkos. SearchLoadedPageText ir SearchLoadedDocumentText randa kiekvieną eilutės pasirodymą glifų lygio tikslumu, o ReplaceLoadedPageText ir ReplaceLoadedDocumentText vietoje perrašo atitinkamus baitus – su sąlyga, kad kiekvienas pakaitinis simbolis gali būti iš naujo užkoduotas naudojant pradinį šriftą. Šį fizinį apribojimą šis straipsnis aptaria atvirai, užuot paslėpęs išnašoje
Užklausa, lemianti šios funkcijos poreikį, visada yra kasdieniška. Įmonė pakeičia pavadinimą ir trys tūkstančiai archyvuotų sąskaitų faktūrų vis dar turi senąjį pavadinimą. Sutarties šablonas buvo išsiųstas su praėjusių metų galiojimo pabaigos data. Produkto kodas buvo pašalintas ir kiekviename duomenų lape, kuriame jis minimas, vietoj jo reikia įrašyti naują kodą. Teksto redagavimo programoje kiekvienas iš šių darbų užtruktų trisdešimt sekundžių. PDF faile tai yra tikrai sudėtinga problema, o supratimas, kodėl taip yra, leidžia arba tinkamai naudoti API, arba pateikti pranešimą apie klaidą, kuris iš tikrųjų yra tiesiog specifikacijos citavimas
Kodėl teksto pakeitimas PDF faile yra toks sudėtingas?
Teksto pakeitimas PDF faile yra sudėtingas, nes PDF puslapyje nėra redaguojamo teksto – jame yra pozicionuoti glifai. Pagal ISO 32000-1 §9.4 teksto rodymo modelį turinio srautas valdo tokius operatorius kaip Tj ir TJ, kurie nupiešia simbolių kodų sekas koordinatėse, nustatytose teksto matricoje. Šie kodai nėra Unicode; jie yra indeksai į bet kokį kodavimą, kurį deklaruoja puslapio šriftas, o atvaizdavimas atgal į skaitomus simbolius gali būti /ToUnicode CMap lentelėje, kodavimo skirtumų masyve arba CID atvaizdavimo grandinėje. Čia nėra pastraipos objekto, teksto srauto ir jokios garantijos, kad vienas vizualus žodis išvis saugomas kaip viena eilutė
Pakeitimas prideda antrą sudėtingumo sluoksnį po iškodavimo: turite tiksliai žinoti, kurie pradinio srauto baitai sukūrė kiekvieną glifą, kad galėtumėte įterpti naujus baitus būtent į tą sritį ir niekur kitur. Teksto išgaviklis gali sau leisti išmesti baitų pozicijas, kai tik išgauna Unicode tekstą. Pakeitiklis to padaryti negali. Štai kodėl „HotPDF“ padalino šį darbą per dvi versijas – v2.251.0 buvo sukurtas poslinkių sekimo ir paieškos sluoksnis, o v2.252.0 ant jo pastatė perrašymo sluoksnį
Teksto radimas: glifų lygio paieška su baitų poslinkių sekimu
„HotPDF“ funkcija SearchLoadedDocumentText randa kiekvieną paieškos eilutės pasirodymą lygindama ją su iškoduota puslapio Unicode glifų seka, o ne su neapdorotais srauto baitais, todėl atitiktis nustatoma nepriklausomai nuo to, kaip šriftas ją užkodavo. Pagrindinė infrastruktūra buvo pristatyta versijoje v2.251.0: turinio srauto žetonų analizatorius (tokenizer) įrašo StartOfs/EndOfs baitų diapazoną kiekvienam eilutės operandui – įskaitant jo skirtukus ( ) arba < > – o kiekvienas iškoduotas glifas turi TokenIndex/ItemIndex/ByteOffset trejetą, rodantį atgal į tikslų operandą, TJ masyvo elementą ir kodo vienetą, kuris jį sukūrė. Tas pats glifų interpretatorius valdo išgavimo API, aprašytą straipsnyje apie teksto išgavimą iš įkelto PDF failo Delphi aplinkoje; paieška tiesiog išsaugo kilmės duomenis, kuriuos išgavimas atmeta
Kiekviena atitiktis grąžinama kaip THPDFTextMatch įrašas, kuriame yra puslapio indeksas, imtinai glifų diapazonas, vartotojo erdvės X/Y pradžios koordinatės bei atitikties plotis, šaltinio žetonas, elemento indeksas ir pats surastas tekstas. To pakanka, kad būtų galima valdyti paryškinimo sluoksnį, peržiūros sąsają arba atlikti pakeitimo žingsnį. Paieška, kuri nieko neranda, grąžina tuščią masyvą, o ne praneša apie klaidą, todėl iškvietimo šablonas išlieka paprastas
var
Matches: THPDFTextMatchArray;
I: Integer;
begin
Pdf.SearchLoadedDocumentText('Acme Corp', False, Matches);
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;
Vienas sąmoningas dizaino pasirinkimas vertas dėmesio. Kai CaseSensitive yra False, palyginimas pakeičia raidžių registrą tik ASCII simboliams: pilnas Unicode raidžių registro keitimas veikia skirtingai įvairiose Delphi 5 – XE įrankių grandinėse, kurias palaiko „HotPDF“, o paieškos API, kuri randa skirtingas atitiktis priklausomai nuo to, kuris kompiuteris sukūrė jūsų programą, yra blogesnė nei ta, kuri turi dokumentuotą, nuspėjamą ribą. Lotynų kalbos verslo tekstams – pavadinimams, kodams, datoms – ASCII registro keitimas apima praktinius atvejus
Teksto pakeitimas: atvirkštinis kodavimas ir chirurginis sujungimas
Funkcija ReplaceLoadedDocumentText, pridėta „HotPDF“ versijoje v2.252.0, perrašo kiekvieną ieškomos eilutės pasirodymą paleisdama iškodavimo mechanizmą atbuline eiga. Funkcija HPDFEncodeUnicode yra atvirkštinė simbolių kodų dešifratoriui: ji eina ta pačia strategijos grandine atgal – /ToUnicode bfchar ir bfrange paieška, kodavimo srauto CID atvaizdavimas, Type0 tapatybės atvaizdavimas ir iš anksto apibrėžtos WinAnsi bei MacRoman lentelės, kad kiekvieną pakaitinį simbolį paverstų simbolių kodo baitais, kurių tikisi pradinis šriftas. Užkoduoti baitai serijozuojami į tinkamo formato eilutės literalą arba šešioliktainę eilutę, atspindint žetonų analizatoriaus taisykles, kad analizavimo ir pakartotinio serijozavimo ciklas būtų stabilus
Pats sujungimas yra chirurginis, o ne masinis. Tik baitų diapazonas, kurį apima atitiktis, pakeičiamas eilutės operande; neatitinkantys baitai tame pačiame operande, tarpai tarp žetonų ir visi aplinkiniai operatoriai išsaugomi pažodžiui, baitas po baito. Pakeitus bca eilutėje abcabc, gaunamas rezultatas a + pakeitimas + bc, o ne sugadintas operandas. Pakeitimai gali būti trumpesni arba ilgesni už ieškomą tekstą – literalai serijozuojami iš naujo, o srauto /Length atnaujinamas – ir kiekvienas kelių srautų puslapio /Contents srautas apdorojamas atskirai, todėl puslapis išlieka teisingo formato
var
ReplaceCount: Integer;
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;
Note kas API nedaro: ji neperformatuoja puslapio iš naujo. PDF neturi teksto pertakumo (reflow), todėl pakeitimas, kuris vizualiai platesnis už originalą, tiesiog užims daugiau horizontalios vietos ir gali uždengti viską, kas buvo nupiešta jo dešinėje. Vienodo arba panašaus ilgio pakeitimai – datos, versijos eilutės, dalių numeriai, pavadinimų korekcijos – yra tinkamiausia vieta. Didelio masto teksto pakeitimas turėtų būti atliekamas pradiniame dokumente, o ne PDF faile
Kodėl negalite pakeisti teksto simboliais, kurių šrifto subgrupė niekada neturėjo?
Negalite pakeisti teksto simboliu, kurio įterpto šrifto subgrupė niekada neturėjo, nes baitų seka, kuri pasirinktų tą simbolį, tiesiog neegzistuoja šrifto atvaizdavimo lentelėse. Kai PDF kūrėjas įterpia subgrupuotą šriftą, jo /ToUnicode CMap ir kodavimo struktūros apima tik tuos glifus, kuriuos iš tikrųjų naudojo pradinis dokumentas. HPDFEncodeUnicode gali atlikti tik egzistuojančio atvaizdavimo atvirkštinį procesą: jei dokumente niekada nebuvo raidės E tame šrifte, nėra simbolio kodo, į kurį būtų galima sugrąžinti E. Tai yra fizinė failo savybė, o ne konkrečios bibliotekos apribojimas – joks įrankis negali išburti glifų atvaizdavimo, kuris niekada nebuvo įterptas
„HotPDF“ šią nesėkmę apdoroja konservatyviai. Jei nors vienas pakaitinis simbolis negali būti iš naujo užkoduotas, visas to žodžio pasirodymas praleidžiamas – be išimčių, be dalinio sugadinto teksto, o pats pasirodymas tiesiog neįskaičiuojamas į ReplaceCount. Praktinė pasekmė: palyginkite ReplaceCount su atitikčių skaičiumi iš ankstesnės paieškos ir vertinkite skirtumą kaip signalą. Aukščiau pateiktame datos pavyzdyje skaitmuo 6 turi būti kur nors dokumento tekste tame pačiame šrifte, kad perrašymas pavyktų – tikėtina, sąskaitoje faktūroje, bet tai niekada negarantuojama apskritai. Kai reikalingi simboliai tiesiog neprieinami, o tikslas yra pašalinti jautrų tekstą, o ne jį pakeisti, tikrasis turinio šalinimas vis tiek išlieka geresniu įrankiu; žiūrėkite straipsnį apie įkeltų PDF failų redagavimą (redaction) ir pertvarkymą Delphi aplinkoje, kad pasirinktumėte šį kelią
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;
Antroji praleidimo sąlyga tame pranešime yra kita dokumentuota riba: paieškos eilutė, kuri apima kelis eilutės operandus – pavyzdžiui, Hello, padalintą tarp elementų [(He)(llo)] TJ – randama paieškos metu, nes paieška atitinka iškoduotą glifų seką, tačiau praleidžiama pakeitimo metu, nes perrašymas per operando ribas reikalautų gretimų baitų sričių sujungimo. Paieškos ir vėlesnio tikrinimo seka leidžia pamatyti abi šias ribas, o ne jas nutylėti
Kas keičiasi faile išsaugant?
Pakeistas /Contents srautas išsaugomas nesuspaustas. „FlateDecode“ suspausti srautai išspaudžiami redagavimui, o kai „HotPDF“ įrašo atstatytus baitus, ji pašalina srauto įrašą /Filter ir atnaujina /Length, užuot vėl jį suspaudusi. Gautas PDF yra visiškai teisingas ir įprastai atvaizduojamas populiariose peržiūros programose; kompromisas yra didesnis failas kiekvienam redaguotam srautui. Grupiniam apdorojimui, kuris apdoroja tūkstančius dokumentų, įvertinkite šį padidėjimą arba paleiskite atskirą suspaudimo žingsnį vėliau. Kaip perrašyti objektai išsaugant sąveikauja su dokumento kryžminių nuorodų struktūra, aprašyta straipsnyje apie objektų srautus ir prieaugio atnaujinimus HotPDF sistemoje
Visa kita faile paliekama ramybėje. Nepaliesti srautai išlaiko savo suspaudimą, šriftai ir vaizdai nėra perrašomi, o operando lygio sujungimas reiškia, kad net redaguoti srautai skiriasi nuo pradinio tik ten, kur buvo atitiktis. Šis konservatyvumas yra tyčinis: kuo daugiau įkelto dokumento dalių biblioteka perrašo, tuo daugiau galimybių pažeisti kurį nors kūrėjo specifiškumą, kurio ji nenumatė
Teksto paieška ir pakeitimas papildo išgavimą, redagavimą (redaction) ir puslapio atvaizdavimą „HotPDF“ įkeltų dokumentų įrankių rinkinyje. Visi jie valdomi tuo pačiu turinio srauto interpretatoriumi ir yra prieinami nuo Delphi 5 iki dabartinių „RAD Studio“ versijų be išorinių priklausomybių. Išsamią API nuorodą ir bandomąją versiją rasite „HotPDF Component“ produkto puslapyje