Transakcinė spaustuvė grąžina jūsų 80 000 puslapių išrašų partiją su vienos eilutės atmetimu: "ne PDF/VT, RIP negali talpykluoti". Failas puikiai atsidaro kiekvienoje peržiūros programoje ant jūsų stalo, spalvos teisingos, duomenys sujungti korektiškai. Tačiau būtent to skaitmeninė spaudos mašina ir neprašė. Didelio greičio kintamųjų duomenų spauda gyvena arba miršta nuo to, ar spaudos įranga gali atpažinti, kad kliento logotipo blokas 1 puslapyje baitas į baitą yra tas pats objektas kaip ir 40 000 puslapyje, vieną kartą jį atvaizduoti ir po to pakartotinai naudoti. PDF/VT yra standartas, kuris šį pažadą paverčia mašiniškai patikrinamu, o "atrodo teisingai" čia yra būtent spąstai, nes struktūra, kurią skaito RIP, ekrane yra nematoma
PDFiumPas šią struktūrą pateikia per nedidelį TPdf paviršių: SaveAsPdfVT ją įrašo, ValidatePdfVT ją patikrina. Šis straipsnis apie tai, ką tiksliai šie du metodai įrašo į diską ir ką jie tikrina, kur ISO 16612-2 yra griežtesnis nei iš pradžių atrodo, ir kurios dalys yra sąžiningi struktūriniai inkarai, o ne pilnas preflight, už kurį galite išrašyti sąskaitą klientui
Ką standartizuoja PDF/VT ir kodėl pirmiausia ateina PDF/X
PDF/VT (ISO 16612-2:2010) nėra naujas failo formatas. Tai optimizavimo metaduomenų sluoksnis, pritvirtintas prie PDF/X failo, ir ši tvarka yra konstrukciškai svarbi. Standartas apibrėžia tris atitikties lygius, tačiau tik du iš jų įvardija PDF failą: PDF/VT-1, vieną savarankišką dokumentą, ir PDF/VT-2, failų rinkinio modelį, kuriame puslapiai remiasi bendrais išoriniais resursais. Trečiasis žymuo, kurį galite pamatyti, PDF/VT-2s, apskritai nėra failo lygio reikšmė; jis gyvena MIME srauto antraštėje, aprašytoje A priede. Jei randate kodą, kuris dokumento XMP įrašo GTS_PDFVTVersion = "PDF/VT-2s", tas kodas yra neteisingas
Nederinama taisyklė vienam failui yra PDF/X pagrindas. ISO 16612-2 §6.2.1 reikalauja, kad kiekvienas PDF/VT-1 failas kartu būtų ir galiojantis PDF/X-4 failas. PDF/VT-2 failų rinkinys pagal §6.2.2 turi remtis PDF/X-4p, PDF/X-5g arba PDF/X-5pg. Dėl to PDF/VT rašyklė negali tiesiog pridėti poros identifikavimo raktų: ji turi neštis visą PDF/X-4 žymeklių rinkinį, o tai reiškia OutputIntent, įterptą ICC paskirties profilį, atitinkamus XMP ir dokumento Info įrašus, trailer /ID ir jokio šifravimo. Praleiskite bent vieną iš jų ir turėsite failą, kuris teigia esąs PDF/VT, bet žlunga tą akimirką, kai atitinkantis vartotojas patikrina pagrindą. PDFiumPas PDF/X-4 sluoksnį laiko PDF/VT išsaugojimo dalimi, todėl jokio atskiro SaveAsPdfX prieš tai kviesti nereikia; injektorius abu sluoksnius įrašo vienu praėjimu
Failo įrašymas su SaveAsPdfVT
Minimalus kvietimas reikalauja tik aktyvaus dokumento, nes TPdfVTSaveOptions.Default pateikia integruotą sRGB ICC profilį ir atitiktį pvc1. Išsaugojimas viduje vykdo tris žingsnius: pašalina bet kokį saugumą (įterpti aiškaus teksto žymeklius į užšifruotą objektų srautą reikštų jį sugadinti), perkelia dokumento esamą Info žodyną ir trailer /ID į žymeklių rinkinį taip, kad XMP ir Info reikšmės sutaptų, ir tada per incremental update prideda PDF/X-4 bei PDF/VT objektus
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;
Tikram gamybiniam išvedimui beveik visada norėsite perrašyti OutputIntent savo spaudos charakterizavimo profiliu, o ne palikti bendrinį sRGB atsarginį variantą. ICC baitus ir sąlygos identifikatorius pateikite per 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;
Viena detalė šiame pavyzdyje yra sąmoningas apsauginis turėklas, o ne ribojimas, dėl kurio galima ginčytis. Nustatymas Opt.Conformance := pvc2 nesukuria PDF/VT-2 failo. Rašyklė bet kokį ne pvc1 prašymą normalizuoja atgal į pvc1, nes PDF/VT-2 yra failų rinkinio formatas, o vieno failo rašyklė, kuri fiziškai prirašo vieną išvesties dokumentą, negali surinkti išorinių resursų rinkinio, kurio reikalauja §6.2.2. Reikšmė pvc2 egzistuoja skaitymo keliui, kad ValidatePdfVT galėtų atpažinti ir pranešti apie esamą failų rinkinio dokumentą; tai nėra rašymo tikslas
DPart medis: struktūra, kurią iš tikrųjų skaito RIP
PDF/VT šerdis yra Document Part (DPart) hierarchija. Ji leidžia spaudai padalyti ilgą partiją į įrašus, grupuoti įrašus į gavėjus ar pašto paketus ir prijungti Document Part Metadata, kad paskesnė įranga galėtų kiekvieną vienetą maršrutuoti ir apmokestinti. ISO 16612-2 §6.5 apibrėžia laidyną: katalogas turi /DPartRoot, šakninis DPart mazgas turi /DPartRootNode ir /NodeNameList, kuriame įvardijamas kiekvienas hierarchijos lygis, lapiniai DPart apima puslapių medžio intervalus, o kiekvienas puslapis, priklausantis daliai, per puslapio lygio /DPart įrašą rodo atgal į savo lapą
Kai šaltinio dokumente jau yra naudotina hierarchija, SaveAsPdfVT ją išsaugo. Kai jos nėra, rašyklė sintetina minimalią: vieną dokumento lygio DPart, kuris dengia esamą puslapių medį jo tvarka, su prie kiekvieno gyvo puslapio objekto pridėta /DPart atgaline nuoroda ir vieno lygio /NodeNameList [/Document]. Būkite sau sąžiningi, kas yra toks minimalus medis. Tai struktūrinis inkaras, patenkinantis §6.5 formos reikalavimus; tai nėra verslo metaduomenys. Jis negali išgalvoti gavėjų, pašto vienetų ribų ar produktų partijų, nes šios informacijos šaltinyje niekada nebuvo. Jei turite duomenų pagal gavėją, tikimasi, kad gilesnį DPart medį susikursite patys ir atitinkamai išplėsite /NodeNameList
Validacija, kuri žengia toliau nei vien raktų buvimas
ValidatePdfVT grąžina TPdfVTValidationResult įrašą su trimis dalykais: aptikta Conformance, problemų aibe Issues ir pagalbiniu IsCompliant, kuris tampa true tik tada, kai atitiktis yra tikras lygis, o problemų aibė tuščia. Problemų enum sąmoningai detalus, kad nesėkmės rezultatas pasakytų, kurį punktą praleidote, o ne tiesiog „neteisinga“:
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;
Dvi patikros, kurias verta suprasti giliau, yra atitikties poravimas ir DPart perėjimas, nes abi anksčiau buvo per daug atlaidžios ir vėliau buvo sugriežtintos pagal specifikaciją. Poravimo pusėje validatorius taiko tikslų atitikimą, o ne principą „bet koks PDF/X tiks“: PDF/VT-1 failas priimamas tik su PDF/X-4 pagrindu, o PDF/VT-2 failas tik su PDF/X-4p, PDF/X-5g arba PDF/X-5pg. PDF/VT-1 žymuo ant PDF/X-1a pagrindo yra pranešamas kaip klaida, o ne praleidžiamas mostelėjus ranka
DPart perėjime gyvena didžioji dalis griežtumo. Neužtenka, kad katalogas turėtų /DPartRoot raktą, nes suklastotas tuščias objektas arba objektas be puslapio nuorodų vis tiek negali būti suvartotas. HasValidDPartHierarchy ir rekursinis ValidateDPartNode pereina per visą struktūrą: seka tėvų nuorodas, atmeta pasikartojančius vaikus ir ciklus, reikalauja, kad /Start ir /DParts būtų tarpusavyje nesuderinami, ir verčia lapų intervalus dengti puslapių medį gylio paieškos tvarka taip, kad kiekvieno puslapio /DPart rodytų į tą lapą, kuriame jis gyvena. Visi šie vidiniai gedimai į viešą enum nesiplečia, o susiveda į vieną pvviMissingDPartRoot bitą, todėl šią vieną vėliavą reikėtų skaityti kaip „DPart hierarchija nenaudotina“, o ne pažodžiui kaip „šakninio rakto nėra“
Trys sintaksiniai spąstai, kuriuos validatorius dabar priverstinai tikrina
Keli praėjimai per §6.5 lentelę 4 iškėlė formas, kurias ankstesnės versijos priimdavo, bet standartas neleidžia. Tai būtent tie dalykai, kuriuos ranka kuriamas DPart medis sugadina dažniausiai, todėl verta juos įvardyti tiesiai:
/DPartsyra masyvų masyvas, o ne plokščias masyvas. Kiekvienas išorinio masyvo elementas pats turi būti netiesioginių nuorodų masyvas. Plokščias/DParts [9 0 R]atmetamas; atitinkanti forma yra/DParts [[9 0 R] [10 0 R]]. Tai neleidžia nehierarchinei struktūrai apsimesti galiojančiu lygiu/Endžymi tik tikrą daugiapuslapį intervalą. Lapinis DPart gali turėti/Endtik tada, kai jis taip pat turi/Start, o/Endpuslapių medžio tvarka privalo eiti po/Start. Išsigimęs/Start 3 0 R /End 3 0 Rdabar daro hierarchiją nenaudotiną, užuot buvęs perskaitytas kaip vieno puslapio dalis/NodeNameListvardai po PDF name unescaping turi išlikti galiojančiais XML NMTOKEN. Toks vardas kaip/Bad#20Nameišsiplečia į vardą su tarpu, o tai nebegaliojantis tokenas. Įgyvendinimas daro lengvą ASCII patikrą (raidės, skaitmenys,.,-,_,:ir ne ASCII baitai), kuri pagauna tarpus ir skyriklių klaidas neatmesdama teisėtų lokalizuotų ar tiekėjo specifinių vardų
XMP žymekliai: du būdai įrašyti tą pačią savybę
PDF/VT identifikacija gyvena XMP po pdfvtid vardų erdve, konkrečiai GTS_PDFVTVersion ir GTS_PDFVTModDate, šalia standartinių xmp:CreateDate ir xmp:ModifyDate. Subtilybė, dėl kurios naivūs skaitytuvai klaidingai praneša apie trūkumus, yra ta, kad kiekviena iš šių reikšmių gali būti serializuota dviem būdais: kaip elemento tekstas (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) arba kaip RDF atributas ant description elemento. PDFiumPas skaito abi formas, todėl failas, kurį kitas įrankis įrašė atributų stiliumi, nėra baudžiamas. Jis taip pat vykdo §6.3 nuoseklumo taisyklę, kad GTS_PDFVTModDate turi sutapti su xmp:ModifyDate; neatitikimas pakelia pvviModDateMismatch
Dar viena taisyklė iš to paties skyriaus: nežinoma GTS_PDFVTVersion reikšmė išsaugoma kaip pvcUnknown, o ne sukrenta atgal į pvcNone. Šis skirtumas svarbus praktiškai. pvcNone reiškia „jokio PDF/VT žymens apskritai, paprastas PDF“, o pvcUnknown reiškia „kažkas įspaudė versiją, kurios šis validatorius neatpažįsta“ - tarp jų ir PDF/VT-2s atvejį. Sulieti abu reikštų paslėpti netaisyklingą failą tame pačiame kibire kaip ir paprastą dokumentą
Kur baigiasi garantija
Verta būti tiksliems dėl ribos, ką šie metodai žada, nes kintamųjų duomenų spaudos atitiktis reiškia tikrus pinigus. DPart ir poravimo patikros yra baitų lygio struktūrinė validacija. Jos patvirtina, kad optimizavimo skeletas, PDF/X-4 pagrindo žymekliai, OutputIntent ir XMP egzistuoja bei yra tarpusavyje nuoseklūs. Tačiau tai nėra turinio lygio PDF/X-4 preflight: jos netikrina, ar kiekviena spalva patenka į deklaruotą išvesties sąlygą, ar visi šriftai įterpti, ar nepraslydo uždraustas skaidrumo maišymo kraštinis atvejis. Užsakymui, kurį siunčiate ant sutartinės spaudos mašinos, poruokite PDFiumPas struktūrinę validaciją su specializuotu PDF/X preflight varikliu ir bandomuoju spausdinimu taip, kaip patikrintumėte bet kurį kitą atitikties teiginį. Struktūrinis sluoksnis pagauna gedimus, kurie tyliai sulaužo RIP talpyklinimą; tai yra viena visos patikros pusė, bet ne visa patikra
Jei šias patikras integruojate į platesnius leidimo vartus, tas pats baitų lygio skenavimo metodas palaiko ir kitus bibliotekos standartų darbus, įskaitant objektų ir kryžminių nuorodų srautų validavimą prieš failui apskritai pasiekiant preflight, taip pat bendrų objektų discipliną už pakartotinai naudojamų puslapio antspaudų su Form XObjects, kuri dokumentą padaro draugišką RIP nuo pat pradžių. Čia aprašyti PDF/VT ir PDF/X išsaugojimo bei validavimo API yra PDFium VCL komponento, skirto Delphi ir C++Builder, dalis; produkto puslapyje pateikiamas visas atitikties nuorodynas