Tehnički članak

Varijabilni fontovi u PDF-u: statičko instanciranje u Delphiju

PDF nema koncept varijabilnog fonta. Font ugrađen u PDF fajl je fiksan skup kontura sa fiksnim metrikama, pa varijabilni font mora biti sveden na jednu statičku instancu pre nego što uđe u dokument. HotPDF obavlja to instanciranje interno: pregledate ose varijabilnog fonta, birate koordinate poput debljine 620 ili širine 87.5, a biblioteka ugrađuje te vrednosti u kompletan, samostalan program fonta koji svaki usaglašen PDF čitač može da renderuje

Razlog zašto je ovo bitno je praktičan, ne teorijski. Livnice tipova sve više isporučuju jedan varijabilni fajl umesto tuceta statičkih debljina, a dizajnerski timovi biraju vrednosti koje nijedna imenovana instanca ne nudi. Bez instanciranja, generator izveštaja ili vraća se na podrazumevanu instancu, čime se odbacuje dizajnerska odluka, ili ugrađuje ceo varijabilni font i nada se da će pregledač poštovati koordinate osa o kojima nema pojma, što nijedan čitač nije obavezan da radi

Šta instanciranje zapravo mora da ponovo izgradi?

OpenType varijabilni font čuva jednu podrazumevanu konturu po glifu plus skup delta vrednosti indeksiranih po poziciji u dizajn prostoru. Primena koordinate ose nije stvar upisivanja broja u zaglavlje; to znači prolazak kroz tabelu gvar, interpolaciju delta vrednosti za traženu lokaciju, pomeranje tačaka, a zatim ponovno izračunavanje svega što je izvedeno iz tih tačaka. HotPDF ponovo izgrađuje konture glifova, dugu tabelu loca, kompletne horizontalne i vertikalne metrike, globalni granični okvir fonta i podešavanje kontrolne sume sfnt-a

Jednako je važno šta se uklanja. Statička instanca ne sme da zadrži fvar, avar, gvar, HVAR, VVAR, MVAR, STAT ili cvar, a zastareo DSIG takođe mora da ode, jer potpisani bajtovi više ne postoje. Ostavljanje bilo čega od ovoga iza sebe proizvodi font koji tvrdi da je varijabilan dok nosi konture koje su već pomerene, a čitači koji zaista primenjuju varijacije će ih onda primeniti drugi put

Fantomske tačke i zamka dvostruke primene

Najsuptilnije pravilo u celom procesu tiče se metrika. U gvar, broj tačaka za glif pokriva tačke konture, ili tačke komponente za složeni glif, plus četiri fantomske tačke koje kodiraju levu bočnu ivicu, širinu pomaka i njihove vertikalne ekvivalente. Te fantomske tačke su i same podložne delta vrednostima

Tako kada font ima tabelu gvar, HotPDF izvodi horizontalne i vertikalne metrike iz interpolovanih fantomskih tačaka i ne primenjuje dodatno HVAR ili VVAR. Dodavanje oba je klasična greška: ista varijacija se primeni dvaput, a svaka širina pomaka ispadne malo preširoka, što se ispoljava kao tekst koji postepeno klizi udesno duž poravnatog reda. Samo kada font nema gvar, biblioteka ugrađuje magacin metrikih varijacija direktno u hmtx ili vmtx

Dva dodatna detalja čuvaju poštenost geometrije. Fantomske tačke nikada ne učestvuju u interpolaciji konture, pa se tačke koje nisu izričito navedene za jednostavan glif izvode sa IUP po konturi, uz isključenje fantomskih tačaka. A složeni glifovi dobijaju svoje delta vrednosti primenjene na pomake komponenti koji koriste XY parametre, posle čega se granice deteta rekurzivno ponovo izračunavaju. Ta rekurzija je ograničena po dubini i proveravana na cikluse, jer zlonameran ili tek pokvaren graf komponenti inače može rekurzivno da se poziva bez kraja

Pregledanje dizajn prostora pre izbora

Prvi poziv u svakom toku rada instanciranja je InspectVariableFont, koji prijavljuje ose i imenovane instance koje je livnica definisala. Zapisi osa nose oznaku od četiri bajta, minimalnu, podrazumevanu i maksimalnu vrednost, zastavice i ID imena; imenovane instance nose ID imena podfamilije, zastavice, opcioni ID PostScript imena i po jednu koordinatu za svaku osu:

var
  Pdf: THotPDF;
  Axes: THPDFVariableFontAxisArray;
  Instances: THPDFVariableFontNamedInstanceArray;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.InspectVariableFont('C:\Fonts\Inter.ttf', Axes, Instances) then
    begin
      for I := 0 to High(Axes) do
        Writeln(Format('%s  min=%.1f default=%.1f max=%.1f',
          [string(Axes[I].Tag), Axes[I].MinimumValue,
           Axes[I].DefaultValue, Axes[I].MaximumValue]));
      Writeln(Format('%d named instance(s) defined', [Length(Instances)]));
    end
    else
      Writeln('not a variable font - embed it as an ordinary TrueType face');
  finally
    Pdf.Free;
  end;
end;

Prijavljivanje opsega ose je bitno jer se vrednosti ose ograničavaju na opseg koji font deklariše, ne na opseg koji vaš korisnički interfejs nudi. Klizač koji dozvoljava korisniku da traži debljinu 1000 na fontu čija se osa wght zaustavlja na 900 treba ispraviti u interfejsu, ne tiho na nivou fonta, jer će se inače štampani izlaz razlikovati od pregleda

Biranje koordinata i emitovanje dokumenta

Biranje ose je stanje-zavisno i primenjuje se na fontove registrovane nakon toga. SetVariableFontAxis uzima štampljivu ASCII oznaku od četiri bajta i konačnu vrednost, i odbacuje sve ostalo izuzetkom umesto da to tiho ignoriše. ClearVariableFontAxes resetuje izbor, a GetVariableFontAxisSelections prijavljuje šta je trenutno na čekanju, što vredi beležiti u mašinama za izveštaje gde je nekoliko putanja koda moglo dotaći isti objekat dokumenta. Sama familija se bira po imenu preko SetFont, potpuno kao svako drugo ugrađeno TrueType lice:

begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.BeginDoc;

    Pdf.SetVariableFontAxis('wght', 620);   // poludebeo, ne imenovana instanca
    Pdf.SetVariableFontAxis('wdth', 87.5);  // blago sabijen
    Pdf.CurrentPage.SetFont('Inter', [], 11);
    Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');

    Pdf.ClearVariableFontAxes;              // nazad na podrazumevanu instancu
    Pdf.CurrentPage.SetFont('Inter', [], 10);
    Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Događaj OnVariableFontInstance se okida dok se svaka instanca proizvodi i prijavljuje vrednosti osa koje su korišćene, što je najjeftiniji način da se u dnevniku dokaže šta dati PDF zapravo sadrži. Pošto svaki različit skup koordinata proizvodi drugačiji program fonta, tretirajte izbore osa kao deo ključa keša fonta; mehanika keširanja je opisana u trajnom kešu podskupa fonta

Kako instanciranje utiče na podskup i oblikovanje?

Instanciranje se pokreće pre podskupljanja, i taj redosled je ispravan. Instancirani font je normalno statičko TrueType lice, pa ga obični uzimač podskupa tretira kao svako drugo: izračunava zatvorenje glifova, zadržava glifove koje dokument stvarno koristi i odbacuje ostalo. Interakcija koju treba znati jeste da su dva različita izbora ose iste familije dva različita programa fonta, pa dokument koji meša debljinu 400 i debljinu 620 ugrađuje dva podskupa, ne jedno deljeno lice sa dve instance

Oblikovanje u principu nije pogođeno i vredi ga proveriti u praksi. Funkcije rasporeda žive u GSUB i GPOS, koje instanciranje čuva, pa ligature i stilske alternative i dalje rade kao što je opisano u OpenType GSUB stilskim alternativama. Ono što se menja jeste pozicioniranje: sabijena instanca ima uže pomake od podrazumevane, pa svaki raspored koji je merio tekst pre instanciranja merio je pogrešne širine. Merite sa istim izborom ose sa kojim ćete renderovati, i neslaganje nestaje

Poslednja odbrambena napomena iz implementacije, korisna za svakog ko proširuje ovu putanju. Fontovi bez vertikalnih metrika i dalje evaluiraju dinamičke argumente niza na mestu Delphi poziva, pa se nizovi u vreme parsiranja uvek dodeljuju umesto oslanjanja na proveru HasVerticalMetrics da preseče put pored praznog indeksa. To je vrsta detalja na nivou jezika koja pretvara naizgled zaštićenu granu u kršenje pristupa tačno na fontovima koje niste testirali

Podrška za varijabilne fontove uklapa se u isti pipeline fontova kao ugrađivanje, podskupljanje i zatvorenje glifova, što je detaljnije opisano u zatvorenju podskupa fonta i oblikovanim glifovima. Kompletan skup tipografskih funkcija za Delphi i C++Builder je naveden na stranici HotPDF Delphi PDF komponente