PDFlibPas löser tecken som det valda teckensnittet inte kan rita genom att söka igenom en fallback-kedja av installerade typsnitt, kluster för kluster, samtidigt som formning och bidirektionell löpordning bevaras. Du aktiverar det med SetAutomaticFontFallback, utökar kedjan med AddFontFallback, och endast de fallback-typsnitt som faktiskt används i utdata bäddas in i filen
Problemet det löser är ett som varje dokumentgenerator stöter på första gången ett kundnamn kommer in med ett skriftsystem mallens teckensnitt aldrig förutsåg. Felet är tyst, och det är just det som gör det kostsamt
Varför försvinner text som saknar stöd, i stället för att ge ett fel?
Därför att PDF saknar begreppet ett teckensnitt som inte kan rita ett tecken. Ett enkelt teckensnitt mappar bytekoder till glyfnamn via en kodning; ett sammansatt teckensnitt mappar koder via en CMap till glyfindex. Be om en glyf som typsnittet inte innehåller och du får glyfindex noll, .notdef, som de flesta typsnitt ritar som ingenting eller som en tom ruta. Filen är strukturellt giltig, textoperatorn är korrekt formad, och sidan renderas. Det blir bara tomt där namnet borde stå
Inget i ISO 32000-1 kräver att en producent lägger märke till det. En generator som skriver text utan att kontrollera täckning skapar en tekniskt konform PDF som tyst har förlorat innehåll, och förlusten dyker upp på en kunds skärm veckor senare. Det är därför fallback-funktionen och rapporten över saknade glyfer levereras tillsammans: att lösa det som går att lösa är bara halva jobbet, och att rapportera det som inte kunde lösas är den andra halvan
Fallback sker per kluster, inte per kodpunkt
Granularitet är detaljen som skiljer en fungerande implementation från en som bara verkar fungera. Text är inte en sekvens av oberoende tecken. En devanagari-stavelse, en emoji med en hudtonsmodifierare, en grundbokstav med kombinerande diakritiska tecken: vart och ett är ett kluster som måste renderas av ett enda teckensnitt, eftersom formningsbesluten inuti det beror på tabeller i just det typsnittet
PDFlibPas löser kluster, så ett kluster som ett fallback-typsnitt täcker ritas helt av det typsnittet. Att dela mitt i ett kluster och rita hälften från det primära teckensnittet och hälften från en fallback skulle ge ett resultat som tekniskt sett finns men är synligt trasigt, vilket rimligen är värre än det tomrum du började med. Löpordningen bevaras också, så en fallback inuti ett höger-till-vänster-block ändrar inte ordningen på texten runt omkring; samma mekanik ligger till grund för det vertikala radlayout som beskrivs i vertikal skrivriktning för japanska och kinesiska
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutomaticFontFallback(1);
// Sökordning: första träff vinner, så placera de bredaste typsnitten sist
Lib.AddFontFallback('Microsoft YaHei'); // förenklad kinesiska
Lib.AddFontFallback('Meiryo'); // japanska
Lib.AddFontFallback('Segoe UI Symbol');
Lib.AddFontFallback('Segoe UI Emoji');
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_REPORT);
Lib.AddTrueTypeFont('Arial', 1); // 1 = bädda in typsnittet
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;
Ordna kedjan medvetet. Upplösningen tar det första typsnittet som täcker klustret, så ett brett pan-Unicode-teckensnitt som placeras först vinner nästan allt, och dina noggrant utvalda skriftspecifika typsnitt konsulteras aldrig. Placera de specifika typsnitten först och samlingstypsnittet sist
Rapportera eller avbryt: vilket fel vill du ha?
SetMissingGlyphPolicy tar PDF_MISSING_GLYPH_REPORT, det bakåtkompatibla standardvärdet, eller PDF_MISSING_GLYPH_ABORT. Under rapportpolicyn fortsätter textoperationen, olösliga kodpunkter tas bort som tidigare, och var och en registreras. Under avbrottspolicyn avvisas textoperationen innan något innehåll skrivs, och LastErrorCode sätts till 521
Välj utifrån vad dokumentet ska användas till. En batch med interna rapporter bör fortsätta renderas och logga luckorna, eftersom en något ofullständig rapport i dag är bättre än ingen rapport alls. Ett juridiskt bindande avtal, en faktura, eller något med ett namn på sig bör avbrytas, eftersom ett tyst borttappat tecken i ett partsnamn är en brist du vill upptäcka i din egen process snarare än i en tvist. Avbrottspolicyn misslyckas innan skrivning, så ingen halvfärdig innehållsström lämnas kvar
var
Lib: TPDFlib;
Report: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
// ... bygg 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 är medvetet maskinläsbar och begränsad. Varje händelse innehåller sidan, UTF-16-indexet inuti strängen, kodpunkten i både numerisk form och som U+XXXX, teckensnittet som valdes, dess typ och operationen som stötte på problemet, så att ett supportärende kan namnge exakt tecken i stället för att beskriva ett symptom. Spåraren behåller de senaste 256 händelserna, vilket räcker för att diagnostisera ett dokument och är tillräckligt litet för att en patologisk körning inte kan förvandla diagnostik till ett minnesproblem
Mätning och ritning måste stämma överens
Bredmätning använder samma klustermedvetna fallback-beslut som ritning. Det låter självklart och är precis det de flesta egenbyggda fallback-lager gör fel: de lappar ihop ritvägen, lämnar mätningen på det primära teckensnittet, och varje textruta, högerjustering och tabellkolumn slutar med att beräknas från bredder som inte stämmer med det som faktiskt renderades
Eftersom båda vägarna delar samma upplösning upptar en sträng som mäts före ritning exakt den bredd den mättes till, inklusive fallback-blocken. Det är det som gör det säkert att aktivera fallback globalt i stället för bara på de ställen du granskat för hand
Bara det du faktiskt använde bäddas in
Fallback-typsnitt bäddas in lat: ett typsnitt i kedjan som aldrig löste ett kluster bidrar med ingenting till utdata. Ett dokument som innehåller ett kinesiskt tecken och 5 000 latinska bär inte med sig ett helt CJK-typsnitt; det bär med sig vad subset-steget producerade för just den glyfen, vilket är beteendet som beskrivs i filstorleksoptimering och teckensnittssubset
Den latheten gör det billigt att konfigurera en bred kedja. Registrera de typsnitt som ditt dokumentbestånd kan tänkas behöva över varje lokal du betjänar, och varje enskild PDF betalar bara för det den faktiskt använde. För dokument du inte själv har genererat, där de saknade typsnitten redan finns inuti en befintlig fil, ser reparationsvägen annorlunda ut och beskrivs i att bädda in saknade teckensnitt i en befintlig PDF
Ett driftsättningsförbehåll är värt att säga rent ut: fallback löser mot typsnitt installerade på maskinen som kör koden. En server utan installerade CJK-typsnitt har ingenting att falla tillbaka på, och rapporten talar om det redan vid det första dokumentet i stället för efter det första klagomålet. Leverera de teckensnitt du är beroende av, och bekräfta licensvillkoren för att bädda in dem
PDFlibPas är ett PDF-bibliotek för Delphi, C++Builder och Lazarus med matchande DLL- och ActiveX-gränssnitt, så fallback- och saknad-glyf-API:erna är tillgängliga även för icke-Pascal-anropare. Fullständig dokumentation finns på sidan för PDFlibPas Delphi PDF-bibliotek