Tehnični članak

Izris nevdelanih pisav PDF s sistemskimi pisavami v Delphiju

Ko PDF ne vdelava pisave, komponenta HotPDF izriše ta tekst z nameščeno pisavo Windows, ki jo izbere HPDFMapBaseFontToSystem: dekodira ime /BaseFont, odstrani stilski del, poskusi več črkovanj, dokler GDI ne potrdi, da je družina nameščena, izmeri manjkajoče širine standardnih 14 iz metriko-združljivih pisav in pretvori eno-bajtne kode v Unicode, preden riše. Vsak od teh korakov obstaja, ker je naivna verzija odpovedala na resničnih datotekah. Izrisovalnik strani RenderLoadedPageToBitmap dobro opravi z vdelanimi programi; to je zgodba o pisavah, ki jih v datoteki sploh ni

Zakaj GDI tiho izriše napačno pisavo za nevdelano pisavo?

GDI nikoli ne poroča o manjkajoči pisavi: podajte CreateFontIndirect ime pisave, ki ga ne pozna, in tiho izbere nadomestek, pogosto drug serif brez krepke teže. Zgodnji izrisovalnik je PDF ime posredoval skoraj dobesedno, zato TimesNewRoman,Bold, TimesNewRomanPS-BoldMT in SegoeUI-Semibold niso ujemale ničesar in izšle v tem, kar je GDI izbral. Imena so lahko še slabša. ISO 32000-1 §7.3.5 dovoli, da ime zapiše kateri koli bajt kot #xx, proizvajalci CJK pa po navadi črkujejo imena pisav kot ubežni UTF-8 ali zapuščinske bajte kodne strani; pred v2.766.69 so sami ubežni zapisi postali ime pisave. HPDFMapBaseFontToSystem zdaj najprej dekodira ubežne zapise, kot znake vrne veljavno zaporedje bajtov UTF-8 in vse ostale visoke bajte bere v sistemski kodni strani

Odrez stila je kraj, kjer hevristika ugrizne. Vejica vedno konča družino (Arial,Bold da Arial), vezaj pa samo, kadar je beseda za njim stil: Bold, Italic, Oblique, Regular, Roman, Medium, Light, Black, Heavy, Semi, Demi, Thin, Extra, Ultra ali Condensed. To pravilo obdrži MS-Mincho cela, medtem ko Calibri-Light spremeni v Calibri. HotPDF nato poskusi črkovanja družine, ki jim sledijo črkovanja družine brez pripone PSMT, MT ali PS. Od v2.768.18 ta črkovanja pokrivajo vsako izbiro presledkov na mestih, kjer se lahko začne beseda: pred veliko začetnico, ki sledi majhni črki (MyriadPro postane Myriad Pro), pri zadnji veliki začetnici teka, ki mu sledi majhna črka (UIGothic), in za vodilnim MS (MSPGothic), od povsem razmaknjene oblike do imena, kakršno je zapisano; čez štiri taka mesta sta poskušani samo povsem razmaknjeno in zlepljeno ime. Presledkov ni mogoče uporabiti slepo, ker Windows nekatera beseda drži skupaj: SimSun je nameščen točno pod tem črkovanjem, MicrosoftYaHei, MicrosoftJhengHei in MSPGothic pa pripadajo Microsoft YaHei, Microsoft JhengHei in MS PGothic. Pred v2.768.18 je maper postavil presledek pred vsako notranjo veliko začetnico, zato je iskal MicrosoftYaHei kot Microsoft Ya Hei in ga nikoli ne našel. Kandidat šteje kot nameščen, kadar CreateFontIndirect, sleden s GetTextFace, vrne zahtevano ime, ali, od v2.768.18, kadar ga tabela name izbrane pisave našteje kot družino, polno ali tipografsko ime družine v katerem koli jeziku; odgovor se predpomni na ime, zato dokumenti s številnimi ne-nameščenimi pisavami ne preizprašujejo Windows za vsakim imenom na vsaki strani več

Cevovod HotPDF, ki izriše nevdelane pisave PDF s sistemskimi pisavami v Delphiju: HPDFMapBaseFontToSystem dekodira ubežne bajte #xx v imenu /BaseFont, odstrani pripone stilov Bold, Italic in Light, medtem ko obdrži MS-Mincho cela, zgradi kandidatska črkovanja, kot sta Myriad Pro in Microsoft YaHei, in sprejme enega samo, kadar GetTextFace ali tabela imen pisave potrdita nameščeno ime
GDI nikoli ne poroča o manjkajoči pisavi, tiho jo nadomesti — preslikava poskusi svoje kandidate po vrsti in zaupa samo imenu, ki ga vrne GDI ali našteje tabela imen izbrane pisave

Ker je preslikovalna funkcija javna v enoti HPDFRenderFontMetrics, lahko predpoletno poročilo pokaže, s katero nameščeno družino se bo izrisala vsaka ne-vdelana pisava, z uporabo naštevanja pisav, ki ga THotPDF že izpostavlja za naložene dokumente:

uses
  HPDFDoc, HPDFRenderFontMetrics;

procedure ListSystemFontMappings(Pdf: THotPDF; Log: TStrings);
var
  Page, I: Integer;
  Info: THPDFLoadedFontInfo;
begin
  for Page := 0 to Pdf.LoadedPageCount - 1 do
    for I := 0 to Pdf.GetLoadedFontCount(Page) - 1 do
      if Pdf.GetLoadedFontInfo(Page, I, Info) and not Info.IsEmbedded then
        Log.Add(Format('page %d  /%s  %s -> %s',
          [Page + 1, string(Info.ResourceName), string(Info.FontName),
           HPDFMapBaseFontToSystem(Info.FontName)]));
end;

Kako HotPDF izmeri standardne pisave 14, ki nimajo /Widths?

HotPDF izmeri manjkajoče zasuke na nameščeni pisavi z enakimi metriko, ker ISO 32000-1 §9.6.2.2 dovoli standardnim 14 pisavam izpustiti /Widths, knjižnica pa nima priloženih tabel AFM. Arial nosi metriko Helvetica, Times New Roman nosi Times in Courier New nosi Courier, zato HPDFMeasureBaseFontWidths ustvari ujemajočo pisavo pri lfHeight = -1000 in pokliče GetCharWidth32W; pri tej višini je rezultat že v enotah 1/1000 em, ki jih uporabljajo širine PDF. Izrisovalnik najprej pretvori vsako kodo v Unicode skozi /Encoding, /BaseEncoding in /Differences, s privzetkom StandardEncoding. Izvoz SVG in izvlečenje teksta sta naletela še na eno past: standardna pisava Type 1 brez vsakršnega /Encoding je izdelala dekodirnik brez podatkov o kodiranju, izvoz SVG je nikoli ni registriral in vsaka izmerjena širina je ostala neuporabljena. Podati nakazani StandardEncoding je to popravilo, pod pogojem, da je označena kot vnaprej določeno kodiranje; poslana po poti imena CMap pa dekodira vsako kodo kot 0 in vsaka širina ji sledi

Krepko, ležeče in napaka za ena v zastavicah opisovalnika pisave

Vnos /Flags opisovalnika pisave šteje svoje bite od 1, ne od 0, zato je ForceBold bit 19 ($40000), Italic pa bit 7 ($40), po ISO 32000-1 tabeli 123. Stara koda je testirala $20000, kar je bit 18, SmallCap. Napaka je preživela od v2.345.0 do v2.766.53, ker je /FontDescriptor skoraj vedno posredna referenca, graditelj pisav pa je bral samo neposredne objekte, zato se celotna veja zastavic ni nikoli zagnala, ista slepota pa je spregledala /Widths 12 0 R in razpostavila tekst pri rezervnem zasuku 500 enot. Ko je v2.766.53 začel razreševati posredne reference skozi izrisovalnik, je bilo treba bit popraviti v isti spremembi, sicer bi vsaka pisava malih velikih črk nenadoma izšla krepko:

const
  // ISO 32000-1 tabela 123 šteje bitne položaje od 1
  FD_ITALIC     = $00040;  // bit 7
  FD_SMALLCAP   = $20000;  // bit 18, ni teža
  FD_FORCEBOLD  = $40000;  // bit 19

procedure ApplyDescriptorFlags(Flags: Integer; var LF: TLogFont);
begin
  if (Flags and FD_FORCEBOLD) <> 0 then
    LF.lfWeight := FW_BOLD;
  if (Flags and FD_ITALIC) <> 0 then
    LF.lfItalic := 1;
end;
Bitno številčenje vnosa /Flags opisovalnika pisave PDF: ISO 32000-1 tabela 123 šteje od bita 1, tako da je Italic $40 pri bitu 7, SmallCap $20000 pri bitu 18 in ForceBold $40000 pri bitu 19, preizkus opisovalnika pri HotPDF s $20000 pa je ciljal SmallCap in ostal neškodljiv samo, dokler posredne reference /FontDescriptor niso bile nikoli razrešene
Veja zastavic je bila mrtva koda celih štirideset verzij, ker je bil opisovalnik posreden — ko so se reference razrešile, je bit z napako za ena postal viden tekst malih velikih črk

Zakaj pisave CJK s CMap UCS2 dobijo napačne širine?

Tekst CJK z vnaprej določenim CMap UCS2 izriše prave glife, a napačno razmaknjenost, kadar izrisovalnik ravna s kodo kot s CID, ker je /W indeksiran po CID, ne po znakovni kodi. Pri STSong-Light in UniGB-UCS2-H koda slučajno enaka vrednosti Unicode, zato GDI izriše prave znake in hrošč se skrije v zasukih: male črke prispejo kot kode 97 in več, padejo izven vnosa /W, kot je [1 95 500], in vse dobijo privzeto širino /DW 1000. Od v2.766.56 izrisovalnik HotPDF bere kode skozi območja kodnega prostora CMap (ISO 32000-1 §9.7.6.2) in jih preslika v CID, preden išče širine. Uporabljajo se samo vgrajene tabele UCS2 in UTF16 ter vdelani tokovi CMap; identitetno približanje za nekaj, kot je GBK-EUC-H, bi izgledalo le kot podprto, medtem ko daje napačen izhod, zato se izrisovalnik ne igra

Zakaj tekst CJK s CMap UCS2 izriše prave glife pri napačnih zasukih: /W je indeksiran po CID, medtem ko so kode vrednosti Unicode, zato pri STSong-Light in UniGB-UCS2-H male kode 97 in več zgrešijo vnos /W [1 95 500] in vzamejo privzetek /DW, popravljeno v HotPDF s preslikavo kod v CID skozi območja kodnega prostora CMap
Hrošč se skrije, ker je koda tukaj enaka Unicode — glifi izgledajo prav, medtem ko vsak zasuk tiho pade na privzetek, zato izris CJK ocenjujte po razmaknjenosti, ne po oblikah

Zakaj se znaki z diakritikami spremenijo v vprašaje na kitajskem Windows?

Eno-bajtne kode nikoli ne smejo doseči ANSI (»A«) funkcij GDI, ker GetGlyphOutlineA in GetGlyphIndicesA interpretirata bajte v sistemski kodni strani, TextOutA pa uporablja izbrani nabor znakov pisave. Na kitajskem sistemu je Arial bajt $A9 (znak avtorskih pravic v Windows-1252) postal vodilni bajt GBK in se izrisal kot »?«, past, v katero je naravnost stopila pot ne-navedenih orisov, dodana v v2.766.83. v2.767.3 vpraša uresničeno pisavo po njenem naboru znakov s GetTextCharset, to pretvori v kodno stran skozi TranslateCharsetInfo, požene bajt skozi MultiByteToWideChar in pokliče funkcije W; simbolne pisave namesto tega uporabljajo U+F000 plus kodo. Kodiranja, ki se ne strinjajo z Windows-1252 — /Differences, StandardEncoding, MacRomanEncoding — se preslikajo v Unicode, preden jih vidi katera koli sistemska pisava

Kje so meje risanja s sistemskimi pisavami?

Izris s sistemskimi pisavami je približek, komponenta HotPDF pa je iskrena glede tega, kjer se ustavi. Pred v2.768.18 je preizkus namestitve primerjal samo ime, ki ga vrne GetTextFace, na lokaliziranem Windows pa ta funkcija poroča ime družine v sistemskem jeziku, zato je bil Microsoft YaHei na kitajskem Windows ali Yu Mincho na japonskem Windows ocenjen kot manjkajoč in izrisan z nadomestkom GDI; od v2.768.18 se pisava, ki pride nazaj pod drugim imenom, išče tudi v tabeli name pisave, take pisave pa se najdejo. Metrična združljivost je zagotovljena samo za družine Helvetica, Times in Courier; Symbol se preslika v Symbol in ZapfDingbats v Wingdings, kar je zasilna rešitev, ne ujemanje. Predpoletni pregled zgoraj vidi tudi samo pisave v slovarju /Resources vsake strani, ne tistih, navedenih iz znotraj form XObjects. Ko kode še vedno ni mogoče izrisati, to poroča sledenje nerazrešenih glif ob času risanja, kar je boljši signal kot gledanje sličic z očmi

Trajen popravek sedi na strani avtorstva. HotPDF sam zapisuje z FontEmbedding privzeto nastavljenim na True, nadomešča vdelano Arial, tudi ko koda pokliče SetFont s Helvetica, vdelani tekst pa gre skozi izrisovalnik glif vdelanih pisav, namesto skozi katero koli ugibanje zgoraj. Poceni varovalka za dohajajoče datoteke je opozoriti pred izrisom, kadar preslikana družina ni na seznamu zasonskih pisav:

// VCL: Screen.Fonts našteva nameščena imena družin (enota Forms)
function MissingSystemFamily(const BaseFont: AnsiString): Boolean;
begin
  Result := Screen.Fonts.IndexOf(HPDFMapBaseFontToSystem(BaseFont)) < 0;
end;

Za celo komponento, vključno z izrisovanjem strani, izvlečenjem teksta in podnaborjenjem pisav na strani pisanja, glejte stran izdelka HotPDF Delphi PDF component