Een PDF die er perfect uitziet op uw machine en wordt weergegeven als een rij lege vierkantjes op die van iemand anders, is het meest voorkomende lettertypedefect in documentsoftware. Het betekent bijna nooit dat de tekst onjuist is. De tekens zijn intact, de codering is prima, de tekens (glyphs) zijn er simpelweg niet. Wat er tussen de twee machines verschilde, is welke lettertypen op het besturingssysteem waren geïnstalleerd. Het verschil tussen een draagbaar bestand en een kwetsbaar bestand is één beslissing die is genomen toen de pagina werd geschreven: of het lettertype is ingesloten in de PDF of dat werd aangenomen dat het aan de ontvangende kant aanwezig is
Om te begrijpen waarom dat gebeurt, en waarom een andere fout zoekbaar ogende tekst oplevert die bij kopiëren als wartaal wordt uitgevoerd, moet u kijken naar hoe een PDF tekst opslaat. Het slaat geen zinnen op. Het slaat glyfcodes op, plus een lettertypeprogramma, plus tabellen die de een aan de ander toewijzen. Elke weergave- of extractiebug bevindt zich in een kloof tussen deze drie. Hieronder volgt een rondleiding door dat mechanisme, gebaseerd op ISO 32000, met de Delphi-aanroepen die het besturen waar deze van belang zijn
Tekens, codes en glyfen zijn drie verschillende dingen
De terminologie is soms verwarrend, omdat het dagelijks taalgebruik drie verschillende ideeën samenvoegt tot het woord 'letter'. Een teken is een abstracte schrijfeenheid, het idee van de hoofdletter A, in Unicode geïdentificeerd als U+0041. Een glyf (tekenvorm) is een getekende vorm, de omtrek van krommen en lijnen die een bepaald lettertype gebruikt om dat teken weer te geven. Daartussen zit de code: de byte of bytes in de content stream die de viewer vertellen welke glyf in het huidige lettertype moet worden getekend
PDF werkt met codes. Wanneer een inhoudsstroom een reeks (string) toont, zijn die bytes indexen naar het actieve lettertype, geen Unicode. De codering van het lettertype bepaalt dat een code van 65 betekent: "teken de glyf die is opgeslagen onder 65", en niets in die bewerking weet dat het resultaat er voor een mens uitziet als een A. Dat is wat ervoor zorgt dat een PDF overal identiek wordt weergegeven waar hij de glyfen kan vinden. Het is ook de reden waarom extractie een ander probleem is dan weergave: tekenen heeft alleen code-naar-glyf nodig, lezen heeft code-naar-Unicode nodig. Dat zijn twee verschillende tabellen die van elkaar kunnen verschillen of afzonderlijk kunnen ontbreken
De lettertypetypen die u in de praktijk zult tegenkomen
ISO 32000 definieert verschillende lettertypewoordenboektypen. In de praktijk gebruikt een document dat u ontvangt of genereert er één van de drie. Weten naar welk type u kijkt, verklaart veel van wat er mis kan gaan
Type 1 is de originele PostScript-contourindeling van Adobe, opgebouwd uit kubische Bezier-krommen. De veertien standaardlettertypen die elke conforme reader moet leveren, de families Helvetica, Times, Courier, Symbol en ZapfDingbats, zijn Type 1. Een lettertypewoordenboek dat een van deze noemt, kan het lettertypeprogramma legaal weglaten. Dit is het enige geval waarin het weglaten van het insluiten van een lettertype veilig is volgens de specificatie en niet op basis van geluk. Voor elk ander Type 1-lettertype moet het programma worden ingesloten, of de viewer vervangt het door iets anders, meestal een lettertype dat qua metrische waarden vergelijkbaar is, maar er zichtbaar anders uitziet
TrueType gebruikt kwadratische krommen en komt uit de wereld van Apple en Microsoft. De meeste systeemlettertypen vallen hieronder, en dit is wat u het vaakst zult insluiten. Een eenvoudig TrueType-lettertype in PDF is beperkt tot single-byte-codes, zodat zo'n lettertype maximaal 256 glyfen tegelijk kan adresseren. Die limiet is de structurele reden dat CJK en andere grote schriften niet op een eenvoudig lettertype kunnen worden gebaseerd
Type 0, het samengestelde of CID-keyed lettertype, is het antwoord op deze limiet. Het gebruikt multi-byte-codes en een CMap om ze door te sturen naar een onderliggend CIDFont, waarvan de contouren zelf TrueType of CFF/Type 1 zijn. Dit is het enige lettertype dat duizenden glyfen kan bevatten. Elke PDF die Chinees, Japans, Koreaans of een brede meertalige mix bevat, gebruikt dus Type 0, of de auteur daar nu bij heeft stilgestaan of niet. De inruil is complexiteit: meer bewegende delen, waarvan er meer correct moeten zijn voor zowel weergave als extractie

Eén detail achter deze afbeelding is bepalend voor de bestandsgrootte. Een lettertype is een bibliotheek van contouren, geen bitmaps met een vaste grootte. Hetzelfde ingesloten programma bedient dus elke puntgrootte op de pagina. Schalen is een transformatie die wordt toegepast tijdens het tekenen. Dit is de reden waarom een kop en de hoofdtekst één ingesloten lettertype delen en de kosten voor het insluiten per lettertype gelden, niet per grootte
Insluiten is het verschil tussen draagbaar en kwetsbaar
Insluiten betekent dat het lettertypeprogramma, de daadwerkelijke contourgegevens, als een stream in de PDF wordt geschreven. Een reader op een machine die nog nooit van uw lettertype heeft gehoord, leest deze contouren rechtstreeks uit het bestand en tekent de exacte glyfen. Als u het insluiten overslaat, gokt u erop dat de bestemming een lettertype met dezelfde naam heeft. Is dat niet het geval, dan valt de viewer terug op een vervanger. Voor de standaard veertien is die vervanging gedefinieerd en goedaardig. Voor al het andere varieert het van een vergelijkbaar alternatief in een ander lettertype, tot lege vierkantjes wanneer geen enkele vervanger het script dekt
Met HotPDF gebeurt dit via een enkele eigenschap, ingesteld voordat het document wordt geopend. FontEmbedding vertelt de bibliotheek om de lettertypen waarmee het tekent in het bestand te verpakken:
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 waar HotPDF de documentstructuur vastlegt, dus FontEmbedding moet vóór deze aanroep true (waar) zijn. Als u het achteraf toewijst, krijgt u geen foutmelding, geen waarschuwing, alleen een bestand dat stilletjes de deur uitging zonder zijn lettertypen. Dat is de ergste soort bug: hij doorstaat elke test op de machine van de ontwikkelaar, waar het lettertype toevallig is geïnstalleerd, en duikt pas op bij de klant, waar dat niet zo is
Insluiten is ook waar licenties en engineering samenkomen. Een lettertypeprogramma bevat vlaggen die beschrijven of het vrijelijk, alleen voor preview, of helemaal niet mag worden ingesloten. Het respecteren van die vlaggen is uw verantwoordelijkheid, niet die van de renderer. "Het werkte" is niet hetzelfde als "het was toegestaan"
Subsetting: sluit alleen de glyfen in die u heeft gebruikt
Volledig insluiten schrijft het gehele lettertypeprogramma in het bestand. Een groot CJK TrueType-lettertype kan enkele megabytes groot zijn, en het in zijn geheel insluiten om slechts een tiental tekens weer te geven, is zonde op een manier die zich opstapelt in een document van meerdere pagina's. Subsetting lost dit op door alleen de glyfen weg te schrijven waarnaar het document verwijst, waarna het lettertype wordt hernoemd met een code van zes letters en een plusteken. Dit ziet u in de ABCDEF+Calibri-vorm in de lijst met lettertypen van elke PDF met een subset. Hierdoor verwarren readers het gedeeltelijke lettertype nooit met een volledig systeemlettertype met dezelfde naam
Voor de meeste gegenereerde documenten is subsetting de juiste standaardoptie. Het houdt de bestandsgrootte proportioneel aan de inhoud in plaats van aan het bronlettertype, wat vooral van belang is bij grote meertalige lettertypen die anders het bestand zouden domineren. De enige kanttekening is dat een subset alleen de tekens bevat die tijdens het maken zijn gebruikt. Als een later proces probeert tekst toe te voegen aan een subset, is het mogelijk dat de benodigde glyfen niet in het bestand staan. Dit vormt een reële beperking voor incrementele bewerkingen van de PDF van iemand anders
Unicode-lettertypen en het probleem met CJK-vierkantjes
Wanneer de tekst geen gewoon Latijns is, voldoet het pad van het eenvoudige lettertype niet meer. De oplossing is om expliciet een lettertype te registreren dat Unicode ondersteunt, en HotPDF er een Type 0-lettertype van te laten bouwen. RegisterUnicodeTTF laadt een TrueType-bestand via het pad; daarna is de geregistreerde naam net als elke andere bruikbaar in SetFont:
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 zijn hierbij doorslaggevend. Het lettertype moet de schriften in de string ondersteunen: een TrueType-lettertype met uitsluitend Latijnse tekens zal geen Chinese glyfen tevoorschijn toveren omdat u erom vraagt. Het resultaat is opnieuw lege vierkantjes, dit keer omdat de glyf echt niet bestaat in dat lettertype. Bovendien moet insluiten ingeschakeld blijven, want een Type 0-lettertype dat is samengesteld uit een geregistreerde TTF is betekenisloos voor een reader die de contouren niet kan vinden. Voor gemengde inhoud is de duurzame keuze een lettertype met een brede dekking, waarbij de Noto- en Arial Unicode MS-families de gebruikelijke antwoorden zijn, ingesloten en voorzien van een subset
Van-rechts-naar-links en complexe schriften voegen een shaping-laag toe bovenop de dekking. HotPDF biedt RtLTextOut voor Arabisch en Hebreeuws, dat de herordening van de richting afhandelt. U geeft de logische volgorde door en de bibliotheek legt de opmaak vast. Arabisch correct krijgen betekent dekking plus shaping plus richting, drie afzonderlijke dingen. Een vierkantje kan daar betekenen dat een van de drie is mislukt
De ToUnicode-tabel: waar kopiëren en plakken leeft
Al het bovenstaande heeft betrekking op het tekenen. Extractie is het spiegelbeeld en mislukt om zijn eigen redenen. Een viewer geeft een pagina weer met behulp van de code-naar-glyf-toewijzing van het lettertype, maar wanneer een gebruiker tekst selecteert en kopieert, moet de viewer diezelfde codes weer omzetten in Unicode. Die omgekeerde toewijzing (reverse mapping) is de ToUnicode CMap, een optionele stream die aan het lettertype is gekoppeld
Wanneer dit aanwezig en correct is, verschijnt gekopieerde tekst als de juiste tekens. Wanneer dit ontbreekt of verkeerd is, of als het lettertype werd gesubset met aangepaste glyfcodes en er geen ToUnicode is geschreven, ziet de pagina er perfect uit en wordt het klembord gevuld met wartaal: glyfcodes die worden gelezen alsof ze Unicode zijn, terwijl dit voor een aangepaste subset niet het geval is. Dit is de reden waarom een gescand document met een OCR-tekstlaag doorzoekbaar kan zijn, terwijl een oorspronkelijk digitale PDF van een onzorgvuldige generator dat niet is. Weergave en extractie zijn gebaseerd op verschillende tabellen. Een bestand kan dus de ene test doorstaan en de andere falen. Als extractie belangrijk is voor uw uitvoer, beschouw een correcte ToUnicode-map dan als een vereiste en verifieer dit door tekst uit een voorbeeld te kopiëren, in plaats van erop te vertrouwen dat het er is
Hoe u een lettertypebug snel diagnosticeert
De faalmodus vertelt u waar u moet zoeken. Lege vierkantjes op een andere machine betekenen bijna altijd dat een lettertype niet was ingesloten, dus controleer eerst de insluiting en daarna de glyfdekking. Vierkantjes die zelfs op uw eigen machine verschijnen, wijzen op een dekkingsprobleem: het lettertype bevat dat script niet, ongeacht of het is ingesloten. Tekst die correct wordt weergegeven maar als onzin wordt gekopieerd, is een ToUnicode-probleem, geen weergaveprobleem. Rommelen met lettertypen of insluiten lost het niet op, omdat de weergave zelf nooit stuk was. Om een voltooid bestand te lezen, opent u het in Acrobat en kijkt u bij Documenteigenschappen, Lettertypen: een gezonde vermelding toont het type, vermeldt 'Ingesloten' (Embedded) of 'Ingesloten subset' (Embedded Subset) en noemt de codering. Een lettertype dat ingesloten moet zijn maar dat niet is, maakt zich daar kenbaar voordat een klant dat doet
Niets van dit alles is exotisch zodra de scheiding tussen teken, code en glyf duidelijk is. Sluit de lettertypen in waarmee u tekent, subset de grote lettertypen, gebruik een Unicode-lettertype en RegisterUnicodeTTF zodra de tekst het Latijn verlaat, en behoud een correcte ToUnicode-map als de tekst door iemand zal worden geëxtraheerd. Als u dit goed doet, verschijnen de vierkantjes niet meer. Voor de achterliggende mechanismen toont de anatomie van een minimale PDF waar het lettertypewoordenboek in de objectboom (object tree) zit, en de rondleiding door de documentstructuur behandelt hoe resources over pagina's worden gedeeld
De hier getoonde aanroepen SetFont, FontEmbedding en RegisterUnicodeTTF maken deel uit van de HotPDF-component voor Delphi en C++Builder