Articol tehnic

Fonturi variabile în PDF: instanțe statice în Delphi

PDF nu are niciun concept de font variabil. Un font încorporat într-un fișier PDF este un set fix de contururi cu metrici fixe, așa că un font variabil trebuie redus la o instanță statică înainte de a putea intra într-un document. HotPDF realizează această instanțiere intern: inspectezi axele unui font variabil, alegi coordonate precum greutatea 620 sau lățimea 87,5, iar biblioteca fixează acele valori într-un program de font complet, autonom, pe care orice cititor PDF conform îl poate randa

Motivul pentru care contează este practic, nu teoretic. Turnătoriile de tip livrează tot mai des un singur fișier variabil în loc de o duzină de greutăți statice, iar echipele de design aleg valori pe care nicio instanță denumită nu le oferă. Fără instanțiere, un generator de rapoarte fie revine la instanța implicită, ceea ce elimină decizia de design, fie încorporează întregul font variabil și speră că vizualizatorul respectă coordonate de axă pe care nu are cum să le cunoască, ceea ce niciun cititor nu este obligat să facă

Ce trebuie efectiv reconstruit la instanțiere?

Un font variabil OpenType stochează un contur implicit per glif plus un set de delte indexate după poziția în spațiul de design. Aplicarea unei coordonate de axă nu înseamnă scrierea unui număr într-un antet; înseamnă parcurgerea tabelului gvar, interpolarea deltelor pentru locația cerută, deplasarea punctelor și apoi recalcularea a tot ce a derivat din acele puncte. HotPDF reconstruiește contururile glifelor, tabelul lung loca, metricile orizontale și verticale complete, caseta de încadrare globală a fontului și ajustarea sumei de control sfnt

La fel de important este ce se elimină. O instanță statică nu trebuie să păstreze fvar, avar, gvar, HVAR, VVAR, MVAR, STAT sau cvar, iar un DSIG învechit trebuie și el eliminat, deoarece octeții semnați nu mai există. Lăsarea oricăruia dintre ele produce un font care pretinde că este variabil, dar poartă contururi deja deplasate, iar cititorii care chiar aplică variații le vor aplica atunci a doua oară

Punctele fantomă și capcana aplicării duble

Cea mai subtilă regulă din tot procesul privește metricile. În gvar, numărul de puncte pentru un glif acoperă punctele conturului, sau punctele componentelor pentru un glif compus, plus patru puncte fantomă care codifică marginea laterală stângă, lățimea de avans și echivalentele lor verticale. Acele puncte fantomă sunt și ele supuse deltelor

Așadar, când un font are un tabel gvar, HotPDF derivă metricile orizontale și verticale din punctele fantomă interpolate și nu mai aplică suplimentar HVAR sau VVAR. Adăugarea ambelor este eroarea clasică: aceeași variație se aplică de două ori, iar fiecare lățime de avans iese ușor prea mare, ceea ce se manifestă ca text care derivă treptat spre dreapta pe un rând justificat. Doar când un font nu are gvar, biblioteca fixează stocarea de variație a metricilor direct în hmtx sau vmtx

Alte două detalii păstrează geometria corectă. Punctele fantomă nu participă niciodată la interpolarea conturului, așa că punctele neenumerate explicit pentru un glif simplu sunt deduse cu IUP per contur, cu punctele fantomă excluse. Iar glifele compuse primesc deltele lor aplicate la offseturile componentelor care folosesc parametri XY, după care limitele copil sunt recalculate recursiv. Acea recursivitate este limitată în adâncime și verificată pentru cicluri, deoarece un graf de componente ostil sau pur și simplu defect ar putea altfel recursiona la nesfârșit

Inspectarea spațiului de design înainte de a alege

Primul apel din orice flux de instanțiere este InspectVariableFont, care raportează axele și instanțele denumite pe care le-a definit turnătoria. Înregistrările de axă poartă eticheta de patru octeți, valorile minimă, implicită și maximă, indicatori și un ID de nume; instanțele denumite poartă un ID de nume de subfamilie, indicatori, un ID opțional de nume PostScript și câte o coordonată per axă:

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;

Raportarea intervalului axei contează deoarece valorile axei sunt limitate la intervalul pe care fontul îl declară, nu la intervalul pe care îl oferă interfața ta. Un cursor care lasă un utilizator să ceară greutatea 1000 pe un font a cărui axă wght se oprește la 900 ar trebui corectat în interfață, nu tăcut la nivelul fontului, altfel rezultatul tipărit va contrazice previzualizarea

Selectarea coordonatelor și emiterea documentului

Selecția axei are stare și se aplică fonturilor înregistrate ulterior. SetVariableFontAxis primește o etichetă ASCII imprimabilă de patru octeți și o valoare finită, respingând orice altceva cu o excepție în loc să o ignore tăcut. ClearVariableFontAxes resetează selecția, iar GetVariableFontAxisSelections raportează ce este momentan în așteptare, ceea ce merită logat în motoare de rapoarte unde mai multe căi de cod pot fi atins același obiect document. Familia în sine este selectată după nume prin SetFont, exact ca orice altă față TrueType încorporată:

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

    Pdf.SetVariableFontAxis('wght', 620);   // semibold, nu o instanță denumită
    Pdf.SetVariableFontAxis('wdth', 87.5);  // ușor condensat
    Pdf.CurrentPage.SetFont('Inter', [], 11);
    Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');

    Pdf.ClearVariableFontAxes;              // înapoi la instanța implicită
    Pdf.CurrentPage.SetFont('Inter', [], 10);
    Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');

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

Evenimentul OnVariableFontInstance se declanșează pe măsură ce fiecare instanță este produsă și raportează valorile de axă folosite, cea mai ieftină metodă de a dovedi într-un jurnal ce conține de fapt un anumit PDF. Deoarece fiecare set distinct de coordonate produce un program de font distinct, tratează selecțiile de axă ca parte din cheia ta de cache de fonturi; mecanica de cache este descrisă în cache-ul persistent de subseturi de fonturi

Cum interacționează instanțierea cu subsetarea și formarea?

Instanțierea rulează înainte de subsetare, iar acea ordine este cea corectă. Fontul instanțiat este o față TrueType statică normală, așa că subsetatorul obișnuit îl tratează ca pe oricare altul: calculează închiderea de glife, păstrează glifele pe care documentul le folosește efectiv și elimină restul. Interacțiunea de care trebuie să fii conștient este că două selecții de axă diferite ale aceleiași familii sunt două programe de font diferite, așa că un document care amestecă greutatea 400 și greutatea 620 încorporează două subseturi, nu o singură față comună cu două instanțe

Formarea nu este afectată în principiu și merită verificată în practică. Funcțiile de layout trăiesc în GSUB și GPOS, pe care instanțierea le păstrează, așa că ligaturile și alternativele stilistice continuă să funcționeze după cum este descris în alternativele stilistice OpenType GSUB. Ce se schimbă este poziționarea: o instanță condensată are avansuri mai înguste decât cea implicită, așa că orice layout care a măsurat textul înainte de instanțiere a măsurat lățimile greșite. Măsoară cu aceeași selecție de axă cu care vei randa, iar discrepanța dispare

O ultimă notă defensivă din implementare, utilă pentru oricine extinde această cale. Fonturile fără metrici verticale tot evaluează argumentele de array dinamic la punctul de apel Delphi, așa că array-urile de la momentul analizei sunt întotdeauna alocate, în loc să se bazeze pe o verificare HasVerticalMetrics pentru a sări peste un index gol. Este genul de detaliu la nivel de limbaj care transformă o ramură aparent protejată într-o încălcare de acces exact pe fonturile cu care nu ai testat

Suportul pentru fonturi variabile se integrează în același pipeline de fonturi cu încorporarea, subsetarea și închiderea de glife, descris mai în detaliu în închiderea subsetului de fonturi și glifele formate. Setul complet de funcții de tipografie pentru Delphi și C++Builder este listat pe pagina componentei HotPDF PDF pentru Delphi