Neem een bestand waarvan de information dictionary zegt /Title (Annual Maintenance Report) terwijl zijn XMP-pakket helemaal geen dc:title heeft. Viewers tonen de titel zonder mopperen, toch wijst een PDF/A-validator het bestand af omdat de twee metadatakopieën uit elkaar lopen. Een tweede bestand oogt net zo gezond maar heeft geen pdfaid:part-entry in zijn XMP, en de validator antwoordt met tientallen fouten: hij beoordeelt het bestand als gewone PDF en rapporteert elke regel van het geclaimde deel als gefaald, allemaal vanuit één ontbrekende regel
De twee problemen vragen heel verschillende hoeveelheden werk. Metadata op elkaar afstemmen is een smalle fix die op elk bestand kan draaien. Een willekeurige PDF in een archiefexemplaar veranderen betekent strippen wat het doeldeel verbiedt, toevoegen wat het vereist, de claim uitspreken, en daarna de bytes valideren die er werkelijk uitkomen. We hebben de conversie met opzet rond die laatste stap gebouwd. ConvertToPDFA, die met de Enterprise-editie (TPDFlibEnterprise) wordt meegeleverd samen met GetPDFAConversionReport, slaat het omgezette document in het geheugen op, draait de PDF/A-controle op die bytes en geeft alleen 1 terug wanneer die controle slaagt
Waarom zakt een PDF die netjes opent voor een PDF/A-controle?
Beide mislukkingen komen uit metadataregels. Een PDF legt zijn titel, auteur, producent en data vast in de document information dictionary, en PDF/A vereist dezelfde feiten ook in een XMP-metadatapakket; ISO 19005-1 §6.7.3 vraagt dat de twee equivalent zijn, en PDF/A-2 en PDF/A-3 houden de regel vast. Van de bestanden die op deze regel zakken is het gebruikelijke patroon een producent die de information dictionary vulde en nooit de XMP-helft schreef, dus de validator vindt een titel op de ene plek en niets op de andere
De ontbrekende identificatie is stiller. De entries pdfaid:part en pdfaid:conformance zijn wat een bestand tot PDF/A markeren, en een bestand kan volkomen goed gevormd zijn zonder hen. Het geval dat we het vaakst zien is een applicatie die zijn eigen XMP-pakket aanlevert en het pakket vervangt dat de PDF-writer genereerde; het custom pakket draagt de eigen velden van de applicatie en geen pdfaid-entries, dus de foutenlijst wijst nergens in de buurt van de echte oorzaak
RepairDocumentMetadata behandelt beide gevallen en geeft het aantal entries terug dat hij veranderde. Hij is in elke editie beschikbaar, net als SetPDFAMode en SanitizeDocument. De reparatie loopt Title, Author, Subject, Keywords, Creator, Producer, CreationDate en ModDate na. Waar alleen de XMP een waarde draagt wordt de information dictionary eruit gevuld, dus niets dat al is vastgelegd wordt weggegooid. Waar beide waarden dragen en ze verschillen, wint de information dictionary. We hebben die keuze gemaakt omdat de information dictionary de helft is die producenten het vaakst zetten, wat haar de waarschijnlijkste weergave maakt van wat de auteur voor ogen had
Is jouw XMP de autoritatieve kopie (zeg, samengesteld in een documentmanagementsysteem), duw dan de waarden die je wilt in de information dictionary met SetInformation en draai pas daarna de reparatie. Andersom zal de reparatie jouw samengestelde XMP-titel overschrijven met wat de oorspronkelijke producent schreef
De identificatiereparatie vraagt één ding van je: het document moet weten op welk deel hij mikt. Een vers geladen bestand heeft geen PDF/A-modus staan, dus roep SetPDFAMode aan vóór de reparatie. Met een modus actief schrijft RepairDocumentMetadata de matchende pdfaid-entries, voegt pdfaid:rev met het jaar 2020 toe voor de deel-4-modi, en zet dc:format op application/pdf, wat validators ook controleren
uses
SysUtils, PDFlibrary;
procedure RepairIncoming(const InName, OutName: string);
var
Lib: TPDFlib;
Fixed: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile(InName, '') <> 1 then
raise Exception.CreateFmt('Cannot open %s', [InName]);
// De samengestelde titel gaat eerst de information dictionary in,
// want die kopie wint wanneer de twee helften uit elkaar lopen
Lib.SetInformation(2, 'Annual Maintenance Report 2025');
Lib.SetPDFAMode(3); // 3 = PDF/A-2b, zie de tabel hieronder
Fixed := Lib.RepairDocumentMetadata;
Writeln(Format('%d metadata entries brought into agreement', [Fixed]));
Lib.SaveToFile(OutName);
finally
Lib.Free;
end;
end;
Het doeldeel en zijn modusnummer kiezen
Kies het deel op grond van wat het document moet dragen, en zoek pas daarna het nummer op. SetPDFAMode en ConvertToPDFA delen één nummeringsschema, en dat loopt niet in de volgorde die de deelnamen suggereren
| Modus | Deel en niveau | Toelichting |
|---|---|---|
| 1 | PDF/A-1a | PDF 1.4, getagd |
| 2 | PDF/A-1b | PDF 1.4 |
| 3 | PDF/A-2b | PDF 1.7 |
| 4 | PDF/A-2a | PDF 1.7, getagd |
| 5, 6, 7 | PDF/A-3b, 3a, 3u | Ingesloten bestanden van elk type toegestaan |
| 8 | PDF/A-2u | PDF 1.7, Unicode-tekst vereist |
| 9, 10, 11 | PDF/A-4, 4e, 4f | PDF 2.0; 4e voegt 3D en rich media toe, 4f staat ingesloten bestanden toe |
Modus 3 is PDF/A-2b en modus 4 is PDF/A-2a. Die omkering vangt mensen die vanaf 1a omhoog tellen en aannemen dat het a-niveau altijd voorop loopt. PDF/A-2u duikt op bij 8, na de deel-3-modi
De keuze met echte gevolgen gaat over bijlagen. De delen 1 en 2 verbieden ingesloten bestanden, dus ConvertToPDFA verwijdert ze voor de modi 1 tot en met 4 en modus 8. Zet een Factur-X- of ZUGFeRD-factuur om naar modus 3 en de gestructureerde XML-factuur is uit de uitvoer verdwenen, samen met de reden waarom het bestand bestond. Richt voor alles dat een bijlage draagt op modus 5 (PDF/A-3b), het deel waarop die factuurformaten ook zijn gedefinieerd, of modus 11 als je PDF 2.0 nodig hebt. Elke bijlage in een deel-3- of deel-4-bestand moet via /AFRelationship vermelden hoe hij zich tot het document verhoudt, en PDF Library for Delphi schrijft die entry voor elk ingesloten bestand dat hij in die modussen opslaat
Deel 4 identificeert zich anders. Hij heeft geen conformance-letter voor gewone PDF/A-4, gebruikt E of F voor de twee extensies, en vereist pdfaid:rev met het jaar waarin zijn deel verscheen. ConvertToPDFA tilt het bestand naar PDF 2.0 voor de modi 9 tot en met 11, want ISO 19005-4 §6.1.2 bouwt het deel op PDF 2.0, en de controle beoordeelt zulke bestanden op PDF 2.0 in plaats van 1.7
Wat ConvertToPDFA in het bestand verandert
ConvertToPDFA voert een vaste reeks reparaties uit, noteert elke stap in een platte-tekstrapport, slaat het resultaat in het geheugen op en valideert die bytes. Een gezakte controle geeft 0 terug, en een modus buiten 1 tot en met 11 ook, wat terugkeert voordat er überhaupt een rapport is geschreven. De reeks is het kennen, want elke stap is iets dat de uitvoer verliest of verwint:
- Een versleuteld document met onbekend wachtwoord stopt de conversie onmiddellijk met een
blocked:-regel; van binnenuit is er niets te lezen, laat staan te repareren - JavaScript op documentniveau en de open action die hem draait worden verwijderd, samen met elke additional-actions-entry en de action-types die geen deel toestaat, waaronder Launch, Sound, Movie, ResetForm, ImportData, Hide, SetOCGState, Rendition en Trans. GoTo- en URI-links blijven, dus navigatie blijft werken
- Ingesloten bestanden worden verwijderd voor de delen die hen verbieden, zoals hierboven beschreven
- Annotaties die verborgen zijn of nooit getoond worden verwijderd. PDF/A vereist dat annotaties zichtbaar en printbaar zijn, en ze zichtbaar maken zou veranderen hoe de pagina eruitziet, dus we kozen voor verwijdering
- De weergaven van formuliervelden worden opnieuw gegenereerd voordat de vlag
/NeedAppearanceswordt gewist, zodat ingevulde waarden zichtbaar blijven - Fonts die worden gebruikt maar niet zijn ingesloten worden ingesloten vanuit de fonts die op de machine staan geïnstalleerd
- Objecten waar niets meer naar verwijst worden weggegooid, en een sRGB-output-intent wordt toegevoegd als het bestand er geen heeft
- Tenslotte worden deel en niveau in de XMP vastgelegd, en worden de bytes gecontroleerd
Op een server is de fontstap degene die een conversie het vaakst laat vastlopen. Een document dat een font gebruikt dat nergens op de conversiehost bestaat kan niet van binnenuit het bestand worden gerepareerd, en het rapport zegt dat. Installeer de fonts die je documenten werkelijk gebruiken op de conversiemachine; de walkthrough over ontbrekende fonts insluiten in een bestaande PDF behandelt die stap op zichzelf
Hoe zou een batchjob met een gezakte conversie omgaan?
Behandel de retourwaarde als poort en het rapport als werkbrief. Archiveer de bestanden die 1 teruggeven en route de rest naar een persoon met het rapport erbij. Sla de uitvoer van een gezakte conversie niet in het archief op alleen omdat hij netter oogt dan de invoer. Dat bestand draagt nu een PDF/A-claim die de controle weigerde te bevestigen, en dat is precies de situatie die de verificatiestap moet voorkomen
Het rapport van GetPDFAConversionReport is één regel per actie, elk beginnend met een werkwoord: removed:, regenerated:, repaired:, embedded:, added:, stated:, plus blocked: als iets de run stopte, en een afsluitende result:-regel. De controle achter het oordeel stopt bij de eerste mislukking, dus wanneer een bestand zakt draait de conversie de controle nog eens volledig en toont het rapport elke kwestie in één keer. Routeren op de prefixes kost een paar regels
uses
Classes, SysUtils, PDFlibrary, PDFlibEnterprise;
function ArchiveOne(const InName, ArchiveDir, ReviewDir: string): Boolean;
var
Lib: TPDFlibEnterprise;
Report: TStringList;
I: Integer;
Blocked: Boolean;
begin
Result := False;
Lib := TPDFlibEnterprise.Create;
Report := TStringList.Create;
try
if Lib.LoadFromFile(InName, '') <> 1 then
Exit;
Result := Lib.ConvertToPDFA(5) = 1; // 5 = PDF/A-3b, houdt bijlagen vast
Report.Text := Lib.GetPDFAConversionReport;
if Result then
Lib.SaveToFile(IncludeTrailingPathDelimiter(ArchiveDir) + ExtractFileName(InName))
else
begin
Blocked := False;
for I := 0 to Report.Count - 1 do
if Pos('blocked:', Report[I]) = 1 then
Blocked := True;
// Laat het origineel onaangeroerd; alleen het rapport reist mee
Report.SaveToFile(IncludeTrailingPathDelimiter(ReviewDir) +
ChangeFileExt(ExtractFileName(InName), '.pdfa-report.txt'));
if Blocked then
Writeln(InName, ': needs a password or a rebuilt source file');
end;
finally
Report.Free;
Lib.Free;
end;
end;
Een blocked:-regel betekent meestal dat de oplossing buiten de PDF ligt: een wachtwoord, een ander bronbestand, of een herhaling van wat het document produceerde. Gewone validatiekwesties na een volledige conversie zijn vaker content die betekenis draagt en niet simpelweg kan worden verwijderd, en dat zijn de gevallen waarin een persoon moet beslissen
De PDF/A-markering lezen die een bestand al draagt
Controleer vóór het omzetten wat het document al over zichzelf claimt. Bestanden van scanners, kantoorpakketten en andere PDF-tools dragen vaak al een PDF/A-markering, en een geldig PDF/A-3-bestand opnieuw omzetten naar deel 2 kost zijn bijlagen voor niets
De val is dat XMP toestaat dezelfde property op twee manieren te schrijven. Sommige producenten schrijven het deel als kindelement, <pdfaid:part>2</pdfaid:part>; anderen schrijven het als attribuut op rdf:Description, pdfaid:part="2". Beide zijn gewoon, geldige XMP. Een detector die maar één vorm zoekt oordeelt dat bestanden uit de andere producentenfamilie geen markering dragen, en een checker die zo is gebouwd beoordeelt uiteindelijk een geldig PDF/A-2-bestand op deel 1 en meldt hem als ongemarkeerd en van te hoge PDF-versie, precies het omgekeerde van de waarheid. De validator in PDF Library for Delphi leest beide vormen. Sniff je de markering zelf, doe dan hetzelfde:
function DeclaredPDFAPart(Lib: TPDFlib): string;
const
ElemTag = '<pdfaid:part>';
AttrDQ = 'pdfaid:part="';
AttrSQ = 'pdfaid:part=''';
var
Packet: string;
P: Integer;
begin
Result := '';
Packet := Lib.GetDocumentMetadata;
P := Pos(ElemTag, Packet); // elementvorm
if P > 0 then
Exit(Copy(Packet, P + Length(ElemTag), 1));
P := Pos(AttrDQ, Packet); // attribuutvorm, dubbele quotes
if P = 0 then
P := Pos(AttrSQ, Packet); // attribuutvorm, enkele quotes
if P > 0 then
Result := Copy(Packet, P + Length(AttrDQ), 1);
end;
Een leeg resultaat betekent geen markering in geen van beide vormen. Een cijfer vertelt welk deel de producent voor ogen had, en dat getal is degene om tegen je doel af te zetten voordat je beslist of je überhaupt omzet
Scripts opschonen voordat het bestand het archief ingaat
Archiefconversie en beveiligingssanering draaien meestal samen, want de content die een beveiligingsbeleid kwijt wil overlapt flink met wat PDF/A verbiedt. SanitizeDocument neemt een bitmasker van SANITIZE_*-vlaggen aan en geeft terug hoeveel verwijderingen hij toepaste. ConvertToPDFA zelf roept hem aan met SANITIZE_JAVASCRIPT or SANITIZE_OPEN_ACTION als eerste stap
Eén detail weegt voor validators. Scripts op documentniveau wonen in een /JavaScript-naamboom onder de /Names-dictionary van de catalogus, en wanneer het laatste script wordt verwijderd gaat de nu lege boom met hem mee. Een lege boom kondigt nog steeds dat het document scripts heeft, en sommige checkers reageren op de sleutel zelf zonder te tellen wat eronder zit
Welke tool ook gesaneerd heeft, verifieer het opgeslagen bestand met een verse laadbeurt in plaats van te vertrouwen op de toestand in het geheugen. Heropenen is goedkoop:
var
Lib, Check: TPDFlib;
begin
Lib := TPDFlib.Create;
Check := TPDFlib.Create;
try
Lib.LoadFromFile('upload.pdf', '');
Lib.SanitizeDocument(SANITIZE_JAVASCRIPT or SANITIZE_OPEN_ACTION);
Lib.SaveToFile('upload-clean.pdf');
Check.LoadFromFile('upload-clean.pdf', '');
if Check.GlobalJavaScriptCount > 0 then
raise Exception.Create('Document-level scripts survived the save');
finally
Check.Free;
Lib.Free;
end;
end;
Voor de naburige stappen in een archiefpijplijn zie de gidsen voor PDF/A- en PDF/UA-preflight, echte redaction en contentverwijdering, en PDF/A-3 XMP-extensieschema's voor Factur-X, dat de metadatakant oppakt zodra een gearchiveerd document ook gestructureerde factuurdata draagt
Alle hier getoonde conversie, metadata-reparatie en validatie draait binnen je eigen Delphi-, C++Builder- of Lazarus-proces, zonder externe converter of commandline-validator in de keten. De productpagina van PDF Library for Delphi zet de PDF/A-delen, edities en doelplatformen op een rij