Teknisk artikkel

PDF/A- og PDF/UA-preflight i Delphi med PDF Library for Delphi

PDF/A og PDF/UA besvarer to spørsmål som ikke har noe med hverandre å gjøre, og å behandle dem som én avkrysningsboks for tilgjengelighet og arkivering er slik ødelagte filer havner i et arkiv med samsvarsmerkelapp. PDF/A spør om en fil fortsatt vil gjengis trofast om tjue år. PDF/UA spør om hjelpeteknologi kan lese den i dag. Et dokument kan bestå den ene med glans og stryke på den andre, så den eneste ærlige dommen kommer av å kjøre begge, og av å kjøre dem før filen skrives ut, ikke etter at et system nedstrøms har stolt på samsvarsidentifikatoren som er bakt inn i metadataene. Den identifikatoren er en egenerklæring. Ingenting i formatet krever at den er sann, og en applikasjon som skriver «PDF/A-1b» inn i XMP-en uten å validere mot standarden, produserer en fil som ser samsvarende ut for enhver konsument som bare leser merkelappen. losLab PDF Library (PDF Library for Delphi) lukker det gapet for Delphi og C++Builder ved å bygge begge validatorene inn i biblioteket, slik at kontrollen kjører i prosessen uten en ekstern tjeneste å sette opp

To standarder som feller filer av motsatte grunner

ISO 19005 (PDF/A) er en gjengivelseskontrakt. En konform fil må gjengis identisk om flere tiår på programvare som aldri så systemet som produserte den, så reglene angriper eksterne avhengigheter: hver skrift innebygd, farge forankret i en innebygd ICC OutputIntent eller uttrykt i et enhetsuavhengig rom, ingen kryptering i PDF/A-1, ingen JavaScript, XMP-metadata som stemmer overens med dokumentinformasjonsordboken. ISO 14289 (PDF/UA) er i stedet en semantikkontrakt. Hjelpeteknologi må kunne gå gjennom dokumentet og komme ut med mening, og den bor i et helt annet lag: et komplett strukturtre, alternativ tekst på figurer, en dokumenttittel satt for visning, overskriftsnivåer som ikke hopper, tabellhoderelasjoner som overlever når siden er borte fra skjermen

Fordi de to standardene patruljerer ulike lag, er det filene som ligger mellom dem, som biter deg. Et arkivperfekt dokument kan være stumt for en skjermleser. Et vakkert tagget dokument kan referere til en skrivebordsskrift som ikke vil finnes om ti år. Publisering i offentlig sektor er det vanlige stedet der begge kravene lander samtidig, og en pipeline der kan ikke presse dem sammen til én port. Funnene går til forskjellige folk. Ikke-innebygde skrifter er en defekt i koden som genererer PDF-en, mens manglende alternativ tekst hører til den som eier innholdsmalene, og en rapport som blander de to, blir bare videresendt to ganger

Hvilken del av PDF/A du sikter mot, betyr like mye som om du treffer. PDF/A-1 er frosset på PDF 1.4 og avviser gjennomsiktighet og JPEG2000, som moderne rapportutdata griper til uten å tenke seg om. PDF/A-2 (ISO 19005-2, bygd på ISO 32000-1) godtar begge og er det fornuftige standardvalget for et nytt arkiv. PDF/A-3 går lenger og tillater innebygde filer av enhver type, som er det regulerte e-fakturaformater hviler på. Et team som fortsatt standardiserer på PDF/A-1b i 2026, bærer som regel på et krav noen skrev for femten år siden, og å reforhandle måldelen er ofte billigere enn å strippe gjennomsiktighet ut av hvert diagram systemet sender fra seg

PDF Library for Delphi-diagram som sammenligner PDF/A-reproduksjonskontrakten med PDF/UA-semantikkontrakten, pluss en bestått-ikke-bestått-matrise av dokumenter som tilfredsstiller den ene og stryker på den andre
PDF/A sikrer trofast rendring tiår frem i tid, mens PDF/UA sikrer assistert lesing i dag, og ingen av vurderingene innebærer den andre

Strukturerte funn ved mottak

Inngangspunktet i det flate API-et er CheckFileCompliance, med testvelgeren 1 for PDF/A og 2 for PDF/UA. Den gir tilbake et strenglistehåndtak hvis elementer er enkeltfunn, ett per linje, som er nøyaktig den formen en automatisk port vil gå gjennom:

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 betyr "ingen funn" ELLER "filen kan ikke leses" -- skill dem før du slipper gjennom
    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;

To detaljer avgjør om dette kan kjøre uten tilsyn. Den første er en returverdi som betyr to motsatte ting. CheckFileCompliance returnerer 0 når filen er fullt samsvarende, og også når filen ikke lot seg åpne i det hele tatt, fordi en tom resultatliste internt kollapser til 0 i begge tilfeller. En port som leser 0 som bestått, vinker ødelagte opplastinger rett inn i arkivet, så skill tilfellene med LastErrorCode før du stoler på nullen, slik porten ovenfor gjør. Den andre gjelder hvor filen er i livsløpet sitt. Kontrolløren kjører på bibliotekets strømmende leser i stedet for på den fulle dokumentmodellen, åpner filen direkte med lesedeling og kaller aldri LoadFromFile, og det er derfor den kan tygge seg gjennom inndata på flere gigabyte uten å bygge et objekttre. Den samme strømmende åpningen feiler mens en annen prosess fortsatt holder filen for skriving, og en pågående opplasting er nettopp den tilstanden. Sett porten etter at overføringen er ferdig

Det strømmende designet betaler seg igjen under belastning. Hver kontroll åpner inndataene sine skrivebeskyttet og deler dem for lesing, så en korpusgransking skalerer ut over arbeidertråder eller prosesser med én TPDFlib-instans per arbeider og ingen strid mellom dem. Ressursen som trenger disiplin, er selve håndtaket. Hvert resultat forskjellig fra null fra CheckFileCompliance forblir allokert til du kaller ReleaseStringList, og en langtlevende port som glemmer å frigjøre dem, krasjer ikke, den blør bare minne sakte til noen begynner å lete etter hvorfor

Rapporter for mennesker, differ for byggeporter

En funnliste har rett form for en port og feil form for en e-post til malteamet. CreatePreflightReport gjengir den samme analysen som lesbar prosa, CreatePreflightReportEx legger til en velger for rapportformat, og SavePreflightReport skriver den til disk slik at rapporten kan følge med inne i den leverte dokumentpakken. Mange arkivkontrakter gjør den rapporten til en leveranse i seg selv, ikke bare et internt artefakt

Medlemmet av denne familien som stille fortjener plassen sin, er ComparePreflightReports. Samsvar er en regresjonsflate som enhver annen del av oppførselen. En maljustering, en nylisensiert bedriftsskrift eller en bibliotekoppgradering kan hver for seg innføre et funn som ikke fantes forrige utgivelse, og ingen av dem kunngjør seg selv. Hold gullrapporter for et sett representative dokumenter under versjonskontroll, generer dem på nytt etter hver endring, og kjør ComparePreflightReports for å beregne differansen. En tom diff er et utgivelsesartefakt verdt å ta vare på. Et overraskende funn feller bygget, som er et langt billigere sted å oppdage det enn revisjonen

Å generere utdata som består ved første kjøring

Preflight gjør nytten sin på filer som kommer fra andre steder. For dokumenter din egen kode produserer, er det å finne brudd etter generering og lappe dem tilbake den trege veien rundt. PDF Library for Delphi har en modus på genereringssiden for hver standard, og du kan slå på begge for det samme dokumentet:

PDF Library for Delphi-diagram over skalert preflight-inntak med én strømmende bibliotekforekomst per arbeider, portvoktede åpninger etter at opplastinger er ferdige, og frigitte funnlister
Porter åpner bare etter at overføringer er fullført, hver arbeider streamer inndata skrivebeskyttet gjennom en privat bibliotekinstans, og hvert returnerte håndtak skylder en frigivelse
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: påkrevd for PDF/UA
    // ... tegn tagget innhold her ...
    Diag := Pdf.GetPDFUADiagnostics;
    if Diag <> '' then
      Writeln('fix before shipping: ', Diag);
    Pdf.SaveToFile('statement.pdf');
    // preflighten som teller, kjører på den lagrede filen:
    Writeln(Pdf.CreatePreflightReport('statement.pdf', '', 1, 0));
  finally
    Pdf.Free;
  end;
end;

Fellen gjemmer seg ved lagring. Flere av samsvarsreparasjonene skjer mens dokumentet serialiseres, ikke når du slår på modusen: å tvinge fram utskriftsflagget på annotasjoner, å skrive standardverdien AFRelationship for innebygde filer i PDF/A-3, å normalisere tabulatorrekkefølge og beskrivelser av skjemafelter for PDF/UA. Dokumentet som ligger i minnet, er ikke byte-identisk med det som lander på disk, så den eneste preflight-dommen som betyr noe, er den som beregnes fra den lagrede filen. Valider statement.pdf selv. Ikke slutt deg til samsvar fra objektet som fortsatt ligger i minnet, for de bytene du da ville dømt, er ikke de bytene du leverte

PDF Library for Delphi-diagram over lagringstids konformitetsfikser brukt under serialisering, derfor hører preflight hjemme på den lagrede PDF-filen snarere enn modellen i minnet
Serialisering tvinger gjennom annotasjonsutskriftsflagg, standardiserer AFRelationship og normaliserer tab-rekkefølge, så minneinspeksjon dømmer byte ingen noensinne sender

Fakturascenarioer som bærer maskinlesbar XML ved siden av det visuelle dokumentet, følger mønsteret fra ZUGFeRD og Factur-X, som er bygd på PDF/A-3. Disse bør sette vedleggsrelasjonen eksplisitt med SetPDFA3DefaultAFRelationship, siden ISO 19005-3 krever at hver innebygde fil erklærer sin rolle i forhold til dokumentet. La den stå usatt, og den innebygde XML-en er bare en blob uten uttalt formål, noe validatoren legger merke til

Uavhengige dommere: veraPDF og Acrobat

En produsent bør ikke være eneste dommer over sine egne utdata. Kontrollørene i PDF Library for Delphi gir deg raske, strukturerte dommer i prosessen, som er det du vil ha i den varme kodestien, men utgivelsesporten for en arkivbatch bør likevel sende utdataene gjennom en validator ingen på teamet ditt har skrevet. veraPDF er referanseimplementasjonen for PDF/A som vedlikeholdes av fellesskapet, og verktøyet de fleste arkiver navngir i aksepteringskriteriene sine, så det er det man skal stemme overens med. Acrobats preflight-profiler er en nyttig oppmann når veraPDF og kontrollen i prosessen er uenige. Noter validatorens navn og versjon ved siden av hver lagrede rapport. En påstand om at en fil besto veraPDF, sier svært lite uten byggenummeret som lot den bestå, siden verktøyet strammer reglene sine mellom utgivelser

Validatorer er faktisk uenige i randsonene av standardene, og når de er det, er svaret ikke å velge det verktøyet du liker. Krymp filen til et minimalt utvalg som fortsatt utløser uenigheten, og les den mot standardteksten. En time med det avdekker som regel én av to ting: en ekte verktøyfeil verdt å melde oppstrøms, eller en klausul teamet ditt har lest galt og bør skrive ned i samsvarsnotatene, så neste person slipper å ta omkampen

Kryptert inndata får en snarvei. Begge kontrollørene tar et passordargument, men en PDF/A-1-fil med en krypteringsordbok er allerede ikke-konform, fordi ISO 19005-1 forbyr kryptering rett ut, så en kryptert innsending kan avvises før noen dypere analyse kjører. Å finne ut hva en krypteringsordbok faktisk gir, er en oppgave for seg, dekket i gransking av PDF-kryptering og -tillatelser

PDF/UA-funn kan nesten alltid spores tilbake til hvordan strukturtreet ble forfattet i utgangspunktet, og taggeteknikkene bak det finnes i å bygge strukturtrær for tagget PDF i Delphi. Arkiver som også krever digitale signaturer, bør pare denne porten med arbeidsflyten i PAdES-signering og -validering. Den fullstendige API-referansen for preflight finnes på produktsiden for losLab PDF Library for Delphi