Trekk emoji eller et japansk familieregister-navn ut av en PDF som tekst, og utdataen viser en boks, et spørsmålstegn, eller ingenting i det hele tatt der tegnet skulle vært. PDFium-komponentens Character[]-egenskap er vanligvis grunnen: den leser hver glyf gjennom FPDFText_GetUnicode, som returnerer et fullt Unicode-kodepunkt som en usignert 32-bit verdi, og eksponerer det deretter til Delphi som en enkelt 16-bit WideChar. Ethvert kodepunkt forbi U+FFFF kan ikke gjøre den turen i ett stykke, og korrupsjonen viser seg aldri mens man ser på den gjengitte siden, fordi gjengivelse og tekstuttrekking går gjennom separate kodeveier i PDFium — et dokument kan vise sine emoji perfekt og likevel gi deg søppel i det øyeblikket du leser Character[] i en løkke og bygger en streng ut av det
Basic Multilingual Plane, og hvorfor WideChar stopper ved U+FFFF
Delphis WideChar er en 16-bit type som bare kan holde én UTF-16-kodeenhet. Unicodes Basic Multilingual Plane, området U+0000 til U+FFFF, passer nøyaktig inni det, noe som er grunnen til at latin, kyrillisk, gresk, og den vanlige CJK Unified Ideographs-blokken alle gjør en rundtur gjennom en enkelt WideChar uten hendelser. To familier av tegn faller rutinemessig utenfor det i ekte dokumenter: emoji, mange av dem i Emoticons-blokken som starter ved U+1F600, og sjeldne CJK-ideogrammer fra CJK Unified Ideographs Extension B, området U+20000 til U+2A6DF reservert for mindre vanlige kinesiske, japanske, og koreanske tegn, inkludert mange person- og stedsnavn. UTF-16 håndterer alt over U+FFFF med et surrogatpar — to 16-bit kodeenheter, et høyt surrogat i området $D800 til $DBFF etterfulgt av et lavt surrogat i $DC00 til $DFFF, som sammen koder ett kodepunkt — og matematikken bak den paringen er fast nok til å demonstrere direkte i Pascal
function ToSurrogatePair(CodePoint: LongWord; out Hi, Lo: WideChar): Boolean;
var
V: LongWord;
begin
Result := CodePoint > $FFFF;
if Result then
begin
V := CodePoint - $10000;
Hi := WideChar($D800 + (V shr 10));
Lo := WideChar($DC00 + (V and $3FF));
end;
end;
Mat U+1F600, det glisende-fjes-emojiet, gjennom den funksjonen, og resultatet er et høyt surrogat på $D83D og et lavt surrogat på $DE00, to 16-bit verdier, ikke én. Ingen av halvdelene betyr noe på egen hånd; en enslig $D83D som sitter i en streng uten noen $DE00 bak den, er et dinglende surrogat, og det meste tekst-håndterende kode som støter på en, enten dropper den, erstatter den med en erstatningsglyf, eller kaster en feil
Hvorfor returnerer FPDFText_GetUnicode en verdi Character[] ikke kan holde?
FPDFText_GetUnicode returnerer en LongWord, en full 32-bit verdi, fordi PDF-tekstkoding allerede bærer den komplette Unicode-skalarverdien for hver glyf. En PDFs ToUnicode-CMap kartlegger tegnkoder til Unicode-tekst, og når en glyf representerer det som uformelt kalles et astralt-plan-tegn — hva som helst forbi Basic Multilingual Plane — er den kartleggingen et fullt kodepunkt, ikke et 16-bit fragment. PDFium dekoder det tilbake til en skalarverdi internt og returnerer det over DLL-grensen gjennom FPDFText_GetUnicode, og den grensen er nøyaktig der en 32-bit verdi må bli til noe en Delphi-egenskap kan gi tilbake til koden din
Den opplagte implementasjonen er WideChar(FPDFText_GetUnicode(TextPage, Index)), og den er også den feilaktige. En hard cast fra en 32-bit verdi til en 16-bit type beholder bare de nedre 16 bitene og kaster resten stille bort, uten unntak og uten rekkeviddesjekk. For U+1F600 betyr det å beholde $F600 og miste det faktum at den sanne verdien noensinne var over U+FFFF, noe som produserer en kodeenhet som ikke engang er et gyldig dinglende surrogat, bare et urelatert Basic Multilingual Plane-tegn som tilfeldigvis deler de nedre bitene. Sett sammen noen tusen av dem i en streng, og nedstrøms kode har ingen måte igjen å skille et korrupt tegn fra et legitimt
Hva Character[] og Charcode[] returnerer for astrale-plan-kodepunkter nå
PDFium-komponentens Character[]- og Charcode[]-egenskaper returnerer U+FFFD, Unicode-erstatningstegnet, hver gang det underliggende kodepunktet overstiger U+FFFF, i stedet for å avkutte det stille. Den vakten sitter direkte inne i egenskapsgetteren bak Character[]
function TPdf.GetCharacter(Index: Integer): WideChar;
var
Code: LongWord;
begin
LoadTextPage;
Code := FPDFText_GetUnicode(FTextPage, Index);
if Code > $FFFF then
Result := #$FFFD // astral-plane code point: cannot fit in one WideChar
else
Result := WideChar(Code);
end;
Å returnere U+FFFD i stedet for et avkuttet fragment, er en bevisst, snever fiks snarere enn et redesign. Character[] og Charcode[] er typet som WideChar på både TPdf og TPdfView, og å utvide den returtypen til å bære et fullt kodepunkt, ville brutt hver eksisterende kaller som forventer at én glyf per indeks betyr én 16-bit verdi. U+FFFD er selve Unicode-standardens designerte plassholder for nøyaktig denne situasjonen, så en kaller som sjekker for den, får et definert, dokumentert signal i stedet for stille feil data. Ett grensetilfelle verdt å kjenne til: U+FFFD er også et legitimt tegn i sin egen rett, så på det sjeldne dokumentet som allerede inneholder en ekte erstatningstegn-glyf, er den indeksen umulig å skille fra et avkuttet astralt tegn ved verdi alene
Hvordan trekker man ut emoji og CJK Extension B-tekst korrekt i Delphi?
Kall Text i stedet for å gå gjennom Character[] hver gang det faktiske tekstinnholdet betyr noe, fordi Text leser gjennom FPDFText_GetText og returnerer en full WString med riktige surrogatpar for hvert astralt-plan-tegn i området, i stedet for én fast-bredde-verdi per indeks. Pdf.Text(0, MaxInt), eller forkortelsen Pdf.Text, trekker ut en hel side korrekt i ett kall, og Pdf.Text(StartIndex, Count) trekker et mindre område på samme måte. Character[] fortjener fortsatt sin plass når man bare trenger posisjons-, skrifttype-, eller flaggdata ved en indeks og aldri rører selve kodepunktet — CharacterOrigin[], FontSize[], og CharacterMapError[] bryr seg ikke om den underliggende glyfen var astral
function ExtractLineSafely(Pdf: TPdf): WString;
var
I: Integer;
begin
Result := '';
for I := 0 to Pdf.CharacterCount - 1 do
if not (Pdf.CharacterGenerated[I] or Pdf.CharacterMapError[I]) then
Result := Result + Pdf.Text(I, 1); // full code point, never a truncated WideChar
end;
Hopp-over-generert-og-ikke-kartlagt-sjekken i den løkken er det samme mønsteret brukt for ren tekstuttrekking i å trekke ut tekst fra PDF-dokumenter med PDFium-komponenten; den eneste endringen er den siste linjen, som bytter en direkte Character[I]-tillegging mot et ett-indeks-kall inn i Text, slik at astrale tegn ankommer som komplette surrogatpar i stedet for erstatnings-plassholdere
Hvor dette faktisk biter: chat-eksporter, personnavn, og innebygde CJK-skrifttyper
Emoji dukker opp overalt der en PDF fanger uformell kommunikasjon: eksporterte chat-logger, app-store-anmeldelses-dumper, ticketing-system-transkripsjoner lagret som PDF for et compliance-arkiv. CJK Extension B dukker opp i et snevrere, men mer høyt-innsats-sted, person- og stedsnavn, fordi japanske familieregistre, kinesiske husholdningsregistreringsjournaler, og taiwanske identitetsdokumenter er klassiske kilder til tegn som aldri kom inn i den vanlige CJK-blokken. En lønns- eller identitetsverifiserings-pipeline som trekker ut navn fra skannede offentlige papirer, er nøyaktig den typen arbeidsbelastning der et stille ødelagt tegn blir til en mislykket match snarere enn en kosmetisk glitch
Sjeldne CJK-ideogrammer pleier også å reise med skrifttypeproblemer, ikke bare kodingsproblemer, fordi en skrifttype må bære en glyf for et kodepunkt i U+20000-området før noe i det hele tatt kan gjengis, og få installerte systemskrifttyper gjør det. Alle som allerede går gjennom FontIsEmbedded[] per tegn slik å lese PDF-skrifttype-egenskaper med PDFium-komponenten beskriver, bør sjekke den samme indeksen for begge problemene sammen: en indeks som returnerer U+FFFD fra Character[] og rapporterer en ikke-innebygd skrifttype, er et dokument som verken vil trekke ut eller skrive ut det tegnet korrekt, og fiksen hører hjemme oppstrøms i hvordan PDF-en ble produsert, ikke i uttrekkingskoden din
Character[]-, Charcode[]-, og Text-egenskapene beskrevet her er en del av den standard PDFium-komponenten for Delphi og C++Builder