Технічна стаття

Варіативні шрифти в PDF: статичне інстансування в Delphi

У PDF немає поняття варіативного шрифту. Шрифт, вбудований у файл PDF, — це фіксований набір контурів із фіксованими метриками, тож варіативний шрифт треба звести до одного статичного екземпляра, перш ніж він потрапить у документ. HotPDF виконує це інстансування внутрішньо: ви оглядаєте осі варіативного шрифту, обираєте координати, наприклад насиченість 620 або ширину 87.5, а бібліотека запікає ці значення в повну, самодостатню шрифтову програму, яку відрендерить будь-який відповідний PDF-переглядач

Причина, чому це важливо, практична, а не теоретична. Шрифтові студії дедалі частіше постачають один варіативний файл замість десятка статичних насиченостей, а дизайнерські команди обирають значення, яких не надає жоден іменований екземпляр. Без інстансування генератор звітів або відкочується до типового екземпляра, що зводить нанівець дизайнерське рішення, або вбудовує весь варіативний шрифт і сподівається, що переглядач врахує координати осей, про які він не має жодного способу дізнатися, а жоден переглядач цього робити не зобов'язаний

Що саме має перебудувати інстансування?

Варіативний шрифт OpenType зберігає один типовий контур на гліф плюс набір дельт, індексованих за позицією в просторі дизайну. Застосування координати осі — це не питання запису числа в заголовок; це означає обхід таблиці gvar, інтерполяцію дельт для запитаної позиції, переміщення точок, а потім перерахунок усього, що було похідним від цих точок. HotPDF перебудовує контури гліфів, довгу таблицю loca, повні горизонтальні та вертикальні метрики, глобальний обмежувальний прямокутник шрифту та коригування контрольної суми sfnt

Не менш важливо те, що видаляється. Статичний екземпляр не повинен зберігати fvar, avar, gvar, HVAR, VVAR, MVAR, STAT чи cvar, а застарілий DSIG також слід прибрати, оскільки підписаних байтів більше не існує. Залишення будь-чого з цього дає шрифт, який стверджує, що він варіативний, хоча несе вже переміщені контури, і переглядачі, які застосовують варіації, застосують їх удруге

Фантомні точки та пастка подвійного застосування

Найтонше правило в усьому процесі стосується метрик. У gvar кількість точок гліфа охоплює точки контуру, або точки компонентів для складеного гліфа, плюс чотири фантомні точки, що кодують ліве бічне зміщення, ширину просування та їхні вертикальні відповідники. Ці фантомні точки самі підлягають дельтам

Тож коли шрифт має таблицю gvar, HotPDF виводить горизонтальні та вертикальні метрики з інтерпольованих фантомних точок і додатково не застосовує HVAR чи VVAR. Застосування обох — класична помилка: та сама варіація застосовується двічі, і кожна ширина просування виходить трохи завеликою, що проявляється як текст, який поступово зсувається вправо вздовж вирівняного рядка. Лише коли шрифт не має gvar, бібліотека запікає сховище варіацій метрик безпосередньо в hmtx або vmtx

Дві додаткові деталі зберігають чесність геометрії. Фантомні точки ніколи не беруть участі в інтерполяції контуру, тож точки, не перелічені явно для простого гліфа, виводяться за допомогою IUP окремо для кожного контуру, з винятком фантомних точок. А складені гліфи отримують свої дельти, застосовані до зміщень компонентів, що використовують параметри XY, після чого межі дочірніх елементів перераховуються рекурсивно. Ця рекурсія обмежена за глибиною й перевіряється на цикли, бо інакше ворожий чи просто зламаний граф компонентів міг би рекурсувати без кінця

Огляд простору дизайну перед вибором

Перший виклик у будь-якому робочому процесі інстансування — це InspectVariableFont, який повідомляє осі та іменовані екземпляри, визначені шрифтовою студією. Записи осей несуть чотирибайтний тег, мінімальне, типове та максимальне значення, прапорці та ідентифікатор імені; іменовані екземпляри несуть ідентифікатор імені підродини, прапорці, необов'язковий ідентифікатор PostScript-імені та одну координату на вісь:

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;

Повідомлення діапазону осі важливе, оскільки значення осі обмежуються діапазоном, який декларує шрифт, а не діапазоном, який пропонує ваш інтерфейс. Повзунок, що дозволяє користувачеві запросити насиченість 1000 у шрифті, чия вісь wght зупиняється на 900, слід виправляти в інтерфейсі, а не мовчки на рівні шрифту, інакше надрукований результат розходитиметься з попереднім переглядом

Вибір координат і формування документа

Вибір осей є станозалежним і застосовується до шрифтів, зареєстрованих після нього. SetVariableFontAxis приймає чотирибайтний друкований ASCII-тег і скінченне значення, а все інше відхиляє винятком, а не тихо ігнорує. ClearVariableFontAxes скидає вибір, а GetVariableFontAxisSelections повідомляє, що зараз очікує на застосування — це варто логувати в рушіях звітів, де кілька шляхів коду можуть торкатися того самого об'єкта документа. Саму гарнітуру обирають за іменем через SetFont, точнісінько як будь-яку іншу вбудовану гарнітуру TrueType:

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

    Pdf.SetVariableFontAxis('wght', 620);   // напівжирний, не іменований екземпляр
    Pdf.SetVariableFontAxis('wdth', 87.5);  // злегка згущений
    Pdf.CurrentPage.SetFont('Inter', [], 11);
    Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');

    Pdf.ClearVariableFontAxes;              // повернення до типового екземпляра
    Pdf.CurrentPage.SetFont('Inter', [], 10);
    Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');

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

Подія OnVariableFontInstance спрацьовує при формуванні кожного екземпляра й повідомляє значення осей, які було використано — це найдешевший спосіб довести в журналі, що саме містить певний PDF. Оскільки кожен окремий набір координат дає окрему шрифтову програму, ставтеся до вибору осей як до частини ключа кешу шрифтів; механіка кешування описана у статті постійний кеш підмножин шрифтів

Як інстансування взаємодіє з підмноженням і формуванням

Інстансування виконується перед підмноженням, і саме такий порядок правильний. Проінстансований шрифт — це звичайна статична гарнітура TrueType, тож звичайний засіб підмноження ставиться до нього як до будь-якого іншого: обчислює замикання гліфів, зберігає гліфи, які документ справді використовує, і відкидає решту. Взаємодія, про яку варто пам'ятати, полягає в тому, що два різні вибори осей однієї гарнітури — це дві різні шрифтові програми, тож документ, що поєднує насиченість 400 і 620, вбудовує дві підмножини, а не одну спільну гарнітуру з двома екземплярами

Формування в принципі не зазнає впливу, але варто перевірити це на практиці. Функції компонування живуть у GSUB і GPOS, які інстансування зберігає, тож лігатури та стилістичні альтернативи й далі працюють, як описано у статті стилістичні альтернативи OpenType GSUB. Змінюється позиціювання: згущений екземпляр має вужчі просування, ніж типовий, тож будь-яке компонування, що вимірювало текст до інстансування, виміряло неправильні ширини. Вимірюйте з тим самим вибором осей, з яким рендеритимете, і розбіжність зникає

Остання захисна примітка з реалізації, корисна для тих, хто розширює цей шлях. Шрифти без вертикальних метрик все одно обчислюють аргументи динамічних масивів у точці виклику Delphi, тож масиви на момент розбору завжди виділяються, а не покладаються на перевірку HasVerticalMetrics, яка мала б скорочено пропустити порожній індекс. Це той тип деталі на рівні мови, яка перетворює начебто захищену гілку на порушення доступу саме на тих шрифтах, з якими ви не тестували

Підтримка варіативних шрифтів вписується в той самий конвеєр обробки шрифтів, що й вбудовування, підмноження та замикання гліфів, докладніше описані у статті замикання підмножин шрифтів і сформовані гліфи. Повний набір типографічних можливостей для Delphi та C++Builder наведено на сторінці компонента HotPDF PDF для Delphi