Technický článek

Variabilní fonty v PDF: statické instance v Delphi

PDF nezná pojem variabilní font. Font vložený do souboru PDF je pevná sada obrysů s pevnými metrikami, takže variabilní font musí být před vložením do dokumentu zredukován na jednu statickou instanci. HotPDF toto vytváření instancí provádí interně: prozkoumáte osy variabilního fontu, zvolíte souřadnice, například řez 620 nebo šířku 87,5, a knihovna tyto hodnoty zapeče do kompletního, samostatného programu fontu, který dokáže vykreslit každá konformní čtečka PDF

Důvod, proč na tom záleží, je praktický, nikoli teoretický. Písmolijny stále častěji dodávají jeden variabilní soubor místo tuctu statických řezů a návrhářské týmy volí hodnoty, které neposkytuje žádná pojmenovaná instance. Bez vytváření instancí se generátor sestav buď vrátí k výchozí instanci, čímž zahodí návrhové rozhodnutí, nebo vloží celý variabilní font a doufá, že prohlížeč dodrží souřadnice os, o kterých nemá jak vědět, což žádná čtečka není povinna dělat

Co musí vytváření instance ve skutečnosti přepočítat?

Variabilní font OpenType ukládá jeden výchozí obrys na glyf plus sadu delt indexovaných podle pozice v návrhovém prostoru. Použití souřadnice osy není otázkou zapsání čísla do hlavičky; znamená to projít tabulku gvar, interpolovat delty pro požadovanou polohu, posunout body a poté přepočítat vše, co bylo z těchto bodů odvozeno. HotPDF přepočítává obrysy glyfů, dlouhou tabulku loca, kompletní vodorovné a svislé metriky, globální ohraničující rámeček fontu a úpravu kontrolního součtu sfnt

Stejně důležité je to, co se odstraňuje. Statická instance nesmí zachovat fvar, avar, gvar, HVAR, VVAR, MVAR, STAT ani cvar, a musí zmizet i zastaralý DSIG, protože podepsané bajty už neexistují. Ponechání kterékoli z těchto tabulek vytvoří font, který se tváří jako variabilní, ale nese obrysy, které už byly posunuty, a čtečky, jež variace skutečně aplikují, je pak aplikují podruhé

Fantomové body a past dvojité aplikace

Nejjemnější pravidlo celého procesu se týká metrik. V tabulce gvar počet bodů glyfu zahrnuje body obrysu, nebo body komponent u složeného glyfu, plus čtyři fantomové body, které kódují levý postranní odstup, šířku posunu a jejich svislé ekvivalenty. I tyto fantomové body podléhají deltám

Když tedy font má tabulku gvar, HotPDF odvozuje vodorovné a svislé metriky z interpolovaných fantomových bodů a navíc neaplikuje HVAR ani VVAR. Přidání obou je klasická chyba: stejná variace se aplikuje dvakrát a každá šířka posunu vyjde mírně širší, což se projeví jako text, který se v zarovnaném řádku postupně posouvá doprava. Pouze pokud font tabulku gvar nemá, knihovna zapeče úložiště metrických variací přímo do hmtx nebo vmtx

Dva další detaily udržují geometrii přesnou. Fantomové body se nikdy nepodílejí na interpolaci obrysu, takže body, které nejsou u jednoduchého glyfu výslovně uvedeny, se odvozují pomocí IUP pro každý obrys zvlášť, přičemž fantomové body jsou vyloučeny. A u složených glyfů se delty aplikují na posuny komponent, které používají parametry XY, po čemž se rekurzivně přepočítají ohraničující rámečky potomků. Tato rekurze má omezenou hloubku a kontrolu cyklů, protože jinak by se nepřátelský nebo prostě porušený graf komponent mohl rekurzivně opakovat bez konce

Prozkoumání návrhového prostoru před volbou

Prvním voláním v jakémkoli pracovním postupu vytváření instancí je InspectVariableFont, které vypíše osy a pojmenované instance definované písmolijnou. Záznamy os nesou čtyřbajtový tag, minimální, výchozí a maximální hodnotu, příznaky a ID názvu; pojmenované instance nesou ID názvu podrodiny, příznaky, volitelné ID názvu PostScript a jednu souřadnici na 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;

Vypsání rozsahu os má význam, protože hodnoty os se omezují na rozsah deklarovaný fontem, ne na rozsah, který nabízí vaše uživatelské rozhraní. Posuvník, který uživateli umožní požadovat řez 1000 u fontu, jehož osa wght končí na 900, by měl být opraven v rozhraní, ne tiše na úrovni fontu, jinak se tištěný výstup bude lišit od náhledu

Volba souřadnic a vytvoření dokumentu

Volba os je stavová a platí pro fonty registrované až poté. SetVariableFontAxis přijímá čtyřbajtový tisknutelný tag ASCII a konečnou hodnotu a cokoli jiného odmítne výjimkou, místo aby to tiše ignoroval. ClearVariableFontAxes resetuje volbu a GetVariableFontAxisSelections vypisuje, co je momentálně nastaveno, což se vyplatí logovat v generátorech sestav, kde se stejného objektu dokumentu mohlo dotknout několik cest kódu. Samotná rodina se vybírá podle názvu přes SetFont, přesně jako u kteréhokoli jiného vloženého fontu TrueType:

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

    Pdf.SetVariableFontAxis('wght', 620);   // polotučný řez, není pojmenovaná instance
    Pdf.SetVariableFontAxis('wdth', 87.5);  // mírně zúžené
    Pdf.CurrentPage.SetFont('Inter', [], 11);
    Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');

    Pdf.ClearVariableFontAxes;              // zpět na výchozí instanci
    Pdf.CurrentPage.SetFont('Inter', [], 10);
    Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');

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

Událost OnVariableFontInstance se spouští při vytvoření každé instance a hlásí použité hodnoty os, což je nejlevnější způsob, jak v logu doložit, co daný PDF ve skutečnosti obsahuje. Protože každá odlišná sada souřadnic vytváří odlišný program fontu, považujte volbu os za součást klíče vyrovnávací paměti fontů; mechanika ukládání do vyrovnávací paměti je popsána v článku trvalá vyrovnávací paměť podmnožin fontů

Jak vytváření instancí souvisí s tvorbou podmnožin a tvarováním

Vytváření instancí probíhá před tvorbou podmnožin, a toto pořadí je správné. Instancovaný font je běžný statický font TrueType, takže s ním obyčejný tvůrce podmnožin zachází jako s kterýmkoli jiným: spočítá uzávěr glyfů, ponechá glyfy, které dokument skutečně používá, a zbytek zahodí. Je třeba mít na paměti, že dvě různé volby os téže rodiny jsou dva různé programy fontu, takže dokument, který kombinuje řez 400 a řez 620, vloží dvě podmnožiny, nikoli jeden sdílený font se dvěma instancemi

Tvarování v principu není ovlivněno, ale v praxi se vyplatí ověřit. Rozvržení uchovávají tabulky GSUB a GPOS, které vytváření instancí zachovává, takže ligatury a stylistické alternativy fungují dál, jak popisuje článek stylistické alternativy OpenType GSUB. Mění se umístění: zúžená instance má užší posuny než výchozí, takže jakékoli rozvržení, které měřilo text před vytvořením instance, změřilo špatné šířky. Měřte se stejnou volbou os, s jakou budete vykreslovat, a nesrovnalost zmizí

Poslední obranná poznámka z implementace, užitečná pro každého, kdo tuto cestu rozšiřuje. Fonty bez svislých metrik přesto na místě volání v Delphi vyhodnocují argumenty dynamického pole, takže pole se v době analýzy vždy alokují, místo aby se spoléhalo na kontrolu HasVerticalMetrics, která by měla obejít prázdný index. Jde o detail na úrovni jazyka, který dokáže zdánlivě ošetřenou větev proměnit v access violation přesně u fontů, se kterými jste netestovali

Podpora variabilních fontů zapadá do stejné pipeline zpracování fontů jako vkládání, tvorba podmnožin a uzávěr glyfů, což podrobněji popisuje článek uzávěr podmnožin fontů a tvarované glyfy. Kompletní sada typografických funkcí pro Delphi a C++Builder je uvedena na stránce komponenty HotPDF PDF pro Delphi