Tehnični članak

Spremenljivo podatkovno tiskanje PDF/VT v Delphi z PDFium VCL

Transakcijska tiskarna vam 80.000-stranski tek izpiskov vrne z enovrstično zavrnitvijo: "ni PDF/VT, RIP ne more predpomniti." Datoteka se brez težav odpre v vsakem pregledovalniku na vaši mizi, barve so pravilne, podatki so združeni pravilno. Nič od tega pa ni tisto, kar je zahteval digitalni tiskarski stroj. Visokohitrostno tiskanje s spremenljivimi podatki živi ali umre glede na to, ali tiskarski stroj prepozna, da je blok z logotipom stranke na strani 1 bajt za bajtom isti objekt kot na strani 40.000, ga izriše enkrat in ga nato znova uporabi. PDF/VT je standard, ki to obljubo naredi strojno preverljivo, in "videti je pravilno" je točno past, saj je struktura, ki jo bere RIP, na zaslonu nevidna

PDFiumPas to strukturo izpostavi prek majhne površine na TPdf: SaveAsPdfVT jo zapiše, ValidatePdfVT jo preveri. Ta članek obravnava, kaj ti dve metodi dejansko zapišeta na disk in kaj pregledata, kje je ISO 16612-2 strožji, kot se sprva zdi, in kateri deli so poštena strukturna sidra namesto popolnega preflight preverjanja, ki bi ga lahko zaračunali stranki

Kaj PDF/VT standardizira in zakaj je PDF/X na prvem mestu

PDF/VT (ISO 16612-2:2010) ni nova oblika datoteke. Gre za plast optimizacijskih metapodatkov, pritrjeno na datoteko PDF/X, in ta vrstni red nosi breme. Standard definira tri ravni skladnosti, vendar samo dve izmed njih poimenujeta datoteko PDF: PDF/VT-1, en sam samozadosten dokument, in PDF/VT-2, model nabora datotek, kjer strani sklicujejo skupne zunanje vire. Tretji žeton, ki ga lahko vidite, PDF/VT-2s, pa sploh ni vrednost na ravni datoteke; živi v glavi toka MIME, opisani v prilogi A. Če najdete kodo, ki v XMP dokumenta vtisne GTS_PDFVTVersion = "PDF/VT-2s", je ta koda napačna

Neizpogajljivo pravilo za eno datoteko je osnova PDF/X. ISO 16612-2 §6.2.1 zahteva, da je vsaka datoteka PDF/VT-1 tudi veljavna datoteka PDF/X-4. Nabor datotek PDF/VT-2 pa mora po §6.2.2 temeljiti na PDF/X-4p, PDF/X-5g ali PDF/X-5pg. Zato zapisovalnik PDF/VT ne more samo pripeti nekaj identifikacijskih ključev: s seboj mora nositi celoten nabor označevalcev PDF/X-4, kar pomeni OutputIntent, vdelan destinacijski ICC profil, ustrezne vnose XMP in dokumentnega Info, trailer /ID, ter brez šifriranja. Izpustite karkoli od tega in dobite datoteko, ki trdi, da je PDF/VT, a pade v trenutku, ko skladni porabnik preveri osnovo. PDFiumPas plast PDF/X-4 obravnava kot del shranjevanja PDF/VT, zato prej ne kličete ločenega SaveAsPdfX; injektor obe plasti zapiše v enem prehodu

Zapis datoteke s SaveAsPdfVT

Minimalni klic ne potrebuje ničesar razen aktivnega dokumenta, ker TPdfVTSaveOptions.Default zagotovi vgrajen ICC profil sRGB in skladnost pvc1. Shranjevanje interno poteka v treh korakih: odstrani vso varnost (vbrizgavanje navadnih označevalcev v šifriran object stream bi ga pokvarilo), premosti obstoječi slovar Info dokumenta in trailer /ID v nabor označevalcev, tako da se vrednosti XMP in Info ujemajo, nato pa skozi incremental update pripne objekte PDF/X-4 in PDF/VT

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    if Pdf.LoadFromFile('statements-merged.pdf') then
    begin
      // Default options: built-in sRGB OutputIntent, PDF/VT-1, synthesised DPart
      if Pdf.SaveAsPdfVT('statements-pdfvt.pdf') then
        Writeln('PDF/VT-1 written')
      else
        Writeln('Save failed (document not active?)');
    end;
  finally
    Pdf.Free;
  end;
end;

Za resnični produkcijski izhod skoraj vedno želite OutputIntent prepisati z značilnostjo vašega tiskarskega stroja, ne z generičnim nadomestkom sRGB. Bajte ICC in identifikatorje pogojev podajte prek TPdfVTSaveOptions:

var
  Pdf: TPdf;
  Opt: TPdfVTSaveOptions;
  Icc: TBytes;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.LoadFromFile('directmail-merged.pdf');
    Icc := LoadIccProfile('GRACoL2013_CRPC6.icc');  // your own loader

    Opt := TPdfVTSaveOptions.Default;
    Opt.Conformance := pvc1;            // pvc2 is normalised to pvc1 on write
    Opt.IccProfileData := Icc;
    Opt.OutputConditionIdentifier := 'CGATS21_CRPC6';
    Opt.OutputCondition := 'Commercial print, coated, CRPC6';
    Opt.RegistryName := 'http://www.color.org';
    Opt.Title := 'Spring 2026 Direct Mail Run';
    Opt.Trapped := ptvFalse;           // PDF/X Info /Trapped state

    Pdf.SaveAsPdfVT('directmail-pdfvt.pdf', Opt);
  finally
    Pdf.Free;
  end;
end;

Ena podrobnost v tem izseku je namerna varovalka, ne omejitev, o kateri bi se lahko prepirali. Nastavitev Opt.Conformance := pvc2 ne ustvari datoteke PDF/VT-2. Zapisovalnik vsako zahtevo, ki ni pvc1, normalizira nazaj na pvc1, ker je PDF/VT-2 format nabora datotek, zapisovalnik ene datoteke, ki pripne en izhodni dokument, pa fizično ne more sestaviti nabora zunanjih virov, ki ga zahteva §6.2.2. Vrednost pvc2 obstaja za bralno pot, da lahko ValidatePdfVT prepozna in prijavi obstoječ dokument nabora datotek; ni cilj za zapis

Drevo DPart: struktura, ki jo RIP dejansko bere

Jedro PDF/VT je hierarhija Document Part (DPart). Prav ta omogoča, da tiskarski stroj dolg tek razdeli na zapise, zapise združi po prejemnikih ali poštnih svežnjih in pripne Document Part Metadata, da lahko nadaljnja oprema vsak kos usmeri in obračuna. ISO 16612-2 §6.5 določa povezave: katalog nosi /DPartRoot, korensko vozlišče DPart nosi /DPartRootNode in /NodeNameList, ki poimenuje vsako raven hierarhije, listni DPart pokriva razpone drevesa strani, vsaka stran, ki pripada delu, pa prek vnosa na ravni strani /DPart kaže nazaj na svoj list

Kadar izvorni dokument že vsebuje uporabno hierarhijo, jo SaveAsPdfVT ohrani. Kadar je ni, zapisovalnik sintetizira minimalno: en sam DPart na ravni dokumenta, ki po vrsti pokrije trenutno drevo strani, s povratnim sklicem /DPart, pripetim vsakemu živemu objektu strani, in z enonivojskim /NodeNameList [/Document]. Bodite pošteni do sebe glede tega, kaj to minimalno drevo je. Gre za strukturno sidro, ki zadosti zahtevam oblike iz §6.5; ni poslovni metapodatek. Ne more si izmisliti prejemnikov, meja poštnih kosov ali produktnih serij, ker teh informacij v viru nikoli ni bilo. Če imate podatke po prejemnikih, se od vas pričakuje, da zgradite globlje drevo DPart in ustrezno razširite /NodeNameList, da se ujema z ravnmi, ki jih ustvarite

Validacija, ki gre dlje od same prisotnosti ključev

ValidatePdfVT vrne zapis TPdfVTValidationResult s tremi stvarmi: zaznano Conformance, množico Issues in pomočnik IsCompliant, ki je true samo takrat, ko je skladnost resnična raven in je množica težav prazna. Enumeracija težav je namenoma natančna, zato neuspešen rezultat pove, kateri člen ste zgrešili, namesto da bi zgolj rekel "neveljavno":

var
  Pdf: TPdf;
  Res: TPdfVTValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.LoadFromFile('statements-pdfvt.pdf');
    Res := Pdf.ValidatePdfVT;

    if Res.IsCompliant then
      Writeln('PDF/VT compliant: ', VTLevelName(Res.Conformance))
    else
    begin
      if pvviMissingDPartRoot in Res.Issues then
        Writeln('DPart hierarchy missing or unusable');
      if pvviMissingPdfXIdentifier in Res.Issues then
        Writeln('PDF/X-4 base identifier absent');
      if pvviMissingOutputIntent in Res.Issues then
        Writeln('OutputIntent / ICC profile missing');
      if pvviEncryptionPresent in Res.Issues then
        Writeln('Encrypted - PDF/X forbids this');
    end;
  finally
    Pdf.Free;
  end;
end;

Dve preverjanji, ki ju je vredno razumeti v globino, sta parjenje skladnosti in sprehod po DPart, ker sta bili obe nekoč preveč prizanesljivi in sta bili zaostreni, da se ujemata s specifikacijo. Na strani parjenja validator dela natančno ujemanje, ne pa "katerikoli PDF/X je dovolj": datoteka PDF/VT-1 je sprejeta samo na osnovi PDF/X-4, datoteka PDF/VT-2 pa samo na PDF/X-4p, PDF/X-5g ali PDF/X-5pg. Označevalec PDF/VT-1 na osnovi PDF/X-1a se prijavi, ne pa spusti skozi

Sprehod po DPart je mesto, kjer živi največ strogosti. Ni dovolj, da katalog vsebuje ključ /DPartRoot, ker ponarejen prazen objekt ali objekt brez povezav na strani še vedno ni uporaben za porabo. HasValidDPartHierarchy in rekurzivni ValidateDPartNode sledita celotni strukturi: sledita nadrejenim povezavam, zavračata podvojene otroke in cikle, uveljavljata, da sta /Start in /DParts medsebojno izključna, ter zahtevata, da listni razponi strani pokrijejo drevo strani v globinskem vrstnem redu in da /DPart na vsaki strani kaže na list, ki jo vsebuje. Vse te notranje napake se sesedejo v en sam bit težave pvviMissingDPartRoot namesto širitve javne enumeracije, zato to zastavico razumite kot "hierarhija DPart je neuporabna", ne dobesedno kot "korenski ključ manjka."

Tri sintaktične pasti, ki jih validator zdaj uveljavlja

Zaporedni prehodi skozi §6.5, tabela 4, so razkrili oblike, ki so jih prejšnje različice sprejele, standard pa ne. To so točno vrste napak, ki jih ročno zgrajeno drevo DPart naredi narobe, zato jih je vredno izrecno izpostaviti:

  • /DParts je polje polj, ne plosko polje. Vsak element zunanjega polja mora biti sam polje posrednih sklicev. Ploski /DParts [9 0 R] se zavrne; skladna oblika je /DParts [[9 0 R] [10 0 R]]. To prepreči, da bi se nehierarhična struktura pretvarjala za veljavno raven
  • /End označuje samo resničen večstranski razpon. Listni DPart lahko nosi /End samo, kadar ima tudi /Start, in /End mora v vrstnem redu drevesa strani pasti pozneje kot /Start. Degenerirani /Start 3 0 R /End 3 0 R zdaj naredi hierarhijo neuporabno, namesto da bi se bral kot enostranski del
  • /NodeNameList imena morajo po odstranitvi ubežnih zaporedij v PDF preživeti kot XML NMTOKEN. Ime, kot je /Bad#20Name, se razširi v ime s presledkom, kar ni veljaven žeton. Implementacija naredi lahek ASCII-preverjalnik (črke, števke, ., -, _, :, plus bajti, ki niso ASCII), kar ujame napake s presledki in ločili, ne da bi zavrnilo legitimna lokalizirana ali dobaviteljsko specifična imena

Označevalci XMP: dva načina zapisa iste lastnosti

Identifikacija PDF/VT živi v XMP pod imenskim prostorom pdfvtid, natančneje v GTS_PDFVTVersion in GTS_PDFVTModDate, poleg standardnih xmp:CreateDate in xmp:ModifyDate. Podrobnost, ki pri naivnih bralnikih povzroča lažna poročila o "manjka", je ta, da se lahko katerikoli od teh zapisov serializira na dva načina: kot besedilo elementa (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) ali kot RDF-atribut na elementu description. PDFiumPas bere obe obliki, zato datoteka, ki jo je drugo orodje zapisalo v atributnem slogu, ni kaznovana. Uveljavlja tudi pravilo doslednosti iz §6.3, da mora biti GTS_PDFVTModDate enako xmp:ModifyDate; neujemanje sproži pvviModDateMismatch

Še eno pravilo iz istega člena: neznana vrednost GTS_PDFVTVersion se ohrani kot pvcUnknown, namesto da bi se zložila nazaj v pvcNone. Ta razlika je operativno pomembna. pvcNone pomeni "sploh ni označevalca PDF/VT, to je navaden PDF", medtem ko pvcUnknown pomeni "nekaj je vtisnilo različico, ki je ta validator ne prepozna" (med njimi primer PDF/VT-2s). Če bi to dvoje zlili, bi napačno oblikovana datoteka končala v istem košu kot navaden dokument

Kje se jamstvo konča

Vredno je natančno določiti mejo tega, kar te metode obljubljajo, ker je skladnost pri tiskanju s spremenljivimi podatki povezana z resničnim denarjem. Preverjanja DPart in parjenja so strukturna validacija na ravni bajtov. Potrdijo, da so optimizacijsko ogrodje, osnovni označevalci PDF/X-4, OutputIntent in XMP prisotni ter notranje dosledni. Niso pa vsebinsko preverjanje PDF/X-4: ne preverjajo, da je vsaka barva znotraj deklariranega izhodnega pogoja, da so vse pisave vdelane ali da se ni izmuznil kak prepovedan robni primer prosojnega mešanja. Za opravilo, ki ga pošiljate na pogodbeni tiskarski stroj, združite strukturno validacijo PDFiumPas z namenskim preflight motorjem PDF/X in testnim odtisom, tako kot bi zdravorazumsko preverili katerokoli drugo trditev o skladnosti. Strukturna plast ujame napake, ki tiho zlomijo predpomnjenje RIP; je polovica popolnega preverjanja, ne pa celota

Če ta preverjanja vgrajujete v širšo izdajno zaporo, isti pristop pregleda na ravni bajtov podpira tudi drugo delo knjižnice s standardi, vključno z validiranjem tokov objektov in medsklicev še preden datoteka sploh doseže preflight, ter disciplino skupnih objektov za ponovno uporabne žige strani s Form XObjects, zaradi katere je dokument sploh prijazen do RIP. API-ji za zapis in validacijo PDF/VT in PDF/X, opisani tukaj, so del komponente PDFium VCL za Delphi in C++Builder, katere stran izdelka vsebuje celoten referenčni pregled skladnosti