Teknisk artikkel

PDF-skrifttyper og tekst: Hvorfor glyfer blir til bokser

En PDF som ser perfekt ut på din maskin, men gjengis som en rad tomme bokser på en annens, er den vanligste skrifttypefeilen i dokumentprogramvare, og det betyr nesten aldri at teksten er feil. Tegnene er intakte, kodingen er fin, glyfene er rett og slett ikke der. Det som endret seg mellom de to maskinene, er hvilke skrifttyper operativsystemet hadde installert, og gapet mellom en bærbar fil og en skjør en, er en avgjørelse tatt da siden ble skrevet: om skrifttypen fulgte med inni PDF-en eller ble antatt å være til stede på den andre siden

Å forstå hvorfor det skjer, og hvorfor en separat feil produserer søkbar tekst som kopieres ut som vrøvl, innebærer å se på hvordan PDF lagrer tekst. Det lagrer ikke setninger. Det lagrer glyfkoder pluss et skrifttypeprogram pluss tabeller som tilordner det ene til det andre, og hver feil i gjengivelse eller utvinning lever i et gap mellom disse tre. Det som følger er en omvisning i dette maskineriet, forankret i ISO 32000, med Delphi-kallene som styrer det der de betyr noe

Tegn, koder og glyfer er tre forskjellige ting

Vokabularet forvirrer folk fordi hverdagslig tale slår sammen tre distinkte ideer til ordet "bokstav." Et tegn (character) er en abstrakt skriveenhet, ideen om en stor A, identifisert i Unicode som U+0041. En glyf er en tegnet form, kurve-og-stamme-omrisset en bestemt skrifttype bruker for å skildre det tegnet. Mellom dem sitter koden: byten eller bytene i innholdsstrømmen som forteller visningsprogrammet hvilken glyf i den gjeldende skrifttypen som skal males

PDF jobber med koder. Når en innholdsstrøm viser en streng, er disse bytene indekser inn i den aktive skrifttypen, ikke Unicode. Skrifttypens koding bestemmer at en kode på 65 betyr "tegn glyfen arkivert under 65," og ingenting i den operasjonen vet at resultatet ser ut som en A for et menneske. Det er det som gjør at PDF gjengis identisk overalt der den kan finne glyfene, og det er også grunnen til at utvinning (extraction) er et separat problem fra visning: tegning krever bare kode-til-glyf, lesing krever kode-til-Unicode, og det er to forskjellige tabeller som kan være uenige eller mangle uavhengig av hverandre

Skrifttypetypene du faktisk vil møte

ISO 32000 definerer flere ordboktyper for skrifttyper, og i praksis bruker et dokument du mottar eller genererer en av tre. Å vite hvilken du ser på forklarer det meste av det som kan gå galt

Type 1 er Adobes opprinnelige PostScript-omrissformat, bygget av kubiske Bezier-kurver. De fjorten standard skrifttypene enhver samsvarende leser må levere, familiene Helvetica, Times, Courier, Symbol og ZapfDingbats, er Type 1, og en skrifttypeordbok som navngir en av dem, kan lovlig utelate skrifttypeprogrammet. Det er det ene tilfellet der det å la en skrifttype være uinnebygd er trygt i henhold til spesifikasjonen i stedet for ved flaks. For ethvert annet Type 1-snitt må programmet bygges inn, ellers bytter visningsprogrammet ut noe, vanligvis en metrisk lik, men synlig annerledes skrifttype

TrueType bruker kvadratiske kurver og kom fra Apple- og Microsoft-verdenen. Det er det de fleste systemskrifttyper er, og det du oftest vil bygge inn. En enkel TrueType-skrifttype i PDF er begrenset til enkelt-byte-koder, så én slik skrifttype kan adressere maksimalt 256 glyfer om gangen. Det taket er den strukturelle grunnen til at CJK og andre store skrivesystemer ikke kan fungere på en enkel skrifttype

Type 0, den sammensatte (composite) eller CID-nøklede skrifttypen, er svaret på den grensen. Den bruker flerbytekoder og et CMap for å rute dem gjennom en underordnet CIDFont, hvis omriss selv er enten TrueType eller CFF/Type 1. Dette er den eneste skrifttypetypen som kan bære tusenvis av glyfer, så enhver PDF som inneholder kinesisk, japansk, koreansk eller en bred flerspråklig blanding, bruker Type 0 uansett om forfatteren tenkte over det eller ikke. Byttehandelen er kompleksitet: flere bevegelige deler, og flere av dem må være riktige for både gjengivelse og utvinning

En TrueType-skrifttype gjengitt ved 12, 18, 24 og 36 punkter i en PDF, og viser at et enkelt innebygd omriss skalerer til hvilken som helst størrelse

Én detalj bak det bildet driver filstørrelsen. En skrifttype er et bibliotek av omriss, ikke bitkart i fast størrelse, så det samme innebygde programmet tjener hver punktstørrelse på siden. Skalering er en transformasjon som brukes på tegnetidspunktet, noe som er grunnen til at en overskrift og brødteksten deler ett innebygd snitt, og hvorfor innbyggingskostnaden er per skrifttype, ikke per størrelse

Innbygging er forskjellen på bærbar og skjør

Innbygging (embedding) betyr at skrifttypeprogrammet, selve omrissdataene, skrives inn i PDF-en som en strøm. En leser på en maskin som aldri har hørt om skrifttypen din, leser disse omrissene rett fra filen og tegner nøyaktige glyfer. Dropp innbygging, og du vedder på at destinasjonen har en skrifttype med samme navn; når den ikke har det, faller visningsprogrammet tilbake på en erstatning. For de fjorten standardtypene er den substitusjonen definert og godartet. For alt annet spenner det fra en nesten-treffer i et annet skrifttypesnitt til resultatet med den tomme boksen når ingen erstatning dekker skrivesystemet i det hele tatt

Med HotPDF er kontrollen en enkelt egenskap, satt før dokumentet åpnes. FontEmbedding ber biblioteket om å pakke snittene det tegner med inn i filen:

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'report.pdf';
    Pdf.Compression := cmFlateDecode;
    Pdf.FontEmbedding := True;          // outlines travel inside the file
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Calibri', [], 11);
    Pdf.CurrentPage.TextOut(72, 760, 0, 'Dette gjengis likt på en maskin uten Calibri.');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Rekkefølgen er ikke kosmetisk. BeginDoc er der HotPDF forplikter dokumentstrukturen, så FontEmbedding må være sann før det kallet. Tilordne den etterpå, og det er ingen feil, ingen advarsel, bare en fil som stille gikk ut uten skrifttypene sine. Det er den verste typen feil: den består hver test på utviklerens maskin, der skrifttypen tilfeldigvis er installert, og dukker bare opp på en kundes, der den ikke er det

Innbygging er også der lisensiering møter ingeniørvitenskap. Et skrifttypeprogram bærer flagg som beskriver om det kan bygges inn fritt, bare for forhåndsvisning, eller ikke i det hele tatt. Å respektere disse flaggene er ditt ansvar, ikke gjengiverens, og "det fungerte" er ikke det samme som "det var tillatt."

Delmengder (Subsetting): bygg bare inn de glyfene du brukte

Full innbygging skriver hele skrifttypeprogrammet inn i filen. Et stort CJK TrueType-snitt kan komme opp i flere megabyte, og å bygge inn hele for å vise et dusin tegn er sløsing på en måte som forsterkes over et flersides dokument. Delmengder (subsetting) løser dette ved å bare skrive de glyfene dokumentet refererer til, og deretter gi skrifttypen nytt navn med en sekstavs tagg og et plusstegn, formen ABCDEF+Calibri i en hvilken som helst delmengde-PDFs skrifttypeliste, slik at en leser aldri forveksler det delvise snittet med en full systemskrifttype med samme navn

For de fleste genererte dokumenter er delmengder riktig standardvalg. Det holder filstørrelsen proporsjonal med innholdet i stedet for kildeskrifttypen, noe som betyr mest for de store flerspråklige skrifttypene som ellers ville dominert filen. Det ene forbeholdet er at en delmengde bare inneholder det som ble brukt på opprettelsestidspunktet. Hvis en nedstrøms prosess prøver å legge til tekst i en delmengde-skrifttype senere, er glyfene den trenger kanskje ikke i filen, en reell begrensning på inkrementell redigering av andres PDF

Unicode-skrifttyper og CJK-boksproblemet

Når teksten ikke er ren latin, tar enkelt-skrifttype-stien slutt, og løsningen er å registrere en Unicode-kapabel skrifttype eksplisitt og la HotPDF bygge en Type 0-skrifttype fra den. RegisterUnicodeTTF laster inn en TrueType-fil etter bane; etter det er det registrerte navnet brukbart i SetFont som ethvert annet:

Pdf.FontEmbedding := True;
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansCJKsc-Regular.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('NotoSansCJKsc-Regular', [], 14);
Pdf.CurrentPage.TextOut(72, 720, 0, '你好,世界 こんにちは 안녕하세요');
Pdf.EndDoc;

To ting avgjør om dette lykkes eller ikke. Skrifttypen må dekke skrivesystemene i strengen: en kun-latin TrueType vil ikke gro kinesiske glyfer fordi du ba den om det, og resultatet er tomme bokser igjen, denne gangen fordi glyfen genuint ikke finnes i det snittet. Og innbygging må være på, fordi en Type 0-skrifttype satt sammen fra en registrert TTF er meningsløs for en leser som ikke kan finne omrissene. For blandet innhold er det holdbare valget et bredt dekkende snitt, der Noto- og Arial Unicode MS-familiene er de vanlige svarene, innebygd og med delmengder

Høyre-til-venstre og komplekse skrivesystemer legger til et formingslag (shaping layer) på toppen av dekning. HotPDF eksponerer RtLTextOut for arabisk og hebraisk, som håndterer den retningsbestemte omorganiseringen slik at du sender logisk rekkefølge og lar biblioteket legge den ut. Å få arabisk riktig er dekning pluss forming pluss retning, tre separate ting, og en boks der kan bety at hvilken som helst av dem mislyktes

ToUnicode-tabellen: der kopier-og-lim-inn lever

Alt ovenfor gjelder tegning. Utvinning (extraction) er speilbildet og feiler av sine egne grunner. Et visningsprogram gjengir en side ved hjelp av skrifttypens kode-til-glyf-tilordning, men når en bruker velger tekst og kopierer den, må visningsprogrammet gjøre de samme kodene tilbake til Unicode. Den omvendte tilordningen er ToUnicode CMap, en valgfri strøm knyttet til skrifttypen

Når den er til stede og riktig, kommer kopiert tekst ut som de riktige tegnene. Når den er fraværende eller feil, eller skrifttypen var en delmengde med egendefinerte glyfkoder og ingen ToUnicode ble skrevet, ser siden perfekt ut og utklippstavlen fylles med søppel: glyfkoder lest som om de var Unicode, noe de for en egendefinert kodet delmengde ikke er. Dette er grunnen til at et skannet dokument med et OCR-tekstlag kan være søkbart mens en digitalt født PDF fra en skjødesløs generator ikke er det. Gjengivelse og utvinning trekker på forskjellige tabeller, så en fil kan tilfredsstille den ene og feile den andre. Hvis utvinning er viktig for utdataene dine, behandle et riktig ToUnicode-kart som et krav, og verifiser det ved å kopiere tekst ut av en prøve i stedet for å stole på at det er der

Hvordan diagnostisere en skrifttypefeil raskt

Feilmodusen forteller deg hvor du skal lete. Tomme bokser på en annen maskin betyr nesten alltid en skrifttype som ikke ble bygget inn, så sjekk innbygging først og glyfdekning etterpå. Bokser som dukker opp selv på din egen maskin peker mot dekning: skrifttypen inneholder ikke det skrivesystemet, uavhengig av innbygging. Tekst som gjengis riktig, men kopieres ut som vrøvl, er et ToUnicode-problem, ikke et gjengivelsesproblem, og å tukle med skrifttyper eller innbygging vil ikke fikse det fordi tegningen aldri var ødelagt. For å lese en ferdig fil, åpne den i Acrobat og se på Dokumentegenskaper, Skrifttyper: en sunn oppføring viser typen, sier Innebygd (Embedded) eller Innebygd delmengde (Embedded Subset), og navngir kodingen. En skrifttype som burde vært innebygd og ikke er det, kunngjør seg der før en kunde gjør det

Ingenting av dette er eksotisk når delingen mellom tegn, kode og glyf er klar. Bygg inn skrifttypene du tegner med, bruk delmengder for de store, grip etter et Unicode-snitt og RegisterUnicodeTTF i det øyeblikket tekst forlater latin, og behold et riktig ToUnicode-kart hvis noen vil utvinne teksten. Få disse riktig, og boksene slutter å dukke opp. For den omkringliggende mekanikken viser anatomien til en minimal PDF hvor skrifttypeordboken sitter i objekttreet, og gjennomgangen av dokumentstruktur dekker hvordan ressurser deles på tvers av sider

Kallene SetFont, FontEmbedding og RegisterUnicodeTTF som vises her er en del av HotPDF-komponenten for Delphi og C++Builder