Teknisk artikkel

Variable fonter i PDF: statisk instansiering i Delphi

PDF har ikke noe begrep om en variabel font. En font innebygd i en PDF-fil er et fast sett med konturer med fast metrikk, så en variabel font må reduseres til én statisk instans før den kan gå inn i et dokument. HotPDF utfører denne instansieringen internt: du inspiserer aksene til en variabel font, velger koordinater som vekt 620 eller bredde 87.5, og biblioteket bygger disse verdiene inn i et komplett, selvstendig fontprogram som enhver PDF-leser som følger standarden, kan gjengi

Grunnen til at dette betyr noe, er praktisk snarere enn teoretisk. Fonthus leverer i økende grad én variabel fil i stedet for et dusin statiske vekter, og designteam velger verdier som ingen navngitt instans tilbyr. Uten instansiering faller en rapportgenerator enten tilbake til standardinstansen, noe som kaster bort designbeslutningen, eller den bygger inn hele den variable fonten og håper visningsklienten respekterer akseverdier den ikke har noen måte å kjenne til, noe ingen leser er pålagt å gjøre

Hva må instansieringen egentlig bygge om?

En OpenType-variabel font lagrer én standardkontur per glyf pluss et sett med delta-verdier indeksert etter posisjon i designrommet. Å anvende en akseverdi handler ikke om å skrive et tall inn i en header; det betyr å gå gjennom gvar-tabellen, interpolere delta-verdier for den forespurte posisjonen, flytte punkter, og deretter regne om alt som var avledet fra disse punktene. HotPDF bygger om glyfkonturene, den lange loca-tabellen, den fullstendige horisontale og vertikale metrikken, fontens globale avgrensningsboks og sfnt-sjekksumjusteringen

Like viktig er det som fjernes. En statisk instans må ikke beholde fvar, avar, gvar, HVAR, VVAR, MVAR, STAT eller cvar, og en utdatert DSIG må også fjernes, siden de signerte bytene ikke lenger finnes. Å la noen av disse bli igjen gir en font som hevder å være variabel, samtidig som den bærer konturer som allerede er flyttet, og lesere som faktisk anvender variasjoner, vil da anvende dem en gang til

Fantompunkter, og fellen med dobbel anvendelse

Den mest subtile regelen i hele prosessen gjelder metrikk. I gvar dekker punkttallet for en glyf konturpunktene, eller komponentpunktene for en sammensatt glyf, pluss fire fantompunkter som koder venstre sidebredde, forflytningsbredden og deres vertikale motstykker. Disse fantompunktene er selv gjenstand for delta-verdier

Så når en font har en gvar-tabell, avleder HotPDF horisontal og vertikal metrikk fra de interpolerte fantompunktene og anvender ikke i tillegg HVAR eller VVAR. Å legge til begge er den klassiske feilen: den samme variasjonen blir anvendt to ganger, og hver forflytningsbredde blir litt for bred, noe som viser seg som tekst som gradvis driver mot høyre gjennom en blokkjustert linje. Bare når en font ikke har noen gvar, bygger biblioteket metrikkvariasjonslagringen direkte inn i hmtx eller vmtx

To ytterligere detaljer holder geometrien ærlig. Fantompunkter deltar aldri i konturinterpolasjon, så punkter som ikke er eksplisitt oppgitt for en enkel glyf, blir utledet med IUP per kontur, med fantompunktene ekskludert. Og sammensatte glyfer får sine delta-verdier anvendt på komponentforskyvninger som bruker XY-parametere, hvorpå underordnede avgrensninger regnes om rekursivt. Den rekursjonen er begrenset i dybde og sykluskontrollert, fordi en fiendtlig eller bare ødelagt komponentgraf ellers kan rekurere uten ende

Inspisere designrommet før du velger

Det første kallet i enhver instansieringsflyt er InspectVariableFont, som rapporterer aksene og de navngitte instansene fonthuset definerte. Akseposter bærer den firebyte-lange taggen, minimums-, standard- og maksimumsverdiene, flagg og en navne-ID; navngitte instanser bærer en underfamilie-navne-ID, flagg, en valgfri PostScript-navne-ID og én koordinat per akse:

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;

Å rapportere akseintervallet er viktig fordi akseverdier klemmes fast til intervallet fonten oppgir, ikke til intervallet grensesnittet ditt tilbyr. En glidebryter som lar en bruker be om vekt 1000 på en font der wght-aksen stopper på 900, bør korrigeres i grensesnittet, ikke stille og lydløst på fontlaget, ellers vil det utskrevne resultatet være i strid med forhåndsvisningen

Velge koordinater og produsere dokumentet

Akseutvalget er tilstandsbasert og gjelder for fonter registrert i etterkant. SetVariableFontAxis tar imot en firebyte-lang, utskrivbar ASCII-tagg og en endelig verdi, og avviser alt annet med et unntak i stedet for å stille ignorere det. ClearVariableFontAxes nullstiller utvalget, og GetVariableFontAxisSelections rapporterer det som for øyeblikket er satt, noe som er verdt å logge i rapportmotorer der flere kodebaner kan ha rørt ved det samme dokumentobjektet. Selve familien velges etter navn gjennom SetFont, akkurat som ethvert annet innebygd TrueType-skriftsnitt ville vært:

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

    Pdf.SetVariableFontAxis('wght', 620);   // semibold, ikke en navngitt instans
    Pdf.SetVariableFontAxis('wdth', 87.5);  // litt smalere
    Pdf.CurrentPage.SetFont('Inter', [], 11);
    Pdf.CurrentPage.TextOut(72, 720, 0, 'Quarterly results');

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

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

Hendelsen OnVariableFontInstance utløses idet hver instans produseres, og rapporterer akseverdiene som ble brukt, noe som er den billigste måten å bevise i en logg hva en gitt PDF faktisk inneholder. Fordi hvert distinkte koordinatsett gir et distinkt fontprogram, bør du behandle akseutvalg som en del av fontbufferens nøkkel; bufringsmekanikken er beskrevet i den persistente skriftsnitt-delmengdebufferen

Hvordan instansiering samvirker med delmengdedanning og forming

Instansiering kjøres før delmengdedanning, og den rekkefølgen er den riktige. Den instansierte fonten er et normalt statisk TrueType-skriftsnitt, så den vanlige delmengdedaneren behandler den som enhver annen: den beregner glyf-lukningen, beholder glyfene dokumentet faktisk bruker og forkaster resten. Samspillet du bør være klar over, er at to forskjellige akseutvalg av samme familie er to forskjellige fontprogrammer, så et dokument som blander vekt 400 og vekt 620, bygger inn to delmengder, ikke ett delt skriftsnitt med to instanser

Forming er i prinsippet upåvirket, og verdt å sjekke i praksis. Layout-funksjoner ligger i GSUB og GPOS, som instansiering bevarer, så ligaturer og stilistiske alternativer fortsetter å fungere som beskrevet i OpenType GSUB stilistiske alternativer. Det som endres, er posisjonering: en smal instans har smalere forflytninger enn standarden, så enhver layout som målte tekst før instansiering, målte feil bredder. Mål med det samme akseutvalget du skal gjengi med, så forsvinner avviket

Én siste defensiv merknad fra selve implementasjonen, nyttig for alle som utvider denne banen. Fonter uten vertikal metrikk evaluerer likevel dynamiske array-argumenter ved Delphi-kallstedet, så arrayer på analysetidspunktet blir alltid allokert i stedet for å stole på en HasVerticalMetrics-sjekk for å korte forbi en tom indeks. Det er den typen detalj på språknivå som gjør en tilsynelatende beskyttet gren til et access violation, nettopp på de fontene du ikke testet med

Støtte for variable fonter passer inn i den samme fontpipelinen som innebygging, delmengdedanning og glyf-lukning, som er beskrevet mer i dybden i skriftsnitt-delmengdelukning og formede glyfer. Det fullstendige typografifunksjonssettet for Delphi og C++Builder er listet opp på HotPDF Delphi PDF-komponentsiden