PDFlibPas implementerer PDF/VCR-1 som en tagged structure-profil: Replacement-definitionen bor under StructTreeRoot som en direkte child-gren, hvor en template-root bærer /O /GTS_Template for at erklære feltsættet, og hver placeholder bærer /O /GTS_Replacement for at binde ét datafelt. Intet ved det er en page-level-ordbog
Den ene sætning er den del, de fleste implementeringer får forkert, og at få den forkert koster mere end en fejlet validering. Et variable-data-job, hvis replacement-plan bliver misfortolket, crasher ikke. Det printer, i stor volumen, med det forkerte felt i den forkerte boks, og den første, der lægger mærke til det, er kunden, der holder halvtreds tusinde mailers i hånden
Hvor replacement-definitionen faktisk bor
PDF/VCR-1 placerer sin replacement-model i det logiske strukturhierarki beskrevet i ISO 32000-1 §14.7, ikke i en privat page-ordbog. Template-roden er et struktur-element, der hænger direkte på StructTreeRoot, kendetegnet ved en attribute-owner på GTS_Template, og den erklærer hele felt-vokabularet ét sted via GTS_Fields plus en valgfri GTS_Pages, der navngiver det felt, som driver sideudvælgelsen
Placeholders er struktur-elementer, der er næstet inde i den rod, med attribute-owner GTS_Replacement. Hver bærer GTS_Generator (som PDFlibPas kræver skal være PassThrough), en GTS_Data-værdi, der navngiver præcis ét erklæret felt, og en valgfri GTS_BBox. Bindingen til siden er heller ikke en page-annotation: Elementet peger på sin side gennem /Pg og på marked-content-sekvensen gennem /K. At vælge struktur frem for en page-ordbog har så en konsekvens, der er let at sige og let at undervurdere: Strukturtræet er en generel graf, man når fra catalog'et, så alt, der forbruger profilen, skal være en tree-walker med alle de farer, det indebærer, frem for en dictionary-reader. Hvis du ikke har arbejdet med strukturtræet før, er mekanikken beskrevet i tagged PDF-struktur og tilgængelighed
Hvorfor lukker et tjek af den første top-level-gren dårlige filer igennem?
Fordi StructTreeRoot kan have mange direkte children, og en validator, der stopper, når den har fundet template-roden, ser aldrig resten af dem. Det er den specifikke fejl, PDFlibPas er bygget til at undgå, og den producerer præcis den slags falsk pass, der er værre end slet intet tjek: En fil, der rapporterer sig konform og indeholder en anden template-root, eller en placeholder, der sidder uden for template-grenen, hvor ingen forbruger vil honorere den
Så ValidatePDFVCRStructure i PDFlibPas kører to pass over det samme træ. Det første pass opremser hver direkte child af StructTreeRoot og tæller dem, der bærer GTS_Template; alt andet end præcis én afvises, før en eneste placeholder er læst. Det andet pass stiger ned i alle top-level-grene, ikke kun template-grenen, og afviser to ting på én gang: Et næstet element, der bærer GTS_Template og ikke er roden, og ethvert GTS_Replacement-element fundet uden for template-grenen
Traverseringen er afgrænset ved 64 niveauer af dybde og 1.000.000 noder, og den registrerer objekt- og generationsnummeret for hvert element, den besøger, så en cyklus eller en gentaget reference rapporteres i stedet for at blive fulgt. De tal er ikke performance-tuning. Et strukturtræ er en objektgraf, en angriber kontrollerer, og en validator, der rekurserer uden et loft, er en stack overflow med en fil-extension
At forfatte en template, og de begrænsninger API'et nægter at bøje
PDFlibPas eksponerer authoring gennem et bracketing-API, der håndhæver de strukturelle regler ved skrivetidspunktet i stedet for at opdage dem ved save-tidspunktet. SetPDFVCRMode skifter dokumentet ind i profilen, hvilket internt kræver PDF/X-4-basistilstanden fra ISO 15930-7 og tænder mark-info-flaget, fordi en PDF/VCR-1-fil uden marked content intet har for en placeholder at pege på. Derfra tager BeginPDFVCRTemplate feltlisten og sideudvælgelsesfeltet, og den er streng på måder, det er værd at kende, før du designer dit dataschema. Feltnavne går gennem PDFVCRValidFieldName, som kun accepterer ASCII-bogstaver, cifre, underscore, bindestreg og punktum, fordi det er de tegn, der serialiserer til et PDF-navn uden escaping-tvetydighed. PDFVCRNormalizeFieldList holder listen case-sensitiv og afviser dubletter med det samme, og sideudvælgelsesfeltet skal selv optræde i den erklærede liste
var
Lib: TPDFlib;
Diag: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
if Lib.SetPDFVCRMode(1) <> 1 then
raise Exception.Create('PDF/VCR mode was refused');
Lib.NewPage;
if Lib.BeginPDFVCRTemplate('Salutation,FullName,AccountNo',
'AccountNo') <> 1 then
raise Exception.Create('Field list rejected');
// Én placeholder binder præcis ét erklæret felt.
Lib.BeginPDFVCRPlaceholder('FullName', 72, 690, 320, 712);
Lib.DrawText(72, 694, 'SAMPLE NAME');
Lib.EndPDFVCRPlaceholder;
Lib.EndPDFVCRTemplate;
Diag := Lib.GetPDFVCRDiagnostics;
if Diag <> '' then
begin
Writeln('PDF/VCR blockers: ', Diag);
Exit;
end;
Lib.SaveToFile('campaign-template.pdf');
finally
Lib.Free;
end;
end;
Bemærk, hvad bracketing'en køber dig. BeginPDFVCRPlaceholder nægter at åbne, medmindre en template er aktiv og tag-stakken er præcis ét niveau dyb, hvilket er sådan, PDFlibPas garanterer, at placeholders er direkte children af template-roden og aldrig næster inde i hinanden. Koordinater valideres også, idet NaN, uendelighed og inverterede rektangler afvises, så en dårlig bounding box fejler ved kaldet frem for i en preflight-rapport en uge senere
Hvorfor har en placeholder brug for sin egen MCID-serialiseringsvej?
Fordi PDF/VCR-1 vil have /K til at være et bart heltal, og det er ikke sådan, en generel tagged-PDF-writer serialiserer forbindelsen mellem et struktur-element og dets indhold. Under ISO 32000-1 §14.6 refererer et struktur-element normalt marked content enten gennem et direkte MCID-heltal, når elementet allerede identificerer sin side gennem /Pg, eller gennem en marked-content-referenceordbog, der navngiver siden eksplicit. Et bibliotek, der emitterer tagged output til mange formål, har tendens til at standardisere på ordbogsformen, fordi den komponerer bedre på tværs af sider
PDFlibPas tog den snævre mulighed her frem for den generelle. SetCurrentStructElemDirectMCIDKid flagger det aktuelle struktur-element, og kun elementer, der bærer det flag, serialiserer /K som et direkte MCID med siden båret på /Pg; alt andet efterlader den eksisterende MCR-ordbogsvej urørt. Det er en bevidst afvejning og af den slags, der er værd at sige højt: At tilføje en profil skal ikke ændre bytes i det output, alle andre tagged dokumenter allerede producerer, så profilen får sin egen vej i stedet for en global switch. Hver eksisterende tagged fil beholder sin tidligere serialisering, og regressionsfladen forbliver på størrelse med den nye feature
Én traversering, to forbrugere
PDFlibPas udleder replacement-manifestet og konformitetsdommen fra den samme to-pass-traversering, hvilket er grunden til, at manifestet kan stoles på som en jobplan. Traverseringen udfylder en TPDFVCRManifestData-record, mens den validerer: Fields fra template-roden, PageSelectionField, når en er erklæret, og et Placeholders-array af TPDFVCRPlaceholderInfo-poster, der bærer FieldName, PageNumber, MCID, HasBBox og BBox. Konformitetsindgangspunktet er CheckCompliancePDFVCR(InputStream, Password, Options, ResultList), og den kører sine tjek i afhængighedsrækkefølge frem for alle på én gang: Først det fulde PDF/X-tjek, så GTS_PDFXVersion mod PDF/X-4, så GTS_PDFVCRVersion læst fra navnerummet http://www.npes.org/pdfvcr/ns/id/, og først derefter struktur-traverseringen. Giver du Options som 1, stopper den ved det første problem, hvilket er det, du vil have i en batch-gate; giver du 0, samler den alt, hvilket er det, du vil have i en rapport
// Verificér en template, du modtog frem for producerede
var
Issues: TStringList;
Stream: TFileStream;
I: Integer;
begin
Issues := TStringList.Create;
Stream := TFileStream.Create('supplier-template.pdf',
fmOpenRead or fmShareDenyWrite);
try
if CheckCompliancePDFVCR(Stream, '', 0, Issues) <> 100 then
for I := 0 to Issues.Count - 1 do
Writeln('PDF/VCR: ', Issues[I]);
finally
Stream.Free;
Issues.Free;
end;
end;
Manifestet er en kontrakt, så det findes kun for konforme filer
BuildPDFVCRManifest returnerer kun JSON, når problemlisten kommer tom tilbage, og ellers en tom string. Det er ikke defensiv kode for sin egen skyld. Et manifest forbruges af en composition-engine, der vil drive rigtigt output fra det, og at levere en delvis feltliste udtrukket fra en fil med to template-rødder inviterer motoren til at behandle en ikke-konform struktur som en troværdig replacement-plan. Gennem dokument-API'et pakker GetPDFVCRManifest(InputFileName, Password) det samme kald og sætter den sidste fejlkode til PDFLIB_ERROR_PDFVCR_COMPLIANCE (608), når filen ikke kvalificerer sig, så en fejlet ekstraktion og en tom template kan skelnes. JSON'en rapporterer fields, pageSelectionField som en string eller null og et placeholders-array, hvis poster giver field, page, mcid og enten en firetals-bbox eller null
var
Lib: TPDFlib;
Manifest: WideString;
begin
Lib := TPDFlib.Create;
try
Manifest := Lib.GetPDFVCRManifest('campaign-template.pdf', '');
if Manifest = '' then
// 608 betyder, at filen ikke er PDF/VCR-1-konform,
// 401 betyder, at filen ikke kunne åbnes
Writeln('No manifest, error ', Lib.LastErrorCode)
else
Writeln(Manifest);
finally
Lib.Free;
end;
end;
Én detalje at bære med ind i din forbruger: page-værdien følger bibliotekets nul-baserede sidekonvention, ikke den et-baserede nummerering, et menneske ser i en viewer. At blande dem sammen er en stille off-by-one, der lægger indhold på det forkerte ark, og den vil ikke blive fanget af nogen validator, fordi begge tal er strukturelt gyldige
Hvor grænserne går
PDF/VCR-1-supporten i PDFlibPas dækker template-authoring, save-gaten, standalone-preflight og manifest-ekstraktion. Den udfører ikke replacement. At substituere data ind i placeholders og producere de personaliserede instanser er composition-trinnet, og det bor uden for biblioteket med vilje, fordi manifestet er præcis den grænseflade, der lader en composition-engine skrives uafhængigt. Save-gaten afviser også i stedet for at reparere. Modsat profiler, hvor et forkert annotation-flag kan normaliseres lydløst, er hver PDF/VCR-1-begrænsning, som PDFVCRReadyForSave håndhæver, én, hvor et gæt ville ændre, hvad dokumentet betyder: At opfinde en template-root eller at vælge, hvilket felt en ubundet placeholder tilhører, producerer en fil, der validerer og lyver. Den samme reparations-eller-afvis-grænse diskuteres for en anden profil i PDF/E-1-author-tilstanden og afgrænset preflight, og workflowen for at køre flere konformitetstjek over ét dokument er dækket i compliance- og signing-workbenche
Authoring, den afgrænsede struktur-traversering, save-gaten og manifest-ekstraktoren følger alle med i PDFlibPas Delphi PDF-biblioteket, hvilket betyder, at en template kan produceres under profilen og derefter verificeres gennem en separat kodevej, der aldrig så writer-tilstanden, og den adskillelse er den eneste grund til, at en konformitetspåstand er noget værd