Livrezi un convertor care marchează fiecare fișier ca PDF/A-1b, sistemul de evidență al clientului le ingerează timp de un an, apoi un audit trece tot lotul prin veraPDF și o treime revin neconforme. Nimic nu s-a blocat, nicio excepție nu a fost ridicată, fișierele se deschid fără probleme în orice vizualizator de pe biroul tău. Pur și simplu nu erau standardul pe care l-ai aplicat peste ele. Acesta este modul normal de eșec pentru PDF-ul de arhivare, și de aceea „am setat steagul” nu este niciodată aceeași afirmație cu „validează”
Primul lucru de înțeles despre PDFium și PDF/A este că motorul nu are nicio legătură cu asta. PDFium redă, parsează și scrie PDF, dar suprafața sa publică nu are niciun ConvertToPDFA, niciun scriitor OutputIntent, nicio interfață XMP. Fiecare parte a conformității de arhivare, pachetul XMP, OutputIntent-ul și profilul său ICC, marcajele catalogului, validarea, trăiește în PDFiumPas însuși, într-o unitate pur Pascal de aproximativ 2.000 de linii (FPdfPdfa.pas) care parsează octeții salvați și îi rescrie printr-o actualizare incrementală. Dacă știi unde se face treaba, știi și unde se ascund erorile, iar ele nu se ascund în PDFium
Ce cere de fapt PDF/A și unde mușcă
PDF/A nu este un singur format. ISO 19005 definește trei părți (PDF/A-1, -2, -3) și, în interiorul fiecăreia, niveluri de conformitate care promit lucruri diferite. Nivelul B (de bază) garantează doar că aspectul vizual poate fi reprodus. Nivelul A (accesibil) adaugă un arbore de structură etichetat și mapare Unicode peste B. Nivelul U, care există doar pentru părțile 2 și 3, se află între ele: text Unicode fiabil fără întregul arbore de structură. ISO 19005-1 nu are Nivel U, o restricție pe care biblioteca o encodează direct
Câteva dintre regulile formatului sunt cele care mușcă în practică. Criptarea este interzisă în mod explicit (ISO 19005-1 §6.1.3 și succesoarele sale): un fișier PDF/A nu poate conține un /Encrypt dicționar. Documentul trebuie să declare o condiție de redare a ieșirii printr-un OutputIntent al cărui destinatar este un profil ICC valid (§6.2.3.2). Chiar afirmația de conformitate trebuie să apară ca metadate XMP sub schema de identificare PDF/A. Nivelul A cere în plus §6.8 structură logică, arborele de etichete care face documentul lizibil de mașini. Ratezi oricare dintre acestea și un validator de conformitate respinge fișierul chiar dacă se redă perfect
Singurul apel care produce o arhivă
PDFiumPas expune întregul flux din spatele TPdf.SaveAsPdfA. Suprasarcina simplă preia o conformitate țintă și are implicit PDF/A-1b, care este setarea potrivită pentru cazul obișnuit „fă-l lizibil pentru totdeauna”
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;
Sub capotă, acesta este un pas în două etape. SaveAsPdfA cere mai întâi PDFium să serializeze documentul cu FPDF_SaveAsCopy, apoi pasează acel flux de octeți către InjectPdfAMarkers, care adaugă metadatele XMP, OutputIntent-ul sRGB cu profilul ICC încorporat și un catalog rescris ca actualizare incrementală. Sursa este citită de la poziția zero, iar destinația este scrisă de la poziția zero; arborele original de obiecte rămâne intact, iar marcajele sunt adăugate după obiectele existente %%EOF. Dacă ai nevoie de octeți, nu de un fișier, SaveAsPdfAToStream ia un TStream și aceleași opțiuni
Alegerea conformității cu înregistrarea de opțiuni
Pentru a viza o anumită parte și un anumit nivel, transmite o TPdfASaveOptions înregistrare. Câmpul ei Conformance primește o TPdfAConformance valoare. Enumerația acoperă fiecare combinație validă și nimic altceva: pac1b, pac1a pentru partea 1; pac2b, pac2u, pac2a pentru partea 2; pac3b, pac3u, pac3a pentru partea 3, plus pacUnknown și pacNone pentru partea de validare. Nu există pac1u, pentru că acel nivel nu există în standard
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;
Cea mai mare parte a înregistrării poate rămâne goală. Lasă Title, Author, Subject, Keywords, Creator, și Producer gol și SaveAsPdfA le completează automat din dicționarul Info al documentului prin FPDF_GetMetaText. Lasă CreationDate și ModDate goale și folosește ora UTC curentă pentru ambele date XMP. Lasă DocumentId și InstanceId goale, iar biblioteca le precompletează din FPDF_GetFileIdentifier, revenind la un ID determinist derivat din octeții sursă. Singurul câmp pe care ai putea dori să-l suprascrii intenționat este IccProfileData: gol înseamnă profilul sRGB IEC61966-2.1 inclus, dar un flux de lucru CMYK sau în tonuri de gri ar trebui să-l furnizeze pe al său
De ce Nivelul A se degradează și de ce aceasta este alegerea onestă
Iată o subtilitate care îi încurcă pe cei care se așteaptă ca un indicator să fie o garanție. Poți cere pac1a pe un document care nu are arbore de etichete, dar PDF/A-1a cere structura logică din §6.8, iar biblioteca nu poate fabrica un arbore de structură dintr-un PDF fără etichete. În loc să emită un fișier care pretinde Nivelul A deși nu îl îndeplinește, SaveAsPdfA verifică dacă există o structură etichetată reală (/StructTreeRoot plus /MarkInfo cu /Marked true) și, dacă lipsește, coboară pretenția: pac1a devine pac1b, pac2a devine pac2b, și așa mai departe în toate cele trei părți. Helper-ele interne sunt PdfAIsLevelA și PdfADowngradeToLevelB
Motivul merită spus pe față: un fișier care declară onest nivelul pe care îl atinge este mai util decât unul care minte în privința unui nivel pe care nu îl atinge. Nivelul U este tratat diferit. Detectarea unei acoperiri Unicode reale ar însemna un test naiv "are /ToUnicode" care degradează prea mult documentele legitime (WinAnsi și codările similare sunt exceptate), astfel încât partea de salvare emite revendicarea U așa cum a declarat-o apelantul și lasă discrepanța să fie semnalată la validare. Dacă ai nevoie de o arhivă Nivel A garantată, etichetează documentul înainte să-l convertești; convertorul nu va inventa o structură care nu există
Capcana ICC pe care doar un validator real o prinde
Aceasta este eroarea care a oferit cea mai grea lecție, deoarece verificatorul propriu al bibliotecii a trecut peste ea, în timp ce veraPDF, validatorul de referință ISO 19005, nu a făcut-o. PDF/A cere ca profilul de destinație al OutputIntent să fie un flux ICCBased valid, iar §6.2.3.2 obligă un verificator să valideze acel flux ca spațiu de culoare. Un flux ICCBased trebuie să declare /N, numărul de componente de culoare. O versiune timpurie a injectorului scria dicționarul fluxului ICC doar cu /Length și fără /N, iar veraPDF a respins rezultatul cu "The N entry (value null)... is missing"
Ceea ce a făcut-o insidioasă este că respingerea apărea doar pentru PDF/A-1b și -1a. Modelele de conformitate pentru partea 2 și partea 3 nu rulau acea verificare anume asupra profilului de destinație, astfel încât structura injectată identic era validată sub pac2b, pac3b și pac2u dar eșua sub pac1b doar pe baza valorii pdfaid:part. Un test unitar nu ar fi putut niciodată să o vadă, deoarece verificatorul propriu al bibliotecii ValidatePdfACompliance a verificat doar că /DestOutputProfile cheia exista, nu ce se afla în dicționarul fluxului. Testele interne au rămas verzi; validarea reală pentru arhivare a eșuat
Soluția este IccComponentCount, care citește semnătura spațiului de culoare al datelor de la offsetul 16 din antetul ICC și o mapează la un număr de componente: GRAY este 1, RGB , Lab , iar XYZ sunt 3, CMYK este 4, iar un profil necunoscut revine implicit la 3. Acest număr ajunge în dicționarul fluxului ca /N. Este calculat, nu codificat static la 3, astfel încât un apelant care furnizează un profil CMYK sau grayscale prin IccProfileData tot primește valoarea corectă. Lecția mai largă este metodologică: verificatorul din bibliotecă și un validator autoritar au fiecare puncte oarbe, iar ieșirea PDF/A trebuie testată cap la cap împotriva unei implementări de referință precum veraPDF, nu lăsată pe seama auto-verificărilor. Aceeași disciplină a actualizării incrementale care stă la baza arhivelor curate este acoperită în validarea fluxurilor comprimate de obiecte și xref, ceea ce contează deoarece PDF-urile moderne pe care le consumă injectorul sunt adesea construite pe fluxuri de referințe încrucișate
Criptarea, fluxurile xref și alte cazuri-limită
Deoarece ISO 19005 interzice criptarea, calea de salvare o elimină înainte de scriere. SaveAsPdfA aplică FPDF_REMOVE_SECURITY la serializare, astfel încât o sursă criptată (încărcată cu parola ei) este decriptată pe drumul spre arhivă. Pentru un document necriptat, aceasta nu produce niciun efect și nu schimbă nimic. Concluzia este aceeași constrângere pe care HotPDF o impune din direcția opusă: un singur fișier nu poate fi în același timp criptat și PDF/A. Când un flux de lucru le cere pe ambele, soluția sunt două artefacte, o copie criptată pentru distribuire și o copie separată, curată, pentru arhivă
Încă un caz-limită rămâne invizibil până când lovește: documentele PDF 1.5+ care folosesc un flux pur de referințe încrucișate și nu poartă niciun trailer cuvânt-cheie. Injectorul citește trailerul pentru a găsi /Info și a-i adăuga actualizarea incrementală, iar acesta trebuie să accepte forma xref-stream, altfel un astfel de document ar fi copiat mai departe cu marcajele eliminate în tăcere. ISO 32000-1 §7.5.6 permite în mod explicit ca o actualizare incrementală clasică a trailerului să urmeze unui document xref-stream, cu /Prev care indică offsetul xref-stream, exact structura pe care o emite injectorul. Chiar FPDF_SaveAsCopy dacă PDFium însuși scrie mereu un trailer clasic, astfel încât în fluxul normal injectorul nu întâlnește niciodată o sursă xref-stream pură, calea de citire o gestionează pentru documentele care vin din altă parte
Verifică înainte să ai încredere în afirmație
Biblioteca include un verificator la nivel de octet, TPdf.ValidatePdfA, care returnează un TPdfAValidationResult. Câmpul său Conformance raportează nivelul detectat, iar Issues este un set de valori TPdfAValidationIssue; metoda comodă IsCompliant este adevărată doar când a fost detectat un nivel real și setul de probleme este gol. Ruleaz-o ca prim filtru rapid într-un lot
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;
Fii sincer în privința a ceea ce obții cu asta. Verificatorul la nivel de octet depistează probleme structurale (un OutputIntent lipsă, o acțiune interzisă, un /Encrypt, transparență acolo unde partea 1 o interzice) cu mare încredere, iar detectarea încorporării fonturilor folosește o euristică bazată pe număr care raportează intenționat doar un semnal cu încredere mare, în loc să urmărească acoperirea pe fiecare glif. Ce nu face este analiza operatorilor din fluxul de conținut, care ar necesita un parser complet de conținut și este, prin design, în afara domeniului. Pentru un prag de lansare, asociază verificatorul din bibliotecă cu veraPDF: verificatorul este instantaneu și rulează peste tot fără DLL, veraPDF este autoritar. Integrarea acestei asocieri într-o rulare în lot este subiectul CLI-ului pentru raportul de preflight în lot, care este locul potrivit pentru această validare într-un flux de arhivare real
SaveAsPdfA, InjectPdfAMarkers, iar ValidatePdfA API-urile prezentate aici vin împreună cu PDFium Component pentru Delphi, C++Builder și Lazarus/FPC. Pagina produsului oferă legătura către referința completă a API-ului, inclusiv enumerarea completă a conformității și înregistrarea de opțiuni din spatele acestor exemple