Ga naar inhoud
getnextpdf.com

Waarom de tekst in een PDF niet echt tekst is

Wanneer je een PDF leest, zie je woorden. Het bestand bevat geen woorden. Het bevat instructies om vormen op coördinaten te schilderen, en die vormen lijken toevallig op letters. De kloof tussen wat een PDF tekent en wat ze betekent is de reden dat een pagina er foutloos kan uitzien en toch als onzin geëxtraheerd wordt. Deze pagina gaat over die kloof, en over de kleine, afzonderlijke afbeelding die ze dicht.

Alles wat je met een PDF doet als tekst — haar selecteren, kopiëren, doorzoeken, indexeren voor terugvinden, hardop voorlezen met een schermlezer — is afhankelijk van het terugwinnen van tekens die het bestand nooit rechtstreeks heeft opgeslagen. Als dat terugwinnen mislukt, is de mislukking onzichtbaar. De pagina wordt nog steeds weergegeven. Niemand merkt het tot iemand een alinea in een e-mail kopieert en □□□□ krijgt, of een contract van 400 pagina’s doorzoekt naar een clausule die er duidelijk staat en niets vindt.

Dat is een dure categorie bug, juist omdat ze elke visuele controle overleeft. Er is niets te zien. Het document oogt gezaghebbend en is, voor machinale doeleinden, stom.

  • Een PDF tekent glyphs — visuele vormen die via numerieke codes uit een font worden gekozen — geen Unicode-tekens.
  • Dezelfde vorm kan verschillende tekens betekenen, en hetzelfde teken kan door verschillende glyphs worden getekend. Uiterlijk en betekenis zijn van nature ontkoppeld.
  • Om tekst er weer uit te krijgen, draait een reader het toonpad omgekeerd: code naar teken. De /ToUnicode-CMap van het font is de opzoektabel voor die omgekeerde stap (Spec: ISO 32000-2, §9.10).
  • Geen /ToUnicode, of een verkeerde, betekent verminkt kopiëren-en-plakken en mislukt zoeken — terwijl de pagina nog steeds pixelperfect is.
  • Dit is het betekenisprobleem. Of de glyph er goed uitziet is het afzonderlijke uiterlijkprobleem dat behandeld wordt in Fonts: the hard part. NextPDF zendt een correcte /ToUnicode uit zodat de rondgang getrouw is.

Begin met hoe tekst überhaupt op de pagina komt. Een content stream zegt niet “schrijf het woord file.” Het selecteert een font en geeft vervolgens een string van codes door aan een tekstoperator (Spec: ISO 32000-2, §9). Elke code is een index — een positie in een font-programma — en het font verandert die index in een glyph-omtrek en schildert die. De code 70 is niet het teken F. Het is “slot 70 in dit font”, en slot 70 bevat toevallig een F-vormige kromme. Kies een ander font en slot 70 zou een sneeuwvlok kunnen zijn.

Een code betekent dus alleen iets ten opzichte van de encoding van het font. Voor een simpel font is die encoding ruwweg één byte per glyph. Voor de schriften die duizenden glyphs nodig hebben — CJK, of welk volledig-Unicode-document dan ook — is dat niet genoeg ruimte, en grijpt de PDF naar een composite (Type 0) font (Spec: ISO 32000-2, §9.7). Een composite font leest multibyte-codes via een CMap, verandert ze in CID’s (character identifiers), en beeldt CID’s af op glyphs. Het is een elegante indirectie die één font een enorme glyph-set laat adresseren. Het is ook nog een plek waar het spoor van “wat wordt getoond” naar “wat het betekent” kan doodlopen.

Draai het nu om. Om tekst te extraheren, neemt een reader de codes die hij in de content stream aantreft en moet hij tekens terugwinnen (Spec: ISO 32000-2, §9.10). De encoding die de glyph tekende ging teken naar glyph; extractie heeft glyphcode naar teken nodig, en die richting is niet gegarandeerd inverteerbaar. Een subset-font kan zijn glyphs hernummerd hebben. De CID’s van een composite font kunnen privé zijn. De vorm op de pagina draagt geen inherente Unicode-betekenis.

De oplossing is de omgekeerde afbeelding naast het font mee te leveren. Die afbeelding is de /ToUnicode-CMap: voor elke code die het document gebruikt, legt ze de Unicode-waarde (of -waarden) vast waarvoor die code staat. Daarmee is extractie een schone opzoeking. Zonder haar moet een reader gokken op basis van font-encodings en heuristieken — en gokken is precies waar fi een vraagteken wordt.

  1. Your charactersThe Unicode text you set, for example the word 'file'.
  2. Encoding → codesThe font's encoding turns characters into numeric codes; a composite font routes them through a CMap to CIDs.
  3. Codes → glyphsEach code selects a glyph outline, which is painted to the page. This is the part you see.
  4. Extraction reverses itA reader reads the codes back and looks each one up in /ToUnicode to recover Unicode.
  5. Text againWith a correct /ToUnicode, copy, search, indexing, and screen readers all get the original characters back.
The two directions of PDF text. Rendering goes character → glyph and is what you see. Extraction goes code → character and is what you can copy, search, and read aloud — and it only works when the /ToUnicode map closes the loop.

NextPDF schrijft de omgekeerde afbeelding vanzelfsprekend. Wanneer het een composite font inbedt, zendt het de CIDFontType2-afstammeling, de Type0-ouder en een /ToUnicode-CMap uit zodat codes terug naar Unicode worden opgelost — het encodingwerk dat beschreven wordt in Fonts: the hard part. Wat het hier waard is om apart te zetten is het waarom: de /ToUnicode-stream gaat er niet over de pagina er goed uit te laten zien. De glyphs zien er zonder haar al goed uit. Het is het enige artefact dat de tekst extraheerbaar houdt.

De beroemdste plek waar dit aan de oppervlakte komt, is de fi-ligatuur. Veel fonts tekenen f en i als één gecombineerde glyph, omdat de punt van de i botst met de haak van de f. Op de pagina is het één vorm, getekend door één code. De vraag is wat er gebeurt als je haar kopieert.

% A /ToUnicode entry that maps the single ligature code to TWO characters,
% so selecting the 'fi' glyph copies out as 'f' then 'i' — not one mystery box.
1 beginbfchar
<0085> <00660069> % code 0x85 -> U+0066 'f' U+0069 'i'
endbfchar

Die ene regel is het verschil tussen een zoekopdracht naar “file” die wel overeenkomt en een zoekopdracht naar “file” die stilletjes elk voorkomen mist dat met de ligatuur is weergegeven. Beeld de ligatuurcode af op de tekenreeks f + i van twee tekens, en het woord is doorzoekbaar en kopieerbaar. Laat haar ongekoppeld, en de pagina toont nog steeds perfect file terwijl de tekst stil niets zegt dat een machine kan lezen.

Daarom kan “fi” als één teken of als twee worden geëxtraheerd, en daarom is het verschil niet willekeurig — het is wat de /ToUnicode-afbeelding ook maar opgedragen kreeg te zeggen. Een correcte afbeelding ontleedt de ligatuur terug in haar tekens. NextPDF zendt precies dat soort regel uit, zodat een ligatuur op de pagina twee gewone letters op het klembord is.

De valkuil, benoemd: “de tekst wordt weergegeven, dus de tekst is in orde.” Weergave en extractie zijn verschillende mechanismen die verschillende gegevens lezen. Weergave volgt het code-naar-glyphpad en is wat je ogen controleren. Extractie volgt het code-naar-tekenpad via /ToUnicode en is wat elke machinale consument controleert. Een document kan het eerste glansrijk doorstaan en het tweede volledig falen, want naar zijn constructie is er niets visueels dat het verraadt.

Een tweede, subtielere valkuil: aannemen dat een glyph “vanzelfsprekend” weet welk teken hij is. Dat doet hij niet. Een glyph is een vorm met een index. De afbeelding terug naar een teken is aanvullende informatie die de producent moet leveren. Als de producent haar nooit heeft geschreven, kan geen enkele reader haar getrouw terugwinnen — hij kan alleen gokken.

Faithful text extraction from documents NextPDF writes — edition availability
EditionAvailability
CoreNextPDF emits a correct /ToUnicode CMap for the fonts it embeds, including the ligature and composite-font cases, so the documents it produces are searchable and copyable.
ProNot in this edition
EnterpriseNot in this edition

Een /ToUnicode-afbeelding kan alleen informatie dragen die het bronfont daadwerkelijk blootlegt. Waar een glyph geen bepaalbaar teken heeft — een puur decoratief teken, een private-use-symbool zonder Unicode-toewijzing — kan geen enkele afbeelding betekenis verzinnen die er nooit was. NextPDF zendt de waarheidsgetrouwe afbeelding uit die het kan afleiden; het fabriceert geen tekens om een gat te vullen.

Deze pagina gaat over de documenten die NextPDF schrijft. Het is geen reparatietool voor willekeurige binnenkomende PDF’s waarvan de /ToUnicode al afwezig of fout is; tekst uit die documenten terugwinnen is een afzonderlijk, heuristisch probleem. En getrouwe extractie is noodzakelijk voor toegankelijkheid maar niet het geheel ervan — leesvolgorde, tags en alternatieve tekst worden behandeld in What makes a PDF accessible.

Waarom kopieert dezelfde PDF goed in de ene reader en slecht in de andere? Verschillende readers vallen verschillend terug wanneer /ToUnicode ontbreekt. Sommige gokken op basis van de ingebouwde encoding van het font en boffen bij veelvoorkomende Latijnse tekst; andere niet. Een correcte /ToUnicode haalt de loterij weg — elke conforme reader krijgt dezelfde tekens.

Is dit hetzelfde als het font dat niet is ingebed? Nee. Inbedding gaat over uiterlijk — of de glyph schildert zonder dat het font geïnstalleerd is. /ToUnicode gaat over betekenis — of de code terug op een teken wordt afgebeeld. Een font kan perfect zijn ingebed en toch geen bruikbare /ToUnicode hebben, wat de niet-doorzoekbaar-maar-mooi-mislukking is.

Heeft extractie ooit meer dan één teken per code nodig? Ja — de ligatuurcasus is precies dat: één code wordt afgebeeld op twee tekens. Een /ToUnicode-regel kan een enkele code afbeelden op een korte reeks, en zo komen ligaturen, en sommige samengestelde vormen, terug als hun samenstellende letters.

  • Fonts: the hard part — het zusterstuk over de uiterlijkkant: inbedding, subsetting, en hoe de encoding wordt opgebouwd. Deze pagina is de betekeniskant van dezelfde medaille.
  • What makes a PDF accessible — getrouwe tekst is één ingrediënt; leesvolgorde, tags en alt-tekst zijn de rest.
  • What a PDF actually is — het objectmodel waarbinnen de fonts, encodings en /ToUnicode-streams leven.
  • Glyph — een visuele vorm in een font (een omtrek). Wat een PDF werkelijk schildert. Een glyph heeft een index maar geen inherente tekenbetekenis.
  • Teken — een eenheid van geschreven taal, geïdentificeerd door een Unicode-codepunt. Wat je bedoelt, en wat extractie probeert terug te winnen.
  • Code — de numerieke waarde in een content stream die een glyph uit het huidige font selecteert. Geen Unicode-teken; alleen betekenisvol ten opzichte van het font.
  • CID — een character identifier die een composite font gebruikt om een glyph binnen een grote set te adresseren; een tussenstap tussen code en glyph.
  • Composite (Type 0) font — een font dat multibyte-codes via een CMap leest om CID’s en glyphs te bereiken, gebruikt wanneer één font duizenden glyphs moet adresseren.
  • /ToUnicode — de CMap-stream die de codes die een document gebruikt terugbeeldt op Unicode-waarden; wat PDF-tekst doorzoekbaar, kopieerbaar en toegankelijk maakt.
  • Ligatuur — een enkele glyph die twee of meer letters als één vorm tekent (bijvoorbeeld fi); haar /ToUnicode-regel ontleedt haar terug in haar tekens.