Teknisk artikel

Bädda in typsnitt som saknas i befintliga PDF-filer för PDF/A i Delphi

losLab PDF Library kan bädda in de saknade typsnittsprogrammen i en redan inläst PDF med ett enda anrop: EmbedMissingFonts går igenom varje typsnittsordbok i dokumentet, letar upp det matchande installerade systemtypsnittet med dess BaseFont-namn och skriver tillbaka typsnittsprogrammet till filen. För team som reparerar tredjepartsdokument som misslyckas med PDF/A-validering vid inbäddning av typsnitt, är detta korrigeringen som får preflight-fel 00030 att försvinna

Scenariot är deprimerande vanligt. En arkiveringspipeline tar emot PDF-filer från leverantörer, kunder eller en skanningsbyrå; dokumenten renderas fint på varje skrivbord i byggnaden; och sedan avvisar PDF/A-valideraren hela batchen med samma klagomål upprepat en gång per fil: minst ett typsnitt är inte inbäddat. Ingen uppströms kommer att generera om filerna, så pipelinen måste reparera dem. Den här artikeln täcker den reparationsvägen. Det är följeslagaren till preflight-artikeln, som täcker upptäckt av PDF/A- och PDF/UA-överträdelser: den artikeln berättar vilka dokument som är trasiga, den här fixar det vanligaste sättet de är trasiga på

Varför kräver PDF/A att alla typsnitt bäddas in?

ISO 19005-1 §6.3.4 kräver att varje typsnitt som används av ett kompatibelt dokument bär sitt typsnittsprogram inuti filen, eftersom hela löftet med PDF/A är reproducerbarhet: dokumentet måste renderas identiskt på en maskin om femtio år som inte delar några typsnitt med maskinen som producerade det. Ett icke-inbäddat typsnitt är en instruktion att leta upp Arial någonstans i visningssystemet, och standardens ställningstagande är att ”någonstans i visningssystemet” inte är en arkivgaranti. Oavsett vilka glyfer, mått och täckning det ersättande typsnittet har, är det vad läsaren får, och det kanske inte är vad författaren såg

Den historiska boven i dramat är Standard 14-konventionen. PDF 1.0 lovade att varje visare levereras med Helvetica, Times, Courier, Symbol och ZapfDingbats, så generatorer lärde sig to referera till dessa typsnitt efter namn och inte bädda in någonting, och trettio år av verktyg gör fortfarande exakt det. losLab PDF Library tar kravet på så stort allvar att i PDF/A-skapande läge är AddStandardFont avsiktligt en no-op: biblioteket levererar inte Standard 14-typsnittsprogrammen, kan inte bädda in det som det inte har, och vägrar att skriva en icke-inbäddad referens i ett dokument som hävdar överensstämmelse. Det returnerar 0 utan att välja ett typsnitt, så ett PDF/A-dokument måste använda AddTrueTypeFont med inbäddning istället, och alla Embed=0-begäranden uppgraderas tyst till Embed=1 medan PDF/A-läget är aktivt. Det är skrivsidan. Det svårare problemet är lässidan: ett dokument som någon annan redan har skrivit, fullt av typsnittsordböcker som du inte har skapat

Hur reparerar EmbedMissingFonts ett inläst dokument?

losLab PDF Library reparerar typsnitt på plats istället för att bygga om dem. När en PDF-generator skriver ett icke-inbäddat TrueType-typsnitt är FontDescriptor-ordboken den producerar redan komplett: FontName, FontBBox, Flags, Ascent, Descent, StemV, alla är närvarande. Det enda som skiljer det från ett inbäddat typsnitt är avsaknaden av en post, /FontFile2-strömreferensen som innehåller det faktiska typsnittsprogrammet. Så EmbedMissingFonts rör inte typsnittsordboken, kodningen, breddmatrisen eller någon innehållsström som refererar till typsnittet med resursnamn. Den läser det matchande typsnittsprogrammet från systemet, komprimerar det till ett nytt strömobjekt och lägger till en enda /FontFile2-referens (or /FontFile3 för CIDFontType0-typsnitt) till FontDescriptor som redan finns där. Allt som dokumentets sidor pekar på förblir exakt där det var, vilket är det som gör åtgärden säker att köra på filer som du inte kontrollerar

Täckningen omfattar båda typsnittsarkitekturerna du kommer att möta i praktiken: enkla TrueType-typsnitt och sammansatta Type0/CID-typsnitt, av den typ som produceras för CJK-text och modern Unicode-utdata. Traversaleringen räknar avsiktligt upp varje Font-ordbok i dokumentets objektträd istället för att förlita sig på en resursgenomgång sida för sida, så typsnitt som refereras till från anteckningar eller delas över sidor plockas också upp. API:et är ett enda anrop på det inlästa dokumentet

var
  PDF: TPDFlib;
  Repaired: Integer;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
      raise Exception.Create('Could not load PDF');

    // Går igenom varje Font-ordbok; returnerar hur många typsnitt
    // som fick ett typsnittsprogram. Typsnitt vars program inte kan
    // hittas på systemet hoppas över, misslyckas inte.
    Repaired := PDF.EmbedMissingFonts;
    Writeln(Format('%d font program(s) embedded', [Repaired]));

    PDF.SaveToFile('supplier-invoice-repaired.pdf');
  finally
    PDF.Free;
  end;
end;

En detalj som är värd att veta eftersom den förklarar varför nammatchningen fungerar bättre än en naiv strängjämförelse: biblioteket normaliserar BaseFont-namn innan det letar upp dem. Delmängdsprefix (ABCDEF+-mönstret med sex versaler och ett plustecken) tas bort, suffix i PostScript-stil som ArialMT omvandlas till Arial, och TrueType Collection-filer upptäcks och packas upp så att ett teckensnitt som finns inuti en .ttc fortfarande bäddas in korrekt

Verifiera reparationen med en preflight-rapport

CreatePreflightReport är verifieringssteget, och loopen stängs avsiktligt: samma granskning som fördömde filen bör vara den som godkänner den. Felkod 00030 är PDF/A-djupgranskningsfyndet som lyder ”At least one font is not embedded (FontFile/FontFile2/FontFile3 missing)”, och det rapporteras mot filen som helhet, so ett enda förbisett typsnitt håller det vid liv. Kör rapporten på källfilen, reparera, spara och kör den igen på utdata

function HasFontEmbeddingViolation(PDF: TPDFlib;
  const FileName: string): Boolean;
var
  Report: string;
begin
  // ComplianceTests = 1 väljer PDF/A-kontrollerna
  Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
  Result := Pos('00030', Report) > 0;
end;

För en vy per typsnitt snarare än ett beslut per fil, läs in det reparerade dokumentet igen och räkna upp: FindFonts följt av SelectFont och GetFontIsEmbedded rapporterar inbäddningsstatus typsnitt för typsnitt, vilket är rätt verktyg när ett batchjobb behöver logga exakt vilket teckensnitt i vilken fil som inte kunde repareras. Samma uppräkningsmönster visas i artikeln om extrahering av text, bilder och typsnitt från inlästa PDF-filer, där den matar extrahering istället för reparation

Vad händer när typsnittet inte är installerat på systemet?

EmbedMissingFonts hoppar över alla typsnitt vars program det inte kan hitta, och rapporterar detta via sitt returvärde: om antalet är lägre än antalet icke-inbäddade typsnitt du räknade, är skillnaden typsnitt som systemet inte har. Detta är det ärliga felläget, och det är bättre än alternativen, eftersom att hitta på ett ersättningsprogram för ett typsnitt som namnges i dokumentet skulle ändra renderingen, vilket är exakt vad en arkivreparation aldrig får göra. För dessa fall erbjuder losLab PDF Library EmbedFontProgramFromFile, som bäddar in en användarlevererad .ttf eller .otf i det namngivna typsnittet, så att en pipeline kan leverera de företagstypsnitt den förväntar sig att stöta på och medvetet falla tillbaka på dem

var
  I, FontID: Integer;
begin
  PDF.FindFonts;
  for I := 1 to PDF.FontCount do
  begin
    FontID := PDF.GetFontID(I);
    if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
      if PDF.GetFontIsEmbedded = 0 then
        // Prova det installerade systemtypsnittet först, fall sedan tillbaka
        // till en typsnittsfil som levereras tillsammans med applikationen
        if PDF.EmbedFontProgram(PDF.FontName) = 0 then
          PDF.EmbedFontProgramFromFile(PDF.FontName,
            'fonts\CorporateSans.ttf');
  end;
end;

Två gränser förtjänar att uttalas tydligt. För det första repareras inte Type1-typsnitt i den nuvarande implementeringen: deras /FontFile-post kräver den tresegmenterade PFB-strukturen med explicita längdnycklar, och biblioteket hoppar över dem istället för att skriva en felaktig ström; de är sällsynta i moderna dokument men de förekommer i gamla arkiv. För det andra är inbäddning av ett typsnitt en licensåtgärd. Ett TrueType-typsnitts inbäddningsbehörighet tillhör dess tillverkare, och en reparationspipeline som stoppar in licensierade typsnittsprogram i dokument som lämnar organisationen bör låta någon bekräfta att typsnittslicenserna faktiskt tillåter det. Biblioteket gör vad du ber om; huruvida du får be om det är en fråga för din juridiska avdelning, inte din kompilator

Inbäddning är nödvändig, men inte tillräcklig

Att reparera typsnitt åtgärdar fel 00030, och inget annat. Ett dokument som misslyckas med PDF/A på grund av kryptering, saknade XMP-metadata, en enhetsberoende färgrymd utan en OutputIntent, eller saknade ToUnicode-kartor kommer fortfarande att misslyckas efter att alla typsnitt har bäddats in, vilket är anledningen till att reparationen hör hemma i en preflight-driven loop snarare än att ersätta den. Kör hela rapporten, åtgärda det den namnger och låt rapporten berätta när du är klar. Det finns också en kostnadsdimension: ett fullständigt CJK-typsnittsprogram kan uppgå till megabyte, så att bädda in flera av dem kan blåsa upp ett litet dokument dramatiskt. Motvikten är delmängdsbildning (subsetting), vilket behandlas i artikeln om optimering av PDF-filstorlek och typsnittsdelmängdsbildning, som skär ner varje inbäddat program till de glyfer som dokumentet faktiskt renderar

Förhindra att nya dokument försämras

SetEmbedAllFonts är den förebyggande halvan av samma funktion: ett skydd på skrivsidan som hindrar din egen kod från att producera de dokument som den här artikeln reparerar. Med SetEmbedAllFonts(1) aktivt uppgraderas alla efterföljande AddTrueTypeFont-anrop som begär Embed=0 till en inbäddad referens, vilket utökar den garanti som PDF/A-läget redan upprätthåller till varje dokument. Det påverkar typsnitt som läggs till efter anropet, inte typsnitt som redan finns i en inläst fil, så arbetsfördelningen är ren: SetEmbedAllFonts för dokumenten du skapar, EmbedMissingFonts för dokumenten du ärver

PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// Från och med nu fungerar AddTrueTypeFont(Name, 0)
// som AddTrueTypeFont(Name, 1): ingen icke-inbäddad
// referens kan nå utdatafilen

Båda halvorna, skyddet på skrivsidan och banan för inläsning-reparation-spara, är en del av losLab PDF Library för Delphi, C# och VB.NET, tillsammans med preflight-motorn som verifierar resultatet; produktsidan innehåller hela typsnitts-API-referensen inklusive inbäddnings- och delmängdsbildningsanrop per typsnitt