losLab PDF Library può incorporare i programmi dei font mancanti di un PDF già caricato con una singola chiamata: EmbedMissingFonts scorre ogni dizionario dei font nel documento, individua il font di sistema installato corrispondente tramite il suo nome BaseFont e riscrive il programma del font nel file. Per i team che riparano documenti di terze parti che falliscono la validazione PDF/A sull'incorporamento dei font, questa è la soluzione che fa sparire l'errore di preflight 00030
Lo scenario è scoraggiantemente comune. Una pipeline di acquisizione dell'archivio riceve PDF da fornitori, clienti o da un ufficio di scansione; i documenti vengono visualizzati correttamente su ogni scrivania dell'edificio; quindi il validatore PDF/A rifiuta l'intero lotto con lo stesso reclamo ripetuto una volta per file: almeno un font non è incorporato. Nessuno a monte rigenererà i file, quindi la pipeline deve ripararli. Questo articolo tratta questo percorso di riparazione. È il complemento dell'articolo sul preflight, che copre il rilevamento delle violazioni PDF/A e PDF/UA: quel testo spiega quali documenti sono danneggiati, questo corregge il modo più frequente in cui lo sono
Perché il formato PDF/A richiede che ogni font sia incorporato?
La norma ISO 19005-1 §6.3.4 richiede che ogni font utilizzato da un documento conforme rechi al suo interno il proprio programma del font, poiché l'intera promessa del PDF/A è la riproducibilità: il documento deve essere visualizzato in modo identico su una macchina tra cinquant'anni che non condivide alcun font con la macchina che lo ha prodotto. Un font non incorporato è un'istruzione a cercare Arial da qualche parte sul sistema di visualizzazione, e la posizione dello standard è che "da qualche parte sul sistema di visualizzazione" non è una garanzia di archiviazione. Qualsiasi glifo, metrica e copertura abbia il font sostitutivo, questo è ciò che riceve il lettore, e potrebbe non essere ciò che l'autore ha visto
Il colpevole storico è la convenzione dello Standard 14. Il PDF 1.0 prometteva che ogni visualizzatore includesse Helvetica, Times, Courier, Symbol e ZapfDingbats, quindi i generatori hanno imparato a fare riferimento a quei font per nome e a non incorporare nulla, e trent'anni di strumenti fanno ancora esattamente questo. losLab PDF Library prende questo requisito abbastanza sul serio da far sì che, in modalità di creazione PDF/A, AddStandardFont sia deliberatamente una no-op: la libreria non include i programmi dei font dello Standard 14, non può incorporare ciò che non ha e rifiuta di scrivere un riferimento non incorporato in un documento che dichiara la conformità. Ritorna 0 senza selezionare un font, quindi un documento PDF/A deve invece utilizzare AddTrueTypeFont con incorporamento, e qualsiasi richiesta Embed=0 viene promossa silenziosamente a Embed=1 mentre la modalità PDF/A è attiva. Questo è il lato di scrittura. Il problema più difficile è il lato di lettura: un documento che qualcun altro ha già scritto, pieno di dizionari di font che non hai creato tu
Come ripara un documento caricato EmbedMissingFonts?
losLab PDF Library ripara i font sul posto anziché ricostruirli. Quando un generatore PDF scrive un font TrueType non incorporato, il dizionario FontDescriptor che produce è già completo: FontName, FontBBox, Flags, Ascent, Descent, StemV, tutti presenti. L'unica cosa che lo separa da un font incorporato è l'assenza di una voce, il riferimento allo stream /FontFile2 contenente il programma effettivo del font. Pertanto, EmbedMissingFonts non tocca il dizionario del font, la codifica, l'array delle larghezze o qualsiasi stream di contenuto che faccia riferimento al font per nome della risorsa. Legge il programma del font corrispondente dal sistema, lo comprime in un nuovo oggetto stream e aggiunge un singolo riferimento /FontFile2 (o /FontFile3 per i font CIDFontType0) al FontDescriptor già presente. Tutto ciò a cui puntano le pagine del documento rimane esattamente dove si trovava, il che rende l'operazione sicura da eseguire su file che non controlli
La copertura include entrambe le architetture di font che incontrerai nella pratica: font TrueType semplici e font compositi Type0/CID, del tipo prodotto per testi CJK e output Unicode moderni. L'attraversamento enumera deliberatamente ogni dizionario Font nell'albero degli oggetti del documento anziché fare affidamento su una scansione delle risorse pagina per pagina, in modo da recuperare anche i font a cui si fa riferimento nelle annotazioni o condivisi tra le pagine. L'API è una singola chiamata sul documento caricato
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;
Un dettaglio che vale la pena conoscere perché spiega perché la corrispondenza dei nomi funzioni meglio di un semplice confronto di stringhe: la libreria normalizza i nomi BaseFont prima di cercarli. I prefissi dei subset (il pattern ABCDEF+ di sei lettere maiuscole e un segno più) vengono rimossi, i suffissi in stile PostScript come ArialMT si risolvono in Arial e i file TrueType Collection vengono rilevati e decompressi in modo che un carattere all'interno di un file .ttc venga comunque incorporato correttamente
Verificare la riparazione con un rapporto di preflight
CreatePreflightReport è la fase di verifica e il ciclo è deliberatamente chiuso: la stessa verifica che ha bocciato il file dovrebbe essere quella che lo approva. Il codice di errore 00030 è il risultato dell'audit approfondito PDF/A che recita "Almeno un font non è incorporato (FontFile/FontFile2/FontFile3 mancante)", ed è segnalato per il file nel suo complesso, quindi un singolo font trascurato lo mantiene attivo. Esegui il rapporto sul file sorgente, ripara, salva ed eseguilo nuovamente sull'output
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;
Per una visualizzazione per font anziché un verdetto per file, carica nuovamente il documento riparato ed enumera: FindFonts seguito da SelectFont e GetFontIsEmbedded segnala lo stato di incorporamento font per font, che è lo strumento giusto quando un processo batch deve registrare esattamente quale carattere in quale file non è stato possibile riparare. Lo stesso pattern di enumerazione appare nell'articolo sull'estrazione di testo, immagini e font da PDF caricati, dove alimenta l'estrazione anziché la riparazione
Cosa succede se il font non è installato sul sistema?
EmbedMissingFonts salta qualsiasi font il cui programma non viene trovato e segnala l'omissione tramite il suo valore di ritorno: se il conteggio risulta inferiore al numero di font non incorporati contati, la differenza rappresenta i font che il sistema non ha. Questa è la modalità di errore corretta, ed è preferibile alle alternative, poiché inventare un programma sostitutivo per un font nominato nel documento cambierebbe il rendering, che è esattamente ciò che una riparazione d'archivio non deve mai fare. Per questi casi losLab PDF Library fornisce EmbedFontProgramFromFile, che incorpora un file .ttf o .otf fornito dal chiamante nel font specificato, consentendo a una pipeline di distribuire i font aziendali che prevede di incontrare e di ricorrervi deliberatamente
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;
Due limiti meritano di essere esposti chiaramente. In primo luogo, i font Type1 non vengono riparati nella versione attuale: la loro voce /FontFile richiede la struttura PFB a tre segmenti con chiavi di lunghezza esplicite, e la libreria li salta anziché scrivere uno stream malformato; sono rari nei documenti moderni ma compaiono nei vecchi archivi. In secondo luogo, l'incorporamento di un font è un atto di licenza. I permessi di incorporamento di un font TrueType appartengono alla sua fonderia, e una pipeline di riparazione che inserisce programmi di font con licenza nei documenti che lasciano l'organizzazione dovrebbe far verificare a qualcuno che le licenze dei font lo consentano effettivamente. La libreria farà ciò che viene richiesto; se sia consentito farlo è una domanda per il dipartimento legale, non per il compilatore
L'incorporamento è necessario, non sufficiente
La riparazione dei font cancella l'errore 00030 e nient'altro. Un documento che non supera la conformità PDF/A a causa della crittografia, dei metadati XMP mancanti, di uno spazio colore dipendente dal dispositivo senza un OutputIntent o dell'assenza di mappe ToUnicode continuerà a fallire anche dopo che ogni font è stato incorporato, motivo per cui la riparazione deve essere inserita all'interno di un ciclo guidato dal preflight anziché sostituirlo. Esegui il rapporto completo, correggi ciò che segnala e lascia che sia il rapporto a dirti quando hai finito. C'è anche una dimensione di costo: un programma di font CJK completo può raggiungere i megabyte, quindi incorporarne diversi può gonfiare notevolmente un piccolo documento. Il contrappeso è il subsetting, trattato nell'articolo sull'ottimizzazione delle dimensi dei file PDF e sul subsetting dei font, che riduce ogni programma incorporato ai soli glifi effettivamente renderizzati dal documento
Evitare che i nuovi documenti presentino regressioni
SetEmbedAllFonts rappresenta la parte preventiva della stessa funzionalità: una protezione sul lato scrittura che impedisce al codice di produrre i documenti riparati in questo articolo. Con SetEmbedAllFonts(1) attivo, qualsiasi successiva chiamata a AddTrueTypeFont che richiede Embed=0 viene promossa a un riferimento incorporato, estendendo a ogni documento la garanzia che la modalità PDF/A già applica. Interessa i font aggiunti dopo la chiamata, non i font già presenti in un file caricato, quindi la divisione del lavoro è netta: SetEmbedAllFonts per i documenti creati, EmbedMissingFonts per i documenti ereditati
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
Entrambe le parti, la protezione lato scrittura e il percorso di caricamento-riparazione-salvataggio, fanno parte della losLab PDF Library per Delphi, C# e VB.NET, insieme al motore di preflight che verifica il risultato; la pagina del prodotto contiene il riferimento completo dell'API dei font, comprese le chiamate di incorporamento e subsetting per font