PDF nie zna pojęcia fontu zmiennego. Font osadzony w pliku PDF to stały zestaw konturów o stałych metrykach, więc font zmienny musi zostać zredukowany do jednej statycznej instancji, zanim trafi do dokumentu. HotPDF wykonuje to instancjonowanie wewnętrznie: sprawdzasz osie fontu zmiennego, wybierasz współrzędne, na przykład grubość 620 lub szerokość 87,5, a biblioteka zapisuje te wartości w kompletnym, samodzielnym programie fontu, który wyrenderuje każdy zgodny czytnik PDF
Powód, dla którego to ma znaczenie, jest praktyczny, a nie teoretyczny. Odlewnie czcionek coraz częściej dostarczają jeden plik zmienny zamiast tuzina statycznych grubości, a zespoły projektowe wybierają wartości, których nie zapewnia żadna nazwana instancja. Bez instancjonowania generator raportów albo cofa się do instancji domyślnej, co odrzuca decyzję projektową, albo osadza cały font zmienny i liczy na to, że przeglądarka uwzględni współrzędne osi, o których nie ma skąd wiedzieć — a żaden czytnik nie jest do tego zobowiązany
Co instancjonowanie musi w praktyce przebudować?
Zmienny font OpenType przechowuje jeden domyślny kontur na glif plus zestaw delt indeksowanych pozycją w przestrzeni projektowej. Zastosowanie współrzędnej osi nie polega na wpisaniu liczby do nagłówka; oznacza przejście przez tabelę gvar, interpolację delt dla żądanej lokalizacji, przesunięcie punktów, a następnie ponowne obliczenie wszystkiego, co z tych punktów wynikało. HotPDF przebudowuje kontury glifów, długą tabelę loca, kompletne metryki poziome i pionowe, globalny prostokąt otaczający fontu oraz korektę sumy kontrolnej sfnt
Równie ważne jest to, co zostaje usunięte. Statyczna instancja nie może zachować fvar, avar, gvar, HVAR, VVAR, MVAR, STAT ani cvar, a nieaktualny DSIG również musi zniknąć, ponieważ podpisane bajty już nie istnieją. Pozostawienie którejkolwiek z tych tabel daje font, który twierdzi, że jest zmienny, mimo że niesie kontury już przesunięte, a czytniki rzeczywiście stosujące wariacje zastosują je wtedy po raz drugi
Punkty widmowe i pułapka podwójnego zastosowania
Najsubtelniejsza reguła w całym procesie dotyczy metryk. W gvar liczba punktów glifu obejmuje punkty konturu, a w przypadku glifu złożonego punkty składowych, plus cztery punkty widmowe kodujące lewy odstęp boczny, szerokość przyrostu oraz ich pionowe odpowiedniki. Te punkty widmowe same podlegają deltom
Dlatego gdy font ma tabelę gvar, HotPDF wyprowadza metryki poziome i pionowe z interpolowanych punktów widmowych i nie stosuje dodatkowo HVAR ani VVAR. Zastosowanie obu naraz to klasyczny błąd: ta sama wariacja zostaje zastosowana dwukrotnie i każda szerokość przyrostu wychodzi nieco za duża, co objawia się tekstem stopniowo przesuwającym się w prawo wzdłuż wyjustowanego wiersza. Tylko wtedy, gdy font nie ma gvar, biblioteka zapisuje magazyn wariacji metryk bezpośrednio w hmtx lub vmtx
Dwa dodatkowe szczegóły utrzymują geometrię w porządku. Punkty widmowe nigdy nie biorą udziału w interpolacji konturu, więc punkty niewymienione jawnie dla glifu prostego są wywnioskowane za pomocą IUP dla każdego konturu, z pominięciem punktów widmowych. Glify złożone natomiast otrzymują delty zastosowane do przesunięć składowych korzystających z parametrów XY, po czym granice elementów podrzędnych są ponownie obliczane rekurencyjnie. Ta rekurencja ma ograniczoną głębokość i jest sprawdzana pod kątem cykli, ponieważ wrogi albo po prostu wadliwy graf składowych mógłby inaczej rekurencyjnie wywoływać się bez końca
Sprawdzanie przestrzeni projektowej przed wyborem
Pierwszym wywołaniem w każdym przepływie instancjonowania jest InspectVariableFont, które zwraca osie oraz nazwane instancje zdefiniowane przez odlewnię. Rekordy osi niosą czterobajtowy znacznik, wartości minimalną, domyślną i maksymalną, flagi oraz identyfikator nazwy; nazwane instancje niosą identyfikator nazwy podrodziny, flagi, opcjonalny identyfikator nazwy PostScript oraz jedną współrzędną na oś:
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;
Zgłaszanie zakresu osi ma znaczenie, ponieważ wartości osi są ograniczane do zakresu zadeklarowanego przez font, a nie do zakresu oferowanego przez interfejs użytkownika. Suwak pozwalający użytkownikowi zażądać grubości 1000 dla fontu, którego oś wght kończy się na 900, powinien zostać poprawiony w interfejsie, a nie po cichu na poziomie fontu, w przeciwnym razie wydruk będzie się różnił od podglądu
Wybór współrzędnych i generowanie dokumentu
Wybór osi ma charakter stanowy i dotyczy fontów zarejestrowanych później. SetVariableFontAxis przyjmuje czterobajtowy drukowalny znacznik ASCII oraz wartość skończoną, a wszystko inne odrzuca wyjątkiem zamiast po cichu ignorować. ClearVariableFontAxes resetuje wybór, a GetVariableFontAxisSelections zgłasza, co jest aktualnie ustawione — warto to logować w silnikach raportów, gdzie kilka ścieżek kodu mogło dotknąć tego samego obiektu dokumentu. Sama rodzina jest wybierana po nazwie przez SetFont, dokładnie tak jak każdy inny osadzony krój TrueType:
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginDoc;
Pdf.SetVariableFontAxis('wght', 620); // semibold, nie nazwana instancja
Pdf.SetVariableFontAxis('wdth', 87.5); // lekko zwężony
Pdf.CurrentPage.SetFont('Inter', [], 11);
Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');
Pdf.ClearVariableFontAxes; // powrót do instancji domyślnej
Pdf.CurrentPage.SetFont('Inter', [], 10);
Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Zdarzenie OnVariableFontInstance uruchamia się przy tworzeniu każdej instancji i zgłasza użyte wartości osi, co jest najtańszym sposobem udowodnienia w dzienniku, co dany plik PDF naprawdę zawiera. Ponieważ każdy odrębny zestaw współrzędnych daje odrębny program fontu, traktuj wybory osi jako część klucza pamięci podręcznej fontów; mechanika buforowania jest opisana w trwałej pamięci podręcznej podzbiorów fontów
Jak instancjonowanie współdziała z tworzeniem podzbiorów i kształtowaniem
Instancjonowanie odbywa się przed tworzeniem podzbioru i taka kolejność jest właściwa. Font po instancjonowaniu to zwykły statyczny krój TrueType, więc zwykły mechanizm tworzenia podzbiorów traktuje go tak jak każdy inny: oblicza domknięcie glifów, zachowuje glify faktycznie użyte w dokumencie i odrzuca resztę. Warto pamiętać o jednej interakcji: dwa różne wybory osi tej samej rodziny to dwa różne programy fontu, więc dokument mieszający grubość 400 i grubość 620 osadza dwa podzbiory, a nie jeden wspólny krój z dwiema instancjami
Kształtowanie w zasadzie nie ulega zmianie, choć warto to sprawdzić w praktyce. Funkcje układu mieszkają w GSUB i GPOS, które instancjonowanie zachowuje, więc ligatury i warianty stylistyczne nadal działają tak, jak opisano w wariantach stylistycznych OpenType GSUB. Zmienia się pozycjonowanie: instancja zwężona ma węższe przyrosty niż domyślna, więc każdy układ, który zmierzył tekst przed instancjonowaniem, zmierzył złe szerokości. Mierz przy tym samym wyborze osi, z jakim będziesz renderować, a rozbieżność zniknie
Ostatnia uwaga obronna z samej implementacji, przydatna dla każdego, kto rozszerza tę ścieżkę. Fonty bez metryk pionowych i tak wyliczają argumenty tablic dynamicznych w miejscu wywołania w Delphi, więc tablice na etapie analizy są zawsze alokowane, zamiast polegać na sprawdzeniu HasVerticalMetrics, które miałoby skrócić ścieżkę przy pustym indeksie. To rodzaj szczegółu na poziomie języka, który zmienia pozornie zabezpieczoną gałąź w naruszenie dostępu dokładnie na tych fontach, których nie przetestowano
Obsługa fontów zmiennych wpisuje się w ten sam potok fontów co osadzanie, tworzenie podzbiorów i domknięcie glifów, co opisano dokładniej w domknięciu podzbioru fontu i kształtowanych glifach. Pełny zestaw funkcji typograficznych dla Delphi i C++Builder wymieniono na stronie komponentu PDF HotPDF dla Delphi