Teknisk artikel

Automatisering af PDF-preflight-tjek i Delphi med HotPDF

Filen åbner rent på din maskine. Acrobat viser den, printudsigten ser rigtig ud, hver side er der. Så går den til trykkeriet, eller ind i arkivsystemet, der modtager din månedlige batch, og den kommer tilbage afvist: RGB-billeder i et CMYK-job, ingen /Trapped-nøgle, en output intent, der ikke matcher trykket. Der var intet galt med dokumentet, nogen kunne se. Det var forkert mod en profil, og profilen blev tjekket et sted, du ikke var. Preflight er prepress-navnet for det tjek, og det reelle spørgsmål er, hvor det hører hjemme, når PDF'erne kommer ud af din egen Delphi-kode frem for fra en designers skrivebord

HotPDF giver dig ikke en preflight-funktion at kalde. Komponenten bærer et preflight-rapportvindue i sin GUI-demo, men der er ingen API bag det, en tjeneste eller et build-script kan påkalde, og at foregive andet ville sende dig ud på jagt efter en metode, der ikke findes. Det lyder som et hul, indtil man bemærker, at for filer, man selv genererer, er det at kalde en validator på sit eget output alligevel den forkerte form. Man kontrollerer allerede hver eneste egenskab, en validator ville inspicere. Den nyttige opdeling er at gøre generatoren ude af stand til at udsende en ikke-konform fil og derefter bevise det med et værktøj, man ikke selv skrev

Diagram over en Delphi preflight-pipeline, hvor HotPDF compliance-indstillinger forhindrer dårlige PDF'er under generering, og veraPDF plus Acrobat Preflight beviser resultatet eksternt
Forebyggelse bager PDF/A- og PDF/X-regler ind i selve HotPDF-genereringen, mens eksterne validatorer leverer dommen, som generatoren ikke kan give sig selv

Hvorfor man tjekker sit eget output anderledes

Traditionel preflight forudsætter en fremmeds fil. En eller anden designer, en eller anden applikation, en ukendt kæde af redigeringer producerede den, og man inspicerer den, fordi man ikke aner, hvad der er indeni. Et dokument, ens egen kode har produceret, er ikke en fremmed. Skrifttypeindlejring, farverum, output intent, metadata-blokken: ens program besluttede det hele få millisekunder, før filen ramte disken. At inspicere det bagefter for at opdage valg, man lige har truffet, er tomgangsarbejde. Det billigere træk er at begrænse de valg, så en ikke-konform fil aldrig kommer til at eksistere for at blive fanget

Der er også en troværdighedsgrund til at holde verifikationen ekstern. Et bibliotek, der velsigner sit eget output, retter sin egen eksamen. Når en kundes arkivsystem eller et trykkeris RIP afviser din fil, vejer "vores komponent siger, den er fin" ingenting. Det gør en dom fra veraPDF eller Acrobat derimod, fordi den anden side kører de samme værktøjer

Gør konformitet til en indstilling, ikke en tjekliste

Forebyggelseslaget er blot konfiguration. Sæt PDFACompliance eller PDFXCompliance før BeginDoc, og HotPDF håndhæver de tilsvarende regler for hele genereringspasset: den indlejrer skrifttyper, holder øje med brugen af DeviceRGB og DeviceCMYK mod den output intent, man har erklæret, og afviser funktioner, profilen forbyder. Modsigelserne dukker op ved EndDoc, hvor compliance-portene rejser en undtagelse frem for stille at sende noget af sted, der vil fejle længere nede i strømmen. Når filen er gemt, læser de samme egenskaber tilbage, hvad der rent faktisk blev håndhævet, hvilket er det ene faktum, din pipeline-log har mest brug for:

// Efter EndDoc: registrér de håndhævede profiler sammen med kørselsmetadata
if Pdf.PDFACompliance <> '' then
  Log('Generated as PDF/A level ' + Pdf.PDFACompliance);
if Pdf.PDFXCompliance <> '' then
  Log('Generated as PDF/X profile ' + Pdf.PDFXCompliance);

Sæt disse flag på den samme log-linje som input-datahashen og HotPDF-versionen. Den dag en validator og din generator er uenige om en fil, fortæller den linje dig, hvilken skabelon der producerede den, og hvilken build af biblioteket der var indlæst, og diskussionen, der ellers ville æde en eftermiddag, bliver til en grep. De output intents, ICC-profiler og tagging, der ligger bag disse flag, er beskrevet i guiden til PDF/A-, PDF/X- og PDF/UA-output med HotPDF

En billig første port for filer, du ikke selv genererede

Ikke hver eneste pipeline er rent generativ. Kunder uploader PDF'er, scannere lægger dem i en mappe, partnere vedhæfter dem til e-mail. At skubbe hver eneste af dem gennem en fuld strukturel validator spilder køtid på filer, der ikke engang vil åbne. HotPDF's Direct File API læser nok af en fils struktur til at besvare "er dette overhovedet en brugbar PDF" uden at indlæse hele objekttræet, hvilket gør det til et godt sted at fejle hurtigt:

function TriagePdf(Pdf: THotPDF; const FileName: string): Boolean;
var
  Handle, Pages: Integer;
begin
  Result := False;
  Handle := Pdf.DAOpenFileReadOnly(FileName, '');
  if Handle <= 0 then
    Exit;  // strukturelt ulæselig: sæt i karantæne, valider ikke
  try
    Pages := Pdf.DAGetPageCount(Handle);
    Result := Pages > 0;
  finally
    Pdf.DACloseFile(Handle);
  end;
end;

To fakta om dette API afgør, hvordan man pakker det ind. Genvejen med flad hukommelse gælder kun for ukrypteret input; giver man DAOpenFileReadOnly en adgangskode, falder den stille tilbage til en fuld parsing, så en fil, man ved er krypteret, bør gå gennem DecryptFile til en almindelig arbejdskopi før triage. Og DAGetPageCount betyder intet på et handle, der ikke åbnede rent, så handle-tjekket forbliver strengt, og et ikke-positivt resultat er en afvisning, ikke et nyt forsøg. Flere af disse mønstre findes i Direct File API-artiklen til store PDF-workflows

veraPDF, kørt som en del af build'en

For alt, du hævder er PDF/A eller PDF/UA, er veraPDF den validator, man skal koble ind. Den kører uden GUI, tager en batch, udsender XML eller JSON og navngiver hver fejl efter dens ISO-paragraf, så en regelfejl mod ISO 19005-1, klausul 6.2.2, peger direkte tilbage på en generatorindstilling i stedet for at lade dig gætte. At styre den fra Delphi er almindelig proceskontrol:

function RunVeraPdf(const PdfFile, ReportFile: string): Cardinal;
var
  Cmd: string;
  SI: TStartupInfo;
  PI: TProcessInformation;
begin
  Cmd := Format('cmd /c verapdf.bat --format xml "%s" > "%s"',
    [PdfFile, ReportFile]);
  FillChar(SI, SizeOf(SI), 0);
  SI.cb := SizeOf(SI);
  if not CreateProcess(nil, PChar(Cmd), nil, nil, False,
      CREATE_NO_WINDOW, nil, nil, SI, PI) then
    RaiseLastOSError;
  try
    WaitForSingleObject(PI.hProcess, 120000);  // afgræns ventetiden per fil
    GetExitCodeProcess(PI.hProcess, Result);
  finally
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

Den timeout tjener sin plads. En fejlformet fil kan drive enhver parser ind i et hjørne, den aldrig kommer ud af igen, og en åben-ende ventetid inde i en køworker trækker resten af køen ned med sig. Afgræns ventetiden, giv en timeout sin egen fejlkode, og læg filen til side til et menneske. Når man læser resultatet, så parse XML'en efter regel-identifikatorer, ikke efter den menneskelæsbare tekst. Regel-ID'er overlever validator-opgraderinger; ordlyden af beskederne gør ikke, og en stabil kode er noget, en supportmedarbejder kan søge gamle sager op imod

Hvordan man kører batchen betyder lige så meget som, om hver fil består. Én proces per fil, ikke én per batch, så et giftigt input kun koster den fils timeout og intet andet. Loft antallet af validator-processer ved kerneantallet, fordi det at bygge XML-rapporten er CPU-bundet, og oversubskription bare fører til trashing. Og sæt et størrelsesloft ved indtag, fordi en to-gigabyte scannet bog vil eje køen, uanset hvor tålmodig parseren er. Intet af det er preflight i streng forstand. Det er forskellen mellem en port, der overlever måned-slut-volumen, og én, der bliver slukket den første nat, den går i stå i pipelinen klokken 2 om natten

Diagram over en Delphi batchport, der kører én veraPDF-proces pr. PDF under en afgrænset timeout, miner XML rule-ID'er i stedet for beskeder og arkiverer hver rapport ved siden af sin fil
En intake-vagt begrænser købelastningen, mens én frisk veraPDF-worker pr. fil forhindrer et giftigt input i at blokere buildet

PDF/X er, hvor dette kommer til kort. veraPDF validerer den ikke, så det arbejdende tjek er stadig Acrobats Preflight med den ISO 15930-profil, din trykker navngav. Acrobat vil have et menneske, hvilket betyder stikprøver frem for fuld dækning: den første fil fra en ny skabelon, plus et lille tilfældigt udtræk fra hver batch, mens den automatiserede port håndterer alt, der kan håndteres uden et menneske. Et stikprøvetjek, der rent faktisk kører, slår en komplet automatisering, der forbliver halvfærdig for evigt

En rapport, man stadig vil ønske sig om et år

En preflight-port betaler sig to gange. Én gang, når den standser en dårlig fil ved døren, og igen meget senere, når nogen spørger, hvorfor en bestemt fil blev lukket igennem. Det andet øjeblik er det, der bør diktere formatet, fordi det er det, hvor en tynd rapport lader dig sidde fast. For hver fil, der tjekkes, gem input-hashen, generatorens compliance-flag og biblioteksversion fra log-linjen ovenfor, validatorens navn og version, den profil, den blev tjekket mod, bestået eller ej, og de fejlede regel-ID'er med sidetal, hvor validatoren giver dem. Gem den rapport ved siden af den fil, den beskriver. Læg den i et separat system, og det system vil blive udfaset, før det arkiv, det dokumenterer

Undtagelser skal også skrives ned. Når en kunde insisterer på at sende en fil, porten ikke bryder sig om, er svaret ikke at løsne reglen for alle. Registrér hvem der godkendte den fil, på hvilket grundlag, og indtil hvilken dato, og hæft så det dispensationsbevis på dens rapport. En dispensation med et navn og en udløbsdato er en beslutning, nogen ejer. Et tjek, der er kommenteret ud "midlertidigt", er en hændelse, der venter på sin dato

Endnu en vane betaler sig selv: når en fil fejler, kopiér den ind i en navngivet regressionsmappe, før nogen rører den. Næsten hvert eneste preflight-problem, det er værd at debugge, kan spores tilbage til ét bestemt input, og de teams, der holder fast i de inputs, fikser gentagelsen på en time i stedet for at vente på, at den dukker op igen i produktion. Compliance-egenskaberne og Direct File API'et, der vises her, er en del af HotPDF Delphi Component til Delphi og C++Builder, hvis dokumentation dækker hvert kald i sin helhed