Generirajte izvješće, ugradite TrueType font i izlaz se ispravno otvara u svakom pregledniku koji isprobate. Glifovi su ispravni, tekst se može odabrati, datoteka je valjana. Jedino što nije u redu je veličina. Dokument koji je koristio nekoliko desetaka latiničnih znakova nosi cijeli font od 350 KB. Dokument koji je ispisao odlomak kineskog nosi 14 MB CJK font umjesto dijela od pola megabajta koji bi trebao. Nije pokrenuta iznimka, nije zabilježeno upozorenje, a datoteka je prošla validaciju. Ovako izvana izgleda pogrešno poredan korak finalizacije: ništa ne zakazuje, a jedini dokaz je prevelik broj
Bug koji ga je proizveo živio je u HotPDF-u tijekom jedne linije izdanja i u međuvremenu je ispravljen. Vrijedi ga opisati ne kao obavijest o kvaru, već kao lekciju, jer je oblik pogreške općenit. Svaki dokumentni pogon ima fazu finalizacije koja mijenja objekte neposredno prije nego što ih zapiše, a ispravnost te faze u potpunosti ovisi o redoslijedu njezinih koraka u odnosu na serijalizaciju. Stavite jedan korak na pogrešnu stranu pisanja i on ne čini ništa, tiho
Što bi podskupovi fontova trebali raditi
Podskup fonta je dio TrueType datoteke koji dokument stvarno koristi. ISO 32000-1 §9.9 opisuje kako ugrađeni program fonta putuje u toku na koji upućuje deskriptor fonta, a za TrueType program taj tok je /FontFile2 s /Length1 koji daje nekomprimirani broj bajtova. Stvaranje podskupa ponovno zapisuje tablice glyf i loca tako da sadrže samo glifove na koje se dokument odnosi, ponovno numerira identifikatore glifova i dodaje prefiks nazivu /BaseFont sa šesteroslovnom oznakom kao što je ABCDEF+ kako bi se font označio kao podskup, točno onako kako specifikacija zahtijeva. Latinično pismo koje se svodi na deset ili petnaest kilobajta razlika je između vitkog PDF-a i onog koji isporučuje cijelo pismo radi jednog naslova
Trenutak u kojem se to događa je važan. Stvaranje podskupa nije transformacija koju primjenjujete na bajtove koji su već na disku. Ono uređuje graf objekata u memoriji: smanjuje sadržaj toka /FontFile2, ispravlja /Length1 i ponovno zapisuje niz /BaseFont. Sve to mora biti na svom mjestu kada serijalizator prolazi grafom i emitira bajtove. Ako izmjene slete nakon što su bajtovi zapisani, one ažuriraju objekte koje nitko nikada neće pročitati
Simptom i zašto se ništa nije žalilo
Prijavljeno ponašanje bilo je prisutnost punih fontova u izlazu bez dijagnostike. Korisnik koji je registrirao Unicode TrueType font i proizveo normalan dokument otkrio je da je objekt ugrađenog fonta iste duljine kao i izvorna .ttf datoteka te da naziv /BaseFont nije nosio šesteroslovni prefiks podskupa. Izlaz se nikada nije smanjio između pokretanja koja su koristila deset glifova i onih koja su koristila deset tisuća
Odsutnost bilo kakve pogreške je dio koji ovu klasu bugova čini skupom. Rutina za stvaranje podskupova koja se pokreće u pogrešno vrijeme i dalje se izvodi. Ona prolazi kroz akumuliranu upotrebu kodnih točaka, gradi savršeno ispravan podskup i primjenjuje ga na graf objekata u memoriji. Interno je posao obavljen i poziv se vraća čist. Jedino što nije u redu je to što graf objekata koji je uredila više nije stvar koja se zapisuje, jer je pisac već završio. S točke gledišta pozivatelja, dokument je proizveden i spremljen bez incidenata, što je upravo dojam koji ostavlja tiho zatajenje
Korijenski uzrok bio je redoslijed finalizacije
U HotPDF-u se završni rad odvija unutar EndDoc. Korak stvaranja podskupova je interna rutina nazvana BuildAndApplyUnicodeFontSubset. Ona čita skup korištenih kodnih točaka po dokumentu, koji se čuva u bitmapi koju putanja emitiranja teksta popunjava kako se glifovi prikazuju, mapira svaku korištenu kodnu točku kroz predmemoriranu tablicu kodnih točaka u glifove na stvarni identifikator glifa i ponovno zapisuje program fonta oko tog zatvaranja. Kada je Unicode TrueType font registriran, putanja emitiranja postavlja bit u skupu korištenih kodnih točaka za svaki znak koji crta, tako da do trenutka zatvaranja dokumenta pogon točno zna koje glifove podskup mora zadržati
Kvar je bio u tome što je BuildAndApplyUnicodeFontSubset bio pozivan nakon što su SaveToStream ili SaveToFile već serijalizirali dokument. Izmjene koje je program za podskupove napravio u /FontFile2, njegov ispravljeni /Length1 i šesteroslovni prefiks /BaseFont izračunati su na grafu objekata koji je već pretvoren u bajtove. Ispravak je bio promjena redoslijeda u jednom retku: pomicanje poziva podskupova ispred serijalizacije, tako da pisac emitira podskup fonta umjesto originalnog. Ispravljeni niz najprije pokreće podskupove, a nakon toga serijalizira
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansSC-Regular.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Noto Sans SC', [], 12);
Pdf.CurrentPage.TextOut(72, 760, 0, '报表标题 Report Heading');
Pdf.EndDoc; // svođenje na podskup odvija se ovdje, prije zapisa
Pdf.SaveToFile('Report.pdf');
finally
Pdf.Free;
end;
end;
Nakon ispravljanja redoslijeda, ništa se ne mijenja u kodu pozivatelja. Stvaranje podskupova je uključeno prema zadanim postavkama čim se registrira Unicode TrueType font. Registrirate font, započnete dokument, crtate i završite ga, a podskup se gradi od glifova koje ste koristili prije nego što bajtovi napuste memoriju
Zašto je jedan pogrešno postavljen korak cijela kategorija
Razlog zašto je ovo vrijedno lekcije, a ne fusnote, jest taj što EndDoc emitira popis završnih koraka, a svaki od njih je osjetljiv na svoju poziciju u odnosu na pisanje. Stvaranje podskupova fontova je jedan od njih. PDF/A izlaz zahtijeva tok /CIDSet koji točno nabraja identifikatore glifova prisutne u podskupu, što je ograničenje koje ISO 19005 nameće kako bi validator mogao potvrditi da ugrađeni program odgovara onome što deskriptor fonta tvrdi; taj se tok emitira u istom prozoru finalizacije i ovisi o tome da je podskup prethodno izgrađen. PDF/UA-1 zahtijeva, prema ISO 14289-1 §7.18.3, da svaka stranica koja nosi bilješku deklarira /Tabs s vrijednošću /S, a interna rutina pod nazivom EnsurePDFUATabsOnAnnotatedPages utiskuje taj ključ tijekom iste faze. Tamo se također izvode i provjere namjere izlaza
Ista pogreška u redoslijedu koja je onemogućila stvaranje podskupova također je ispustila PDF/UA ključ redoslijeda tabulatora na stranicama s bilješkama, jer se taj korak nalazio na istoj pogrešnoj strani pisanja. veraPDF i PAC prijavljuju nedostatak /Tabs /S kao kršenje kontrolne točke 21-001 protokola Matterhorn. Dakle, jedan pogrešno postavljen poziv nije samo povećao veličinu datoteke; istodobno je tiho prekršio zahtjev za sukladnost pristupačnosti, uz isti nedostatak bilo kakve pogreške. To je opasnost faze finalizacije: njezini koraci dijele preduvjet, a jedna pogreška u redoslijedu može izbaciti nekoliko njih odjednom dok svaki poziv i dalje vraća uspjeh
Kako se zapravo otkriva tiho zatajenje emitiranja
Bug koji ne pokreće iznimku ne hvata se pokretanjem programa. Hvata se pregledom izlaza i njegovom usporedbom s onim što je ulaz trebao proizvesti. Za stvaranje podskupova fontova provjere su konkretne. Usporedite veličinu izlazne datoteke s grubim očekivanjem: dokument koji je dotaknuo nekoliko glifova ne bi trebao biti veličine cijelog pisma. Otvorite ugrađeni objekt fonta i pročitajte njegovu duljinu u bajtovima; podskup /FontFile2 za latinično pismo mali je dio izvorne datoteke. Pročitajte naziv /BaseFont i potvrdite da je prisutan šesteroslovni prefiks, jer je njegova odsutnost izravan signal da podskup nije primijenjen
var
Pdf: THotPDF;
Output: TMemoryStream;
begin
Output := TMemoryStream.Create;
try
Pdf := THotPDF.Create(nil);
try
Pdf.RegisterUnicodeTTF('C:\Fonts\DejaVuSans.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('DejaVu Sans', [], 11);
Pdf.CurrentPage.TextOut(72, 760, 0, 'Subset me');
Pdf.EndDoc;
Pdf.SaveToStream(Output);
finally
Pdf.Free;
end;
// Nekoliko glifova iz fonta od ~700 KB ne smije proizvesti tok od nekoliko stotina KB
if Output.Size > 100 * 1024 then
raise Exception.Create('Font subset did not shrink the output');
finally
Output.Free;
end;
end;
Za PDF/A izlaz provjera je još oštrija, jer validator obavlja posao umjesto vas. Postavite razinu sukladnosti i pokrenite rezultat kroz veraPDF: nedostatak /CIDSet ili podskup koji ne odgovara deskriptoru prijavljuje se kao neuspjela klauzula, umjesto da to sami primijetite vizualno. Prekidači sukladnosti koji pokreću ovaj rad na finalizaciji su svojstva dokumenta. PDFACompliance uzima niz kao što je '2B' za PDF/A-2 razinu B, a PDFUACompliance je logička vrijednost koja uključuje zahtjeve za označeni PDF i redoslijed tabulatora
Pdf := THotPDF.Create(nil);
try
Pdf.PDFACompliance := '2B'; // PDF/A-2 Level B, drives /CIDSet emission
Pdf.PDFUACompliance := True; // stamps /Tabs /S on annotated pages
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansSC-Regular.ttf');
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Noto Sans SC', [], 12);
Pdf.CurrentPage.TextOut(72, 760, 0, '合规报告');
Pdf.EndDoc;
Pdf.SaveToFile('Report_PDFA.pdf');
finally
Pdf.Free;
end;
Inženjerska lekcija
Iz ovoga proizlaze dva pravila. Prvo je da se svaki korak finalizacije koji mijenja objekte mora pokrenuti prije nego što se ti objekti serijaliziraju, a završna faza dokumentnog pogona trebala bi se tumačiti kao uređeni cjevovod u kojem je serijalizacija posljednja radnja, a ne jedna od nekoliko radnji. Drugo je ono koje je ovdje koštalo najviše vremena: za korak emitiranja, odsutnost pogreške nije dokaz uspjeha. Rutina koja gradi ispravan podskup i primjenjuje ga na pogrešan, već zapisani graf ne prijavljuje ništa loše, jer iz njezine vlastite perspektive ništa nije ni bilo pogrešno. Provjera mora gledati artefakt, a ne povratni kod. Provjerite veličinu izlaza, pročitajte duljinu bajta ugrađenog fonta i njegov prefiks /BaseFont te pustite veraPDF da procijeni PDF/A izlaz gdje nedostajući /CIDSet tihi nedostatak pretvara u imenovani neuspjeh
Proizvođačka strana rukovanja fontovima, odnosno način na koji se pisma registriraju i ugrađuju za izlaz izvješća, pokrivena je u našem članku o fontovima i slikama u izlazu izvješća. Validacijska strana, gdje se ovi koraci finalizacije provjeravaju prema standardima, pokrivena je u vodiču o validaciji za PDF/A i PDF/UA. Obje se povezuju s radom na podskupovima i sukladnosti koji je ovdje opisan, a koji se isporučuje kao dio softvera HotPDF Delphi Component za Delphi i C++Builder, zajedno s API-jima za učitavanje, uređivanje, šifriranje i potpisivanje koji su obrađeni drugdje na ovom blogu