Teknisk artikkel

Søk og erstatt tekst i en eksisterende PDF med Delphi

HotPDF Component kan søke og erstatte tekst i en eksisterende PDF fra Delphi og C++Builder. SearchLoadedPageText og SearchLoadedDocumentText finner hver forekomst av en streng med glyfnivå-presisjon, og ReplaceLoadedPageText og ReplaceLoadedDocumentText skriver om de matchende bytene på stedet — forutsatt at hvert erstatningstegn kan re-kodes via den opprinnelige fonten, en fysisk begrensning denne artikkelen behandler ærlig i stedet for å gjemme den bort i en fotnote

Forespørselen bak denne funksjonen er alltid ordinær. Et selskap endrer navn, og tretusen arkiverte fakturaer bærer fortsatt det gamle navnet. En kontraktmal ble levert med fjorårets utløpsdato. En produktkode ble pensjonert, og hvert datablad som nevner den trenger den etterfølgende koden i stedet. I et tekstbehandlingsprogram er hver av disse en jobb på tretti sekunder. I en PDF er det et genuint vanskelig problem, og det å forstå hvorfor utgjør forskjellen mellom å bruke API-et godt og å sende en feilrapport som egentlig bare er et sitat fra spesifikasjonen

Hvorfor er det så vanskelig å erstatte tekst i en PDF?

Å erstatte tekst i en PDF er vanskelig fordi en PDF side ikke inneholder redigerbar tekst — den inneholder posisjonerte glyfer. Under tekstvisningsmodellen i ISO 32000-1 §9.4 driver en innholdsstrøm operatorer som Tj og TJ som tegner sekvenser av tegnkoder ved koordinater etablert av tekstmatrisen. Disse kodene er ikke Unicode; de er indekser i den kodingen sidens font erklærer, og kartleggingen tilbake til lesbare tegn kan ligge i en /ToUnicode-CMap, en kodingsdifferanse-tabell eller en CID-kartleggingskjede. Det finnes ikke noe avsnittsobjekt, ingen tekstflyt, og ingen garanti for at ett visuelt ord i det hele tatt er lagret som én streng

Erstatning legger til et nytt lag med vanskeligheter på toppen av dekoding: du må vite nøyaktig hvilke byte i den opprinnelige strømmen som produserte hver glyf, slik at du kan skjøte nye byte inn i akkurat det området og ingenting annet. En tekstekstraktor har råd til å kaste bort byte-posisjonene så snart den har hentet ut Unicode. Det kan ikke en erstatter. Det er derfor HotPDF delte arbeidet over to utgivelser — v2.251.0 bygde forskyvningssporingen og søkelaget, og v2.252.0 bygde omskrivingslaget på toppen av det

Finne tekst: søk på glyfnivå med sporing av byteforskyvning

HotPDFs SearchLoadedDocumentText finner hver forekomst av en søkestreng ved å matche mot den dekodede Unicode-glyfsekvensen for hver side, ikke mot rå strømbyte, så et treff er et treff uavhengig av hvordan fonten kodet det. Infrastrukturen under ble introdusert i v2.251.0: innholdsstrøm-tokenisereren registrerer et StartOfs/EndOfs byteområde for hver strengoperand — inkludert dens ( )- eller < >-skilletegn — og hver dekodet glyf bærer en trippel TokenIndex/ItemIndex/ByteOffset som peker tilbake til nøyaktig operanden, TJ-array-elementet og kodeenheten som produserte den. Den samme glyftolken driver uthentings-API-et beskrevet i uthenting av tekst fra en lastet PDF i Delphi; søket beholder ganske enkelt opprinnelsen som uthentingen kaster

Hvert treff kommer tilbake som en THPDFTextMatch-post som bærer sideindeksen, det inkluderende glyfområdet, brukerrommets X/Y-utgangspunkt og bredden på treffet, kildetokenet og elementindeksen, og selve den matchende teksten. Det er nok til å drive et uthevingslag, et vurderingsgrensesnitt eller erstatningstrinnet. Et søk som ikke finner noe returnerer en tom array i stedet for å feile, slik at kalletsmønsteret forblir enkelt

Ett bevisst designvalg fortjener en merknad. Når CaseSensitive is False, the comparison folds case for ASCII characters only, by design: full Unicode case folding behaves differently across the Delphi 5 through XE toolchains HotPDF supports, and a search API that finds different matches depending on which compiler built your application is worse than one with a documented, predictable limit. For Latin business text — names, codes, dates — ASCII folding covers the practical cases

Erstatte tekst: omvendt koding og kirurgisk skjøting

ReplaceLoadedDocumentText, lagt til i HotPDF v2.252.0, skriver om hver forekomst av en søkestreng ved å kjøre dekodingsmaskineriet bakover. Funksjonen HPDFEncodeUnicode er det motsatte av tegnkodedekoderen: den går gjennom den samme strategikjeden i revers — /ToUnicode bfchar- og bfrange-oppslag, CID-kartlegging av kodingsstrøm, Type0-identitetskartlegginger og de forhåndsdefinerte WinAnsi- og MacRoman-tabellene — for å gjøre hvert erstatningstegn tilbake til tegnkode-bytene den opprinnelige fonten forventer. De re-kodede bytene blir deretter serialisert til en velformet strengliteral eller heksadesimalstreng, som speiler tokenisererens egne unnslippelsesregler (escaping rules) slik at en runde med analyse → re-serialisering er stabil

Selve skjøtingen er kirurgisk fremfor omfattende. Bare kodebyte-området som dekkes av treffet erstattes inne i strengoperanden; ikke-matchende byte i samme operand, tomrommet mellom tokener og alle omkringliggende operatorer bevares ordrett, byte for byte. Erstatning av bca inne i abcabc gir a + erstatning + bc, ikke en ødelagt operand. Erstatninger kan være kortere eller lengre enn søkestrengen — literalen re-serialiseres og strømmens /Length oppdateres — og hver /Contents-strøm på en flersiders side behandles isolert slik at siden forblir velformet

Hvorfor kan du ikke erstatte tekst med tegn som fontdelsettet aldri inkluderte?

Du kan ikke erstatte tekst med et tegn som det innebygde fontdelsettet aldri inkluderte, fordi bytesekvensen som ville valgt det tegnet rett og slett ikke eksisterer i fontens kartleggingstabeller. Når en PDF-produsent bygger inn en delsett-font, dekker dens /ToUnicode-CMap og kodingsstrukturer bare glyfene det opprinnelige dokumentet faktisk brukte. HPDFEncodeUnicode kan bare reversere en kartlegging som er til stede: hvis dokumentet aldri inneholdt bokstaven E i den fonten, finnes det ingen tegnkode for E å reversere til. Dette er en fysisk egenskap ved filen, ikke en begrensning ved et bestemt bibliotek — ingen verktøy kan fremkalle en glyfkartlegging som aldri ble bygd inn

HotPDF håndterer feilen konservativt. Hvis et enkelt tegn i erstatningen ikke kan re-kodes, hoppes hele den forekomsten av søkestrengen over — ingen unntak, ingen delvis ødelagt tekst, og forekomsten telles rett og slett ikke med i ReplaceCount. Den praktiske konsekvensen: sjekk ReplaceCount mot treffantallet fra et tidligere søk, og behandle et avvik som et signal. I datoeksempelet ovenfor må sifferet 6 vises et eller annet sted i dokumentets tekst med den samme fonten for at omskrivingen skal lykkes — sannsynlig i en faktura, aldri garantert generelt. Når tegnene du trenger rett og slett ikke er tilgjengelige og målet er å fjerne sensitivt innhold fremfor å omformulere den, er ekte innholdsfjerning uansett det beste verktøyet; se redigering og restrukturering av lastede PDF-er i Delphi for den banen

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;

Den andre overhoppingsbetingelsen i den meldingen er den andre dokumenterte grensen: en søkestreng som spenner over flere strengoperander — Hello delt over [(He)(llo)] TJ-elementer, for eksempel — blir funnet av søk, fordi søk matcher den dekodede glyfsekvensen, men blir hoppet over av erstatning, fordi omskriving på tvers av operandgrenser ville kreve sammenslåing av tilstøtende byteområder. Søk-deretter-verifiser gjør begge grensene synlige fremfor lydløse

Hva endres i filen når du lagrer?

En erstattet /Contents-strøm lagres ukomprimert. FlateDecode-komprimerte strømmer dekomprimeres for redigering, og når HotPDF skriver de gjenoppbygde bytene, fjerner den strømmens /Filter-oppføring og oppdaterer /Length i stedet for å komprimere på nytt. Den resulterende PDF-en er fullt gyldig og rendres normalt i vanlige visningsprogrammer; kompromisset er en større fil for hver redigert strøm. For en batchpipeline som behandler tusenvis av dokumenter, må du ta høyde for denne veksten eller kjøre en separat komprimeringsoperasjon etterpå. Hvordan omskrevne objekter samhandler med dokumentets kryssreferansestruktur ved lagring er et eget emne, dekket i objektstrømmer og inkrementelle oppdateringer i HotPDF

Alt annet ved filen forblir urørt. Uendrede strømmer beholder sin komprimering, fonter og bilder blir ikke skrevet om, og skjøtingen på operandnivå betyr at selv de redigerte strømmene bare skiller seg fra originalen der et treff landet. Denne konservatismen er tilsiktet: jo mer av et lastet dokument et bibliotek skriver om, desto flere muligheter har det for å ødelegge en særhet ved produsenten som det ikke forutså

Tekstsøk og -erstatning slutter seg til uthenting, redigering og siderendring i HotPDFs verktøysett for lastede dokumenter, alt drevet av den samme innholdsstrømtolken og tilgjengelig fra Delphi 5 til gjeldende RAD Studio-utgivelser uten eksterne avhengigheter. Den fullstendige API-referansen og prøvenedlastingen finnes på produktsiden for HotPDF Component