PDFlibPas rešava karaktere koje izabrani font ne može da nacrta tako što pretražuje fallback lanac instaliranih fontova, klaster po klaster, uz očuvanje oblikovanja (shaping) i redosleda bidirekcionih tokova. Omogućavate ga pozivom SetAutomaticFontFallback, proširujete lanac pomoću AddFontFallback, a u fajl se ugrađuju samo fallback fontovi koji su stvarno korišćeni za izlaz
Problem koji ovo rešava sreće svaki generator dokumenata čim stigne ime klijenta zapisano u pismu koje font iz šablona nikada nije predvideo. Otkaz je tih, a upravo to ga čini skupim
Zašto nepodržan tekst nestaje umesto da izazove grešku?
Zato što PDF nema pojam fonta koji ne može da nacrta karakter. Prost font mapira bajt kodove u imena glifova preko enkodinga; kompozitni font mapira kodove preko CMap-a u indekse glifova. Zatražite glif koji font ne sadrži i dobićete indeks glifa nula, .notdef, koji većina fontova crta kao ništa ili kao praznu kutiju. Fajl je strukturno validan, operator teksta je pravilno formiran, a stranica se renderuje. Samo je prazna tamo gde je trebalo da stoji ime
Ništa u ISO 32000-1 ne zahteva od proizvođača da ovo primeti. Generator koji upisuje tekst bez provere pokrivenosti proizvodi tehnički usaglašen PDF koji je tiho izgubio sadržaj, a gubitak isplivava na ekranu klijenta nedeljama kasnije. Zato funkcija fallback-a i izveštaj o nedostajućim glifovima idu zajedno: rešavanje onoga što se može rešiti samo je pola posla, a prijavljivanje onoga što nije moglo da se reši druga je polovina
Fallback se dešava po klasteru, ne po kodnoj tački
Granularnost je detalj koji razdvaja implementaciju koja radi od one koja samo deluje uverljivo. Tekst nije niz nezavisnih karaktera. Devanagari slog, emoji sa modifikatorom tona kože, osnovno slovo sa kombinujućim znacima: svaki od njih je jedan klaster koji mora biti renderovan jednim fontom, jer odluke o oblikovanju unutar njega zavise od tabela u tom fontu
PDFlibPas rešava klastere, tako da klaster koji pokriva fallback font u celosti crta taj font. Deljenje usred klastera i crtanje pola iz primarnog fonta, a pola iz fallback fonta proizvelo bi rezultat koji je tehnički prisutan, ali vidljivo neispravan, što je diskutabilno gore od praznine od koje ste krenuli. Redosled toka je takođe očuvan, tako da fallback unutar toka sa desna na levo ne menja redosled okolnog teksta; isti mehanizam stoji iza vertikalnog rasporeda opisanog u vertikalnom pisanju za japanski i kineski
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutomaticFontFallback(1);
// Redosled pretrage: prvo poklapanje pobeđuje, zato najšire fontove stavite poslednje
Lib.AddFontFallback('Microsoft YaHei'); // Pojednostavljeni kineski
Lib.AddFontFallback('Meiryo'); // Japanski
Lib.AddFontFallback('Segoe UI Symbol');
Lib.AddFontFallback('Segoe UI Emoji');
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_REPORT);
Lib.AddTrueTypeFont('Arial', 1); // 1 = ugradi font
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;
Poređajte lanac namerno. Rešavanje uzima prvi font koji pokriva klaster, tako da će široki pan-Unicode font postavljen na prvo mesto pobediti gotovo sve, a vaši pažljivo izabrani fontovi specifični za pismo nikada neće biti konsultovani. Stavite specifične fontove prve, a univerzalni poslednji
Izveštaj ili prekid: koji otkaz želite?
SetMissingGlyphPolicy prima PDF_MISSING_GLYPH_REPORT, kompatibilnu podrazumevanu vrednost, ili PDF_MISSING_GLYPH_ABORT. Pod politikom izveštavanja operacija teksta se nastavlja, nerešive kodne tačke se izostavljaju kao i ranije, a svaka od njih se beleži. Pod politikom prekida operacija teksta se odbija pre nego što bude upisan bilo kakav sadržaj, a LastErrorCode se postavlja na 521
Birajte prema nameni dokumenta. Serija internih izveštaja treba da nastavi renderovanje i beleži praznine, jer danas nešto malo nepotpun izveštaj vredi više nego nikakav izveštaj. Pravno obavezujući ugovor, faktura ili bilo šta sa imenom na sebi treba da prekine, jer je tiho izostavljen karakter u imenu strane u ugovoru nedostatak koji želite da otkrijete u sopstvenom procesu, a ne u sporu. Politika prekida otkazuje pre upisivanja, tako da iza sebe ne ostavlja polovično formiran tok sadržaja
var
Lib: TPDFlib;
Report: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
// ... izgradite dokument ...
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;
Izveštaj je namerno mašinski čitljiv i ograničen. Svaki događaj nosi stranicu, UTF-16 indeks unutar niske, kodnu tačku u numeričkom i U+XXXX obliku, izabrani font, njegov tip i operaciju koja je naišla na problem, tako da tiket podrške može da imenuje tačan karakter umesto da opisuje simptom. Tracker čuva poslednjih 256 događaja, što je dovoljno za dijagnostikovanje dokumenta, a dovoljno malo da patološko izvršavanje ne pretvori dijagnostiku u problem sa memorijom
Merenje i crtanje moraju da se slažu
Merenje širine koristi iste odluke o fallback-u svesne klastera kao i crtanje. Ovo zvuči očigledno, a upravo je to stvar koju većina samostalno napravljenih fallback slojeva pogrešno radi: zakrpe putanju crtanja, a merenje ostave na primarnom fontu, pa svako tekst polje, desno poravnanje i kolona tabele na kraju budu izračunati na osnovu širina koje se ne poklapaju sa onim što je renderovano
Pošto obe putanje dele isto rešavanje, niska izmerena pre crtanja zauzima tačno onu širinu na kojoj je izmerena, uključujući i fallback tokove. Upravo to čini fallback bezbednim za globalno omogućavanje, umesto samo na mestima koja ste ručno proverili
Ugrađuje se samo ono što ste koristili
Fallback fontovi se ugrađuju lenjo (lazily): font u lancu koji nikada nije rešio nijedan klaster ne doprinosi ničim izlazu. Dokument koji sadrži jedan kineski karakter i 5.000 latiničnih ne nosi ceo CJK font; on nosi ono što je prolaz podskupljivanja (subsetting) proizveo za taj jedan glif, što je ponašanje opisano u optimizaciji veličine fajla i podskupljivanju fontova
Ta lenjost čini širok lanac jeftinim za konfigurisanje. Registrujte fontove koji bi mogli zatrebati vašem skupu dokumenata za svaku lokalizaciju koju podržavate, a svaki pojedinačni PDF plaća samo za ono što je zaista koristio. Za dokumente koje niste sami generisali, gde nedostajući fontovi već postoje unutar postojećeg fajla, putanja popravke je drugačija i pokrivena je u ugrađivanju nedostajućih fontova u postojeći PDF
Vredi jasno naglasiti jedno upozorenje za produkciju: fallback se rešava u odnosu na fontove instalirane na mašini koja izvršava kod. Server bez instaliranih CJK fontova nema na šta da se osloni, a izveštaj će vam to reći već kod prvog dokumenta, a ne tek posle prve žalbe. Isporučite fontove o kojima zavisite i potvrdite licenciranje za njihovo ugrađivanje
PDFlibPas je PDF biblioteka za Delphi, C++Builder i Lazarus sa odgovarajućim DLL i ActiveX interfejsima, tako da su fallback i missing-glyph API-ji dostupni i pozivaocima van Pascal-a. Potpuna dokumentacija nalazi se na stranici PDFlibPas Delphi PDF biblioteke