Teknisk artikel

PDF/A och PDF/UA Preflight i Delphi med PDFlibPas

PDF/A och PDF/UA besvarar två frågor som inte har något med varandra att göra, och att behandla dem som en och samma kryssruta för tillgänglighet-och-arkivering är anledningen till att trasiga filer når ett arkiv bärande en efterlevnadsetikett (compliance label). PDF/A frågar om en fil fortfarande kommer att renderas troget om tjugo år. PDF/UA frågar om hjälpmedelsteknik (assistive technology) kan läsa den idag. Ett dokument kan passera den ena klockrent (cold) och misslyckas med den andra, så det enda ärliga utslaget (verdict) kommer från att köra båda, och från att köra dem innan filen skrivs ut, inte efter att ett nedströms system litar på överensstämmelseidentifieraren (conformance identifier) inbakad i dess metadata. Den identifieraren är en självdeklaration. Inget i formatet kräver att den är sann, och en applikation som skriver "PDF/A-1b" i XMP-datan utan att validera mot standarden producerar en fil som ser kompatibel ut för varje konsument som bara läser etiketten. losLab PDF Library (PDFlibPas) stänger det gapet för Delphi och C++Builder genom att bygga in båda validatorerna i biblioteket, så kontrollen körs in-process utan att någon extern tjänst behöver sättas upp (stand up)

Två standarder som underkänner filer av motsatta skäl

ISO 19005 (PDF/A) är ett reproduktionskontrakt. En kompatibel fil måste renderas identiskt årtionden från nu på programvara som aldrig såg systemet som producerade den, så reglerna angriper externa beroenden: varje typsnitt inbäddat, färg förankrad i en inbäddad ICC OutputIntent eller uttryckt i en enhetsoberoende rymd (device-independent space), ingen kryptering i PDF/A-1, inget JavaScript, XMP-metadata som överensstämmer med dokumentinformations-ordboken. ISO 14289 (PDF/UA) är istället ett semantiskt kontrakt. Hjälpmedelsteknik måste kunna gå igenom dokumentet och komma därifrån med mening, vilket lever i ett helt annat lager: ett komplett strukturträd, alternativ text på figurer, en dokumenttitel satt för visning, rubriknivåer som inte hoppar över, tabellhuvudsrelationer (table header relationships) som överlever när sidan väl är utanför skärmen

Eftersom de två standarderna övervakar (police) olika lager, är filerna som biter dig de som sitter mellan dem. Ett arkivperfekt dokument kan vara tyst för en skärmläsare. Ett vackert taggat dokument kan referera till ett skrivbordstypsnitt som inte kommer att existera om tio år. Publicering inom offentlig sektor är den vanliga platsen där båda kraven landar på en gång, och en pipeline där kan inte kollapsa dem i en enda grind (gate). Resultaten går till olika personer. Oinbäddade typsnitt är en defekt i koden som genererar PDF:en, medan saknad alternativ text tillhör den som äger innehållsmallarna (content templates), och en rapport som blandar de två blir bara vidarebefordrad två gånger

Vilken del av PDF/A du siktar på spelar lika stor roll som ifall du träffar den. PDF/A-1 är fryst på PDF 1.4 och avvisar transparens och JPEG2000, vilka båda moderna rapporteringsutdata sträcker sig efter utan att tänka. PDF/A-2 (ISO 19005-2, byggd på ISO 32000-1) accepterar båda och är den vettiga (sane) standarden för ett nytt arkiv. PDF/A-3 går längre och tillåter inbäddade filer av vilken typ som helst, vilket är vad reglerade format för e-fakturering förlitar sig på. Ett team som fortfarande standardiserar på PDF/A-1b 2026 bär vanligtvis på ett krav som någon skrev för femton år sedan, och att omförhandla måldelen (target part) är ofta billigare än att plocka bort (stripping) transparens från varje diagram systemet sänder ut (emits)

Strukturerade resultat vid inläsningstiden (ingestion time)

Den platta API:ns (flat-API) ingångspunkt är CheckFileCompliance, med testväljaren 1 för PDF/A och 2 för PDF/UA. Den räcker tillbaka ett string-list-handtag vars objekt är individuella resultat (findings), en per rad, vilket är exakt formen en automatiserad grind vill gå igenom:

function GateArchiveUpload(Pdf: TPDFlib; const FileName: string): Boolean;
var
  ListId, I: Integer;
begin
  ListId := Pdf.CheckFileCompliance(FileName, '', 1, 0);  // 1 = PDF/A
  if ListId = 0 then
  begin
    // 0 means "no findings" OR "file unreadable" -- disambiguate before passing
    Result := Pdf.LastErrorCode = 0;
    Exit;
  end;
  for I := 0 to Pdf.GetStringListCount(ListId) - 1 do
    LogFinding(FileName, Pdf.GetStringListItem(ListId, I));
  Pdf.ReleaseStringList(ListId);
  Result := False;
end;

Två detaljer avgör om detta körs oövervakat. Det första är ett returvärde som betyder två motsatta saker. CheckFileCompliance returnerar 0 när filen är helt kompatibel och även när filen inte kunde öppnas alls, eftersom en tom resultatlista internt kollapsar till 0 i båda fallen. En grind som läser 0 som godkänt kommer att vinka in korrupta uppladdningar rakt in i arkivet, så klargör (disambiguate) med LastErrorCode innan du litar på nollan, precis som grinden ovan gör. Det andra rör var filen befinner sig i sin livscykel. Kontrolleraren körs på bibliotekets strömmande läsare istället för den fullständiga dokumentmodellen, öppnar filen direkt med läsdelning (read sharing) och anropar aldrig LoadFromFile, vilket är varför den kan tugga sig igenom multigigabyte-inmatning utan att bygga ett objektträd. Samma strömmande öppning misslyckas medan en annan process fortfarande håller filen för skrivning, och en pågående uppladdning är precis det tillståndet. Ställ in grinden (gate) efter att överföringen slutförts

Den strömmande designen lönar sig återigen under belastning. Varje kontroll öppnar sin inmatning skrivskyddat och delar den för läsning, så en corpus-revision (corpus audit) skalar ut över worker-trådar eller processer med en TPDFlib-instans per worker och ingen konflikt (contention) mellan dem. Resursen som kräver disciplin är handtaget självt. Varje icke-noll-resultat från CheckFileCompliance förblir allokerat tills du anropar ReleaseStringList, och en långkörd grind som glömmer att släppa dem kraschar inte, den blöder bara minne långsamt tills någon letar efter varför

Rapporter för människor, diffar för bygg-grindar (build gates)

En resultatlista är rätt form för en grind och fel form för ett e-postmeddelande till mallteamet. CreatePreflightReport renderar samma analys som läsbar prosa, CreatePreflightReportEx lägger till en rapportformatväljare, och SavePreflightReport skriver det till disk så att rapporten kan resa inuti det levererade dokumentpaketet. Många arkivkontrakt gör den rapporten till en leverabel i sin egen rätt, inte bara en intern artefakt

Medlemmen i denna familj som förtjänar sin plats i tysthet är ComparePreflightReports. Överensstämmelse (compliance) är en regressionsyta (regression surface) precis som vilken annan del av beteende som helst. En malljustering (template tweak), ett nyligen licensierat företagstypsnitt, eller en biblioteksuppgradering kan alla introducera ett resultat (finding) som inte fanns där vid förra releasen, och ingen av dem tillkännager sig själv. Håll gyllene rapporter (golden reports) för en uppsättning representativa dokument under versionskontroll, regenerera dem efter varje ändring, och kör ComparePreflightReports för att beräkna deltan. En tom diff är en releaseartefakt värd att behålla. Ett överraskande resultat får bygget att misslyckas, vilket är en mycket billigare plats att upptäcka det på än i revisionen (audit)

Att generera utdata som passerar på första körningen

Preflight tjänar sitt uppehälle (earns its keep) på filer som anländer någon annanstans ifrån. För dokument som din egen kod producerar är att hitta överträdelser efter generering och patcha tillbaka dem i den långsamma vägen runt. PDFlibPas bär ett läge (mode) på genereringssidan för varje standard, och du kan slå på båda för samma dokument:

var
  Pdf: TPDFlib;
  Diag: WideString;
begin
  Pdf := TPDFlib.Create;
  try
    Pdf.NewDocument;
    Pdf.SetPDFAMode(1);
    Pdf.LoadOutputIntentProfile('sRGB-IEC61966-2.1.icc', 'RGB');
    Pdf.SetPDFUAMode('en-US');
    Pdf.SetInformation(1, 'Quarterly Statement');  // /Title: required for PDF/UA
    // ... draw tagged content here ...
    Diag := Pdf.GetPDFUADiagnostics;
    if Diag <> '' then
      Writeln('fix before shipping: ', Diag);
    Pdf.SaveToFile('statement.pdf');
    // the preflight that counts runs on the saved file:
    Writeln(Pdf.CreatePreflightReport('statement.pdf', '', 1, 0));
  finally
    Pdf.Free;
  end;
end;

Fällan gömmer sig vid spartillfället. Flera av överensstämmelsereparationerna (conformance repairs) sker medan dokumentet serialiseras snarare än när du aktiverar läget: att tvinga fram utskriftsflaggan på annotationer, skriva standard-AFRelationship för PDF/A-3 inbäddade filer, normalisera tabbordning och formulärfältsbeskrivningar för PDF/UA. Dokumentet som sitter i minnet är inte byte-identiskt med det som landar på disken, så det enda preflight-utslag som betyder något är det som beräknas från den sparade filen. Validera själva statement.pdf. Härled (infer) inte efterlevnad från objektet som fortfarande är i minnet, eftersom de byten du skulle bedöma inte är de byten du skeppade

Faktureringsscenarier som bär maskinläsbar XML bredvid det visuella dokumentet följer mönstret för ZUGFeRD och Factur-X, vilket bygger på PDF/A-3. De bör ställa in bilagans relation (attachment relationship) explicit med SetPDFA3DefaultAFRelationship, eftersom ISO 19005-3 kräver att varje inbäddad fil deklarerar sin roll i förhållande till dokumentet. Lämna det oinställt och den inbäddade XML:en är bara en blob utan något angivet syfte, vilket validatorn lägger märke till

Oberoende domare (referees): veraPDF och Acrobat

En producent bör inte vara den enda domaren över sin egen utmatning. PDFlibPas-kontrollerarna (checkers) ger dig snabba, strukturerade utslag (verdicts) in-process, vilket är vad du vill ha på den heta vägen, men release-grinden (release gate) för en arkivbatch bör fortfarande skicka utmatningen genom en validator som ingen i ditt team skrev. veraPDF är den gemenskapsunderhållna (community-maintained) referensimplementeringen för PDF/A och verktyget de flesta arkiv namnger i sina acceptanskriterier, så det är det man bör matcha. Acrobats preflight-profiler utgör en användbar utslagsröst (tiebreaker) när veraPDF och in-process-kontrollen är oense. Registrera datornamnet och dess version bredvid varje sparad rapport. Ett påstående om att en fil passerade veraPDF säger väldigt lite utan det byggnummer som godkände den, eftersom verktyget stramar åt sina regler mellan releaser

Validatorer är oense vid standardernas kanter (edges), och när de är det är svaret inte att välja det verktyg du gillar. Krymp (shrink) filen till ett minimalt prov som fortfarande utlöser oenigheten och läs den mot standardtexten. En timme av det för vanligtvis fram en av två saker till ytan: en genuin verktygsbugg värd att lämna in (filing) uppströms (upstream), eller en klausul (clause) ditt team har läst fel och bör skriva ner i efterlevnadsanteckningarna (compliance notes) så att nästa person inte processar den igen (relitigate it)

Krypterad inmatning får en genväg. Båda kontrollerarna tar ett lösenordsargument, men en PDF/A-1-fil med en krypteringsordbok (encryption dictionary) är redan icke-kompatibel, eftersom ISO 19005-1 förbjuder kryptering helt och hållet, så en krypterad inskickning (submission) kan avvisas innan någon djupare analys körs. Att räkna ut vad en krypteringsordbok faktiskt beviljar (grants) är sin egen uppgift, som täcks i PDF-kryptering och granskning av behörigheter

PDF/UA-resultat (findings) spåras nästan alltid tillbaka till hur strukturträdet (structure tree) författades (authored) i första hand, och taggningsteknikerna (tagging techniques) bakom det lever i att bygga taggade PDF-strukturträd i Delphi. Arkiv som också kräver digitala signaturer bör para ihop denna grind (gate) med arbetsflödet i PAdES-signering och validering. Den fullständiga preflight API-referensen (API reference) lever på produktsidan för losLab PDF Library for Delphi