Poimi emoji tai japanilainen perherekisterinimi PDF:stä tekstinä, ja tuloste näyttää laatikon, kysymysmerkin tai ei mitään siinä kohdassa, jossa merkin pitäisi olla. PDFium-komponentin Character[]-ominaisuus on yleensä syy: se lukee jokaisen glyfin funktiolla FPDFText_GetUnicode, joka palauttaa täyden Unicode-koodipisteen etumerkittömänä 32-bittisenä arvona, ja paljastaa sen sitten Delphille yhtenä 16-bittisenä WideChar-arvona. Mikään koodipiste U+FFFF:n yli ei voi tehdä tuota matkaa yhtenä palana, eikä vioittuminen koskaan näy, kun katsot renderöityä sivua, koska renderöinti ja tekstinpoiminta kulkevat erillisten koodipolkujen kautta PDFiumissa — asiakirja voi näyttää emojinsa täydellisesti ja silti antaa sinulle roskaa heti, kun luet Character[]-arvoa silmukassa ja rakennat siitä merkkijonon
Basic Multilingual Plane ja miksi WideChar pysähtyy U+FFFF:ään
Delphin WideChar on 16-bittinen tyyppi, joka voi kantaa vain yhden UTF-16-koodiyksikön. Unicode Basic Multilingual Plane, alue U+0000–U+FFFF, mahtuu siihen täsmälleen, minkä vuoksi latina, kyrillinen, kreikka ja tavallinen CJK Unified Ideographs -lohko kaikki kulkevat edestakaisin yhden WideChar-arvon kautta ilman ongelmia. Kaksi merkkiperhettä putoavat rutiininomaisesti sen ulkopuolelle todellisissa asiakirjoissa: emojit, joista monet ovat Emoticons-lohkossa alkaen U+1F600:sta, ja harvinaiset CJK-ideogrammit CJK Unified Ideographs Extension B:stä, alueesta U+20000–U+2A6DF, joka on varattu harvinaisemmille kiinalaisille, japanilaisille ja korealaisille merkeille, mukaan lukien monet henkilö- ja paikannimet. UTF-16 käsittelee kaiken U+FFFF:n yläpuolella surrogaattiparilla — kaksi 16-bittistä koodiyksikköä, korkea surrogaatti alueella $D800–$DBFF, jota seuraa matala surrogaatti alueella $DC00–$DFFF, jotka yhdessä koodaavat yhden koodipisteen — ja tuon parittamisen taustalla oleva matematiikka on riittävän kiinteä osoitettavaksi suoraan Pascalissa
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;
Syötä U+1F600, virnistävä-kasvot-emoji, tuon funktion läpi, ja tulos on korkea surrogaatti $D83D ja matala surrogaatti $DE00, kaksi 16-bittistä arvoa, ei yksi. Kumpikaan puolisko ei tarkoita mitään yksinään; yksinäinen $D83D, joka istuu merkkijonossa ilman $DE00:ta perässään, on roikkuva surrogaatti, ja useimmat tekstinkäsittelykoodit, jotka kohtaavat sellaisen, joko pudottavat sen, korvaavat sen korvausglyfillä tai nostavat virheen
Miksi FPDFText_GetUnicode palauttaa arvon, jota Character[] ei voi kantaa?
FPDFText_GetUnicode palauttaa LongWord-arvon, täyden 32-bittisen arvon, koska PDF-tekstikoodaus jo kantaa täyden Unicode-skalaariarvon jokaiselle glyfille. PDF:n ToUnicode-CMap kartoittaa merkkikoodit Unicode-tekstiksi, ja kun glyfi edustaa jotain, mitä epävirallisesti kutsutaan astraalitason merkiksi — mitä tahansa Basic Multilingual Planen ulkopuolella — tuo kartoitus on täysi koodipiste, ei 16-bittinen fragmentti. PDFium dekoodaa sen takaisin skalaariarvoksi sisäisesti ja palauttaa sen DLL-rajan yli funktiolla FPDFText_GetUnicode, ja tuo raja on juuri se, missä 32-bittisen arvon on tultava joksikin, jonka Delphi-ominaisuus voi antaa takaisin koodillesi
Ilmeinen toteutus on WideChar(FPDFText_GetUnicode(TextPage, Index)), ja se on myös väärä. Kova tyyppimuunnos 32-bittisestä arvosta 16-bittiseksi tyypiksi pitää vain alimmat 16 bittiä ja heittää lopun hiljaa pois, ilman poikkeusta ja ilman alueentarkistusta. U+1F600:lle tämä tarkoittaa $F600:n säilyttämistä ja sen tosiasian menettämistä, että todellinen arvo oli koskaan U+FFFF:n yläpuolella, mikä tuottaa koodiyksikön, joka ei ole edes pätevä roikkuva surrogaatti, vain toisiinsa liittymätön Basic Multilingual Plane -merkki, joka sattuu jakamaan noita alimpia bittejä. Ketjuta muutama tuhat noista merkkijonoksi, eikä alavirran koodilla ole enää tapaa erottaa vioittunutta merkkiä laillisesta
Mitä Character[] ja Charcode[] palauttavat astraalitason koodipisteille nyt
PDFium-komponentin Character[]- ja Charcode[]-ominaisuudet palauttavat U+FFFD:n, Unicode-korvausmerkin, aina kun taustalla oleva koodipiste ylittää U+FFFF:n, sen sijaan että katkaisisivat sen hiljaa. Tuo vartija istuu suoraan Character[]:n takana olevan ominaisuuden getterissä
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;
U+FFFD:n palauttaminen katkaistun fragmentin sijaan on tarkoituksellinen, kapea korjaus eikä uudelleensuunnittelu. Character[] ja Charcode[] ovat tyypitetty WideChar-tyyppisiksi sekä TPdf:ssä että TPdfView:ssä, ja tuon paluutyypin laajentaminen kantamaan täysi koodipiste rikkoisi jokaisen olemassa olevan kutsujan, joka odottaa yhden glyfin per indeksi tarkoittavan yhtä 16-bittistä arvoa. U+FFFD on Unicode-standardin oma nimetty paikanvaraaja juuri tälle tilanteelle, joten kutsuja, joka tarkistaa sen varalta, saa määritellyn, dokumentoidun signaalin hiljaa väärän datan sijaan. Yksi rajatapaus kannattaa tuntea: U+FFFD on myös laillinen merkki omassa oikeudessaan, joten harvinaisessa asiakirjassa, joka jo sisältää aidon korvausmerkkiglyfin, tuo indeksi ei ole erotettavissa katkaistusta astraalimerkistä pelkän arvon perusteella
Miten poimit emoji- ja CJK Extension B -tekstin oikein Delphissä?
Kutsu Text-funktiota Character[]-läpikäynnin sijaan aina, kun todellinen tekstisisältö on merkityksellinen, koska Text lukee funktion FPDFText_GetText kautta ja palauttaa täyden WString-arvon, jossa on oikeat surrogaattiparit jokaiselle alueella olevalle astraalitason merkille, sen sijaan että se olisi yksi kiinteäleveyksinen arvo per indeksi. Pdf.Text(0, MaxInt), tai lyhennelmä Pdf.Text, poimii koko sivun oikein yhdellä kutsulla, ja Pdf.Text(StartIndex, Count) vetää pienemmän alueen samalla tavalla. Character[] ansaitsee silti paikkansa, kun tarvitset vain sijainti-, fontti- tai lippudataa indeksissä etkä koskaan kosketa itse koodipistettä — CharacterOrigin[], FontSize[] ja CharacterMapError[] eivät välitä siitä, oliko taustalla oleva glyfi astraalinen
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;
Tuon silmukan ohita-luotu-ja-kartoittamaton-tarkistus on sama malli, jota käytetään pelkän tekstin poiminnassa artikkelissa tekstin poiminta PDF-asiakirjoista PDFium-komponentilla; ainoa muutos on viimeinen rivi, joka vaihtaa suoran Character[I]-liitoksen yhden indeksin kutsuun Text-funktioon, jotta astraalimerkit saapuvat täysinä surrogaattipareina korvauspaikanvaraajien sijaan
Missä tämä todella purree: chat-viennit, henkilönimet ja upotetut CJK-fontit
Emojit ilmestyvät kaikkialla, missä PDF vangitsee epävirallista viestintää: viedyt chat-lokit, sovelluskaupan arvostelukaatopaikat, lippujärjestelmätranskriptit, jotka on tallennettu PDF:ksi vaatimustenmukaisuusarkistoa varten. CJK Extension B ilmestyy kapeammassa mutta korkeamman panoksen paikassa, henkilö- ja paikannimissä, koska japanilaiset perherekisterit, kiinalaiset kotitalousrekisteröintitietueet ja taiwanilaiset henkilöllisyysasiakirjat ovat klassisia lähteitä merkeille, jotka eivät koskaan päätyneet tavalliseen CJK-lohkoon. Palkanlaskenta- tai henkilöllisyyden vahvistusputki, joka poimii nimiä skannatuista viranomaispapereista, on juuri sellainen työkuorma, jossa hiljaa silvottu merkki muuttuu epäonnistuneeksi täsmäytykseksi eikä kosmeettiseksi häiriöksi
Harvinaiset CJK-ideogrammit myös taipuvat kulkemaan fonttiongelmien kanssa, ei vain koodausongelmien, koska fontin on kannettava glyfi U+20000-alueen koodipisteelle ennen kuin mikään voi renderöityä lainkaan, ja harvat asennetut järjestelmäfontit tekevät niin. Kenen tahansa, joka jo käy läpi FontIsEmbedded[]-ominaisuutta merkki kerrallaan tavalla, jota PDF-fonttiominaisuuksien lukeminen PDFium-komponentilla kuvaa, pitäisi tarkistaa sama indeksi molempien ongelmien varalta yhdessä: indeksi, joka palauttaa U+FFFD-arvon Character[]-ominaisuudesta ja raportoi upottamattoman fontin, on asiakirja, joka ei poimi eikä tulosta tuota merkkiä oikein, ja korjaus kuuluu alavirtaan siihen, miten PDF tuotettiin, ei poimintakoodiisi
Tässä kuvatut Character[]-, Charcode[]- ja Text-ominaisuudet ovat osa vakiomuotoista PDFium-komponenttia Delphille ja C++Builderille