HotPDF indlejrer OpenType variable fonts med CFF2-outlines ved at konvertere dem, ved de valgte akseværdier, til et CID-keyed CFF1-fontprogram, som PDF-læsere allerede forstår. Almindelig native tekst registreret gennem RegisterUnicodeTTF bruger fontens egne cmap- og hmtx-tabeller, og writeren indlejrer det konverterede program under /FontFile3 med /Subtype /CIDFontType0C, så hver CID er lig det oprindelige glyph ID
Den åbenlyse vej er værd at beskrive, for den fejler uden at fortælle nogen noget. Parse og cache CFF2-tabellerne, og lad derefter den almindelige tekststi løse fonten gennem Windows GDI med en TrueType-only match: GDI har ingen anelse om, hvad den skal stille op med CFF2-outlines, og substituerer i stilhed ArialUnicodeMS. Alt downstream, der læser substituttet, får de forkerte outlines, de forkerte bredder og den forkerte ekstraktion af supplementary-plane-tegn. PDF'en åbner fint. Den er bare ikke sat i den font, du bad om, og derfor skal tekststien arbejde ud fra de registrerede byte i stedet for et GDI-opslag
Hvorfor kan en PDF ikke indlejre en CFF2-font direkte?
PDF har ingen plads til et CFF2-program. ISO 32000-2 §9.9 lister de indlejrede fontprogram-typer, en læser skal kunne håndtere: Type 1 under /FontFile, TrueType under /FontFile2 og under /FontFile3 et rent CFF-program (Type1C eller CIDFontType0C) eller en OpenType-wrapper omkring TrueType- eller CFF-outlines. Alle CFF-varianter i listen betyder CFF version 1, det format, læsere har parset i to årtier
CFF2 er et andet format, der tilfældigvis deler et navn. Det dropper Name- og String INDEX, dropper charsettet, tager glyph advances ud af CharStrings og overlader dem til hmtx og tilføjer blend-operatoren, så én enkelt outline kan beskrive hele designrummet. En CFF1-parser, der møder en CFF2-tabel, har intet grundlag for at gætte, og intet i PDF-specifikationen forpligter den til at prøve. At pakke CFF2-fonten ind i en OpenType-stream og håbe på det bedste er præcis den slags fil, der renderer på én maskine og fejler på den næste, så HotPDF gør det ikke
Konverteringen skal også være statisk. Som artiklen om statisk instansiering af variable fonts forklarer, er en PDF-font et fast sæt outlines med faste metrics, og ingen læser er forpligtet til at anvende aksekoordinater. HotPDF løser derfor variationen først og skriver først derefter CFF1
Native tekst i en CFF2-font
For programkode opfører en CFF2-font sig som enhver anden registreret Unicode-font: vælg akseværdier, registrér filen, vælg familien ved navn, og tegn. RegisterUnicodeTTF beholder en separat kopi af den oprindelige nametabel, så SetFont matcher familienavnet, det fulde navn, PostScript-navnet eller det typografiske familienavn og vælger de frosne statiske fontbyte, uden Windows-installation eller GDI-opslag
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'brochure.pdf';
Pdf.BeginDoc;
// Akseværdier gælder for fonts registreret efter dette kald
Pdf.SetVariableFontAxis('wght', 650);
Pdf.RegisterUnicodeTTF('C:\Fonts\AcmeSerifVF.otf');
// Familie, fuldt navn, PostScript- eller typografisk familienavn fra nametabellen
Pdf.CurrentPage.SetFont('Acme Serif VF', [], 12, DEFAULT_CHARSET);
Pdf.CurrentPage.UnicodeTextOut(72, 100, 0, 'Variable weight 650, CFF2 outlines');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Glyph-ID'er kommer fra den registrerede cmap og advances fra den registrerede hmtx. Et glyph med en advance, der reelt er nul, forbliver nul i stedet for at falde tilbage til en GDI-måling, og et UTF-16 surrogate-par giver præcis én advance, ikke to. BMP-tegn, supplementary-tegn og syntetiske shaping-bærere beholder per-font Unicode-mappings, så et skift til en anden indlejret font eller en ny side ikke lader den anden font opdatere denne fonts subset-tilstand eller låne dens glyph-ID'er
Målingerne følger de samme byte. THPDFPage.GetWideTextWidth og THPDFPage.GetWideCharAdvances læser metrics for den valgte byte-baserede font, så linjebrydning og højrejustering stemmer med det, der tegnes. Én rækkefølge-detalje fanger folk: SetVariableFontAxis-ændringer efter registrering påvirker den næste font, du registrerer, ikke den allerede frosne instans. Vil du have en anden weight, så sæt den nye værdi og registrér igen
Hvordan bygger HPDFConvertCFF2ToCFF det CID-keyed program?
HPDFConvertCFF2ToCFF producerer et råt CID-keyed CFF1-program med en enkelt Font DICT, hvor CID er lig det oprindelige glyph ID for hver plads, også tomme og ubrugte. Den identitet er hele designet. Fontnavnet, PDF /W-bredderne, CFF-advances og ToUnicode CMap refererer alle til samme nummerering, så intet lag skal oversætte mellem glyph-ID'er og CID'er, og intet kan komme ud af trit
På PDF-siden er efterkommeren en CIDFontType0-font med Identity-H- eller Identity-V-encoding, og programstreamen ligger under /FontFile3 med /Subtype /CIDFontType0C. Fordi CID allerede er lig GID, har efterkommeren ingen /CIDToGIDMap, og streamen er udelukket fra det senere stream-replacement-hook brugt til registrerede TrueType-fonte. I modsætning til /Subtype /OpenType-wrapperen udløser den konverterede stream ikke PDF 1.6-versionskravet
Bredderne krævede et lille trick. En CFF1 Type 2 CharString gemmer advance som en valgfri første operand relativt til nominalWidthX, og heltalsoperanden er begrænset til det signerede 16-bit-område. En OpenType-advance er en unsigned 16-bit-værdi, op til 65535. HotPDF sætter nominalWidthX til 32768 og defaultWidthX til 0 og skriver derefter hver advance eksplicit som advance - 32768. En advance på nul bliver -32768, og en advance på 65535 bliver 32767, så hele uint16-området kan repræsenteres uden at clampes ét eneste glyph
Geometrien får samme omhu. FontMatrix er [1/UnitsPerEm 0 0 1/UnitsPerEm 0 0], så en font med 2048 units beholder sine native koordinater. Relative moves og linjer, der er for store til én operand, deles op i repræsentérbare trin, og kubiske kurver subdivideres på en måde, der bevarer deres form. De oprindelige copyright- og licens-poster, den oprindelige nametabel og embedding-flagene følger med i CFF Notice-strengen
At kalde konverteren uden for et dokument
Konverteren er en offentlig funktion i HPDFOpenTypeAdvanced-unit'en, skrevet i Pascal, uden HarfBuzz-afhængighed og uden trin til installation af systemfonte. Det gør den brugbar på egen hånd, for eksempel til at pre-konvertere en font på build time eller til at inspicere, hvad PDF-writeren vil indlejre
uses
System.SysUtils, System.IOUtils, HPDFOpenTypeAdvanced;
procedure ConvertWeight650;
var
Source, Cff: TBytes;
Axes: THPDFVariableFontAxisValueArray;
begin
Source := TFile.ReadAllBytes('C:\Fonts\AcmeSerifVF.otf');
SetLength(Axes, 1);
Axes[0].Tag := 'wght';
Axes[0].Value := 650;
Cff := HPDFConvertCFF2ToCFF(Source, Axes);
if Length(Cff) = 0 then
raise Exception.Create('Not convertible: not CFF2, embedding denied or bad axes');
TFile.WriteAllBytes('AcmeSerif-650.cff', Cff);
end;
Variable input går gennem geometry-only statisk instansiering ved de givne koordinater og dækker både outlines og advances. En statisk CFF2-font accepterer kun et tomt valg; at give den akseværdier behandles som en fejl i stedet for at blive ignoreret. Resultatet er et rent CFF1-program, ikke en sfnt-container, så giv det ikke til kode, der forventer en OpenType-fil
Fejl er en tom TBytes, ikke en exception, i de forventede tilfælde: input, der ikke er CFF2, et aksevalg, som instansiereren ikke accepterer, ikke-finite værdier som NaN og fonts, hvis OS/2 fsType sætter restricted-license embedding (0x0002) eller bitmap-only embedding (0x0200). Tjek altid længden. Indeni EndDoc gør writeren det for dig og udløser "StoreFont: CFF2 outline conversion failed", hvilket er det rigtige valg: et tomt fontprogram i en afsendt PDF er langt værre end en synlig fejl i din log
Budgets og cancellation til serverworkloads
Den valgfrie tredje parameter, THPDFVariableFontControl, begrænser hukommelsen og lader dig annullere en lang konvertering. Uden den bruger konverteren sine defaults: en grænse på 128 MiB på font-input og -output, et samlet arbejdsbudget på 1 GiB og CFF1-grænsen på 65535 byte per CharString
type
TFontJob = class
private
FCancelled: Boolean;
procedure CheckCancel;
public
function Convert(const Source: TBytes): TBytes;
property Cancelled: Boolean read FCancelled write FCancelled;
end;
procedure TFontJob.CheckCancel;
begin
if FCancelled then
Abort; // propagerer ud af HPDFConvertCFF2ToCFF
end;
function TFontJob.Convert(const Source: TBytes): TBytes;
var
Control: THPDFVariableFontControl;
begin
Control := THPDFVariableFontControl.Create(
32 * 1024 * 1024, // maksimalt antal output-byte
Int64(256) * 1024 * 1024, // samlet arbejdsbudget
CheckCancel);
try
// nil-valg: default-instans af en variable font eller en statisk CFF2-font
Result := HPDFConvertCFF2ToCFF(Source, nil, Control);
finally
Control.Free;
end;
end;
Budgetoverskridelser og annullering rapporteres som exceptions, bevidst adskilt fra den tomme-resultat-vej. Et tomt array betyder, at fonten ikke kan konverteres; en exception betyder, at dette forsøg blev standset. Hold de to adskilt i din fejlhåndtering, for at prøve en ukonvertérbart font igen med et større budget hjælper ikke
Hvor meget vejer en indlejret CFF2-font?
Det vigtigste at planlægge efter er filstørrelse: HotPDF indlejrer det komplette konverterede glyph-sæt for CFF2-fonte, uanset om EnableFontSubsetting er til eller fra. En font med flere tusinde glyphs tilføjer dem alle til hvert dokument, der bruger den, selv hvis siden viser én overskrift. Til en brochure er det acceptabelt. Til en rapportserver, der skriver tusindvis af små PDF'er, er det en reel omkostning, du bør måle, før du binder dig
De relaterede størrelsesfeatures følger samme regel. Konverterede CFF2-fonte indlejres hele, så hverken de kompakte font subsets aktiveret af CompressDocument eller den persistente font subset-cache gælder dem. TrueType-fonte i samme dokument subsettes og caches stadig som sædvanlig. Den ene fordel ved at beholde hvert glyph er, at fonts, hvis licens forbyder subsetting, indlejres i en form, der respekterer det
Ressourcegrænserne er endelige konverteringsgrænser, og CFF1 sætter selv loft over, hvad en enkelt CharString kan holde. Et glyph, hvis konverterede CharString overstiger 65535 byte, eller en font, der brænder igennem arbejdsbudgettet, fejler synligt i stedet for at blive skrevet med en tom outline. Det er den tiltænkte trade-off: en højlydt fejl på et eksotisk font er bedre end et dokument med lydløst manglende glyphs
Native CFF2-tekst og den portable CFF1-konverter følger med i HotPDF Delphi PDF component til Delphi og C++Builder, sammen med variable font-instansiering, color glyph- og font subsetting-features dækket andre steder på denne blog