Technisch artikel

PDF/X-validatie in Delphi met de PDFium Component

De PDFium Component voor Delphi valideert drukklare PDF/X-documenten via TPdf.ValidatePdfX, wat ISO 15930-controles implementeert in twee lagen: acht inhoudscontroles op byte-niveau (verboden LZW-compressie, JavaScript, formuliervelden, OPI-referenties, een ontbrekende TrimBox, een niet-ingestelde Trapped-sleutel en meer) plus een PDFium-objectmodelanalyse die FPDFFont_GetIsEmbedded gebruikt om lettertype-insluiting (font embedding) te verifiëren op elk tekstobject van elke pagina. Het resultaat is een record van het type TPdfXValidationResult dat het gedetecteerde conformiteitsniveau noemt en elke overtreding vermeldt als een getypeerde enum, zodat uw Delphi-applicatie een klant precies kan vertellen waarom een bestand bij de drukkerij zal worden geweigerd nog voordat er een drukplaat wordt gemaakt

Als u ooit een opdracht naar een commerciële drukkerij hebt gestuurd en deze terugkreeg met een afwijzing van één regel — "geen TrimBox", "lettertypen niet ingesloten", "Trapped niet ingesteld" — dan kent u de kosten van een late ontdekking. PDF/X is the tegenhanger voor pre-press van PDF/A: waar archief-PDF/A garandeert dat een document er over tientallen jaren identiek uitziet, PDF/X garandeert dat een document morgenochtend identiek scheidt, belicht en snijdt op de RIP van iemand anders. De twee standaarden delen mechanismen (XMP-identificatie, OutputIntents, ingesloten ICC-profielen) maar beantwoorden verschillende vragen. Daarom leest het component aparte validators voor elk — de PDF/A-kant wordt behandeld in PDF/A preflight-validatie met de PDFium Component

Wat vereist ISO 15930 eigenlijk van een drukklare PDF?

ISO 15930 bestaat om een blinde uitwisseling (blind exchange) mogelijk te maken: een ontwerper overhandigt een bestand aan een drukker die hij nog nooit heeft gesproken, en de drukker kan de juiste uitvoer produceren zonder telefoontje, zonder e-mail over een ontbrekend lettertype en zonder gekoppelde afbeelding die op de laptop van de ontwerper is achtergebleven. Elke regel in de standaard dient dat doel. Lettertypen moeten worden ingesloten omdat er niet van kan worden uitgegaan dat de ontvangende RIP deze bezit. Externe verwijzingen zijn verboden omdat het bestand in zichzelf compleet moet zijn. Interactieve functies zijn verboden omdat inkt geen onclick-handler heeft

De PDFium Component herkent drie conformiteitsfamilies en rapporteert deze via de enum TPdfXConformance in het validatieresultaat: pxc1a voor PDF/X-1a:2001 (ISO 15930-1, de strikte CMYK-plus-steunkleur baseline op PDF 1.3/1.4), pxc3 voor PDF/X-3:2002 (ISO 15930-3, die RGB, Lab en ICC-beheerde kleuren toestaat), en pxc4 voor PDF/X-4:2010 (ISO 15930-7, die ten slotte live transparantie en lagen toestaat op een PDF 1.6 basis). Een bestand dat helemaal geen PDF/X-identificatie bevat, komt terug als pxcNone, wat op zelfs een nuttig antwoord is: het document heeft nooit beweerd drukklaar te zijn, en al het andere dat de validator rapporteert legt uit wat er nodig is om dat wel te worden

De verboden zijn logisch als u denkt als een RIP-leverancier. /LZWDecode is verboden in elke PDF/X-variant, zodat een conformerende consument nooit afhankelijk is van een filter met een geschiedenis van compatibiliteit en licenties; Flate doet hetzelfde werk zonder die ballast. JavaScript, AcroForm-velden en /AA additional-action dictionaries zijn verboden omdat een drukbestand een vaste beschrijving moet zijn van markeringen op papier — alles wat het uiterlijk bij het openen kan veranderen, verbreekt de garantie dat wat is goedgekeurd ook is wat wordt gedrukt. OPI (Open Prepress Interface) placeholders zijn verboden omdat ze inherent verwijzingen zijn naar afbeeldingen met een hoge resolutie die ergens anders zijn opgeslagen, en "ergens anders" is precies wat blinde uitwisseling verbiedt

Waarom weigeren drukkerijen PDF's zonder TrimBox?

De TrimBox is de afgewerkte pagina — de rechthoek die overblijft nadat de papiersnijder snijdt. De MediaBox, die elke PDF-pagina heeft, is louter het vel papier: deze omvat afloop (bleed), snijtekens, paskruisen en kleurenbalken. Insluitsynchronisatiesoftware (imposition software) positioneert pagina's op een drukvel aan de hand van hun TrimBoxes. Zonder TrimBox moet de operator raden waar uw visitekaartje daadwerkelijk eindigt, en een verkeerde gok snijdt uw afloop weg of laat een witte rand achter aan één kant. Dat is de reden waarom ISO 15930 een TrimBox (of een ArtBox) op elke pagina vereist, en waarom ValidatePdfX de fout pvxiMissingTrimBox rapporteert wanneer er op geen enkele pagina van het document een /TrimBox-sleutel wordt gevonden

De sleutel /Trapped beantwoordt een andere productievraag. Trapping (overvloeien) is de pre-press techniek waarbij aangrenzende kleuren elkaar licht overlappen, zodat kleine registratie-afwijkingen op de pers geen witte kieren tussen de kleuren veroorzaken. De drukker moet weten of dat werk al is gedaan: het 'trappen' van een al getrapt bestand verdubbelt de overlappen, en het overslaan van trapping bij een niet-getrapt bestand riskeert zichtbare kieren. PDF/X vereist daarom dat de Info-dictionary expliciet /Trapped /True of /Trapped /False vermeldt — een ontbrekende sleutel of /Unknown dwingt een mens tot inspectie van het bestand, wat precies het soort overleg is dat blinde uitwisseling moest elimineren. Het component markeert dit als pvxiTrappedNotSet

De validatie in twee lagen uitvoeren met TPdf.ValidatePdfX

TPdf.ValidatePdfX neemt geen argumenten aan en retourneert een record van het type TPdfXValidationResult met drie leden: Conformance (the gedetecteerde PDF/X-variant), Issues (een Pascal-set van TPdfXValidationIssue-waarden) en een helper IsCompliant. Intern serialiseert het het geladen document naar een geheugenstream, voert de inspecteur op byte-niveau erop uit en doorloopt vervolgens het PDFium-objectmodel voor de lettertype-insluitingscontrole. Een minimale preflight-poort ziet er als volgt uit:

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;

Omdat Issues is een gewone Pascal-set is, kunt u deze indelen naar de behoeften van uw workflow — behandel structurele problemen als harde afwijzingen, behandel pvxiMissingTitle (een SHOULD in de standaard, geen MUST) als een waarschuwing en log de rest. Hetzelfde recordtype voedt ook de rapportgenerator van het component, dus als u liever een door mensen leesbaar document genereert in plaats van te splitsen op enums, is het patroon in het bouwen van een batch preflight-rapport CLI met de PDFium Component ongewijzigd van toepassing op PDF/X

Wat de laag op byte-niveau vangt — en wat hij mist

De laag op byte-niveau is een token-scan over de structurele bytes van het document waarbij de body van de streams is leeggemaakt, zodat een JPEG die toevallig het bytepatroon /JavaScript bevat, geen vals positief resultaat kan veroorzaken. Bovenop de marker-controles (XMP pdfxid:GTS_PDFXVersion, OutputIntent met een ingesloten ICC-profiel, trailer /ID, het versleutelingsverbod) voegt de inhoudscontrole acht controles toe, elk met een eigen enum-waarde:

  • pvxiLzwForbidden — er verschijnt ergens in het bestand een /LZWDecode-filter (verboden in alle PDF/X-varianten)
  • pvxiJavaScriptForbidden — er is een /JavaScript-actie of -namenboom aanwezig
  • pvxiFormFieldsForbidden — er bestaat een /AcroForm-dictionary of /XFA-invoer
  • pvxiAdditionalActions — er is een /AA (additional-actions) dictionary aanwezig
  • pvxiEmbeddedFilesForbidden — er is /EmbeddedFiles of een /FileAttachment-annotatie aanwezig
  • pvxiOpiForbidden — een /OPI- of /Alternates-invoer verwijst naar vervangbare afbeeldingsinhoud
  • pvxiMissingTrimBox — er is op geen enkele pagina een /TrimBox gevonden
  • pvxiTrappedNotSet/Trapped is afwezig of ingesteld op /Unknown

Het scannen op byte-niveau is snel en vereist geen rendering-engine, maar het heeft een inherent blinde vlek bij lettertypen: op dat niveau kan de inspecteur alleen een grove heuristiek toepassen — hij vlagt een document wanneer hij helemaal geen ingesloten lettertypeprogramma vindt. Een bestand met negen ingesloten lettertypen en één stiekem tussengevoegd systeemlettertype ziet er prima uit voor een byte-scan. Dat enkele gat is de reden dat de tweede laag bestaat

Lettertype-insluiting per lettertype via het PDFium-objectmodel

De objectmodel-laag van de PDFium Component beantwoordt de lettertypevraag exact. Na de controle op byte-niveau doorloopt TPdf.ValidatePdfX elke pagina, vraagt FPDFPage_CountObjects om de objectenlijst, en lost voor elk tekstobject de lettertype-handle op via FPDFTextObj_GetFont en vraagt FPDFFont_GetIsEmbedded op. Eén niet-ingesloten lettertype waar dan ook in het document voegt pvxiPdfiumFontNotEmbedded toe aan de set met problemen. De doorloop breekt op twee niveaus vroegtijdig af — hij stopt met het scannen van objecten op een pagina en stopt met het laden van verdere pagina's op het moment dat het probleem is bevestigd — dus bij een afwijkende catalogus van 300 pagina's komt het oordeel vaak al na pagina één

Twee randvoorwaarden die het waard zijn om te weten. Ten eerste heeft deze laag de geladen PDFium-bibliotheek nodig en vereist builds die FPDFFont_GetIsEmbedded exporteren; wanneer de export afwezig is, wordt de controle overgeslagen in plaats van als gefaald gemarkeerd, zodat een oudere DLL nooit fantoomafwijzingen produceert. Ten tweede beantwoordt de controle alleen de vraag "ingesloten of niet" en niets meer — hij maakt geen onderscheid tussen volledige insluiting en subsetting, en controleert de dekking van de glyphs niet. Wanneer een bestand faalt en u moet weten welk lettertype op welke pagina het betreft, sluiten de opsommingstechnieken in het analyseren van PDF-lettertype-eigenschappen met PDFium in Delphi exact aan waar de boolean van de validator ophoudt

Streams valideren zonder een document — of the DLL — te laden

De inspecteur op byte-niveau is ook beschikbaar als een op zichzelf staande functie, ValidatePdfXCompliance(Source: TStream) in de unit FPdfPdfx, en is pure Object Pascal zonder afhankelijkheid van de PDFium DLL. Dat maakt het inzetbaar op plaatsen waar een rendering-engine ongewenst is: een lichtgewicht upload-poort op een webserver, een CI-taak die gegenereerd artwork controleert, of een Lazarus service op een platform waar u liever geen native binaries levert. Voed het met een willekeurige seekable stream:

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;

De afweging is expliciet: het stand-alone pad voert de marker-controles en alle acht inhoudscontroles uit, maar niet de PDFium-laag per lettertype, waardoor het oordeel over lettertypen terugvalt op de grove heuristiek. Een verstandige architectuur gebruikt ValidatePdfXCompliance als de goedkope eerste poort en reserveert de volledige TPdf.ValidatePdfX voor bestanden die daarvoor slagen

Waar deze validator eindigt en een volledige preflight begint

Eerlijkheid is belangrijk bij preflight-gereedschappen, dus hier ligt de grens. ValidatePdfX verifieert identificatie-markers, structurele verboden, pagina-geometrie-sleutels, de Trapped-declaratie en lettertype-insluiting tot aan individuele tekstobjecten. Het meet niet de totale inktdekking, valideert niet of elke kleurruimte legaal is voor de geclaimde variant (bijvoorbeeld de CMYK-only regel van X-1a), controleert de resolutie van afbeeldingen niet tegen het rasterscherm, en evalueert overdruk- en transparantie-afvlakkingsgedrag niet — die vereisen een preflight-engine met kleurbeheer, en de documentatie van de unit zelf raadt aan deze hiermee te combineren voor de definitieve certificering. Wat de validatie in twee lagen u biedt, is dat 80% van de afwijzingen structureel zijn en vroegtijdig kunnen worden gedetecteerd, binnen milliseconden opgevangen in uw eigen Delphi-code in plaats van in de e-mail van de drukker morgen

Beide validatielagen, de PDF/X-marker injectie API's voor het produceren van conforme uitvoer, en de PDF/A, PDF/UA, PDF/E en PDF/VT-validators die dezelfde architectuur delen, worden geleverd in de PDFium Component voor Delphi en C++Builder — één component, van rendering tot pre-press bewaking