Artykuł techniczny

Osadzanie brakujących fontów w istniejących plikach PDF dla PDF/A w Delphi

losLab PDF Library potrafi osadzić brakujące programy fontów w już załadowanym pliku PDF za pomocą jednego wywołania: EmbedMissingFonts przechodzi przez każdy słownik fontów w dokumencie, lokalizuje pasujący zainstalowany font systemowy na podstawie jego nazwy BaseFont i zapisuje program fontu z powrotem w pliku. Dla zespołów naprawiających dokumenty stron trzecich, które nie przechodzą walidacji PDF/A z powodu braku osadzonych fontów, jest to rozwiązanie, które eliminuje błąd preflight 00030

Ten scenariusz jest przygnębiająco powszechny. Potok pobierania archiwum otrzymuje pliki PDF od dostawców, klientów lub biura skanowania; dokumenty renderują się poprawnie na każdym biurku w budynku; a następnie walidator PDF/A odrzuca całą partię z tą samą skargą powtarzaną raz na plik: co najmniej jeden font nie jest osadzony. Nikt u góry nie wygeneruje plików ponownie, więc potok musi je naprawić. Ten artykuł opisuje tę ścieżkę naprawy. Jest on dopełnieniem artykułu o preflight, który obejmuje wykrywanie naruszeń PDF/A i PDF/UA: tamta część mówi, które dokumenty są uszkodzone, a ta naprawia najczęstszy powód ich uszkodzenia

Dlaczego PDF/A wymaga osadzenia każdego fontu?

Norma ISO 19005-1 §6.3.4 wymaga, aby każdy font używany przez zgodny dokument zawierał swój program fontu wewnątrz pliku, ponieważ cała obietnica PDF/A to odtwarzalność: dokument musi renderować się identycznie na maszynie za pięćdziesiąt lat, która nie dzieli żadnych fontów z maszyną, która go wyprodukowała. Nieosadzony font to instrukcja, aby znaleźć font Arial gdzieś w systemie wyświetlania, a stanowisko standardu jest takie, że „gdzieś w systemie wyświetlania” nie stanowi gwarancji archiwalnej. Niezależnie od tego, jakie glify, metryki i pokrycie ma zastępczy font, to właśnie otrzyma czytelnik, i może to nie być to, co widział autor

Historycznym winowajcą jest konwencja Standard 14. PDF 1.0 obiecywał, że każda przeglądarka dostarcza czcionki Helvetica, Times, Courier, Symbol i ZapfDingbats, więc generatory nauczyły się odwoływać do tych fontów po nazwie i niczego nie osadzać, a trzydzieści lat narzędzi wciąż robi dokładnie to samo. losLab PDF Library traktuje ten wymóg na tyle poważnie, że w trybie tworzenia PDF/A metoda AddStandardFont jest celowo operacją bezużyteczną (no-op): biblioteka nie dostarcza programów fontów Standard 14, nie może osadzić tego, czego nie ma, i odmawia zapisu nieosadzonego odniesienia do dokumentu, który deklaruje zgodność. Zwraca 0 bez wyboru fontu, więc dokument PDF/A musi zamiast tego używać AddTrueTypeFont z osadzaniem, a każde żądanie Embed=0 jest cicho podnoszone do Embed=1, gdy aktywny jest tryb PDF/A. To jest strona zapisu. Trudniejszym problemem jest strona odczytu: dokument, który ktoś inny już zapisał, pełen słowników fontów, których nie utworzyłeś

Jak EmbedMissingFonts naprawia załadowany dokument?

losLab PDF Library naprawia fonty w miejscu, zamiast je przebudowywać. Gdy generator PDF zapisuje nieosadzony font TrueType, tworzony przez niego słownik FontDescriptor jest już kompletny: FontName, FontBBox, Flags, Ascent, Descent, StemV są obecne. Jedyną rzeczą dzielącą go od osadzonego fontu jest brak jednego wpisu – referencji strumienia /FontFile2 przechowującej rzeczywisty program fontu. W związku z tym EmbedMissingFonts nie dotyka słownika fontu, kodowania, tablicy szerokości ani żadnego strumienia zawancerzenia, który odwołuje się do fontu poprzez nazwę zasobu. Odczytuje pasujący program fontu z systemu, kompresuje go do nowego obiektu strumienia i dołącza pojedynczą referencję /FontFile2 (lub /FontFile3 dla fontów CIDFontType0) do istniejącego już FontDescriptor. Wszystko, na co wskazują strony dokumentu, pozostaje dokładnie tam, gdzie było, co czyni tę operację bezpieczną do uruchomienia na plikach, których nie kontrolujesz

Zakres obejmuje obie architektury fontów, z którymi spotkasz się w praktyce: proste fonty TrueType oraz kompozytowe fonty Type0/CID, rodzaje produkowane dla tekstu CJK i nowoczesnego wyjścia Unicode. Przejście celowo wylicza każdy słownik Font w drzewie obiektów dokumentu, zamiast polegać na przejściu zasobów strona po stronie, dzięki czemu fonty przywoływane w adnotacjach lub współdzielone na stronach również są wychwytywane. API to pojedyncze wywołanie na załadowanym dokumencie

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');

    // Walks every Font dictionary; returns how many fonts
    // gained a font program. Fonts whose program cannot be
    // found on the system are skipped, not failed.
    Repaired := PDF.EmbedMissingFonts;
    Writeln(Format('%d font program(s) embedded', [Repaired]));

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

Weryfikacja naprawy za pomocą raportu preflight

CreatePreflightReport to krok weryfikacyjny, a pętla jest celowo zamknięta: ten sam audyt, który potępił plik, powinien być tym, który go oczyści. Kod błędu 00030 to wynik szczegółowego audytu PDF/A o treści „Co najmniej jeden font nie jest osadzony (brak FontFile/FontFile2/FontFile3)”, który jest zgłaszany dla pliku jako całości, więc pojedynczy pominięty font utrzymuje błąd. Uruchom raport na pliku źródłowym, napraw, zapisz i uruchom go ponownie na pliku wyjściowym

function HasFontEmbeddingViolation(PDF: TPDFlib;
  const FileName: string): Boolean;
var
  Report: string;
begin
  // ComplianceTests = 1 selects the PDF/A checks
  Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
  Result := Pos('00030', Report) > 0;
end;

W celu uzyskania widoku dla poszczególnych fontów, a nie wyroku dla całego pliku, załaduj ponownie naprawiony dokument i wykonaj iterację: FindFonts, a następnie SelectFont i GetFontIsEmbedded raportują status osadzania font po foncie, co jest odpowiednim narzędziem, gdy zadanie wsadowe musi zarejestrować dokładnie, który krój w którym pliku nie mógł zostać naprawiony. Ten sam wzorzec wyliczania pojawia się w artykule o wyciąganiu tekstu, obrazów i fontów z załadowanych plików PDF, gdzie zasila ekstrakcję zamiast naprawy

Co się dzieje, gdy font nie jest zainstalowany w systemie?

EmbedMissingFonts pomija każdy font, którego programu nie może znaleźć, i zgłasza to pominięcie poprzez swoją wartość zwracaną: jeśli liczba ta jest niższa niż liczba policzonych przez Ciebie nieosadzonych fontów, różnica wynika z fontów, których system nie posiada. Jest to uczciwy tryb niepowodzenia i jest lepszy niż alternatywy, ponieważ wymyślenie programu zastępczego dla fontu nazwanego w dokumencie zmieniłoby renderowanie, czego naprawa archiwalna nigdy nie może zrobić. W takich przypadkach losLab PDF Library udostępnia funkcję EmbedFontProgramFromFile, która osadza dostarczony przez wywołującego plik .ttf lub .otf w nazwanym foncie, dzięki czemu potok może dostarczać fonty firmowe, których napotkania się spodziewa, i celowo z nich korzystać jako z zapasowych

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
        // Try the installed system font first, then fall back
        // to a font file shipped alongside the application
        if PDF.EmbedFontProgram(PDF.FontName) = 0 then
          PDF.EmbedFontProgramFromFile(PDF.FontName,
            'fonts\CorporateSans.ttf');
  end;
end;

Dwie granice zasługują na jasne określenie

Po pierwsze, fonty Type1 nie są naprawiane w obecnej implementacji: ich wpis /FontFile wymaga trzysegmentowej struktury PFB z jawnymi kluczami długości, a biblioteka je pomija, zamiast zapisywać zniekształcony strumień; są one rzadkie w nowoczesnych dokumentach, ale pojawiają się w starych archiwach. Po drugie, osadzanie fontu jest czynnością licencyjną. Uprawnienia do osadzania fontu TrueType należą do jego odlewni, a potok naprawczy, który umieszcza licencjonowane programy fontów w dokumentach opuszczających organizację, powinien uzyskać potwierdzenie, że licencje na fonty rzeczywiście na to pozwalają. Biblioteka zrobi to, o co poprosisz; to, czy możesz o to poprosić, jest pytaniem do Twojego działu prawnego, a no nie do kompilatora

Osadzanie jest konieczne, ale niewystarczające

Naprawa fontów usuwa błąd 00030 i nic więcej. Dokument, który nie przechodzi walidacji PDF/A z powodu szyfrowania, braku metadanych XMP, zależnej od urządzenia przestrzeni barwnej bez OutputIntent lub braku map ToUnicode, nadal nie przejdzie walidacji po osadzeniu wszystkich fontów, dlatego naprawa powinna odbywać się wewnątrz pętli sterowanej raportami preflight, a nie ją zastępować. Uruchom pełny raport, napraw to, co wskaże, i pozwól, aby raport poinformował Cię o zakończeniu pracy. Istnieje również wymiar kosztów: pełny program fontów CJK zajmuje megabajty, so osadzenie kilku z nich może drasticznie zwiększyć rozmiar małego dokumentu. Przeciwwagą jest subsetting (podział na podzbiory), opisany w artykule o optymalizacji rozmiaru plików PDF i podziale fontów na podzbiory, który ogranicza każdy osadzony program do glifów rzeczywiście renderowanych w dokumencie

Zapobieganie regresji nowych dokumentów

SetEmbedAllFonts to zapobiegawcza część tej samej funkcji: zabezpieczenie po stronie zapisu, które uniemożliwia własnemu kodowi tworzenie dokumentów, które ten artykuł naprawia. Przy aktywnym SetEmbedAllFonts(1) każde kolejne wywołanie AddTrueTypeFont żądające Embed=0 jest promowane do odniesienia osadzonego, co rozszerza na każdy dokument gwarancję, którą tryb PDF/A już wymusza. Wpływa to na fonty dodane po wywołaniu, a nie na te, które już znajdują się w załadowanym pliku, więc podział pracy jest wyraźny: SetEmbedAllFonts dla dokumentów, które tworzysz, EmbedMissingFonts dla dokumentów, które dziedziczysz

PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// From here on, AddTrueTypeFont(Name, 0) behaves
// like AddTrueTypeFont(Name, 1): no non-embedded
// reference can reach the output file

Obie połowy, zabezpieczenie po stronie zapisu oraz ścieżka wczytaj-napraw-zapisz, są częścią losLab PDF Library dla Delphi, C# i VB.NET, wraz z silnikiem preflight weryfikującym wynik; strona produktu zawiera pełne referencje API fontów, w tym wywołania osadzania i podziału na podzbiory dla poszczególnych fontów