Odborný článok

Validácia PDF/X v Delphi s PDFium Component

PDFium Component pre Delphi overuje dokumenty PDF/X pripravené na tlač cez metódu TPdf.ValidatePdfX, ktorá implementuje kontrolu podľa ISO 15930 v dvoch vrstvách: osem kontrol obsahu na úrovni bajtov (zakázaná kompresia LZW, JavaScript, polia formulárov, odkazy OPI, chýbajúci TrimBox, nenastavený kľúč Trapped a ďalšie) plus prechod objektovým modelom PDFium, ktorý cez FPDFFont_GetIsEmbedded overí vloženie písma na každom textovom objekte každej strany. Výsledkom je záznam TPdfXValidationResult, ktorý pomenuje zistenú úroveň zhody a každé porušenie vypíše ako typový enum, takže vaša aplikácia v Delphi vie zákazníkovi presne povedať, prečo súbor v tlačiarni neprejde, ešte skôr než niekto vypáli platňu

Ak ste niekedy poslali zákazku kuriérom do komerčnej tlačiarne a dostali ju späť s jednoriadkovým odmietnutím — „no TrimBox“, „fonts not embedded“, „Trapped not set“ — poznáte cenu neskorého zistenia. PDF/X je predtlačovým náprotivkom PDF/A: kým archívne PDF/A zaručuje, že sa dokument o desaťročia vykreslí rovnako, PDF/X zaručuje, že sa dokument zajtra ráno na cudzom RIPe rovnako rozseparuje, obrázkuje a oreže. Obe normy zdieľajú mechaniku (identifikácia cez XMP, OutputIntents, vložené profily ICC), ale odpovedajú na iné otázky, a práve preto komponent dodáva pre každú z nich samostatný validátor — stranu PDF/A pokrýva preflight validácia PDF/A s PDFium Component

Čo vlastne ISO 15930 od tlačového PDF vyžaduje?

ISO 15930 existuje preto, aby umožnila slepú výmenu: dizajnér odovzdá súbor tlačiarovi, s ktorým sa nikdy nerozprával, a tlačiar dokáže vyrobiť správny výstup bez telefonátu, bez e-mailu o chýbajúcom písme a bez linkovaného obrázka, ktorý zostal na dizajnérovom notebooku. Každé pravidlo normy slúži tomuto cieľu. Písma musia byť vložené, lebo o prijímajúcom RIPe sa nedá predpokladať, že ich vlastní. Externé odkazy sú zakázané, lebo súbor musí byť sám osebe úplný. Interaktívne prvky sú zakázané, lebo atrament nemá obsluhu onclick

PDFium Component rozoznáva tri rodiny zhody a hlási ich vo výsledku validácie cez enum TPdfXConformance: pxc1a pre PDF/X-1a:2001 (ISO 15930-1, prísny základ CMYK plus priame farby na PDF 1.3/1.4), pxc3 pre PDF/X-3:2002 (ISO 15930-3, ktoré pripúšťa RGB, Lab a farby riadené profilmi ICC) a pxc4 pre PDF/X-4:2010 (ISO 15930-7, ktoré konečne povoľuje živú priehľadnosť a vrstvy nad základom PDF 1.6). Súbor, ktorý nenesie žiadnu identifikáciu PDF/X, sa vráti ako pxcNone, čo je samo osebe užitočná odpoveď: dokument nikdy netvrdil, že je pripravený na tlač, a všetko ostatné, čo validátor hlási, vysvetľuje, čo by k tomu chýbalo

Zákazy dávajú zmysel, len čo začnete uvažovať ako dodávateľ RIPu. /LZWDecode je zakázaný v každom variante PDF/X, aby vyhovujúci konzument nikdy nezávisel od filtra s históriou kompatibility a licencovania; Flate odvedie tú istú prácu bez tejto batožiny. JavaScript, polia AcroForm a slovníky doplnkových akcií /AA sú zakázané, pretože tlačový súbor musí byť pevným opisom značiek na papieri — čokoľvek, čo dokáže zmeniť vzhľad pri otvorení, ruší záruku, že sa vytlačí to, čo bolo nákladovo odsúhlasené. Zástupné odkazy OPI (Open Prepress Interface) sú zakázané, pretože sú zo svojej podstaty odkazmi na obrázky vo vysokom rozlíšení uložené niekde inde, a „niekde inde“ je presne to, čo slepá výmena zakazuje

Prečo tlačiarne odmietajú PDF bez TrimBoxu?

TrimBox je hotová strana — obdĺžnik, ktorý zostane po reze na gilotíne. MediaBox, ktorý má každá strana PDF, je len arch: zahŕňa spadávku, orezové značky, registračné terče a farebné škály. Vyraďovací softvér umiestňuje strany na tlačový arch podľa ich TrimBoxov; bez neho musí operátor hádať, kde vaša vizitka vlastne končí, a zlý odhad odreže spadávku alebo nechá na jednej hrane biely prúžok. Preto ISO 15930 vyžaduje TrimBox (alebo ArtBox) na každej strane a preto ValidatePdfX vyvolá pvxiMissingTrimBox, keď sa kľúč /TrimBox nenájde na žiadnej strane dokumentu

Diagram PDFium Component zobrazujúci tlačovú stranu PDF s archom MediaBox so spadávkou, orezovými značkami, farebnými škálami a registračnými značkami okolo obdĺžnika TrimBox, ktorý ISO 15930 vyžaduje, pričom pri chýbajúcom kľúči vzniká pvxiMissingTrimBox
MediaBox je celý arch so spadávkou, orezovými značkami a farebnými škálami, kým TrimBox je hotová strana, ktorú musí gilotína nájsť; ISO 15930 ho vyžaduje na každej strane

Kľúč /Trapped odpovedá na inú výrobnú otázku. Presahy sú predtlačovou technikou mierneho prekrývania susedných farieb, aby drobný nesúlad sútlače neotvoril medzi nimi biele medzery. Tlačiar potrebuje vedieť, či už bola táto práca urobená: presahovanie už presahovaného súboru prekrytia zdvojí a vynechanie presahov v súbore bez nich riskuje viditeľné medzery. PDF/X preto vyžaduje, aby slovník Info explicitne uvádzal /Trapped /True alebo /Trapped /False — chýbajúci kľúč alebo /Unknown núti človeka súbor preskúmať, čo je presne ten rozhovor, ktorý mala slepá výmena odstrániť. Komponent to označí ako pvxiTrappedNotSet

Diagram metódy TPdf.ValidatePdfX v PDFium Component pre Delphi, ktorá spúšťa skenovanie tokenov na úrovni bajtov s ôsmimi kontrolami obsahu a prechod objektovým modelom PDFium pre vloženie písiem, zlievajúce sa do jedného záznamu TPdfXValidationResult
ValidatePdfX prežene jeden načítaný dokument cez kontroly obsahu na úrovni bajtov aj cez prechod písmami v objektovom modeli PDFium a obe vrstvy potom zlúči do jedného typového záznamu s výsledkom

Spustenie dvojvrstvovej validácie cez TPdf.ValidatePdfX

TPdf.ValidatePdfX neberie žiadne argumenty a vracia záznam TPdfXValidationResult s tromi členmi: Conformance (zistená príchuť PDF/X), Issues (pascalovská množina hodnôt TPdfXValidationIssue) a pomocná vlastnosť IsCompliant. Interne serializuje načítaný dokument do pamäťového prúdu, prebehne nad ním inšpektor na úrovni bajtov a potom prejde objektový model PDFium kvôli kontrole vloženia jednotlivých písiem. Minimálna preflight brána vyzerá takto:

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;

Keďže Issues je bežná pascalovská množina, môžete ju rozdeliť tak, ako váš postup potrebuje — štrukturálne problémy brať ako tvrdé odmietnutia, pvxiMissingTitle (v norme SHOULD, nie MUST) brať ako varovanie a zvyšok zalogovať. Ten istý typ záznamu napája aj generátor zostáv v komponente, takže ak by ste radšej vydali ľudsky čitateľný dokument než vetvili podľa enumov, vzor z článku budovanie dávkového preflight CLI s PDFium Component platí pre PDF/X bez zmeny

Čo vrstva na úrovni bajtov zachytí — a čo jej unikne

Vrstva na úrovni bajtov je skenovaním tokenov nad štrukturálnymi bajtmi dokumentu s vybielenými telami prúdov, takže JPEG, ktorý náhodou obsahuje bajtovú postupnosť /JavaScript, nemôže spustiť falošný poplach. Nad kontrolami značiek (XMP pdfxid:GTS_PDFXVersion, OutputIntent s vloženým profilom ICC, /ID v traileri, zákaz šifrovania) pridáva obsahový prechod osem kontrol, každú s vlastnou hodnotou enumu:

  • pvxiLzwForbidden — filter /LZWDecode sa objavuje kdekoľvek v súbore (zakázaný vo všetkých variantoch PDF/X)
  • pvxiJavaScriptForbidden — je prítomná akcia /JavaScript alebo strom názvov
  • pvxiFormFieldsForbidden — existuje slovník /AcroForm alebo položka /XFA
  • pvxiAdditionalActions — je prítomný slovník doplnkových akcií /AA
  • pvxiEmbeddedFilesForbidden — je prítomná položka /EmbeddedFiles alebo anotácia /FileAttachment
  • pvxiOpiForbidden — položka /OPI alebo /Alternates odkazuje na nahraditeľný obrazový obsah
  • pvxiMissingTrimBox — na žiadnej strane sa nenašiel /TrimBox
  • pvxiTrappedNotSet — /Trapped chýba alebo je nastavený na /Unknown

Skenovanie bajtov je rýchle a nepotrebuje vykresľovací engine, no pri písmach má vrodené slepé miesto: na tejto úrovni vie inšpektor uplatniť len hrubú heuristiku — dokument označí vtedy, keď nenájde vôbec žiadny vložený program písma. Súbor s deviatimi vloženými písmami a jedným prepašovaným systémovým vyzerá pri skenovaní bajtov v poriadku. Práve pre túto jedinú medzeru existuje druhá vrstva

Vloženie jednotlivých písiem cez objektový model PDFium

Vrstva objektového modelu v PDFium Component odpovie na otázku písiem presne. Po prechode na úrovni bajtov TPdf.ValidatePdfX prejde každú stranu, vypýta si zoznam objektov cez FPDFPage_CountObjects a pri každom textovom objekte vyrieši handle písma cez FPDFTextObj_GetFont a opýta sa FPDFFont_GetIsEmbedded. Jediné nevložené písmo kdekoľvek v dokumente pridá do množiny problémov pvxiPdfiumFontNotEmbedded. Prechod sa skratuje na dvoch úrovniach — prestane skenovať objekty na strane a prestane načítavať ďalšie strany vo chvíli, keď je problém potvrdený — takže pri chybnom 300-stranovom katalógu príde verdikt často už po prvej strane

Dve hraničné poznámky sa oplatí poznať. Po prvé, táto vrstva potrebuje načítanú knižnicu PDFium a vyžaduje buildy, ktoré exportujú FPDFFont_GetIsEmbedded; keď export chýba, kontrola sa preskočí, nie zamietne, takže staršia DLL nikdy nevyrobí prízračné odmietnutia. Po druhé, kontrola odpovedá na „vložené alebo nie“ a nič viac — nerozlišuje úplné vloženie od podmnožiny ani neskúma pokrytie glyfov. Keď súbor neprejde a potrebujete vedieť, ktoré písmo na ktorej strane, techniky výpisu z článku analýza vlastností písiem v PDF s PDFium v Delphi nadviažu presne tam, kde booleovská odpoveď validátora končí

Overovanie prúdov bez načítania dokumentu — a bez DLL

Inšpektor na úrovni bajtov je sprístupnený aj ako samostatná funkcia ValidatePdfXCompliance(Source: TStream) v jednotke FPdfPdfx a je to čistý Object Pascal bez závislosti na DLL knižnice PDFium. Vďaka tomu sa dá nasadiť tam, kde je vykresľovací engine nevítaný: ako ľahká vstupná brána na webovom serveri, ako úloha CI, ktorá preveruje generované podklady, alebo ako služba v Lazaruse na platforme, kam by ste natívne binárky radšej nedodávali. Podajte jej ľubovoľný prúd s možnosťou presunu:

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;

Kompromis je vyslovený otvorene: samostatná cesta spustí kontroly značiek aj všetkých osem kontrol obsahu, ale nie vrstvu jednotlivých písiem cez PDFium, takže jej verdikt o písmach sa vracia k hrubej heuristike. Rozumná architektúra použije ValidatePdfXCompliance ako lacnú prvú bránu a plné TPdf.ValidatePdfX si vyhradí pre súbory, ktoré ňou prešli

Kde tento validátor končí a kde začína plný preflight

V preflight nástrojoch záleží na poctivosti, takže tu je hranica. ValidatePdfX overuje identifikačné značky, štrukturálne zákazy, kľúče s geometriou strany, deklaráciu Trapped a vloženie písiem až po jednotlivé textové objekty. Nemeria celkové krytie farbou, neoveruje, či je každý farebný priestor pre deklarovaný variant legálny (napríklad pravidlo len CMYK vo variante X-1a), nekontroluje rozlíšenie obrázkov voči rastru a nevyhodnocuje správanie pretlače a zjednodušenia priehľadnosti — na to je potrebný farebne riadený preflight engine a vlastná dokumentácia jednotky odporúča na záverečnú certifikáciu spárovať ju práve s ním. Dvojvrstvová kontrola vám dáva tých 80 % odmietnutí, ktoré sú štrukturálne a včas zistiteľné, zachytených v milisekundách vo vašom vlastnom kóde v Delphi namiesto v zajtrajšom e-maile od tlačiara

Obe vrstvy validácie, rozhrania na vkladanie značiek PDF/X pre výrobu vyhovujúceho výstupu aj validátory PDF/A, PDF/UA, PDF/E a PDF/VT postavené na tej istej architektúre sa dodávajú v komponente PDFium Component pre Delphi a C++Builder — jeden komponent, od vykresľovania po strážnu bránu predtlače