PDFlibPas løser tegn, som den valgte skrifttype ikke kan tegne, ved at søge gennem en fallback-kæde af installerede skrifttyper, klynge for klynge, samtidig med at formning og bidirektional rækkefølge bevares. Du aktiverer det med SetAutomaticFontFallback, udvider kæden med AddFontFallback, og kun de fallback-skrifttyper, der faktisk bruges til output, indlejres i filen
Problemet, det løser, møder enhver dokumentgenerator første gang et kundenavn ankommer i et skriftsystem, som skabelonens skrifttype aldrig havde forudset. Fejlen er stille, og det er netop det, der gør den dyr
Hvorfor forsvinder ikke-understøttet tekst i stedet for at give en fejl?
Fordi PDF ikke har noget begreb om en skrifttype, der ikke kan tegne et tegn. En simpel skrifttype afbilder bytekoder til glyfnavne via en kodning; en sammensat skrifttype afbilder koder gennem et CMap til glyfindekser. Bed om en glyf, som skrifttypen ikke indeholder, og du får glyfindeks nul, .notdef, som de fleste skrifttyper tegner som intet eller som en tom kasse. Filen er strukturelt gyldig, tekstoperatoren er velformet, og siden gengives. Der står bare tomt, hvor navnet skulle have været
Intet i ISO 32000-1 kræver, at en producent lægger mærke til det. En generator, der skriver tekst uden at tjekke dækning, producerer en teknisk konform PDF, som lydløst har mistet indhold, og tabet viser sig på en kundes skærm uger senere. Det er derfor fallback-funktionen og rapporten om manglende glyffer følges ad: at løse det, der kan løses, er kun den halve opgave, og at rapportere det, der ikke kunne løses, er den anden halvdel
Fallback sker pr. klynge, ikke pr. kodepunkt
Granularitet er detaljen, der skiller en fungerende implementering fra en plausibel en. Tekst er ikke en sekvens af uafhængige tegn. En devanagari-stavelse, en emoji med en hudtonemodifikator, et grundbogstav med diakritiske mærker: hver af dem er én klynge, der skal gengives af én skrifttype, fordi formningsbeslutningerne i den afhænger af tabeller i netop den skrifttype
PDFlibPas løser klynger, så en klynge, som en fallback-skrifttype dækker, tegnes fuldt ud af den skrifttype. At dele midt i en klynge og tegne halvdelen med den primære skrifttype og halvdelen med en fallback ville give et teknisk til stede, men visuelt ødelagt resultat, hvilket nok er værre end det tomme, du startede med. Rækkefølgen bevares også, så en fallback inden i et højre-til-venstre-forløb ikke omarrangerer den omgivende tekst; den samme mekanik ligger bag den lodrette opsætning beskrevet i lodret skrivning for japansk og kinesisk
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutomaticFontFallback(1);
// Søgerækkefølge: første match vinder, så placér de bredeste skrifttyper sidst
Lib.AddFontFallback('Microsoft YaHei'); // Forenklet kinesisk
Lib.AddFontFallback('Meiryo'); // Japansk
Lib.AddFontFallback('Segoe UI Symbol');
Lib.AddFontFallback('Segoe UI Emoji');
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_REPORT);
Lib.AddTrueTypeFont('Arial', 1); // 1 = indlejr skrifttypen
Lib.SetTextSize(11);
Lib.DrawText(72, 720, 'Invoice for 北京示例科技有限公司');
Lib.DrawText(72, 700, 'Delivery status: on time');
Lib.SaveToFile('invoice.pdf');
finally
Lib.Free;
end;
end;
Bestem kædens rækkefølge bevidst. Løsningen vælger den første skrifttype, der dækker klyngen, så en bred pan-Unicode-skrifttype placeret først vinder næsten alt, og dine omhyggeligt udvalgte skriftspecifikke skrifttyper bliver aldrig konsulteret. Sæt de specifikke skrifttyper først og altmuligmanden sidst
Rapportér eller afbryd: hvilken fejl vil du have?
SetMissingGlyphPolicy tager PDF_MISSING_GLYPH_REPORT, den kompatible standard, eller PDF_MISSING_GLYPH_ABORT. Under rapportpolitikken fortsætter tekstoperationen, ikke-løselige kodepunkter droppes som før, og hvert af dem registreres. Under afbrydelsespolitikken afvises tekstoperationen, før noget indhold skrives, og LastErrorCode sættes til 521
Vælg ud fra, hvad dokumentet skal bruges til. En batch af interne rapporter bør fortsætte med at gengive og logge hullerne, fordi en lidt ufuldstændig rapport i dag er bedre end ingen rapport overhovedet. En juridisk bindende kontrakt, en faktura eller noget med et navn på bør afbryde, fordi et lydløst droppet tegn i et parts navn er en defekt, du vil opdage i din egen proces frem for i en tvist. Afbrydelsespolitikken fejler før skrivning, så der efterlades ingen halvfærdig indholdsstrøm
var
Lib: TPDFlib;
Report: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
// ... byg dokumentet ...
if Lib.DrawText(72, 660, CustomerName) <> 1 then
if Lib.LastErrorCode = PDFLIB_ERROR_MISSING_GLYPH then
begin
Report := Lib.GetMissingGlyphReportJSON;
// {"valid":false,"policy":1,"eventCount":1,"events":[
// {"sequence":1,"documentIndex":0,"page":1,"utf16Index":12,
// "codePoint":21271,"unicode":"U+5317","fontName":"Arial",
// "fontType":"TrueType","operation":"DrawText"}]}
EscalateToOperator(Report);
end;
finally
Lib.Free;
end;
end;
Rapporten er bevidst maskinlæsbar og afgrænset. Hver hændelse angiver siden, UTF-16-indekset inde i strengen, kodepunktet i både numerisk og U+XXXX-form, den valgte skrifttype, dens type og operationen, der ramte problemet, så en supportsag kan navngive det præcise tegn i stedet for at beskrive et symptom. Sporeren beholder de seneste 256 hændelser, hvilket er nok til at diagnosticere et dokument og lille nok til, at et patologisk forløb ikke kan gøre diagnostik til et hukommelsesproblem
Måling og tegning skal stemme overens
Bredemåling bruger de samme klyngebevidste fallback-beslutninger som tegning. Det lyder indlysende og er det, de fleste hjemmelavede fallback-lag får galt: de lapper tegnestien, lader målingen blive på den primære skrifttype, og hver tekstboks, højrejustering og tabelkolonne ender med at blive beregnet ud fra bredder, der ikke matcher det, der faktisk blev gengivet
Fordi begge stier deler løsningen, optager en streng, der måles før tegning, den bredde, den blev målt til, inklusive fallback-forløbene. Det er det, der gør det sikkert at aktivere fallback globalt frem for kun de steder, du har gennemgået manuelt
Kun det, du brugte, indlejres
Fallback-skrifttyper indlejres dovent: en skrifttype i kæden, der aldrig løste en klynge, bidrager med intet til outputtet. Et dokument, der indeholder ét kinesisk tegn og 5.000 latinske, bærer ikke en fuld CJK-skrifttype; det bærer det, som subsetting-processen producerede for den ene glyf, hvilket er den adfærd, der beskrives i filstørrelsesoptimering og skrifttype-subsetting
Den dovenskab gør en bred kæde billig at konfigurere. Registrér de skrifttyper, dit dokumentsæt kan have brug for på tværs af alle sprog, du betjener, og hver enkelt PDF betaler kun for det, den faktisk brugte. For dokumenter, du ikke selv genererede, hvor de manglende skrifttyper allerede sidder i en eksisterende fil, er reparationsstien en anden og dækkes i indlejring af manglende skrifttyper i en eksisterende PDF
Et forbehold ved udrulning er værd at nævne klart: fallback løser mod skrifttyper installeret på den maskine, der kører koden. En server uden CJK-skrifttyper installeret har intet at falde tilbage på, og rapporten vil fortælle dig det ved det første dokument frem for efter den første klage. Send de skrifttyper, du er afhængig af, med, og bekræft licensvilkårene for at indlejre dem
PDFlibPas er et Delphi-, C++Builder- og Lazarus-PDF-bibliotek med matchende DLL- og ActiveX-grænseflader, så fallback- og manglende-glyf-API'erne også er tilgængelige fra ikke-Pascal-kaldere. Fuld dokumentation findes på PDFlibPas' produktside for Delphi PDF-biblioteket