Articolo tecnico

Convalida PDF/X in Delphi con il componente PDFium

Il componente PDFium per Delphi convalida i documenti PDF/X pronti per la stampa tramite TPdf.ValidatePdfX, che implementa la verifica dello standard ISO 15930 in due livelli: otto controlli sul contenuto a livello di byte (compressione LZW vietata, JavaScript, campi modulo, riferimenti OPI, TrimBox mancante, chiave Trapped non impostata e altro) più un passaggio basato sul modello a oggetti di PDFium che utilizza FPDFFont_GetIsEmbedded per verificare l'incorporamento dei font su ogni oggetto di testo di ciascuna pagina. Il risultato è un record TPdfXValidationResult che identifica il livello di conformità rilevato ed elenca ciascuna violazione sotto forma di enum, consentendo all'applicazione Delphi di spiegare all'utente il motivo esatto per cui un file verrà rifiutato in tipografia prima della produzione delle matrici

Chiunque abbia inviato un lavoro a una tipografia commerciale ricevendolo indietro con una riga di rifiuto — "nessun TrimBox", "font non incorporati", "Trapped non impostato" — conosce l'impatto di una scoperta tardiva. Lo standard PDF/X rappresenta la controparte di prestampa di PDF/A: mentre il PDF/A per archiviazione garantisce che un documento si visualizzi in modo identico a distanza di decenni, il PDF/X garantisce che il file venga separato, rasterizzato e rifilato in modo identico sul RIP di un'altra tipografia domani mattina. I due standard condividono la struttura di base (identificazione XMP, OutputIntent, profili ICC incorporati) ma rispondono a esigenze diverse, motivo per cui il componente distribuisce validatori separati per ciascuno di essi — la parte PDF/A è descritta nell'articolo sulla convalida preflight PDF/A con il componente PDFium

Cosa richiede effettivamente lo standard ISO 15930 da un PDF pronto per la stampa?

Lo standard ISO 15930 nasce per rendere possibile lo scambio alla cieca (blind exchange): un grafico consegna un file a uno stampatore con cui non ha mai parlato, e quest'ultimo è in grado di produrre l'output corretto senza telefonate di chiarimento, email per richiedere font mancanti o immagini collegate rimaste sul computer del grafico. Ogni regola dello standard persegue questo scopo. I font devono essere incorporati in quanto non si può presumere che il RIP di destinazione li possieda. I riferimenti esterni sono vietati poiché il file deve essere completo in se stesso. Le funzioni interattive sono escluse in quanto l'inchiostro su carta non possiede gestori di eventi al clic

Il componente PDFium riconosce tre famiglie di conformità e le segnala tramite l'enum TPdfXConformance nel risultato di convalida: pxc1a per PDF/X-1a:2001 (ISO 15930-1, lo standard rigoroso CMYK e spot basato su PDF 1.3/1.4), pxc3 per PDF/X-3:2002 (ISO 15930-3, che ammette colori gestiti tramite RGB, Lab e ICC) e pxc4 per PDF/X-4:2010 (ISO 15930-7, che consente la trasparenza e i livelli su base PDF 1.6). Un file privo di qualsiasi identificazione PDF/X viene segnalato come pxcNone, esito utile in sé: indica che il documento non è stato configurato come pronto per la stampa, e i dettagli riportati dal validatore illustrano i passaggi necessari per renderlo conforme

I divieti risultano logici nell'ottica di un fornitore di RIP. Il filtro /LZWDecode è vietato in ogni variante PDF/X per evitare che un lettore conforme dipenda da un filtro con criticità storiche di licenza e compatibilità; l'algoritmo Flate esegue lo stesso compito senza tali problematiche. I JavaScript, i campi AcroForm e i dizionari di azioni aggiuntive /AA sono vietati poiché un file di stampa deve essere una rappresentazione fissa su carta — qualsiasi elemento in grado di alterare l'aspetto all'apertura vanifica la garanzia che ciò che è stato verificato corrisponda a quanto stampato. I segnaposto OPI (Open Prepress Interface) sono vietati poiché costituiscono riferimenti a immagini ad alta risoluzione salvate in altre posizioni, e la necessità di risorse esterne è proprio ciò che lo scambio alla cieca vieta

Perché le tipografie rifiutano i PDF privi di TrimBox?

Il TrimBox definisce la pagina finita — il rettangolo che rimane dopo il taglio della ghigliottina. Il MediaBox, presente in ogni pagina PDF, rappresenta invece il foglio grezzo: include il bleed (abbondanza), i segni di taglio, i crocini di registro e le barre di colore. I software di imposizione posizionano le pagine su una lastra di stampa in base ai relativi TrimBox; senza di esso, l'operatore deve indovinare dove termini il biglietto da visita, e una stima errata rischia di tagliare l'abbondanza o lasciare un bordo bianco su un lato. Per questo lo standard ISO 15930 richiede un TrimBox (or un ArtBox) su ogni pagina, e per questo la funzione ValidatePdfX solleva pvxiMissingTrimBox se la chiave /TrimBox risulta mancante in una qualsiasi pagina del documento

La chiave /Trapped risponde a una diversa esigenza di produzione. Il trapping è la tecnica di prestampa che prevede di sovrapporre leggermente i colori adiacenti per evitare che lievi fuori registro creino spazi bianchi tra di essi. Lo stampatore deve sapere se questo lavoro sia già stato eseguito: applicare il trapping a un file che lo contiene già raddoppia le sovrapposizioni, mentre saltarlo su un file non predisposto rischia di evidenziare spazi bianchi. Lo standard PDF/X richiede che il dizionario Info indichi esplicitamente /Trapped /True o /Trapped /False — una chiave mancante o impostata su /Unknown costringe un operatore a ispezionare il file, che è proprio il tipo di intervento che lo scambio alla cieca mira a evitare. Il componente segnala questo errore come pvxiTrappedNotSet

Eseguire la convalida a due livelli con TPdf.ValidatePdfX

La funzione TPdf.ValidatePdfX non accetta parametri e restituisce un record TPdfXValidationResult con tre membri: Conformance (la variante PDF/X rilevata), Issues (un set Pascal di valori TPdfXValidationIssue) e un helper IsCompliant. Internally serializza il documento caricato in un flusso di memoria, esegue l'ispezione a livello di byte e poi analizza il modello a oggetti di PDFium per la verifica dell'incorporamento di ciascun font. Un controllo preflight minimo segue questo schema:

uses PDFium, FPdfPdfx;

procedure CheckPrintReadiness(const FileName: string);
var
  Pdf: TPdf;
  Res: TPdfXValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Active := True;

    Res := Pdf.ValidatePdfX;
    Writeln('Detected conformance: ',
      Ord(Res.Conformance)); // pxc1a, pxc3, pxc4, pxcNone...

    if Res.IsCompliant then
      Writeln('PDF/X checks passed')
    else
    begin
      if pvxiMissingTrimBox in Res.Issues then
        Writeln('REJECT: no /TrimBox on the pages');
      if pvxiTrappedNotSet in Res.Issues then
        Writeln('REJECT: /Trapped missing or /Unknown');
      if pvxiPdfiumFontNotEmbedded in Res.Issues then
        Writeln('REJECT: a page uses a non-embedded font');
      if pvxiLzwForbidden in Res.Issues then
        Writeln('REJECT: LZWDecode filter present');
    end;
  finally
    Pdf.Free;
  end;
end;

Poiché Issues è un normale set Pascal, è possibile strutturarlo in base alle esigenze del flusso di lavoro — trattando gli errori strutturali come rifiuti bloccanti, il valore pvxiMissingTitle (raccomandato dallo standard ma non obbligatorio) come avviso e registrando il resto nei log. Lo stesso tipo di record alimenta anche il generatore di report del componente, per cui se preferisci produrre un documento leggibile anziché gestire i singoli enum, lo schema descritto nell'articolo sulla creazione di una interfaccia a riga di comando per report preflight con il componente PDFium si applica a PDF/X senza variazioni

Cosa rileva il livello a byte, e cosa ignora

Il livello a byte esegue una scansione dei token sui byte strutturali del documento con i corpi dei flussi esclusi, in modo che un file JPEG contenente casualmente la sequenza /JavaScript non possa generare un falso positivo. Oltre alle verifiche dei marcatori (XMP pdfxid:GTS_PDFXVersion, OutputIntent con profilo ICC incorporato, trailer /ID e il divieto di crittografia), il controllo dei contenuti aggiunge otto verifiche, ciascuna associata a un proprio valore enum:

  • `pvxiLzwForbidden` — presenza del filtro `/LZWDecode` in qualsiasi punto del file (vietato in tutte le varianti PDF/X)
  • `pvxiJavaScriptForbidden` — presenza di un'azione o albero di nomi `/JavaScript`
  • `pvxiFormFieldsForbidden` — presenza di un dizionario `/AcroForm` o una voce `/XFA`
  • `pvxiAdditionalActions` — presenza di un dizionario di azioni aggiuntive `/AA`
  • `pvxiEmbeddedFilesForbidden` — presenza di `/EmbeddedFiles` o di un'annotazione `/FileAttachment`
  • `pvxiOpiForbidden` — presenza di una voce `/OPI` o `/Alternates` che fa riferimento a contenuti immagine sostituibili
  • `pvxiMissingTrimBox` — nessun `/TrimBox` trovato nelle pagine
  • `pvxiTrappedNotSet` — `/Trapped` assente o impostato su `/Unknown`

La scansione a livello di byte risulta rapida e non richiede un motore di rendering, ma presenta una lacuna intrinseca con i font: a questo livello l'ispezione può solo applicare un'euristica generica — segnala un documento se non trova alcun programma di font incorporato. Un file con nove font incorporati e uno di sistema non rilevato supererebbe il controllo a livello di byte. Questo singolo limite giustifica l'esistenza del secondo livello di convalida

Incorporamento dei font tramite il modello a oggetti di PDFium

Il livello basato sul modello a oggetti del componente PDFium risponde alla domanda sui font in modo preciso. Dopo la scansione a livello di byte, TPdf.ValidatePdfX analizza ciascuna pagina, richiede a FPDFPage_CountObjects l'elenco degli oggetti e, per ogni oggetto di testo, risolve l'handle del font tramite FPDFTextObj_GetFont interrogando poi FPDFFont_GetIsEmbedded. Un solo font non incorporato in qualsiasi punto del documento aggiunge pvxiPdfiumFontNotEmbedded al set dei problemi. La scansione viene interrotta in anticipo a due livelli — non appena l'errore viene confermato, si interrompe l'analisi degli oggetti sulla pagina e si evita il caricamento delle pagine successive — per cui, su un catalogo di 300 pagine non conforme, l'esito viene spesso definito già dopo la prima pagina

Due dettagli implementativi da considerare. Primo, questo livello richiede il caricamento della libreria PDFium e build che esportino la funzione FPDFFont_GetIsEmbedded; in assenza di tale esportazione, il controllo viene saltato senza generare errori, per cui una DLL obsoleta non produrrà falsi rifiuti. Secondo, il controllo verifica unicamente se il font sia incorporato o meno, senza distinguere tra incorporamento completo e subsetting o esaminare la copertura dei glifi. Se un file non supera il controllo e occorre determinare quale font su quale pagina sia interessato, le tecniche di ordinamento descritte nell'articolo sull'analisi delle proprietà dei font PDF con PDFium in Delphi consentono di approfondire l'indagine

Convalidare i flussi senza caricare il documento — o la DLL

L'ispezione a livello di byte è esposta anche come funzione indipendente, ValidatePdfXCompliance(Source: TStream) nell'unità FPdfPdfx, ed è scritta in puro Object Pascal senza dipendenze dalla DLL di PDFium. Questo la rende utilizzabile in ambienti in cui un motore di rendering non è indicato: un controllo leggero al caricamento su un server web, un job CI per verificare i file grafici generati o un servizio Lazarus su piattaforme in cui si preferisce non distribuire binari nativi. È possibile passare qualsiasi flusso (stream) supportato:

uses Classes, FPdfPdfx;

function QuickPdfXGate(const FileName: string): Boolean;
var
  Fs: TFileStream;
  Res: TPdfXValidationResult;
begin
  Fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfXCompliance(Fs);
    Result := Res.IsCompliant and (Res.Conformance <> pxcNone);
  finally
    Fs.Free;
  end;
end;

Il compromesso è chiaro: il percorso indipendente esegue i controlli dei marcatori e tutte le otto verifiche sul contenuto, ma esclude il livello basato sui font di PDFium, per cui la verifica dei font si affida all'euristica generica. Un'architettura indicata prevede di utilizzare ValidatePdfXCompliance come primo controllo rapido ed economico, riservando la chiamata completa TPdf.ValidatePdfX per i file che lo superano

Dove finisce questo validatore e inizia un preflight completo

L'affidabilità degli strumenti di preflight richiede trasparenza, quindi descriviamo i limiti del componente. ValidatePdfX verifica i marcatori di identificazione, i divieti strutturali, le chiavi geometriche delle pagine, la descrizione Trapped e l'incorporamento dei font per i singoli oggetti di testo. Non misura la copertura totale dell'inchiostro, non verifica che ogni spazio colore sia conforme alla variante specificata (ad esempio la regola di solo CMYK per X-1a), non controlla la risoluzione delle immagini rispetto alla lineatura e non valuta la sovrastampa o la rimozione delle trasparenze — operazioni che richiedono un motore preflight con gestione dei colori, che la documentazione dell'unità consiglia di affiancare per una certificazione finale. Il controllo a due livelli rileva l'80% dei rifiuti strutturali in pochi millisecondi direttamente nel codice Delphi, evitando di scoprirlo in seguito tramite una notifica dello stampatore

Entrambi i livelli di convalida, le API di inserimento dei marcatori PDF/X per produrre output conformi, e i validatori PDF/A, PDF/UA, PDF/E e PDF/VT (che condividono la stessa architettura) sono forniti all'interno del componente PDFium per Delphi e C++Builder — un solo componente, per la visualizzazione e il controllo di prestampa