Odborný článok

PDF/A Archival Compliance in Delphi with PDFium VCL

Nasadíte konvertor, ktorý označí každý súbor ako PDF/A-1b, zákazníkov systém evidencie ho rok ingestuje a potom audit pošle celú dávku cez veraPDF, pričom tretina sa vráti ako nezhodná. Nič nespadlo, nevyhodila sa žiadna výnimka a súbory sa na každom prehliadači na vašom stole otvárajú bez problémov. Len jednoducho nespĺňali štandard, ktorý ste na ne natlačili. Toto je bežný spôsob zlyhania archivného PDF, a preto tvrdenie „nastavili sme príznak“ nikdy nie je to isté ako „validuje to“

Prvá vec, ktorú treba pri PDFium a PDF/A pochopiť, je, že samotný engine s tým nemá nič spoločné. PDFium vykresľuje, parsuje a zapisuje PDF, ale jeho verejný povrch nemá žiadne ConvertToPDFA, no OutputIntent writer, no XMP API. Every part of archival conformance, the XMP packet, the OutputIntent and its ICC profile, the catalog markers, the validation, lives in PDFiumPas itself, in a roughly 2,000-line pure-Pascal unit (FPdfPdfa.pas) s približne 2 000 riadkami čistého Pascalu, ktorá parsuje uložené bajty a prepíše ich inkrementálnou aktualizáciou. Keď viete, kde sa táto práca deje, viete aj, kde sa skrývajú chyby, a neskrývajú sa v PDFium

Čo PDF/A skutočne vyžaduje a kde to bolí

PDF/A nie je jeden formát. ISO 19005 definuje tri časti PDF/A-1, PDF/A-2 a PDF/A-3 a v rámci každej z nich úrovne zhody, ktoré sľubujú odlišné veci. Úroveň B zaručuje iba reprodukovateľný vizuálny vzhľad. Úroveň A nad ňu pridáva tagovanú štruktúru a Unicode mapovanie. Úroveň U, ktorá existuje len pre časti 2 a 3, sedí medzi nimi: spoľahlivý Unicode text bez plného stromu štruktúry. ISO 19005-1 úroveň U nemá a knižnica toto obmedzenie kóduje priamo

A handful of the format's rules are the ones that bite in practice. Encryption is forbidden outright (ISO 19005-1 §6.1.3 and its successors): a PDF/A file cannot carry an /Encrypt dictionary. Dokument musí deklarovať výstupné podmienky vykreslenia pomocou OutputIntent, ktorého cieľom je platný ICC profil. Samotné tvrdenie o zhode sa musí objaviť ako XMP metadáta v schéme identifikácie PDF/A. Úroveň A navyše vyžaduje logickú štruktúru, strom tagov, ktorý robí dokument strojovo čitateľným. Ak čokoľvek z toho chýba, overovač zhody súbor odmietne, aj keď sa vykresľuje dokonale

Jediné volanie, ktoré vytvorí archív

PDFiumPas exposes the whole pipeline behind TPdf.SaveAsPdfA. Jednoduché preťaženie berie cieľovú zhodu a predvolene používa PDF/A-1b, čo je správny default pre bežný prípad „urob z tohto niečo, čo sa bude dať navždy vykresliť“

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;

Pod kapotou ide o dvojkrok. SaveAsPdfA first asks PDFium to serialize the document with FPDF_SaveAsCopy, then hands that byte stream to InjectPdfAMarkers, ktorá pripojí XMP metadáta, sRGB OutputIntent s vloženým ICC profilom a prepísaný katalóg formou inkrementálnej aktualizácie. Zdroj sa číta od pozície nula a cieľ sa zapisuje od pozície nula. Pôvodný strom objektov zostane nedotknutý a markery sa len pripoja za existujúci %%EOF. If you need the bytes rather than a file, SaveAsPdfAToStream takes a TStream and the same options

Voľba zhody pomocou záznamu options

Ak chcete cieliť konkrétnu časť a úroveň, odovzdajte záznam TPdfASaveOptions record. Its Conformance field takes a TPdfAConformance value. The enumeration covers every valid combination and nothing else: pac1b, pac1a for part 1; pac2b, pac2u, pac2a for part 2; pac3b, pac3u, pac3a for part 3, plus pacUnknown and pacNone for the validation side. There is no pac1u, pretože taká úroveň v štandarde neexistuje

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;

Väčšinu záznamu môžete nechať prázdnu. Pole Title, Author, Subject, Keywords, Creator, and Producer nechajte prázdne a SaveAsPdfA ich automaticky doplní zo slovníka Info dokumentu cez FPDF_GetMetaText. Nechajte CreationDate and ModDate prázdne a použije aktuálny čas UTC pre oba dátumy v XMP. Nechajte DocumentId and InstanceId prázdne a knižnica ich predvyplní z FPDF_GetFileIdentifier, s fallbackom na deterministické ID odvodené zo zdrojových bajtov. Pole, ktoré možno budete chcieť prepísať zámerne, je IccProfileData: prázdne znamená pribalený profil sRGB IEC61966-2.1, ale workflow CMYK alebo grayscale by mal dodať vlastný

Prečo sa úroveň A degraduje a prečo je to poctivá voľba

Tu je jemnosť, ktorá zaskočí ľudí čakajúcich, že príznak znamená záruku. Môžete požiadať o pac1a on a document that has no tag tree, but PDF/A-1a requires §6.8 logical structure, and the library cannot manufacture a structure tree out of an untagged PDF. Rather than emit a file that claims Level A while failing it, SaveAsPdfA checks for a real tagged structure (/StructTreeRoot plus /MarkInfo with /Marked true) and, if it is absent, downgrades the claim: pac1a becomes pac1b, pac2a becomes pac2b a tak ďalej naprieč všetkými tromi časťami. Interné helpery sú PdfAIsLevelA and PdfADowngradeToLevelB

Dôvod stojí za to povedať priamo: súbor, ktorý poctivo deklaruje úroveň, ktorú skutočne spĺňa, je užitočnejší než súbor, ktorý klame o úrovni, ktorú nespĺňa. Pri úrovni U je situácia iná. Overiť skutočné pokrytie Unicode by znamenalo naivný test „má to /ToUnicode“, ktorý by zbytočne degradoval legitímne dokumenty, preto save strana zapisuje tvrdenie U presne tak, ako ho zadal volajúci, a prípadný rozpor necháva odhaliť validačnej strane. Ak potrebujete garantovaný archív Level A, otagujte dokument ešte pred konverziou. Konvertor nevymyslí štruktúru, ktorá v súbore nie je

Úskalie ICC, ktoré odhalí len skutočný validátor

Toto zlyhanie prinieslo najtvrdšiu lekciu, pretože vlastný checker knižnice ním prešiel, kým veraPDF, referenčný validátor ISO 19005, nie. PDF/A vyžaduje, aby cieľový profil v OutputIntent bol platný stream ICCBased. Stream ICCBased musí deklarovať /N, the number of colour components. An early version of the injector wrote the ICC stream dictionary with only /Length and no /N, a veraPDF výsledok odmietlo s hláškou, že položka N chýba

Zákernosť spočívala v tom, že odmietnutie sa objavilo iba pri PDF/A-1b a PDF/A-1a. Modely zhody pre časť 2 a časť 3 túto konkrétnu kontrolu na destination profile nespúšťali, takže identická injektovaná štruktúra validovala pod pac2b, pac3b, and pac2u but failed under pac1b on nothing more than the pdfaid:part. Jednotkový test to nemohol odhaliť, pretože vlastné ValidatePdfACompliance only checked that the /DestOutputProfile kontrolovalo len to, či kľúč

existuje, nie čo je vo vnútri slovníka streamu. Interné testy zostali zelené, ale reálna archivná validácia zlyhala.IccComponentCountOprava je GRAY is 1, RGB , Lab , and XYZ are 3, CMYK is 4, with an unknown profile defaulting to 3. That count goes into the stream dictionary as /N. It is computed, not hard-coded to 3, so that a caller who supplies a CMYK or grayscale profile through IccProfileData, takže aj volajúci, ktorý dodá vlastný profil CMYK alebo grayscale cez , dostane správnu hodnotu. Širšia lekcia je metodická: checker v knižnici aj autoritatívny validátor majú každý svoje slepé miesta a výstup PDF/A treba testovať end-to-end proti referenčnej implementácii, napríklad veraPDF, nie dôverovať iba samokontrolám. Tej istej disciplíne inkrementálnych aktualizácií pri čistých archívoch sa venuje článok validating compressed object and xref streams

, čo je dôležité, pretože moderné PDF, ktoré injektor spracúva, často stoja na cross-reference streamoch

Šifrovanie, xref streamy a ďalšie hranySaveAsPdfAKeďže ISO 19005 šifrovanie zakazuje, save path ho pred zápisom odstráni. FPDF_REMOVE_SECURITY aplikuje

, takže šifrovaný zdrojový dokument, načítaný s heslom, sa cestou do archívu dešifruje. Pri nezašifrovanom dokumente je to no-op a nič nemení. Dôsledok je rovnaký limit, ktorý z opačnej strany presadzuje HotPDF: jeden súbor nemôže byť súčasne šifrovaný aj PDF/A. Keď workflow potrebuje oboje, odpoveďou sú dva artefakty, šifrovaná kópia na distribúciu a samostatná čistá kópia do archívu.trailerEšte jedna hrana zostáva neviditeľná, kým vás nehryzne: dokumenty PDF 1.5+ používajúce čistý cross-reference stream bez kľúčového slova /Info and append its incremental update, and it has to accept the xref-stream form, otherwise such a document would be copied through with the markers silently dropped. ISO 32000-1 §7.5.6 explicitly permits a classic trailer incremental update to follow an xref-stream document, with /Prev pointing at the xref-stream offset, which is exactly the structure the injector emits. PDFium's own FPDF_SaveAsCopy. Téma xref-streamových trailerov bola vyriešená už skôr, takže object-stream práca potrebovala pridať len krok inflate bez potreby dekódovať type-2 xref entries alebo rozmotávať PNG predictor

Overte to skôr, než uveríte tvrdeniu

Knižnica obsahuje checker na úrovni bajtov, TPdf.ValidatePdfA, which returns a TPdfAValidationResult. Its Conformance field reports the detected level and Issues is a set of TPdfAValidationIssue values; the convenience method IsCompliant, je true iba vtedy, keď bola zistená skutočná úroveň a množina problémov je prázdna. V dávke ho používajte ako rýchlu prvú bránu

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;

Buďte úprimní v tom, čo tým získavate. Checker na úrovni bajtov s vysokou istotou zachytí štrukturálne problémy, chýbajúci OutputIntent, zakázanú akciu, prítomný /Encrypt, transparentnosť tam, kde ju časť 1 zakazuje, a detekcia vložených fontov používa heuristiku založenú na počtoch. Čo nerobí, je analýza operátorov v content streamoch, pretože na to by bol potrebný plný parser obsahu a to je zámerne mimo rozsah. Ako release gate spárujte checker v knižnici s veraPDF: checker je okamžitý a beží všade bez DLL, veraPDF je autoritatívny. Zapojeniu tejto dvojice do dávkového behu sa venuje článok batch preflight report CLI, a práve tam takáto validácia patrí v reálnom archivnom workflow

API SaveAsPdfA, InjectPdfAMarkers, and ValidatePdfA ukázané tu sú súčasťou PDFium Component pre Delphi, C++Builder a Lazarus/FPC. Produktová stránka odkazuje na plný referenčný opis API vrátane kompletnej enum hodnoty zhody a options záznamu za týmito príkladmi