Du leverer en konverter som merker hver fil som PDF/A-1b, kundens arkivsystem tar dem inn i ett år, og så kjører en revisjon hele batchen gjennom veraPDF og en tredel kommer tilbake som ikke samsvarende. Ingenting krasjet, ingen unntak ble kastet, filene åpner seg fint i alle visere på skrivebordet ditt. De var bare ikke standarden du stemplet på dem. Dette er den normale feilmodusen for arkiv-PDF, og det er grunnen til at «vi satte flagget» aldri er det samme som «den validerer»
Det første du må forstå om PDFium og PDF/A er at motoren ikke har noe med dette å gjøre. PDFium rendrer, parser og skriver PDF, men det offentlige grensesnittet har verken ConvertToPDFA, en OutputIntent-skriver eller et XMP-API. Hele delen som står for arkivkompatibiliteten, XMP-pakken, OutputIntent og ICC-profilen, katalogmarkørene og valideringen, ligger i PDFiumPas selv, i en om lag 2 000 linjer lang ren Pascal-enhet (FPdfPdfa.pas) som parser den lagrede byte-strømmen og skriver den om gjennom en inkrementell oppdatering. Å vite hvor arbeidet skjer forteller deg hvor feilene skjuler seg, og de skjuler seg ikke i PDFium
Hva PDF/A faktisk krever, og hvor det slår inn
PDF/A er ikke ett format. ISO 19005 definerer tre deler (PDF/A-1, -2, -3) og, innen hver av dem, samsvarsnivåer som lover ulike ting. Nivå B (grunnleggende) garanterer bare at den visuelle fremstillingen kan gjenskapes. Nivå A (tilgjengelig) legger til et tagget strukturtre og Unicode-mapping oppå B. Nivå U, som bare finnes for del 2 og 3, ligger mellom dem: pålitelig Unicode-tekst uten hele strukturtreet. ISO 19005-1 har ikke nivå U, en begrensning biblioteket koder direkte inn
En håndfull av formatets regler er de som faktisk slår inn i praksis. Kryptering er rett og slett forbudt (ISO 19005-1 §6.1.3 og etterfølgerne): en PDF/A-fil kan ikke ha en /Encrypt-ordbok. Dokumentet må erklære en gjengivelsesbetingelse gjennom en OutputIntent, og destinasjonen må være en gyldig ICC-profil (§6.2.3.2). Selve samsvarserklæringen må ligge som XMP-metadata under PDF/A-identifikasjonsskjemaet. Nivå A krever i tillegg §6.8, den logiske strukturen, taggetreet som gjør dokumentet maskinlesbart. Bommer du på noe av dette, avviser en samsvarsverifiserer filen selv om den gjengis perfekt
Det ene kallet som lager et arkiv
PDFiumPas eksponerer hele pipelinen bak TPdf.SaveAsPdfA. Den enkle varianten tar et målsamsvarsnivå og bruker som standard PDF/A-1b, som er riktig valg for det vanlige tilfellet «gjør dette lesbart for alltid»
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('invoice.pdf');
// Default conformance is pac1b (PDF/A-1b)
if Pdf.SaveAsPdfA('invoice_archive.pdf') then
// file now carries XMP, sRGB OutputIntent, and catalog markers
else
raise Exception.Create('PDF/A save failed');
finally
Pdf.Free;
end;
end;
Bak kulissene er dette et totrinns løp. SaveAsPdfA ber først PDFium serialisere dokumentet med FPDF_SaveAsCopy, og sender deretter byte-strømmen til InjectPdfAMarkers, som legger til XMP-metadata, sRGB OutputIntent med den innebygde ICC-profilen og en omskrevet katalog som en inkrementell oppdatering. Kilden leses fra posisjon null og målet skrives fra posisjon null; det opprinnelige objekt-treet blir liggende urørt, og markørene kommer inn etter den eksisterende %%EOF. Trenger du bytes i stedet for en fil, tar SaveAsPdfAToStream en TStream med de samme opsjonene
Velg samsvarsnivået med opsjonsrecorden
For å målrette en bestemt del og et bestemt nivå sender du en TPdfASaveOptions-record. Feltet Conformance tar en TPdfAConformance-verdi. Oppregningen dekker alle gyldige kombinasjoner og ingenting annet: pac1b, pac1a for del 1; pac2b, pac2u, pac2a for del 2; pac3b, pac3u, pac3a for del 3, pluss pacUnknown og pacNone for valideringssiden. Det finnes ingen pac1u, fordi det nivået ikke finnes i standarden
var
Pdf: TPdf;
Opts: TPdfASaveOptions;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('report.pdf');
Opts := TPdfASaveOptions.Default;
Opts.Conformance := pac2u; // PDF/A-2u: reliable Unicode text
Opts.Title := 'Quarterly Report 2026';
Opts.Author := 'Finance';
// Leave IccProfileData empty to use the built-in sRGB IEC61966-2.1 profile
if not Pdf.SaveAsPdfA('report_a2u.pdf', Opts) then
raise Exception.Create('PDF/A-2u save failed');
finally
Pdf.Free;
end;
end;
Det meste av recorden kan stå tomt. La Title, Author, Subject, Keywords, Creator og Producer stå blanke, så fyller SaveAsPdfA dem automatisk fra dokumentets Info-ordbok via FPDF_GetMetaText. La CreationDate og ModDate stå blanke, så bruker den gjeldende UTC-tiden for begge XMP-datoene. La DocumentId og InstanceId stå blanke, så forhåndssetter biblioteket dem fra FPDF_GetFileIdentifier, med en deterministisk reserve-ID utledet fra kildebytene. Det eneste feltet du kanskje bevisst vil overstyre, er IccProfileData: tomt betyr den innebygde sRGB IEC61966-2.1-profilen, men en CMYK- eller gråtone-arbeidsflyt bør levere sin egen
Hvorfor nivå A nedgraderes, og hvorfor det er det ærlige valget
Her er en nyanse som feller folk som forventer at et flagg skal være en garanti. Du kan be om pac1a på et dokument som ikke har noe tagget strukturtre, men PDF/A-1a krever §6.8, den logiske strukturen, og biblioteket kan ikke trylle frem et strukturtre av en utagget PDF. I stedet for å skrive ut en fil som hevder nivå A mens den ikke oppfyller det, sjekker SaveAsPdfA om det faktisk finnes en tagget struktur (/StructTreeRoot pluss /MarkInfo med /Marked true) og nedgraderer påstanden hvis den mangler: pac1a blir til pac1b, pac2a blir til pac2b, og så videre for alle tre delene. De interne hjelpefunksjonene er PdfAIsLevelA og PdfADowngradeToLevelB
Resonnementet er verdt å si rett ut: en fil som ærlig oppgir nivået den faktisk oppfyller, er mer nyttig enn en som lyver om et nivå den ikke har. Nivå U håndteres annerledes. Å oppdage ekte Unicode-dekning ville kreve en naiv «har den /ToUnicode»-test, og det ville nedgradere legitime dokumenter for langt, fordi WinAnsi og lignende kodinger er unntatt, så lagringssiden sender U-kravet slik kalleren oppga det og lar avviket bli fanget på valideringssiden i stedet. Hvis du trenger et garantert nivå A-arkiv, må du tagge dokumentet før du konverterer det; konverteren finner ikke på struktur som ikke er der
ICC-fellen bare en ekte validerer fanger
Dette er feilen som lærte den hardeste leksen, fordi bibliotekets egen sjekk godkjente den mens veraPDF, ISO 19005-referansevalidatoren, ikke gjorde det. PDF/A krever at OutputIntent-destinasjonsprofilen er en gyldig ICCBased-strøm, og §6.2.3.2 krever at en verifikator validerer den strømmen som et fargerom. En ICCBased-strøm må erklære /N, antallet fargekomponenter. En tidlig versjon av injektoren skrev ICC-strømordboken med bare /Length og uten /N, og veraPDF avviste resultatet med "The N entry (value null)... is missing"
Det som gjorde det lumsk, var at avvisningen bare slo inn for PDF/A-1b og -1a. Samsvarmodellene for del 2 og del 3 kjørte ikke akkurat den kontrollen på destinasjonsprofilen, så den identiske injiserte strukturen validerte under pac2b, pac3b og pac2u, men feilet under pac1b på ingenting annet enn pdfaid:part-verdien. En enhetstest kunne aldri se det, fordi bibliotekets egen ValidatePdfACompliance bare sjekket at /DestOutputProfile-nøkkelen fantes, ikke hva som lå i strømordboken. Interne tester forble grønne, mens den reelle arkivvalideringen feilet
Fiksen er IccComponentCount, som leser datafargerom-signaturen ved offset 16 i ICC-hodet og mapper den til et komponentantall: GRAY er 1, RGB , Lab og XYZ er 3, CMYK er 4, og en ukjent profil faller tilbake til 3. Det tallet går inn i strømordboken som /N. Det beregnes, ikke hardkodes til 3, slik at en kallende som leverer en CMYK- eller gråtoneprofil via IccProfileData fortsatt får riktig verdi. Den bredere lærdommen er metodisk: den interne sjekken og en autoritativ validator har hver sine blindsoner, og PDF/A-output må testes ende til ende mot en referanseimplementering som veraPDF i stedet for å stole på selvkontroller. Den samme disiplinen for inkrementelle oppdateringer som ligger bak rene arkiver, er dekket i validering av komprimerte objekt- og xref-strømmer, noe som betyr noe fordi moderne PDF-er injektoren behandler ofte er bygget på kryssreferansestrømmer
Kryptering, xref-strømmer og andre grenser
Fordi ISO 19005 forbyr kryptering, fjerner lagringsstien den før skriving. SaveAsPdfA bruker FPDF_REMOVE_SECURITY når den serialiserer, så en kryptert kilde, lastet med passordet sitt, blir dekryptert på vei inn i arkivet. På et ukryptert dokument er dette en ingen-operasjon og endrer ingenting. Konsekvensen er den samme begrensningen HotPDF håndhever fra den andre retningen: én fil kan ikke være både kryptert og PDF/A. Når en arbeidsflyt trenger begge deler, er svaret to filer, en kryptert kopi for distribusjon og en separat ren kopi for arkivet
En annen grense er usynlig til den biter: PDF 1.5+-dokumenter som bruker en ren kryssreferansestrøm og ikke har noe trailer-nøkkelord. Injektoren leser trailer-delen for å finne kildens /Info og legge til den inkrementelle oppdateringen, og den må godta xref-strømformen, ellers ville et slikt dokument blitt kopiert videre med markørene stille droppet. ISO 32000-1 §7.5.6 tillater uttrykkelig at en klassisk trailer-inkrementell oppdatering følger etter et xref-strøm-dokument, med /Prev som peker på xref-strøm-offseten, og det er akkurat den strukturen injektoren skriver ut. PDFiums egen FPDF_SaveAsCopy skriver alltid en klassisk trailer, så i den normale pipelinen møter aldri injektoren en ren xref-strømkilde, men lesestien håndterer den for dokumenter som kommer inn utenfra
Verifiser før du stoler på påstanden
Biblioteket leverer en byte-nivå-sjekker, TPdf.ValidatePdfA, som returnerer en TPdfAValidationResult. Feltet Conformance rapporterer det oppdagede nivået, og Issues er et sett med TPdfAValidationIssue-verdier; hjelpetoden IsCompliant er bare sann når et reelt nivå er oppdaget og settet med problemer er tomt. Kjør den som en rask første port i en batch
var
Pdf: TPdf;
Res: TPdfAValidationResult;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('invoice_archive.pdf');
Res := Pdf.ValidatePdfA;
if Res.IsCompliant then
Writeln('Conformant: detected level ', Ord(Res.Conformance))
else
Writeln('Issues found: ', SizeOf(Res.Issues), ' flags set');
finally
Pdf.Free;
end;
end;
Vær ærlig om hva dette kjøper deg. Byte-nivå-sjekkeren finner strukturelle problemer med høy sikkerhet, som en manglende OutputIntent, en forbudt handling, en tilstedeværende /Encrypt eller transparens der del 1 forbyr det, og fontinnbyggingsdeteksjonen bruker en telleheuristikk som bevisst bare rapporterer et høy-sikkerhetssignal i stedet for å jage dekning per glyf. Det den ikke gjør, er analyse av operatorer i innholdsstrømmen, noe som ville kreve en full content-parser og er bevisst utenfor omfanget. For en releasegate bør du pare den interne sjekken med veraPDF: sjekken er umiddelbar og kjører overalt uten DLL, mens veraPDF er autoritativ. Hvordan du kobler den paringen inn i en batchkjøring, er temaet for CLI-en for batch forhåndsvalideringsrapporter, som er der denne valideringen hører hjemme i en reell arkivarbeidsflyt
API-ene SaveAsPdfA, InjectPdfAMarkers og ValidatePdfA som vises her følger med PDFium Component for Delphi, C++Builder og Lazarus/FPC. Produktsiden peker til hele API-referansen, inkludert hele samsvaroppregningen og opsjonsrecorden bak disse eksemplene