PDF súbor, ktorý dorazí na hranicu produkcie — či už ide o tlačovú frontu, archív alebo zákaznícky portál na nahrávanie dokumentov — by mal prejsť auditom predtým, než sa vôbec začne vykresľovať. Súbor môže obsahovať spúšťaciu akciu (Launch action) nastavenú na spustenie externého programu, obrázky príliš hrubé na to, aby prežili tlač, šifrovací slovník, ktorý zakazuje samotnú tlačovú úlohu, pre ktorú bol odoslaný, alebo štítok PDF/A, ktorému vôbec nezodpovedá. Kontrola dokumentu podľa takýchto pravidiel predtým, ako vstúpi do pracovného toku, sa nazýva predletová kontrola (preflighting). API PDFium v jazyku C poskytuje Delphi všetko potrebné na to, aby sa tieto kontroly implementovali priamo, bez vykreslenia čo i len jedinej stránky
Tento článok stavia samotné kontroly: štyri triedy na audit, z ktorých každá je malou rutinou, čo pripája zistenia do zdieľaného zoznamu výsledkov. Interaktívne prvky, metriky zdrojov, stav zabezpečenia a značky noriem získajú funkčný kód vrátane výpočtov. Ak potrebujete aparát obklopujúci tieto kontroly — cykly pre hromadné spracovanie priečinkov (batch folder loops), reportovacie súbory JSON a HTML, izoláciu na úrovni jednotlivých súborov — komponent PDFium (PDFium Component) dodáva hotový engine na predletovú kontrolu (preflight engine) a článok o hromadnej preflight kontrole cez CLI (batch preflight CLI) pokrýva túto mechaniku zospodu. Tieto dva účelovo zdieľajú jeden spoločný slovník výstupných kódov (exit-code vocabulary), takže audítor napísaný tu bez problémov zapadne priamo pod tento dávkový ovládač (batch driver)
Záznam o zistení a dohoda o návratových kódoch (exit-code contract)
Každá kontrola zapisuje do jedného plochého typu záznamu, pretože alternatíva, kedy každá kontrola tlačí svoj vlastný voľný text, neumožňuje následné počítanie, filtrovanie alebo prahovanie. Štyri polia postačujú
uses
System.SysUtils, System.Math, System.IOUtils,
System.Generics.Collections, pdfium_lib;
type
TFindingSeverity = (fsInfo, fsWarning, fsError);
TPreflightFinding = record
Severity: TFindingSeverity;
Code: string; // stable machine key, e.g. 'ACT-LAUNCH'
Page: Integer; // 1-based; 0 means document level
Message: string; // for humans; free to reword between releases
end;
TFindings = TList<TPreflightFinding>;
procedure Add(Findings: TFindings; Severity: TFindingSeverity;
const Code: string; Page: Integer; const Msg: string);
var
F: TPreflightFinding;
begin
F.Severity := Severity;
F.Code := Code;
F.Page := Page;
F.Message := Msg;
Findings.Add(F);
end;
Následné nástroje nadväzujúce v reťazci kľúčujú (keys) na Code, nikdy nie na text Message, ktorý sa môže voľne meniť. Návratový kód procesu (process exit code) sa riadi rovnakou dohodou o troch hodnotách ako v článku o dávkovom spracovaní: 0 znamená, že súbor nevygeneroval žiadne zistenia, 1 znamená, že zistenia existujú, a 2 znamená, že samotný audit sa nemohol spustiť, pretože súbor sa nepodarilo rozobrať (failed to parse) alebo vyžaduje heslo. Je dôležité udržať kód 2 oddelene. Priečinok poškodených naskenovaných dokumentov poukazuje na pokazený skener v procese pred nami, nie na náhly pád v oblasti dodržiavania predpisov (compliance collapse), a ak sa tieto dva prípady spoja, pošle to niekoho naháňať úplne nesprávny problém
Interaktívne prvky: skripty, ciele na spustenie (launch targets), externé odkazy
PDFium klasifikuje každú akciu, ktorú nájde, pomocou celočíselného typu. Konštanty z hlavičkového súboru fpdf_doc.h stoja za to presne zachytiť, pretože nesprávne skopírované hodnoty spôsobujú, že je skener ticho slepý. Skutočný výpočet (enumeration) je PDFACTION_UNSUPPORTED = 0, PDFACTION_GOTO = 1, PDFACTION_REMOTEGOTO = 2, PDFACTION_URI = 3, PDFACTION_LAUNCH = 4 a PDFACTION_EMBEDDEDGOTO = 5. Všimnite si, čo tam chýba: neexistuje tu žiadny člen pre JavaScript. Skripty na úrovni dokumentu (document-level scripts) nie sú akciami s odkazom a nikdy sa neukážu prostredníctvom FPDFAction_GetType; dajú sa vymenovať pomocou samostatnej rodiny volaní. Audítor, ktorý testuje typy akcií s vymyslenou konštantou pre JavaScript sa skompiluje, spustí a nič nenájde – navždy
const
PDFACTION_GOTO = 1; // in-document jump: harmless
PDFACTION_REMOTEGOTO = 2; // jump into another local file
PDFACTION_URI = 3; // opens an external URL
PDFACTION_LAUNCH = 4; // starts an external program
PDFACTION_EMBEDDEDGOTO = 5; // jump into an embedded file
function ActionTarget(Doc: FPDF_DOCUMENT; Action: FPDF_ACTION;
AType: ULONG): string;
var
Buf: array[0..2047] of AnsiChar;
begin
FillChar(Buf, SizeOf(Buf), 0);
if AType = PDFACTION_URI then
FPDFAction_GetURIPath(Doc, Action, @Buf, SizeOf(Buf))
else
FPDFAction_GetFilePath(Action, @Buf, SizeOf(Buf));
Result := string(UTF8String(PAnsiChar(@Buf)));
end;
procedure AuditPageActions(Doc: FPDF_DOCUMENT; Page: FPDF_PAGE;
PageNo: Integer; Findings: TFindings);
var
StartPos: Integer;
Link: FPDF_LINK;
Action: FPDF_ACTION;
AType: ULONG;
begin
StartPos := 0;
while FPDFLink_Enumerate(Page, @StartPos, @Link) <> 0 do
begin
Action := FPDFLink_GetAction(Link);
if Action = nil then
Continue; // destination-only link, nothing to flag
AType := FPDFAction_GetType(Action);
case AType of
PDFACTION_LAUNCH:
Add(Findings, fsError, 'ACT-LAUNCH', PageNo,
'Launch action targets "' + ActionTarget(Doc, Action, AType) + '"');
PDFACTION_URI:
Add(Findings, fsWarning, 'ACT-URI', PageNo,
'link opens ' + ActionTarget(Doc, Action, AType));
PDFACTION_REMOTEGOTO, PDFACTION_EMBEDDEDGOTO:
Add(Findings, fsWarning, 'ACT-XFILE', PageNo,
'cross-file destination "' + ActionTarget(Doc, Action, AType) + '"');
end; // PDFACTION_GOTO stays silent by design
end;
end;
procedure AuditDocumentBehaviors(Doc: FPDF_DOCUMENT; Findings: TFindings);
var
N: Integer;
begin
N := FPDFDoc_GetJavaScriptActionCount(Doc);
if N > 0 then
Add(Findings, fsError, 'JS-DOC', 0,
Format('%d document-level JavaScript action(s) run on open', [N]));
N := FPDFDoc_GetAttachmentCount(Doc);
if N > 0 then
Add(Findings, fsWarning, 'ATT-EMB', 0,
Format('%d embedded file attachment(s)', [N]));
end;
Rozdelenie závažnosti (severity split) tu slúži ako zakódovaná stratégia uplatňovaných zásad (policy). Akcia Launch je chyba, pretože spustenie ľubovoľného programu je najnebezpečnejšia vec, ktorú kliknutie v PDF dokumente dokáže vykonať, a žiadna faktúra to nevyžaduje. Externé URI predstavujú výstrahy (warnings): bežne sa nachádzajú v legitímnych dokumentoch, ale recenzent by si mal vedieť pozrieť cieľ aj bez samotného kliknutia, keďže text zobrazený na odkaze a jeho skutočný konečný cieľ sa vôbec nemusia zhodovať. Skoky cez GoTo do vnútorných častí dokumentu predstavujú štruktúru, nie správanie a vo výslednom reportovanom hodnotení nezaujmú vôbec miesto — ak audítorský predletový kontrolný nástroj spúšťa falošný poplach na každý záznam v obsahu dokumentu, skôr naučí používateľov zvyku bez povšimnutia ho zakaždým ignorovať. Pri požiadavkách smerujúcim k načítaniu samotných blokov so skriptom skrývajúcim sa za počtom zistených JavaScript udalostí ako aj k určeniu prislúchajúcich na rozpoznanie stupňa MDP podpisu alebo lokalizácie prvkov typu XFA, článok zameraný na audit bezpečnostných rizík preberá do rovnakých uplatnení v krokoch túto presne rovnakú vrstvu s nasadením k prácam na zabalení pomocou objektu prislúchajúcemu ku komponentu (component's object wrapper)
Metriky zdrojov: efektívne rozlíšenie obrázkov DPI
Obrázok vo vnútri dokumentu formátu PDF neponúka ani nemá vo výbave priradené a vyhradené nejaké vlastné a určené DPI (DPI of its own). Oplýva len s pixelmi a na stránke ho následne program posadí (places those pixels) na ohraničené a vytýčené územie za ohraničeniami reprezentované pomocou bodiek u bodov (points), kde zhruba pre 72 nanesených takýchto bodiek spoločne pripadá na jeden palec (inch). Samotné rozlíšenie existuje iba v určení podielu dvoch z nich. To je hlavný a určujúci dôvod hovoriac k veci zistenia okolností toho druhu, kde prečo i pri rovnakých proporciách zameraných obmedzeniami na ohraničenú veľkosť obrazu pre 600 s rozmermi krát 400 zadefinovaná u snímky ohraničenom pre detail s čistým profilom rovnako ostrom zo stvárnených ako pre malú ikonu odhliadnuc v iných meradlách formátu sa zároveň o zachovanú a tú istú rovnakú zložku pri snímku stáva za celostránkové prevedenie z prednesenej funkcii rozmazaný a pre oči nečitateľný obraz s (blurry mess). Na proces zachovanej od formátu slúžiaceho auditu v ohraničení preukázaných z obmedzení za týmito účelmi je zo sféry formovaných z postupností pri menách o obmedzených zadefinovaných rovnomerne pre obidve u priradených hodnôt dôležité prísť u každého obrazu s presnosťou (needs both numbers for every image): informácie k požiadavkám formátu od určenia a k zdrojovým rozmerom udávaným u parametrov vo pixeloch prečítanými zo samotných metadát u obrázkoch pri uplatňovaní o hraničných obmedzujúcich rámcoch i predpokladaných rovnako z pozícií pre jeho reálne od určených pre ohraničenia z úhrnu o ohraničeniach objektu z uvažovaných s variantmi už (placed rectangle from the object bounds)
procedure AuditPageImages(Page: FPDF_PAGE; PageNo: Integer;
Findings: TFindings);
var
I, ObjCount: Integer;
Obj: FPDF_PAGEOBJECT;
Meta: FPDF_IMAGEOBJ_METADATA;
L, B, R, T: Single;
WidthPt, HeightPt, DpiX, DpiY, EffDpi: Double;
begin
ObjCount := FPDFPage_CountObjects(Page);
for I := 0 to ObjCount - 1 do
begin
Obj := FPDFPage_GetObject(Page, I);
if FPDFPageObj_GetType(Obj) <> FPDF_PAGEOBJ_IMAGE then
Continue;
if FPDFImageObj_GetImageMetadata(Obj, Page, @Meta) = 0 then
Continue;
if FPDFPageObj_GetBounds(Obj, @L, @B, @R, @T) = 0 then
Continue;
WidthPt := R - L; // placed size on the page, in points
HeightPt := T - B;
if (WidthPt <= 0) or (HeightPt <= 0) or
(Meta.Width = 0) or (Meta.Height = 0) then
Continue;
// 72 points = 1 inch, so placed inches = points / 72, and
// effective DPI = source pixels / placed inches.
DpiX := Meta.Width / (WidthPt / 72.0);
DpiY := Meta.Height / (HeightPt / 72.0);
EffDpi := Min(DpiX, DpiY); // the worse axis decides print quality
if EffDpi < 150.0 then
Add(Findings, fsWarning, 'IMG-LOWRES', PageNo,
Format('image %dx%d px placed at %.1fx%.1f pt = %.0f DPI effective',
[Meta.Width, Meta.Height, WidthPt, HeightPt, EffDpi]))
else if EffDpi > 600.0 then
Add(Findings, fsInfo, 'IMG-BLOAT', PageNo,
Format('image is %.0f DPI at placed size; resampling would ' +
'shrink the file with no visible loss', [EffDpi]));
end;
end;
Vznesené priradenia k určeným hodnotám (thresholds) zo zadania sa neriadia pre účel definícii obsiahnutej z podmienkami nad fyzikou (physics), no vyplývajú práve k ohraničeniam určenou s politikou z obmedzených o úmernej hranici. Na spodku v určenom minimálnom zaradení pod ohraničením (floor) rovnako s uplatnenými navigačnými u archívmi spojených zo zóny 150 DPI už obmedzenú a o možnosti i u bežnej tlače v kancelárii je v uplatnenej definície v rovnako zapamätateľnom i formáte celkom určite príznačne už (pixelates) z viditeľného pre pixelovanie a predloženú o prenos na požiadavky na bežnú prácu, čím za hranicou spomenutú s 300 vo sférach spájaných po únikov od uplatnenia k požiadavkám je v zastrešenom prostredí ako i prislúchajúcich z prepojení slúžiacim pri obmedzeniach zo zaradenými priradených o obvyklú sféru pre (commercial target) pre normu vo bežnej v platných a obvyklom stave z komerčnej požiadavky s udanými u (usual) s ohľadom podania do úloh zo zameraní. Pri rovnako nad zaradeniami u požiadavky na hodnotu z 600 je a (anything above 600 buys no visible quality) u k pre účel (quality while inflating file size) od z nafukovania rovnako obmedzujúcich o formátoch za u (inflating) k o uplatnení v postupoch o stránke k (which is why it reports as informational bloat rather than a defect) u do úloh zo informačným oznámeniam (informational bloat). Z úloh od úkonov za obmedzeniach z postúpených, o obmedzeniach z pre upozornenie: (One honest caveat) FPDFPageObj_GetBounds do pre (returns the axis-aligned box) i pre a (so for an image placed with rotation the computed figure underestimates the true density). Pre k sféram u štruktúry (The FPDF_IMAGEOBJ_METADATA struct also carries horizontal_dpi and vertical_dpi fields that PDFium derives from the full transform matrix) u do obmedzenej rovine s (and comparing the two results is a cheap way to spot rotated placements). A k (The same points-to-pixels arithmetic drives rendering in the opposite direction, covered in the JPEG export article)
Bezpečnostný stav: šifrovanie a bity oprávnení
Šifrovanie formátu PDF zo (encryption defines two passwords with different jobs) s (The user password gates decryption: without it the file will not open at all, and FPDF_LoadDocument returns nil with FPDF_GetLastError reporting FPDF_ERR_PASSWORD). Vlastník k (The owner password gates permissions: a file protected only by an owner password opens with no credentials but carries restriction bits that a conforming reader must honor). U k (The load attempt itself is therefore the first security probe) u a (and the distinction decides the exit code — a user-password file is unauditable (code 2), while an owner-password file audits normally and merely accumulates findings)
const
FPDF_ERR_PASSWORD = 4;
function AuditSecurity(const FileName: string;
Findings: TFindings): FPDF_DOCUMENT;
var
Perms: ULONG;
Revision: Integer;
begin
Result := FPDF_LoadDocument(PAnsiChar(AnsiString(FileName)), nil);
if Result = nil then
begin
if FPDF_GetLastError() = FPDF_ERR_PASSWORD then
Add(Findings, fsError, 'SEC-USERPW', 0,
'user (open) password required; audit cannot proceed')
else
Add(Findings, fsError, 'DOC-BROKEN', 0, 'file failed to parse');
Exit;
end;
Revision := FPDF_GetSecurityHandlerRevision(Result);
if Revision >= 0 then // -1 means the file is not encrypted
begin
// Opened with an empty password yet encrypted: owner-password-only.
// Anyone may read it, but the permission bits restrict what a
// conforming reader lets them do. Unencrypted files report all
// bits set, which is why the revision gate comes first.
Perms := FPDF_GetDocPermissions(Result);
Add(Findings, fsInfo, 'SEC-ENC', 0,
Format('encrypted, security handler revision %d', [Revision]));
if (Perms and 4) = 0 then // bit 3: print
Add(Findings, fsWarning, 'SEC-NOPRINT', 0,
'printing is not permitted');
if (Perms and 16) = 0 then // bit 5: copy / extract content
Add(Findings, fsInfo, 'SEC-NOCOPY', 0,
'content extraction is not permitted');
if (Perms and 2048) = 0 then // bit 12: high-resolution print
Add(Findings, fsWarning, 'SEC-LOWPRINT', 0,
'only low-resolution printing is permitted');
end;
end;
Masky od za (The masks come from Table 22 of ISO 32000-1) z (which numbers bits from 1: bit 3 of the /P value is mask 4, bit 5 is 16, bit 12 is 2048). U z (Whether a given finding matters is a routing decision). Za k (A print bureau should bounce a SEC-NOPRINT file at intake, where the submitter gets a clear message, rather than at the RIP three hours before a deadline). S (An archive should treat SEC-ENC itself as a blocker, since encryption and long-term preservation do not mix — a point the standards check is about to make formally)
Značky noriem: čítanie tvrdenia o PDF/A (PDF/A claim)
Súbor u s (A file declares PDF/A conformance in its XMP metadata packet, through the pdfaid:part property (1 through 4) and pdfaid:conformance (the level letter, such as b for visual fidelity or a for full structural tagging)). PDFium k (PDFium's C API offers no XMP accessor; FPDF_GetMetaText reads only the Info dictionary, which is not where the identification lives). U k (The escape hatch is a rule in the standard itself: ISO 19005 requires the XMP metadata stream to be stored uncompressed, precisely so tools can find it without a full PDF parser). Z (A raw byte scan is therefore a legitimate claim detector — and a file whose claim hides inside a compressed stream has already violated the standard it claims)
function PdfAClaim(const FileName: string): string;
var
Bytes: TBytes;
S: RawByteString;
P, Limit: Integer;
begin
Result := ''; // empty = no PDF/A claim present
Bytes := TFile.ReadAllBytes(FileName);
if Length(Bytes) = 0 then
Exit;
SetString(S, PAnsiChar(@Bytes[0]), Length(Bytes));
P := Pos('pdfaid:part', S); // XMP identification schema
if P = 0 then
Exit;
// Handles both <pdfaid:part>2</pdfaid:part> and pdfaid:part="2":
// take the first digit after the property name.
Limit := Min(P + 32, Length(S));
Inc(P, Length('pdfaid:part'));
while (P <= Limit) and not (S[P] in ['1'..'4']) do
Inc(P);
if P <= Limit then
Result := 'PDF/A-' + Char(S[P]);
end;
Zistenie, ktoré to produkuje, je zámerne iba informatívne, pretože tvrdenie (claim) je deklaráciou, nie vlastnosťou súboru. Záznam v XMP je jeden riadok z XML, ktorý môže zapísať akýkoľvek producent (vrátane pokazeného); skutočná zhoda (conformance) znamená to, že súbor reálne spĺňa stovky pravidiel týkajúcich sa vložených písiem, farieb nezávislých na zariadeniach a zakázaných funkcií. Odhalenie tohto tvrdenia vám iba napovie, ktoré súbory máte presmerovať na reálnu validáciu, a nič viac. Vstavaný engine pre predletovú kontrolu, ktorý je súčasťou komponentu, vykonáva takúto validáciu pre rôzne profily z okruhu PDF/A, PDF/UA a PDF/X, a článok o hromadnom preflight cez CLI ukazuje, ako ho prepojiť do potrubia (pipeline) spolu s reportmi, ktoré môže audítor otvoriť neskôr
Beh proti problémovému súboru
Ovládač spája kontroly dokopy: bezpečnosť na prvom mieste, pretože od nej závisí, či vôbec pobeží audit, nasledujú správania na úrovni dokumentu spolu so značkou o štandardoch a nakoniec cyklus pre jednotlivé strany zameraný na akcie a obrázky
function AuditFile(const FileName: string; Findings: TFindings): Integer;
var
Doc: FPDF_DOCUMENT;
Page: FPDF_PAGE;
I: Integer;
Claim: string;
begin
Doc := AuditSecurity(FileName, Findings);
if Doc = nil then
Exit(2); // audit failure, not a verdict
try
AuditDocumentBehaviors(Doc, Findings);
Claim := PdfAClaim(FileName);
if Claim <> '' then
Add(Findings, fsInfo, 'STD-PDFA', 0,
Claim + ' conformance claimed (declaration only, not validated)');
for I := 0 to FPDF_GetPageCount(Doc) - 1 do
begin
Page := FPDF_LoadPage(Doc, I);
if Page = nil then
begin
Add(Findings, fsError, 'PAGE-BROKEN', I + 1, 'page failed to parse');
Continue;
end;
try
AuditPageActions(Doc, Page, I + 1, Findings);
AuditPageImages(Page, I + 1, Findings);
finally
FPDF_ClosePage(Page);
end;
end;
finally
FPDF_CloseDocument(Doc);
end;
if Findings.Count > 0 then
Result := 1
else
Result := 0;
end;
Pri spustení na brožúre, ktorá sa vrátila z externej agentúry, vyzerá výstup takto
> preflight_audit brochure_final.pdf
brochure_final.pdf: 5 finding(s)
[ERROR] ACT-LAUNCH page 3 Launch action targets "..\tools\setup.exe"
[ERROR] JS-DOC doc 2 document-level JavaScript action(s) run on open
[WARNING] IMG-LOWRES page 7 image 412x287 px placed at 396.0x275.8 pt = 75 DPI effective
[WARNING] SEC-NOPRINT doc printing is not permitted
[INFO] STD-PDFA doc PDF/A-2 conformance claimed (declaration only, not validated)
exit code 1
Každý riadok je sám o sebe použiteľný (actionable), ale skutočným verdiktom je až ich kombinácia. Tento súbor deklaruje, že vyhovuje PDF/A-2, no zároveň nesie šifrovací slovník a živý JavaScript, pričom formát PDF/A obe tieto veci vyslovene zakazuje — takže samotné tvrdenie (claim) je preukázateľne nepravdivé ešte predtým, ako pobeží nejaký hĺbkový validátor. Práve to sú rozpory (contradictions), ktoré vyplávajú na povrch cez jednoduchý zoznam zistení, pričom pri boolovskej podobe vnímania ako prešlo/neprešlo často uniknú
Čo vám tento audit nedokáže povedať
Úprimnosť týkajúca sa rozsahu (scope) je to, čo udržuje nástroj na predletovú kontrolu dôveryhodným. Všetko vyššie uvedené načíta to, čo samotný súbor deklaruje sám o sebe: PDFium rozoberá (parses) štruktúru a tento audit ju inventarizuje. Nevykonáva žiadnu PDF/A validáciu — žiadne kontroly pokrytia pre glyfy proti priradeným vloženým písmam, žiadnu analýzu farebného priestoru voči výstupným zámerom (output intents), ani žiadne pravidlá týkajúcich sa úrovne klauzúl zo štandardu, ktoré delia deklaráciu od presnej zhody (conformance); na to už potrebujete dedikovaný validátor, ako je preflight engine v komponente (component's preflight engine) alebo veraPDF. Bity oprávnení (permission bits) sú len deklaráciami, ktoré dodržiavajú príslušné konformné programy na čítanie; nie sú kryptografickými stenami, a preto informácia o zámere ako SEC-NOPRINT opisuje skôr zámer ako vynútiteľné blokovanie (enforcement). Skenovanie pre akcie plošne prejde len anotačné odkazy a skripty na úrovni dokumentu; skripty hlboko ponorené v slovníkoch pre udalosti formulárových polí však naviac vyžadujú API na prácu s formulármi. A ak by ste si audit aj rozšírili o kontrolu podpisov (signature check), to reportuje iba nahlásený deklarovaný zámer, a nie plne preverené kryptografické zistenie — oblasť v ktorej overovanie cez certifikačný reťazec predstavuje odlišnú oddelenú úlohu. Predletový audit je rozhovor pri prijatí (intake interview), a nie samotný súdny proces: jeho úlohou je spraviť smerovacie rozhodnutie (routing decision) tak, aby bolo informované, rýchle a opakovateľné
Poznámka: Rozhrania API pre dokument, stránky, anotácie a objekty obrázkov používané v priebehu tohto auditu, spoločne s obalom na vysokej úrovni v jazyku Delphi (high-level Delphi wrapper) ako aj plnohodnotným predletovým overovacím enginom na noriem a validáciu, sa dodávajú priamo so súčasťou z rodiny produktov PDFium Component