Technical Article

EndDoc bug koji je tiho onemogu?io podskupove fontova

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;                 // subsetting runs here, before the write
    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;
    // A few glyphs from a ~700 KB face must not yield a multi-hundred-KB stream.
    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 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