Teknisk artikel

Variabla typsnitt i PDF: statisk instansiering i Delphi

PDF har inget begrepp för ett variabelt typsnitt. Ett typsnitt inbäddat i en PDF-fil är en fast uppsättning konturer med fasta mått, så ett variabelt typsnitt måste reduceras till en statisk instans innan det kan läggas in i ett dokument. HotPDF utför den instansieringen internt: du inspekterar axlarna hos ett variabelt typsnitt, väljer koordinater som exempelvis vikt 620 eller bredd 87,5, och biblioteket bakar in dessa värden i ett komplett, självständigt typsnittsprogram som varje konform PDF-läsare kan rendera

Anledningen till att detta spelar roll är praktisk snarare än teoretisk. Typsnittsgjuterier levererar allt oftare en enda variabel fil istället för ett dussin statiska vikter, och designteam väljer värden som ingen namngiven instans erbjuder. Utan instansiering faller en rapportgenerator antingen tillbaka på standardinstansen, vilket kastar bort designbeslutet, eller bäddar in hela det variabla typsnittet och hoppas att visaren respekterar axelkoordinater den inte har någon möjlighet att känna till, vilket ingen läsare är skyldig att göra

Vad måste instansieringen egentligen bygga om?

Ett OpenType-variabeltypsnitt lagrar en standardkontur per glyf plus en uppsättning deltan indexerade efter position i designrymden. Att applicera en axelkoordinat är inte en fråga om att skriva ett tal i ett huvud; det innebär att gå igenom gvar-tabellen, interpolera deltan för den begärda positionen, flytta punkter och sedan räkna om allt som härletts från dessa punkter. HotPDF bygger om glyfkonturerna, den långa loca-tabellen, de fullständiga horisontella och vertikala måtten, typsnittets globala begränsningsruta och sfnt-kontrollsummejusteringen

Lika viktigt är vad som tas bort. En statisk instans får inte behålla fvar, avar, gvar, HVAR, VVAR, MVAR, STAT eller cvar, och en föråldrad DSIG måste också bort, eftersom de signerade byten inte längre existerar. Att lämna kvar någon av dessa ger ett typsnitt som utger sig för att vara variabelt medan det bär konturer som redan har flyttats, och läsare som faktiskt applicerar variationer kommer då att applicera dem en andra gång

Fantompunkter och fällan med dubbel applicering

Den mest subtila regeln i hela processen gäller mått. I gvar täcker punkträkningen för en glyf konturpunkterna, eller komponentpunkterna för en sammansatt glyf, plus fyra fantompunkter som kodar vänster sidoavstånd, framstegsbredden och deras vertikala motsvarigheter. Dessa fantompunkter är själva föremål för deltan

Så när ett typsnitt har en gvar-tabell härleder HotPDF horisontella och vertikala mått från de interpolerade fantompunkterna och applicerar inte dessutom HVAR eller VVAR. Att lägga till båda är det klassiska felet: samma variation appliceras två gånger och varje framstegsbredd blir något för bred, vilket visar sig som text som gradvis driver åt höger genom en marginaljusterad rad. Bara när ett typsnitt saknar gvar bakar biblioteket in måttvariationslagret direkt i hmtx eller vmtx

Två ytterligare detaljer håller geometrin ärlig. Fantompunkter deltar aldrig i konturinterpolation, så punkter som inte uttryckligen listas för en enkel glyf härleds med IUP per kontur, med fantompunkterna exkluderade. Och sammansatta glyfer får sina deltan applicerade på komponentförskjutningar som använder XY-parametrar, varefter underordnade gränser räknas om rekursivt. Den rekursionen är djupbegränsad och cykelkontrollerad, eftersom en fientlig eller helt enkelt trasig komponentgraf annars kan rekursera utan slut

Inspektera designrymden innan du väljer

Det första anropet i varje instansieringsflöde är InspectVariableFont, som rapporterar axlarna och de namngivna instanser som gjuteriet definierade. Axelposter bär fyrabytestaggen, minimi-, standard- och maxvärden, flaggor och ett namn-ID; namngivna instanser bär ett underfamiljnamn-ID, flaggor, ett valfritt PostScript-namn-ID och en koordinat per axel:

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;

Att rapportera axelintervallet spelar roll eftersom axelvärden klipps till det intervall som typsnittet deklarerar, inte till det intervall ditt gränssnitt erbjuder. Ett skjutreglage som låter en användare begära vikt 1000 på ett typsnitt vars wght-axel slutar vid 900 bör korrigeras i gränssnittet, inte tyst i typsnittslagret, annars kommer det utskrivna resultatet inte att stämma med förhandsgranskningen

Att välja koordinater och skriva ut dokumentet

Axelvalet är tillståndsbaserat och gäller typsnitt som registreras efteråt. SetVariableFontAxis tar en fyrabytes utskrivbar ASCII-tagg och ett ändligt värde, och avvisar allt annat med ett undantag istället för att tyst ignorera det. ClearVariableFontAxes återställer valet, och GetVariableFontAxisSelections rapporterar vad som för närvarande är i kraft, vilket är värt att logga i rapportmotorer där flera kodvägar kan ha rört samma dokumentobjekt. Själva familjen väljs med namn via SetFont, precis som vilket annat inbäddat TrueType-typsnitt som helst:

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

    Pdf.SetVariableFontAxis('wght', 620);   // halvfet, inte en namngiven instans
    Pdf.SetVariableFontAxis('wdth', 87.5);  // lätt smalare
    Pdf.CurrentPage.SetFont('Inter', [], 11);
    Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');

    Pdf.ClearVariableFontAxes;              // tillbaka till standardinstansen
    Pdf.CurrentPage.SetFont('Inter', [], 10);
    Pdf.CurrentPage.TextOut(72, 700, 0, 'Prepared by the finance team');

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

Händelsen OnVariableFontInstance utlöses när varje instans skapas och rapporterar de axelvärden som användes, vilket är det billigaste sättet att i en logg bevisa vad en given PDF faktiskt innehåller. Eftersom varje distinkt koordinatuppsättning ger ett distinkt typsnittsprogram, behandla axelvalen som en del av din typsnittscachenyckel; cachningsmekaniken beskrivs i den beständiga typsnittsundergruppscachen

Hur samspelar instansiering med undergruppsbildning och formgivning?

Instansiering körs före undergruppsbildning, och den ordningen är den rätta. Det instansierade typsnittet är ett vanligt statiskt TrueType-typsnitt, så den vanliga undergruppsbildaren behandlar det som vilket annat: den beräknar glyfstängningen, behåller de glyfer dokumentet faktiskt använder och släpper resten. Det samspel man bör känna till är att två olika axelval av samma familj är två olika typsnittsprogram, så ett dokument som blandar vikt 400 och vikt 620 bäddar in två undergrupper, inte ett delat typsnitt med två instanser

Formgivning påverkas i princip inte, och det är värt att kontrollera i praktiken. Layoutfunktioner ligger i GSUB och GPOS, vilket instansiering bevarar, så ligaturer och stilistiska alternativ fortsätter att fungera enligt beskrivningen i OpenType GSUB-stilalternativ. Det som förändras är positioneringen: en smalare instans har smalare framsteg än standarden, så all layout som mätte text före instansieringen mätte fel bredder. Mät med samma axelval som du kommer att rendera med, så försvinner avvikelsen

En sista defensiv notering från implementationen, användbar för alla som utökar den här vägen. Typsnitt utan vertikala mått utvärderar ändå dynamiska array-argument vid Delphi-anropsstället, så tolkningstidens arrayer allokeras alltid istället för att förlita sig på en HasVerticalMetrics-kontroll för att kortsluta förbi ett tomt index. Det är den typ av språknivådetalj som förvandlar en till synes skyddad gren till en åtkomstöverträdelse på just de typsnitt du inte testade med

Stödet för variabla typsnitt passar in i samma typsnittspipeline som inbäddning, undergruppsbildning och glyfstängning, vilket beskrivs mer utförligt i typsnittsundergruppsstängning och formgivna glyfer. Den fullständiga typografifunktionsuppsättningen för Delphi och C++Builder listas på sidan för HotPDF Delphi PDF-komponent