Техническа статия

Вариативни шрифтове в 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, което докладва осите и именуваните инстанции, дефинирани от ателието. Записите за оси носят четирибайтовия таг, минималната, стойността по подразбиране и максималната стойност, флагове и ID на име; именуваните инстанции носят ID на име на подсемейство, флагове, незадължителен ID на 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 Delphi PDF компонента