Tehnični članak

Zaznavanje manjkajočih glifov PDF ob risanju v Delphiju

Manjkajoči glif v PDF ni napaka. Proizvajalec zahteva znak, ki ga izbrana pisava ne more preslikati, pisava vrne indeks glifa nič in datoteka, ki pride ven, je strukturno veljavna, se odpira povsod in pokaže prazen kvadrat tam, kjer bi moralo biti ime ali znesek. Nihče v generirajočem cevovodu tega ne izve. Prejemnik pa. HotPDF zapre to zanko z TrackUnresolvedGlyphs: vklopite ga in pot risanja besedila zapiše vsako kodno točko, katere iskanje glifa se razreši v indeks nič, sproži OnUnresolvedGlyph enkrat na unikatno najdbo s kodno točko, pisavo, na kateri je odpovedala, skripto, ki ji pripada, in predlogom pisav, ki bi jo pokrile

Zaznavanje je polovica odgovora. Druga polovica je SetFontFallbackChain, ki registrira urejen seznam pisav po skripti, tako da se običajni primeri razrešijo sami in samo prave vrzeli dosežejo vaš ročaj. Skupaj pretvorita razred napak, ki so ga prej poročale stranke, v pregled ob času gradnje

Zakaj manjkajoči glif ne sproži ničesar?

Ker ISO 32000 ne postavlja nobene obveznosti na proizvajalca, da preveri pokritost, in indeks glifa nič je legitimen glif. Je .notdef, katerega konturo izbere oblikovalec pisave: običajno prazen ali votel pravokotnik, včasih sploh nič. Pregledovalnik, ki ga nariše, se obnaša pravilno. Izvlečenje besedila lahko celo vrne prave znake, ker je preslikava /ToUnicode zapisana iz izvornega besedila in ne iz kontur, zato samodejni preizkus povratnega toka bo vesel prepeljal dokument, katerega vidno besedilo ima luknje

Diagram, zakaj manjkajoči glif PDF ostane tih, ko glif nič nariše prazen kvadrat, medtem ko izvlečenje ToUnicode preide preizkuse povratnega toka
Glif nič je legitimen odgovor .notdef in /ToUnicode je zapisan iz izvornega besedila, zato nič v cevovodu ni obveščeno o vrzeli

Praktična posledica je, da je treba pokritost preveriti v trenutku risanja, ko knjižnica še ve, katera kodna točka je bila zahtevana in kateri glif je pisava dejansko ponudila. Potem je informacija odšla

Zaznavalnik mora gledati stanje podnabora in ne konteksta naprave

Tukaj je prva implementacija šla narobe in razlog je vredna razumevanja, ker velja za vsak pregled pokritosti, privit na besedilni cevovod. HotPDF ima dve besedilni poti. Ena izda skozi registrirano pisavo TrueType Unicode s preslikavo znakov v pomnilniku, zgrajeno ob času registracije. Druga je podedovana pot GDI, ki ustvari svež kontekst naprave in ročaj pisave na tek znakov

Soditi pokritost iz poti GDI je brezupno. Njegova preslikava ni preslikava, ki konča v izdanem toku vsebine, in nista sinhronizirana, zato zaznavalnik, ki bere rezultate GDI, poroča celoten natisljivi razpon ASCII kot nerazrešen. Avtoritativni odgovor živi v registrirani pisavi: preslikava znakov, ki jo razčleni RegisterUnicodeTTF, povprašana skozi GetUnicodeGlyphForCodepoint. Zaznavalnik je zato ograjen na stanje pripravljenosti podnabora in ne na kateri koli pogoj GDI, in preprosto ne teče na dokumentih, ki nikoli niso registrirali pisave Unicode, kar je pravilno, ker so ti dokumenti omejeni na standardna kodiranja vseeno

Druga past sedi ob njemu. Ime družine GDI pisave in ime PostScript, izvlečeno iz binarne pisave ob registraciji, sta različna niza in ne na način, ki ga lahko normalizirate: družina, imenovana Arial Unicode MS, nosi ime PostScript ArialMT. Vsaka vrata, zapisana kot »ali je trenutno izbrana pisava tista, ki smo jo registrirali«, primerjana po imenu, so mrtva koda, ki nikoli ne sproži. Ograjite na stanje, nikoli na imena pisav

Tok zaznavanja nerazrešenih glifov HotPDF, ki prikazuje vrata stanja podnabora, iskanje GetUnicodeGlyphForCodepoint in povezavo dogodka OnUnresolvedGlyph
Pokritost je sodjena iz registrirane preslikave pisave Unicode in ne GDI, vsaka unikatna kodna točka pa sproži en dogodek s predlogom pisave

Ne testirajte zaznavalnika glifov z emodžiji

Očiten testni primer je nasmehajoči obraz in vas bo prepričal, da je zaznavalnik pokvarjen. Pogoste kodne točke emodžijev v astralnih ravninah se razrešijo skozi pot sinteze zasebne rabe, ki jih preslika v indeks glifa neposredno, zato nikoli ne dosežejo splošne veje pokritosti. Zaznavalnik se obnaša pravilno in test meri napačno pot

Uporabite namesto tega nedodeljeno kodno točko. U+0378 je trajno nedodeljen v Unicode, zato ga nobena pisava ne more legitimno preslikati, in vadi natanko vejo, ki jo želite preveriti. To razlikovanje med »funkcija je pokvarjena« in »test je izbral vhod, ki obide funkcijo« stale realne ure in nedodeljene kodne točke so najcenejši način izogibanja

type
  TCoverageAudit = class
  private
    FFindings: TStringList;
  public
    procedure Handle(Sender: TObject;
      const Info: THPDFUnresolvedGlyphInfo);
    property Findings: TStringList read FFindings;
  end;

procedure TCoverageAudit.Handle(Sender: TObject;
  const Info: THPDFUnresolvedGlyphInfo);
begin
  // Sproži enkrat na unikatno kodno točko, ne enkrat na pojavitev
  FFindings.Add(Format('U+%.4X missing in %s (script %d), try: %s',
    [Info.CodePoint, String(Info.FontName), Ord(Info.Script),
     String(Info.SuggestedFonts)]));
end;

// Povezovanje v generirajoče opravilo
Pdf := THotPDF.Create(nil);
try
  Pdf.TrackUnresolvedGlyphs := True;
  Pdf.OnUnresolvedGlyph := Audit.Handle;
  Pdf.RegisterUnicodeTTF('C:\Windows\Fonts\arial.ttf');
  Pdf.BeginDoc;
  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Pdf.CurrentPage.TextOut(50, 720, 0, CustomerName);
  Pdf.EndDoc;
  if Audit.Findings.Count > 0 then
    // Spodletite opravilo namesto pošiljanja strani s kvadrati nanjo
    raise Exception.Create(Audit.Findings.Text);
finally
  Pdf.Free;
end;

Verige zasilnih rešitev so na skripto in ne na pisavo

Razlog, zakaj je zasilna rešitev obsegana po skripti in ne po izvorni pisavi, je, da se vrzeli pokritosti zbirajo po pisavnem sistemu. Latinska besedilna pisava manjka Devanagari, tajščino, Han in emodžije vse naenkrat in zamenjava za vsakega je druga pisava. Deklariranje ene verige na skripto torej opisuje pravo namestitev: ena latinska pisava za telo besedila, ena pisava CJK, ena pisava emodžijev, ena vse-zajemajoča

Diagram zasilnih pisav po skriptah, ki preslika skripte hfsCJK, hfsArabic, hfsEmoji in hfsOther na urejene verige nadomestnih pisav v HotPDF
Vsaka skripta dobi svojo urejeno verigo, zato latinska pisava telesa, ki manjka Han, arabščino ali emodžije, pade skozi na pisavo, ki jo pokrije
// THPDFFontScript pokriva hfsCommon, hfsLatin, hfsGreek, hfsCyrillic,
// hfsHebrew, hfsArabic, hfsIndic, hfsSoutheastAsian, hfsCJK, hfsKana,
// hfsHangul, hfsEmoji in hfsOther
Pdf.SetFontFallbackChain(hfsCJK,
  ['Microsoft YaHei', 'SimSun', 'Yu Gothic']);
Pdf.SetFontFallbackChain(hfsArabic, ['Segoe UI', 'Arial']);
Pdf.SetFontFallbackChain(hfsEmoji, ['Segoe UI Emoji']);
Pdf.SetFontFallbackChain(hfsOther, ['Arial Unicode MS']);

Zasilna rešitev in zaznavanje sta komplementarna in ne alternativna. Verige obravnava pokritost, ki ste jo predvideli; zaznavalnik poroča pokritost, ki je niste, kar na sistemu, ki obdeluje poljubne podatke strank, je zanimiva polovica. Upoštevajte, da zamenjava pisave spremeni metrike, zato se lahko odstavek, ki pade nazaj, preoblikuje; če je razporeditev pomembna, je zapiranje in vedenje podnaborov nadomestne pisave vredno branja v članku o zaprtju podnabora pisave, skripte, ki potrebujejo preurejanje ali spajanje, pa obravnava stopnja oblikovanja, opisana v oblikovanju besedila kompleksnih skript

Kako vgrajiti vedenje naknadno brez tveganja obstoječe poti

Ista izdaja je dodala podedovano zasilno rešitev tabele kern za razmik parov in način, kako je bila obsegana, je vzorec vreden kopiranja. Namesto dodajanja nove točke odločanja logiki kerninga visi zasilna rešitev na veji zgodnjega izstopa, ki je že obstajala za pisave brez tabele GPOS. Moderna pisava z GPOS je nikoli ne doseže, zato je njeno vedenje nespremenjeno po zasnovi in ne po testiranju. Poti, ki ne registrirajo pisave Unicode, proizvedejo dva ničelna odmika, zato so tudi one nespremenjene

To je splošna oblika nizko tveganega naknadnega vgrajevanja v zreli knjižnici upodabljanja: najdite vejo, ki trenutno ne proizvede ničesar, in dajte novo vedenje noter. Pretvori »verjamemo, da to ni povrnilo ničesar« v »to ne more povrniti ničesar«, kar je veliko boljša stvar za povedati o besedilnem motorju, skozi katerega gredo tuji računi

Naredite vrata, ne dnevnik

Najdbe pokritosti so samo koristne, če na njih kaj odpove. V storitvi generiranja dokumentov je produktivna ureditev obdržati sledenje vklopljeno v nočnem regresijskem opravilu proti korpusu pravih imen strank, naslovov in opisov produktov, in spodleteti opravilo na vsaki najdbi. Ker dogodek sproži enkrat na unikatno kodno točko in ne enkrat na pojavitev, izhod ostane dovolj majhen za branje, tudi ko manjka cela skripta

V produkciji je isti ročaj bolje uporabljen kot telemetrija: zapišite kodno točko in pisavo, ostanite služeč dokument in pustite agregatu povedati, katero skripto dodati naslednjo v nabor pisav namestitve. Vedenje upodabljanja za vgrajene in nadomestne pisave je pokrito naprej v upodabljanju glifov vgrajenih pisav, polni seznam lastnosti, vključno z TrackUnresolvedGlyphs, pa je dokumentiran na strani produkta HotPDF Delphi PDF component