Teknisk artikel

PDF-skrifttyper og tekst: Hvorfor glyffer bliver til kasser

En PDF, der ser perfekt ud på din maskine og gengives som en række tomme kasser på en andens, er den mest almindelige skrifttypefejl i dokumentsoftware, og det betyder næsten aldrig, at teksten er forkert. Tegnene er intakte, kodningen er fin, glyfferne er der simpelthen ikke. Det, der har ændret sig mellem de to maskiner, er, hvilke skrifttyper operativsystemet har installeret, og kløften mellem en bærbar fil og en skrøbelig fil er en beslutning truffet, da siden blev skrevet: om skrifttypen rejste inde i PDF'en eller blev antaget at være til stede i den anden ende

At forstå hvorfor det sker, og hvorfor en separat fejl producerer søgbart udseende tekst, der kopieres ud som vrøvl, betyder, at man ser på, hvordan PDF gemmer tekst. Den gemmer ikke sætninger. Den gemmer glyfkoder plus et skrifttypeprogram plus tabeller, der knytter det ene til det andet, og enhver gengivelses- eller udtrækningsfejl lever i et hul mellem de tre. Hvad der følger, er en rundtur i dette maskineri, baseret på ISO 32000, med de Delphi-kald, der styrer det, hvor de betyder noget

Tegn, koder og glyffer er tre forskellige ting

Ordforrådet spænder ben for folk, fordi daglig tale kollapser tre forskellige ideer i ordet "bogstav". Et tegn (character) er en abstrakt skriveenhed, ideen om stort A, identificeret i Unicode som U+0041. En glyf (glyph) er en tegnet form, kurve- og stammeomridset, som en bestemt skrifttype bruger til at afbilde det tegn. Imellem dem sidder koden: byten eller bytene i indholdsstrømmen, der fortæller fremviseren, hvilken glyf i den aktuelle skrifttype der skal males

PDF fungerer i koder. Når en indholdsstrøm viser en streng, er disse bytes indekser ind i den aktive skrifttype, ikke Unicode. Skrifttypens kodning beslutter, at en kode på 65 betyder "tegn glyffen arkiveret under 65", og intet i den operation ved, at resultatet ser ud som et A for et menneske. Det er det, der får PDF til at gengive identisk overalt, hvor den kan finde glyfferne, og det er også grunden til, at udtrækning er et separat problem fra visning: tegning behøver kun kode-til-glyf, læsning behøver kode-til-Unicode, og det er to forskellige tabeller, der kan være uenige eller forsvinde uafhængigt

De skrifttypetyper, du rent faktisk vil møde

ISO 32000 definerer flere skrifttypeordbogstyper, og i praksis bruger et dokument, du modtager eller genererer, én ud af tre. At vide, hvilken en du kigger på, forklarer det meste af, hvad der kan gå galt

Type 1 er Adobes originale PostScript-omridsformat, bygget fra kubiske Bezier-kurver. De fjorten standard skrifttyper, som enhver kompatibel læser skal levere, familierne Helvetica, Times, Courier, Symbol og ZapfDingbats, er Type 1, og en skrifttypeordbog, der navngiver en af dem, kan lovligt udelade skrifttypeprogrammet. Det er det ene tilfælde, hvor det er sikkert at lade en skrifttype være uindlejret ifølge specifikationen snarere end af held. For ethvert andet Type 1-snit skal programmet indlejres, ellers erstatter fremviseren det med noget andet, normalt en metrisk lignende, men synligt anderledes skrifttype

TrueType bruger kvadratiske kurver og kom fra Apple- og Microsoft-verdenen. Det er det, de fleste systemskrifttyper er, og det, du oftest vil indlejre. En simpel TrueType-skrifttype i PDF er begrænset til single-byte-koder, så én sådan skrifttype kan højst adressere 256 glyffer ad gangen. Den grænse er den strukturelle årsag til, at CJK og andre store scripts ikke kan køre på en simpel skrifttype

Type 0, the composite or CID-keyed font, er svaret på den grænse. Den bruger multi-byte koder og en CMap til at dirigere dem gennem en underordnet CIDFont, hvis omrids i sig selv enten er TrueType eller CFF/Type 1. Dette er den eneste skrifttypetype, der kan bære tusindvis af glyffer, så enhver PDF, der indeholder kinesisk, japansk, koreansk eller en bred flersproget blanding, bruger Type 0, uanset om forfatteren tænkte over det eller ej. Byttehandlen er kompleksitet: flere bevægelige dele, hvoraf flere skal være korrekte for både gengivelse og udtrækning

Én TrueType-skrifttype gengivet i 12, 18, 24 og 36 punkter i en PDF, hvilket viser, at et enkelt indlejret omrids skalerer til enhver størrelse

En detalje bag det billede driver filstørrelsen. En skrifttype er et bibliotek af omrids, ikke bitmaps med fast størrelse, så det samme indlejrede program betjener hver punktstørrelse på siden. Skalering er en transformation anvendt på tegnetidspunktet, hvilket er grunden til, at en overskrift og dens brødtekst deler et indlejret snit, og hvorfor indlejringsomkostninger er pr. skrifttype, ikke pr. størrelse

Indlejring er forskellen mellem bærbar og skrøbelig

Indlejring (Embedding) betyder, at skrifttypeprogrammet, de faktiske omridsdata, skrives ind i PDF'en som en strøm. En læser på en maskine, der aldrig har hørt om din skrifttype, læser disse omrids direkte fra filen og tegner nøjagtige glyffer. Springer du indlejring over, satser du på, at destinationen har en skrifttype af samme navn; når den ikke har, falder fremviseren tilbage til en erstatning. For de standard fjorten er den udskiftning defineret og godartet. For alt andet spænder det fra et næsten-match i en anden skrifttypefamilie til den tomme kasse, når ingen erstatning dækker det script overhovedet

Med HotPDF er styringen en enkelt egenskab, der sættes før dokumentet åbnes. FontEmbedding fortæller biblioteket at pakke de snit, det tegner med, ind 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, 'This renders the same on a machine without Calibri.');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Rækkefølgen er ikke kosmetisk. BeginDoc er der, hvor HotPDF forpligter dokumentstrukturen, så FontEmbedding skal være sand før det kald. Tildel den bagefter, og der er ingen fejl, ingen advarsel, kun en fil, der stille og roligt gik ud uden sine skrifttyper. Det er den værste slags fejl: den består hver test på udviklerens maskine, hvor skrifttypen tilfældigvis er installeret, og dukker kun op på en kundes, hvor den ikke er

Indlejring er også der, hvor licensering møder ingeniørarbejde. Et skrifttypeprogram bærer flag, der beskriver, om det frit kan indlejres, kun til forhåndsvisning eller slet ikke. At overholde disse flag er dit ansvar, ikke renderens, og "det virkede" er ikke det samme som "det var tilladt."

Underopsætning: indlejr kun de glyffer, du brugte

Fuld indlejring skriver hele skrifttypeprogrammet ind i filen. Et stort CJK TrueType-snit kan fylde adskillige megabytes, og at indlejre det helt for at vise et dusin tegn er spild på en måde, der forstærkes på tværs af et flersidet dokument. Underopsætning (Subsetting) løser dette ved kun at skrive de glyffer, som dokumentet refererer til, og derefter omdøbe skrifttypen med et sekstavelses tag og et plustegn, formen ABCDEF+Calibri i enhver subset-PDF's skrifttypeliste, så en læser aldrig forveksler det delvise snit med en fuld systemskrifttype af samme navn

For de fleste genererede dokumenter er subsetting den rigtige standard. Det holder filstørrelsen proportional med indholdet snarere end kildeskrifttypen, hvilket betyder mest for de store flersprogede skrifttyper, der ellers ville dominere filen. Den ene undtagelse er, at en delmængde kun indeholder det, der blev brugt på oprettelsestidspunktet. Hvis en downstream-proces forsøger at tilføje tekst til en subset-skrifttype senere, er de glyffer, den skal bruge, muligvis ikke i filen, en reel begrænsning for inkrementel redigering af en andens PDF

Unicode-skrifttyper og CJK kasse-problemet

Når teksten ikke er almindelig latin, løber den enkle skrifttypevej tør, og rettelsen er at registrere en Unicode-kompatibel skrifttype eksplicit og lade HotPDF bygge en Type 0-skrifttype fra den. RegisterUnicodeTTF indlæser en TrueType-fil via sti; derefter kan det registrerede navn bruges i SetFont som ethvert andet:

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 får dette til at virke eller fejle. Skrifttypen skal dække de scripts, der er i strengen: en TrueType, der kun er latinsk, vil ikke gro kinesiske glyffer, fordi du beder den om det, og resultatet er tomme kasser igen, denne gang fordi glyffen reelt ikke eksisterer i det snit. Og indlejring skal forblive slået til, fordi en Type 0-skrifttype samlet fra en registreret TTF er meningsløs for en læser, der ikke kan finde omridsene. For blandet indhold er det holdbare valg et snit med bred dækning, hvor Noto- og Arial Unicode MS-familierne er de sædvanlige svar, indlejret og subset

Højre-til-venstre og komplekse scripts tilføjer et formningslag (shaping layer) oven på dækningen. HotPDF udstiller RtLTextOut til arabisk og hebraisk, som håndterer den retningsbestemte omrokering, så du sender logisk rækkefølge og lader biblioteket lægge det ud. At få arabisk rigtigt er dækning plus formning plus retning, tre separate ting, og en kasse dér kan betyde, at enhver af dem svigtede

ToUnicode-tabellen: hvor copy-paste bor

Alt ovenfor vedrører tegning. Udtrækning (Extraction) er spejlbilledet og fejler af sine egne grunde. En fremviser gengiver en side ved hjælp af skrifttypens kode-til-glyf-kortlægning, men når en bruger vælger tekst og kopierer den, skal fremviseren gøre de samme koder tilbage til Unicode. Den omvendte kortlægning er ToUnicode CMap'en, en valgfri strøm, der er knyttet til skrifttypen

Når den er til stede og korrekt, kommer kopieret tekst ud som de rigtige tegn. Når den er fraværende eller forkert, eller skrifttypen var subset med brugerdefinerede glyfkoder, og der ikke blev skrevet en ToUnicode, ser siden perfekt ud, og udklipsholderen fyldes med skrald: glyfkoder læses, som om de var Unicode, hvilket de for et specialkodet subset ikke er. Dette er grunden til, at et scannet dokument med et OCR-tekstlag kan være søgbart, mens en født digital PDF fra en skødesløs generator ikke er det. Gengivelse og udtrækning trækker på forskellige tabeller, så en fil kan tilfredsstille den ene og fejle den anden. Hvis udtrækning betyder noget for dit output, skal du behandle et korrekt ToUnicode-kort som et krav og bekræfte det ved at kopiere tekst ud af en prøve i stedet for at stole på, at det er der

Sådan diagnosticeres en skrifttypefejl hurtigt

Fejltilstanden fortæller dig, hvor du skal lede. Tomme kasser på en anden maskine betyder næsten altid en skrifttype, der ikke blev indlejret, så tjek indlejring først og glyfdækning derefter. Kasser, der vises selv på din egen maskine, peger på dækning: skrifttypen indeholder ikke det script, uanset indlejring. Tekst, der gengives korrekt, men kopieres ud som vrøvl, er et ToUnicode-problem, ikke et gengivelsesproblem, og at pille ved skrifttyper eller indlejring vil ikke løse det, fordi tegningen aldrig var i stykker. For at læse en færdig fil skal du åbne den i Acrobat og se på Dokumentegenskaber (Document Properties), Skrifttyper (Fonts): en sund post viser typen, siger Embedded eller Embedded Subset og navngiver kodningen. En skrifttype, der burde være indlejret og ikke er det, annoncerer sig selv der, før en kunde gør det

Intet af dette er eksotisk, når først splittelsen mellem tegn, kode og glyf er klar. Indlejr de skrifttyper, du tegner med, subset de store, ræk ud efter et Unicode-snit og RegisterUnicodeTTF i det øjeblik tekst forlader latin, og behold et korrekt ToUnicode-kort, hvis nogen vil trække teksten ud. Få disse ting rigtigt, og kasserne stopper med at dukke op. For mekanikken udenom viser anatomien af en minimal PDF, hvor skrifttypeordbogen sidder i objekttræet, og dokumentstruktur-gennemgangen dækker, hvordan ressourcer deles på tværs af sider

De viste kald, SetFont, FontEmbedding og RegisterUnicodeTTF, er en del af HotPDF Component til Delphi og C++Builder