PDFlibPas implementerar PDF/VCR-1 som en tagged structure-profil: ersättningsdefinitionen bor under StructTreeRoot som en direkt barn-gren, där en mallrot bär /O /GTS_Template för att deklarera fältuppsättningen och varje platshållare bär /O /GTS_Replacement för att binda ett datafält. Ingenting i det hela är en dictionary på sidnivå
Den enda meningen är den del de flesta implementationer får fel, och att få den fel kostar mer än en misslyckad validering. Ett variable-data-jobb vars ersättningsplan missläses kraschar inte. Det trycker, i volym, med fel fält i fel ruta, och den första som märker det är kunden som håller femtiotusen utskick
Var ersättningsdefinitionen faktiskt bor
PDF/VCR-1 lägger sin ersättningsmodell i den logiska strukturhierarkin som beskrivs i ISO 32000-1 §14.7, inte i en privat sid-dictionary. Mallroten är ett strukturelement som hänger direkt i StructTreeRoot, utmärkt av en attribute owner på GTS_Template, och den deklarerar hela fältvokabulären på ett ställe genom GTS_Fields plus en valfri GTS_Pages som namnger fältet som styr sidvalet
Platshållare är strukturelement nästlade inuti den roten, med attribute owner GTS_Replacement. Var och en bär GTS_Generator (som PDFlibPas kräver ska vara PassThrough), ett GTS_Data-värde som namnger exakt ett deklarerat fält och en valfri GTS_BBox. Bindningen till sidan är inte heller en sidannotation: elementet pekar på sin sida genom /Pg och på marked-content-sekvensen genom /K. Att välja struktur framför en sid-dictionary bär sedan en konsekvens som är lätt att uttala och lätt att underskatta: strukturträdet är en allmän graf som nås från katalogen, så allt som konsumerar profilen måste vara en trädvandrare med alla risker det innebär, snarare än en dictionary-läsare. Har du inte arbetat med strukturträdet förut är mekaniken utlagd i tagged PDF-struktur och tillgänglighet
Varför släpper kontroll av bara den första toppnivågrenen igenom dåliga filer?
Därför att StructTreeRoot kan ha många direkta barn, och en validator som stannar efter att ha lokaliserat mallroten ser aldrig resten av dem. Det är det specifika misslyckandet PDFlibPas är byggt för att undvika, och det producerar exakt den typ av falskt godkännande som är värre än ingen kontroll alls: en fil som rapporterar konform och innehåller en andra mallrot, eller en platshållare som sitter utanför mallgrenen där ingen konsument kommer att hedra den
Så ValidatePDFVCRStructure i PDFlibPas kör två pass över samma träd. Det första passet räknar upp varje direkt barn av StructTreeRoot och räknar de som bär GTS_Template; allt annat än exakt ett vägras innan en enda platshållare läses. Det andra passet går ner i alla toppnivågrenar, inte bara mallgrenen, och avvisar två saker samtidigt: ett nästlat element som bär GTS_Template och inte är roten, och varje GTS_Replacement-element som hittas utanför mallgrenen
Vandringen begränsas till 64 nivåer djup och 1 000 000 noder, och den registrerar objekt- och generationnumret för varje element den besöker så att en cykel eller en upprepad referens rapporteras i stället för att följas. De talen är inte prestandajustering. Ett strukturträd är en objektsgraf en angripare kontrollerar, och en validator som rekurserar utan tak är en stack overflow med en filändelse
Att skapa en mall, och de begränsningar API:et vägrar böja sig för
PDFlibPas exponerar mallskapande genom ett bracketing-API som upprätthåller strukturreglerna vid skrivtillfället i stället för att upptäcka dem vid spartillfället. SetPDFVCRMode växlar dokumentet in i profilen, vilket internt kräver PDF/X-4-basläget i ISO 15930-7 och slår på mark-info-flaggan, för en PDF/VCR-1-fil utan marked content har ingenting för en platshållare att peka på. Därifrån tar BeginPDFVCRTemplate emot fältlistan och sidvalsfältet, och den är strikt på sätt värda att känna till innan du designar ditt dataschema. Fältnamn går genom PDFVCRValidFieldName, som bara accepterar ASCII-bokstäver, siffror, understreck, bindestreck och punkt, för det är de tecken som serialiseras till ett PDF-namn utan escapetvetydighet. PDFVCRNormalizeFieldList håller listan skiftlägeskänslig och avvisar dubbletter direkt, och sidvalsfältet måste självt finnas med i den deklarerade listan
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');
// En platshållare binder exakt ett deklarerat fält.
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;
Notera vad bracketingen köper dig. BeginPDFVCRPlaceholder vägrar att öppna om inte en mall är aktiv och taggstacken är exakt en nivå djup, vilket är hur PDFlibPas garanterar att platshållare är direkta barn av mallroten och aldrig nästlar inuti varandra. Koordinater valideras också, med avvisning av NaN, oändlighet och inverterade rektanglar, så en dålig bounding box misslyckas vid anropet snarare än i en preflight-rapport en vecka senare
Varför behöver en platshållare sin egen MCID-serialiseringsväg?
Därför att PDF/VCR-1 vill att /K ska vara ett naket heltal, och det är inte hur en allmän tagged-PDF-skrivare serialiserar länken mellan ett strukturelement och dess innehåll. Enligt ISO 32000-1 §14.6 refererar ett strukturelement normalt marked content antingen genom ett direkt MCID-heltal, när elementet redan identifierar sin sida genom /Pg, eller genom en marked-content-referensdictionary som namnger sidan explicit. Ett bibliotek som sänder ut tagged utdata för många ändamål tenderar att standardisera på dictionary-formen eftersom den komponerar bättre över sidor
PDFlibPas tog det smala alternativet här snarare än det allmänna. SetCurrentStructElemDirectMCIDKid flaggar det aktuella strukturelementet, och bara element som bär den flaggan serialiserar /K som ett direkt MCID med sidan buren på /Pg; allt annat lämnar den befintliga MCR-dictionary-vägen orörd. Det är en medveten avvägning och den är av det slag som är värd att uttala högt: att lägga till en profil ska inte ändra byten i utdata som varje annat tagged dokument redan producerar, så profilen får en egen väg i stället för en global switch. Varje befintlig tagged fil behåller sin tidigare serialisering, och regressionsytan förblir storleken av den nya funktionen
En vandring, två konsumenter
PDFlibPas härleder ersättningsmanifestet och konformitetsutlåtandet ur samma tvåpass-vandring, vilket är varför manifestet kan litas på som jobbplan. Vandringen fyller en TPDFVCRManifestData-post medan den validerar: Fields från mallroten, PageSelectionField när ett sådant deklareras, och en Placeholders-array av TPDFVCRPlaceholderInfo-poster som bär FieldName, PageNumber, MCID, HasBBox och BBox. Konformitetsingångspunkten är CheckCompliancePDFVCR(InputStream, Password, Options, ResultList), och den kör sina kontroller i beroendeordning snarare än alla på en gång: den fullständiga PDF/X-kontrollen först, sedan GTS_PDFXVersion mot PDF/X-4, sedan GTS_PDFVCRVersion läst från namnrymden http://www.npes.org/pdfvcr/ns/id/, och först därefter strukturvandringen. Att passera Options som 1 får den att stanna vid första problemet, vilket är vad du vill ha i en batch-grind; att passera 0 samlar in allt, vilket är vad du vill ha i en rapport
// Verifiera en mall du fått snarare än producerat
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 är ett kontrakt, så det finns bara för konforma filer
BuildPDFVCRManifest returnerar JSON bara när problemlistan kommer tillbaka tom, och returnerar en tom sträng annars. Det är inte defensiv kodning för dess egen skull. Ett manifest konsumeras av en kompositionsmotor som ska driva riktig utdata från det, och att lämna tillbaka en partiell fältlista extraherad ur en fil med två mallrötter inbjuder motorn att behandla en icke-konform struktur som en pålitlig ersättningsplan. Genom dokument-API:et sveper GetPDFVCRManifest(InputFileName, Password) samma anrop och sätter den senaste felkoden till PDFLIB_ERROR_PDFVCR_COMPLIANCE (608) när filen inte kvalificerar, så en misslyckad extrahering och en tom mall går att skilja åt. JSON:en rapporterar fields, pageSelectionField som en sträng eller null, och en placeholders-array vars poster ger field, page, mcid och antingen en fyrsiffrig bbox eller null
var
Lib: TPDFlib;
Manifest: WideString;
begin
Lib := TPDFlib.Create;
try
Manifest := Lib.GetPDFVCRManifest('campaign-template.pdf', '');
if Manifest = '' then
// 608 betyder att filen inte är PDF/VCR-1-konform,
// 401 betyder att filen inte kunde öppnas
Writeln('No manifest, error ', Lib.LastErrorCode)
else
Writeln(Manifest);
finally
Lib.Free;
end;
end;
En detalj att ta med in i din konsument: värdet page följer bibliotekets nollbaserade sidkonvention, inte den ettbaserade numrering en människa ser i en viewer. Att blanda ihop dem är en tyst off-by-one som placerar innehåll på fel ark, och den fångas inte av någon validator eftersom båda talen är strukturellt giltiga
Var gränserna går
PDF/VCR-1-stödet i PDFlibPas täcker mallskapande, spargrinden, fristående preflight och manifestextrahering. Det utför inte ersättningen. Att substituera data in i platshållare och producera de personaliserade instanserna är kompositionssteget, och det bor utanför biblioteket medvetet, för manifestet är precis det gränssnitt som låter en kompositionsmotor skrivas oberoende. Spargrinden vägrar också snarare än reparerar. Till skillnad från profiler där en felaktig annoteringsflagga kan normaliseras tyst, är varje PDF/VCR-1-begränsning som PDFVCRReadyForSave upprätthåller en där gissande skulle ändra vad dokumentet betyder: att uppfinna en mallrot, eller att välja vilket fält en obunden platshållare tillhör, producerar en fil som validerar och ljuger. Samma reparera-versus-vägra-gräns diskuteras för en annan profil i PDF/E-1-authorläget och begränsad preflight, och arbetsflödet för att köra flera konformitetskontroller över ett dokument täcks i konformitets- och signeringsarbetsbänken
Mallskapande, den begränsade strukturvandringen, spargrinden och manifestextraheringen levereras alla i PDFlibPas Delphi PDF-biblioteket, vilket betyder att en mall kan produceras under profilen och sedan verifieras genom en separat kodväg som aldrig såg skrivartillståndet, och den separationen är det enda som gör ett konformitetspåstående värt något