Techninis straipsnis

Dingusių PDF glifų aptikimas piešimo metu Delphi

Dingęs glifas PDF nėra klaida. Gamintojas prašo simbolio, kurio pasirinktas šriftas negali atvaizduoti, šriftas grąžina glifo indeksą nulis, ir failas, kuris išeina, yra struktūriškai teisėtas, atsidaro visur ir rodo tuščią dėžę ten, kur turėtų būti vardas arba suma. Niekas generuojančiame konvejeryje to nesužino. Gavėjas — sužino. HotPDF uždaro tą ratą su TrackUnresolvedGlyphs: įjunkite tai, ir teksto braižymo kelias užrašo kiekvieną kodinį tašką, kurio glifo paieška išsiskleidžia į indeksą nulis, pakeldamas OnUnresolvedGlyph kartą per unikalų radinį su kodiniu tašku, šriftu, kuriame nepavyko, rašto sistema, kuriai jis priklauso, ir šriftų, kurie jį dengtų, pasiūlymu

Aptikimas yra pusė atsakymo. Kita pusė yra SetFontFallbackChain, registruojanti surūšiuotą šriftų sąrašą pagal rašto sistemą, todėl įprasti atvejai išsprendžia savi, o tikros spragos pasiekia jūsų apdorotoją. Kartu jie paverčia defektų klasę, kurią anksčiau pranešdavo klientai, statymo laiko patikra

Kodėl dingęs glifas nepakelia nieko?

Nes ISO 32000 nenumato jokios gamintojo pareigos tikrinti dengimą, o glifo indeksas nulis yra teisėtas glifas. Tai .notdef, kurio kontūrą pasirenka šrifto dizaineris: dažniausiai tuščias arba tuščiaviduris stačiakampis, kartais niekas apskritai. Peržiūros programa, jį piešianti, elgiasi teisingai. Teksto ištrauka gali net grąžinti teisingus simbolius, nes /ToUnicode atvaizdis rašomas iš šaltinio teksto, o ne iš kontūrų, todėl automatinė abipusė patikra linksmai išlaikys dokumentą, kurio matomas tekstas turi skylių

Diagrama, kodėl dingęs PDF glifas lieka tylus: glifas nulis piešia tuščią dėžę, o ToUnicode ištrauka išlaiko abipuses patikras
Glifas nulis yra teisėtas .notdef atsakymas, o /ToUnicode rašomas iš šaltinio teksto, todėl niekas konvejeryje nesužino apie spragą

Praktinė pasekmė ta, kad dengimą reikia tikrinti piešimo akimirką, kai biblioteka dar žino, kuris kodinis taškas prašytas ir kurį glifą šriftas iš tikrųjų pateikė. Vėliau ta informacija dingo

Aptikėjas turi stebėti poaibio būseną, o ne įrenginio kontekstą

Čia pirmasis įgyvendinimas suklydo, ir priežastis verta suprasti, nes ji taikoma bet kuriai dengimo patikrai, prisi kalbtai prie teksto konvejerio. HotPDF turi du teksto kelius. Vienas skleidžia per registruotą Unicode TrueType šriftą su atmintyje sukurtu simbolių atvaizdu, pastatytu registracijos metu. Kitas yra senasis GDI kelias, kuriantis naują įrenginio kontekstą ir šrifto rankenėlę kiekvienam simbolių etapui

Dengimo vertinimas iš GDI kelio yra beviltiškas. Jo atvaizdis nėra tas atvaizdis, kuris atsiduria skleidžiamame turinio sraute, ir abu nesinchronizuoti, todėl aptikėjas, skaitantis GDI rezultatus, praneša visą spausdinamą ASCII intervalą kaip neišspręstą. Autoritetingas atsakymas gyvena registruotame šrifte: simbolių atvaizdyje, kurį išanalizuoja RegisterUnicodeTTF, teikiamame per GetUnicodeGlyphForCodepoint. Aptikėjas todėl užtvertas pagal poaibiui paruoštą būseną, o ne pagal bet kokią GDI sąlygą, ir jis tiesiog nevyksta dokumentuose, kurie niekada neregistravo Unicode šrifto, — kas teisinga, nes tie dokumentai vis tiek apriboti standartinėmis koduotėmis

Antri spąstai sėdi greta. Šrifto GDI šeimos vardas ir iš šrifto dvejetainės failo registracijos metu ištrauktas PostScript vardas yra skirtingos eilutės, ir ne tokiu būdu, kurį galima normalizuoti: šeima, vadinama Arial Unicode MS, neša PostScript vardą ArialMT. Bet kuri užtvara, parašyta kaip „ar dabar pasirinktas šriftas yra tas, kurį registravome“, lyginama pagal vardą, yra negyvas kodas, niekada nepažystantis. Užtverkite pagal būseną, niekada pagal šriftų vardus

HotPDF neišspręstų glifų aptikimo eiga, rodanti poaibio būsenos užtvarą, GetUnicodeGlyphForCodepoint paiešką ir OnUnresolvedGlyph įvykio sujungimą
Dengimas vertinamas iš registruoto Unicode šrifto atvaizdžio, o ne GDI, ir kiekvienas unikalus kodinis taškas pakelia vieną įvykį su šrifto pasiūlymu

Negalvokite tikrinti glifų aptikėjo su emoji

Akivaizdus testinis atvejis yra besišypsantis veidas, ir jis įtikins jus, kad aptikėjas sugedęs. Įprasti astralinių sričių emoji kodiniai taškai išsiskleidžia per privataus naudojimo sintezės kelią, juos tiesiogiai atvaizduojant į glifo indeksą, todėl jie niekada nepasiekia bendrosios dengimo šakos. Aptikėjas elgiasi teisingai, o testas matuoja neteisingą kelią

Vietoj to naudokite nepriskirtą kodinį tašką. U+0378 yra visam laikui nepaskirtas Unicode, todėl joks šriftas teisėtai negali jo atvaizduoti, ir jis pratina tiksliai tą šaką, kurią norite patikrinti. Ta skirtis tarp „savybė sugedusi“ ir „testas parinko įvečią, apėjantį savybę“ kainuoja tikras valandas, o nepriskirti kodiniai taškai yra pigiausias būdas to išvengti

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
  // Pakyla kartą per unikalų kodinį tašką, o ne kartą per pasirodymą
  FFindings.Add(Format('U+%.4X missing in %s (script %d), try: %s',
    [Info.CodePoint, String(Info.FontName), Ord(Info.Script),
     String(Info.SuggestedFonts)]));
end;

// Sujungimas į generuojančią užduotį
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
    // Sustabdykite užduotį, vietoj išsiuntimo puslapio su dėžėmis
    raise Exception.Create(Audit.Findings.Text);
finally
  Pdf.Free;
end;

Atsargos grandinės yra pagal rašto sistemą, o ne pagal šriftą

Priežastis, kodėl atsarga apibrėžiama pagal rašto sistemą, o ne pagal šaltinio šriftą, yra ta, kad dengimo spragos kaupiasi pagal rašto sistemą. Lotyniškam teksto šriftui trūksta Devanagari, thai, Han ir emoji — visko iš karto, o atitikmuo kiekvienam yra kitoks šriftas. Vienos grandinės deklaravimas pagal rašto sistemą todėl aprašo tikrą diegimą: vienas lotynų šriftas pagrindiniam tekstui, vienas CJK šriftas, vienas emoji šriftas, vienas viską gaunantis

Šriftų atsargos pagal rašto sistemą diagrama, priskirianti hfsCJK, hfsArabic, hfsEmoji ir hfsOther sistemas surūšiuotoms pakaitinių šriftų grandinėms HotPDF
Kiekviena rašto sistema gauna savą surūšiuotą grandinę, todėl lotynų pagrindinis šriftas, kuriam trūksta Han, arabų arba emoji, nukrenta į jį dengiantį šriftą
// THPDFFontScript covers hfsCommon, hfsLatin, hfsGreek, hfsCyrillic,
// hfsHebrew, hfsArabic, hfsIndic, hfsSoutheastAsian, hfsCJK, hfsKana,
// hfsHangul, hfsEmoji and 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']);

Atsarga ir aptikimas yra papildantys, o ne alternatyvūs. Grandinės tvarko dengimą, kurio tikėtės; aptikėjas praneša dengimą, kurio netikėtės — sistemoje, apdorojančioje bet kokius klientų duomenis, tai įdomioji pusė. Atkreipkite dėmesį, kad šrifto pakeitimas keičia metrikas, todėl pastraipa, nukritusi į atsargą, gali persimainyti; jeigu išdėstymas svarbus, verta pasidomėti pakeisto šrifto uždarymo ir poaibių elgsena šrifto poaibio uždarymo straipsnyje, o rašto sistemos, kurioms reikia perkėlimo ar sujungimo, tvarko formavimo (shaping) pakopa, aprašyta sudėtingų rašto sistemų teksto formavime

Kaip atnaujinti elgseną, nerizikuojant esamu keliu

Tas pats leidimas pridėjo senojo kern lentelės atsargą porų tarpetams, ir būdas, kuriuo ji buvo apibrėžta, yra modelis, vertas nukopijuoti. Vietoje naujo sprendimų taško pridėjimo prie porinio tarpančio (kerning) logikos, atsarga kabo ant ankstyvo išėjimo šakos, kuri jau egzistavo šriftams be GPOS lentelės. Modernus šriftas su GPOS niekada jos nepasiekia, todėl jo elgsena nepakitusi dėl paties sandėlio, o ne dėl testavimo. Keliai, neregistruojantys Unicode šrifto, duoda du nulinius tarpetus, todėl irgi nepakitę

Tai bendroji mažos rizikos atnaujinimo forma subrendusioje atvaizdavimo bibliotekoje: suraskite šaką, kuri dabar negamina nieko, ir padėkite naują elgseną ten. Tai paverčia „tikime, kad tai nesugrąžino nieko“ į „tai negalėjo sugrąžinti nieko“, kas yra gerokai geresnis dalykas, sakomas apie teksto variklį, per kurį eina svetimų žmonių sąskaitos

Padarykite tai užtvara, o ne žurnalu

Dengimo radiniai naudingi tik tada, jei dėl jų kažkas nepavyksta. Dokumentus generuojančioje tarnyboje vaisingas išdėstymas — laikyti sekimą įjungtą naktiniame regresijos darbe prieš tikrų klientų vardų, adresų ir produktų aprašymų korpusą, ir sustabdyti darbą dėl bet kurio radinio. Kadangi įvykis pakyla kartą per unikalų kodinį tašką, o ne kartą per pasirodymą, išvestis lieka pakankamai maža skaityti net tada, kai dingusi visa rašto sistema

Gamyboje tas pats apdorotojas geriau naudotinas kaip telemetrija: užrašykite kodinį tašką ir šriftą, toliau aptarnaukite dokumentą ir leiskite suvestinei pasakyti, kurią rašto sistemą kitą pridėti prie diegimo šriftų rinkinio. Įterptų ir pakeistų šriftų atvaizdavimo elgsena plačiau dengiama įterptų šriftų glifų atvaizdavime, o visas savybių sąrašas, įskaitant TrackUnresolvedGlyphs, dokumentuotas HotPDF Delphi PDF component produkto puslapyje