Een PDF die op uw eigen machine er perfect uitziet maar op een andere machine als een rij lege vakjes wordt weergegeven, is het meest voorkomende lettertypedefect in documentsoftware. Het betekent bijna nooit dat de tekst zelf fout is. De tekens zijn intact, de codering klopt, maar de glyphs ontbreken gewoon. Wat er verschilt tussen beide machines is welke lettertypen het besturingssysteem heeft geinstalleerd. Het verschil tussen een draagbaar bestand en een kwetsbaar bestand is een beslissing die werd genomen toen de pagina werd aangemaakt: of het lettertype mee in de PDF is opgenomen of als aanwezig op de ontvangstmachine werd aangenomen.
Begrijpen waarom dat gebeurt, en waarom een aparte fout zorgt voor tekst die er doorzoekbaar uitziet maar bij kopiëren onzin oplevert, betekent kijken naar hoe PDF tekst opslaat. PDF slaat geen zinnen op. Het slaat glyph-codes op, een lettertypeprogramma en tabellen die de ene op de andere mappen. Elke render- of extractiefout bevindt zich in een gat tussen die drie. Hieronder volgt een rondleiding door dat mechanisme, gebaseerd op ISO 32000, met de Delphi-aanroepen die er waar nodig toe doen.
Tekens, codes en glyphs zijn drie verschillende dingen
Het vocabulaire brengt mensen in verwarring omdat alledaags taalgebruik drie verschillende concepten samenvoegt in het woord "letter." Een teken is een abstracte schrijfeenheid, het idee van een hoofdletter A, in Unicode aangeduid als U+0041. Een glyph is een getekende vorm, het contourtje dat een bepaald lettertype gebruikt om dat teken weer te geven. Daartussen zit de code: de byte of bytes in de contentstream die de viewer vertellen welke glyph in het huidige lettertype moet worden getekend.
PDF werkt met codes. Wanneer een contentstream een string toont, zijn die bytes indices in het actieve lettertype, geen Unicode. De codering van het lettertype bepaalt dat een code van 65 betekent "teken de glyph die is opgeslagen onder 65," en niets in die bewerking weet dat het resultaat er voor een mens als een A uitziet. Daardoor rendert PDF overal identiek waar het de glyphs kan vinden. Het is ook de reden waarom extractie een apart probleem is ten opzichte van weergave: tekenen heeft alleen code-naar-glyph nodig, lezen heeft code-naar-Unicode nodig, en dat zijn twee verschillende tabellen die het met elkaar oneens kunnen zijn of afzonderlijk kunnen ontbreken.
De lettertypen die u in de praktijk tegenkomt
ISO 32000 definieert verschillende lettertypewoordenboektypen, en in de praktijk gebruikt een document dat u ontvangt of genereert er een van drie. Weten welke u voor u heeft, verklaart het meeste van wat er fout kan gaan.
Type 1 is het originele PostScript-contourformaat van Adobe, opgebouwd uit kubische Bezier-curven. De veertien standaardlettertypen die elke conforme lezer moet leveren, de families Helvetica, Times, Courier, Symbol en ZapfDingbats, zijn Type 1. Een lettertypewoordenboek dat een daarvan noemt, mag het lettertypeprogramma wettelijk weglaten. Dat is het enige geval waarbij een niet-ingesloten lettertype veilig is op grond van de specificatie en niet door geluk. Voor elk ander Type 1-lettertype moet het programma worden ingesloten, anders vervangt de viewer het door iets dat er visueel anders uitziet.
TrueType maakt gebruik van kwadratische curven en komt uit de wereld van Apple en Microsoft. Het is wat de meeste systeemlettertypen zijn, en wat u het vaakst zult insluiten. Een eenvoudig TrueType-lettertype in PDF is beperkt tot enkelbyte-codes, zodat zo'n lettertype maximaal 256 glyphs tegelijk kan adresseren. Die grens is de structurele reden waarom CJK- en andere grote schriften niet op een eenvoudig lettertype passen.
Type 0, het samengestelde of CID-gebaseerde lettertype, is het antwoord op die beperking. Het gebruikt multibyte-codes en een CMap om ze via een afstammend CIDFont te routeren, waarvan de contouren zelf TrueType of CFF/Type 1 zijn. Dit is het enige lettertypetype dat duizenden glyphs kan bevatten, zodat een PDF met Chinees, Japans, Koreaans of een brede meertalige mix altijd Type 0 gebruikt, of de auteur er nu bewust over heeft nagedacht of niet. De prijs is complexiteit: meer onderdelen, en meer daarvan moeten correct zijn voor zowel rendering als extractie.

Een detail achter die afbeelding bepaalt de bestandsgrootte. Een lettertype is een bibliotheek met contouren, geen bitmaps op vaste grootte, zodat hetzelfde ingesloten programma voor elke puntgrootte op de pagina dienst doet. Schaling is een transformatie die tijdens het tekenen wordt toegepast, en dat is de reden waarom een koptekst en de bijbehorende broodtekst één ingesloten lettertype delen en waarom de insluitkosten per lettertype gelden, niet per grootte.
Insluiting is het verschil tussen draagbaar en kwetsbaar
Insluiting betekent dat het lettertypeprogramma, de eigenlijke contourdata, als een stream in de PDF wordt geschreven. Een lezer op een machine die uw lettertype nooit heeft gezien, leest die contouren rechtstreeks uit het bestand en tekent exacte glyphs. Sla insluiting over en u wedt dat de bestemming een lettertype met dezelfde naam heeft. Wanneer dat niet het geval is, valt de viewer terug op een vervangend lettertype. Voor de veertien standaardlettertypen is die vervanging gedefinieerd en onschadelijk. Voor al het andere varieert het van een bijna-gelijke in een ander lettertype tot het lege-vakjesresultaat wanneer geen vervanger het script dekt.
Met HotPDF regelt u dit via één eigenschap die voor het openen van het document wordt ingesteld. FontEmbedding instrueert de bibliotheek om de lettertypen waarmee wordt getekend in het bestand op te nemen:
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;
De volgorde is niet cosmetisch. BeginDoc is het moment waarop HotPDF de documentstructuur vastlegt, dus FontEmbedding moet voor die aanroep op true staan. Wijs het daarna toe en er volgt geen fout, geen waarschuwing, alleen een bestand dat stilletjes zonder zijn lettertypen is verstuurd. Dat is het ergste soort fout: het doorstaat elke test op de machine van de ontwikkelaar, waar het lettertype toevallig geinstalleerd is, en komt pas boven water op die van de klant, waar dat niet zo is.
Insluiting is ook het punt waar licentieverlening en techniek elkaar raken. Een lettertypeprogramma bevat vlaggen die beschrijven of het vrij mag worden ingesloten, alleen voor voorvertoning, of helemaal niet. Die vlaggen te respecteren is uw verantwoordelijkheid, niet die van de renderer. "Het werkte" is niet hetzelfde als "het was toegestaan."
Subsetting: sluit alleen de gebruikte glyphs in
Volledige insluiting schrijft het volledige lettertypeprogramma in het bestand. Een groot CJK TrueType-lettertype kan enkele megabytes beslaan, en het geheel insluiten om een handvol tekens weer te geven is verspilling die over een meerpaginadocument oploopt. Subsetting lost dit op door alleen de glyphs in te sluiten waarnaar het document verwijst, waarna het lettertype wordt hernoemd met een label van zes letters en een plusteken, de vorm ABCDEF+Calibri in de lettertypelijst van elke gemaakte subset-PDF. Een lezer verwart de gedeeltelijke versie zo nooit met een volledig systeemlettertype met dezelfde naam.
Voor de meeste gegenereerde documenten is subsetting de juiste standaard. Het houdt de bestandsgrootte proportioneel aan de inhoud in plaats van aan het bronlettertype, wat het meest telt voor grote meertalige lettertypen die het bestand anders zouden domineren. Het ene voorbehoud is dat een subset alleen bevat wat bij aanmaak werd gebruikt. Als een latere bewerking tekst aan een lettertype met een subset probeert toe te voegen, zijn de benodigde glyphs mogelijk niet in het bestand aanwezig: een echte beperking bij incrementeel bewerken van andermans PDF.
Unicode-lettertypen en het CJK-vakjesprobleem
Wanneer de tekst geen eenvoudig Latijn is, schiet het pad voor eenvoudige lettertypen tekort. De oplossing is een Unicode-geschikt lettertype expliciet registreren en HotPDF een Type 0-lettertype daaruit laten opbouwen. RegisterUnicodeTTF laadt een TrueType-bestand via het pad; daarna is de geregistreerde naam bruikbaar in SetFont zoals elk ander lettertype:
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;
Twee dingen bepalen het succes. Het lettertype moet de schriften in de string dekken: een puur Latijns TrueType groeit geen Chinese glyphs omdat u dat vraagt, en het resultaat zijn opnieuw lege vakjes, ditmaal omdat de glyph gewoon niet in dat lettertype bestaat. En insluiting moet aan blijven staan, want een Type 0-lettertype dat is opgebouwd uit een geregistreerde TTF is zinloos voor een lezer die de contouren niet kan vinden. Voor gemengde inhoud is de duurzame keuze een lettertype met brede dekking. De families Noto en Arial Unicode MS zijn de gebruikelijke antwoorden, ingesloten en met subset.
Rechts-naar-links- en complexe schriften voegen een vormgevingslaag toe bovenop de dekking. HotPDF biedt RtLTextOut voor Arabisch en Hebreeuws, dat de directionele herordening afhandelt zodat u de logische volgorde doorgeeft en de bibliotheek de opmaak verzorgt. Arabisch goed doen vereist dekking, vormgeving en richting, drie afzonderlijke dingen. Een vakje daar kan op elk van de drie wijzen.
De ToUnicode-tabel: waar kopiëren en plakken woont
Alles hierboven betreft weergave. Extractie is het spiegelbeeld en mislukt om eigen redenen. Een viewer rendert een pagina via de code-naar-glyph-tabel van het lettertype, maar wanneer een gebruiker tekst selecteert en kopieert, moet de viewer diezelfde codes terugzetten naar Unicode. Die omgekeerde mapping is de ToUnicode-CMap, een optionele stream die aan het lettertype is gekoppeld.
Wanneer die aanwezig en correct is, wordt gekopieerde tekst als de juiste tekens weergegeven. Wanneer die ontbreekt of onjuist is, of wanneer het lettertype met aangepaste glyph-codes is onderverdeeld en er geen ToUnicode is geschreven, ziet de pagina er perfect uit maar wordt het klembord gevuld met rommel: glyph-codes die worden gelezen alsof het Unicode is, wat voor een aangepast gecodeerde subset niet zo is. Daarom kan een gescand document met een OCR-tekstlaag doorzoekbaar zijn terwijl een born-digital PDF van een nalatige generator dat niet is. Rendering en extractie gebruiken verschillende tabellen, dus een bestand kan aan de ene voldoen en de andere mislukken. Als extractie voor uw uitvoer van belang is, behandel dan een correcte ToUnicode-map als vereiste, en verifieer dit door tekst uit een voorbeeld te kopiëren in plaats van er blindelings op te vertrouwen dat die er is.
Een lettertypefout snel diagnosticeren
De foutmodus vertelt u waar u moet kijken. Lege vakjes op een andere machine betekenen bijna altijd een niet-ingesloten lettertype: controleer eerst de insluiting en daarna de glyph-dekking. Vakjes die zelfs op uw eigen machine verschijnen, wijzen op dekking: het lettertype bevat dat schrift niet, ongeacht de insluiting. Tekst die correct wordt weergegeven maar als onzin wordt gekopieerd, is een ToUnicode-probleem, geen weergaveprobleem. Sleutelen aan lettertypen of insluiting lost dat niet op, want het tekenen was nooit gebroken. Om een klaar bestand te controleren, opent u het in Acrobat en bekijkt u Documenteigenschappen, Lettertypen: een gezonde vermelding toont het type, vermeldt Ingesloten of Ingesloten subset, en noemt de codering. Een lettertype dat ingesloten had moeten zijn maar dat niet is, meldt zich daar voordat een klant dat doet.
Dit alles is niet exotisch zodra het onderscheid tussen teken, code en glyph helder is. Sluit de lettertypen in waarmee u tekent, maak van grote lettertypen een subset, grijp naar een Unicode-lettertype en gebruik RegisterUnicodeTTF zodra de tekst het Latijns schrift verlaat, en houd een correcte ToUnicode-map bij als iemand de tekst wil extraheren. Doe dat goed en de vakjes verdwijnen. Voor de omliggende mechanismen laat de anatomie van een minimale PDF zien waar het lettertypewoordenboek in de objectboom staat, en de doorloop van de documentstructuur behandelt hoe resources over pagina's worden gedeeld.
De aanroepen SetFont, FontEmbedding en RegisterUnicodeTTF die hier worden getoond, maken deel uit van de HotPDF Component voor Delphi en C++Builder.