ไฟล์ PDF ที่เดินทางมาถึงเขตแดนการผลิต — คิวการพิมพ์, คลังข้อมูลสำรอง, หรือพอร์ทัลสำหรับให้ลูกค้าอัปโหลด — ควรได้รับการตรวจสอบก่อนที่สิ่งใดก็ตามจะนำมันไปแสดงผล ไฟล์นั้นอาจมีการดำเนินการประเภท Launch ที่ถูกตั้งค่าให้เริ่มต้นโปรแกรมภายนอก, มีรูปภาพที่หยาบเกินกว่าจะรอดจากการพิมพ์, มีพจนานุกรมการเข้ารหัสที่สั่งห้ามงานพิมพ์ชนิดเดียวกับที่ไฟล์ถูกส่งเข้ามา, หรือมีป้ายกำกับ PDF/A ที่ไม่ตรงกับความเป็นจริง การตรวจสอบเอกสารกับกฎเหล่านี้ก่อนที่มันจะเข้าสู่เวิร์กโฟลว์เรียกว่า การทำพรีไฟลท์ (preflighting) และ C API ของ PDFium จะมอบทุกสิ่งใน Delphi ที่จำเป็นต่อการติดตั้งการตรวจสอบเหล่านี้โดยตรง โดยไม่ต้องทำการเรนเดอร์แม้แต่หน้าเดียว
บทความนี้จะสร้างการตรวจสอบต่างๆ ขึ้นมาเอง: คลาสตรวจสอบสี่คลาส โดยแต่ละคลาสจะเป็นรูทีนขนาดเล็กที่จะนำสิ่งที่พบไปต่อท้ายในรายการผลลัพธ์ที่ใช้ร่วมกัน องค์ประกอบเชิงโต้ตอบ ตัวชี้วัดทรัพยากร สถานะด้านความปลอดภัย และเครื่องหมายมาตรฐาน ล้วนเป็นโค้ดที่ใช้งานได้จริง รวมถึงคณิตศาสตร์ด้วย หากสิ่งที่คุณต้องการคือกลไกที่อยู่รอบๆ การตรวจสอบเหล่านั้น — ลูปโฟลเดอร์แบบแบทช์, ไฟล์รายงาน JSON และ HTML, หรือการแยกไฟล์เป็นรายไฟล์ — คอมโพเนนต์ PDFium จะมาพร้อมกับกลไกพรีไฟลท์สำเร็จรูป และ บทความเกี่ยวกับแบทช์พรีไฟลท์ CLI จะครอบคลุมเรื่องท่อทางเหล่านั้น ทั้งสองส่วนนี้จงใจใช้คำศัพท์เกี่ยวกับ exit-code ชุดเดียวกัน เพื่อให้เครื่องมือตรวจสอบที่เขียนขึ้นที่นี่สามารถเสียบเข้าใต้ไดรเวอร์แบทช์นั้นได้โดยตรง
บันทึกสิ่งที่ค้นพบและข้อตกลงของ exit-code
ทุกๆ การตรวจสอบจะเขียนลงในเรคคอร์ดแบบแบน (flat record type) ประเภทเดียว เนื่องจากอีกทางเลือกหนึ่ง คือการให้การตรวจสอบแต่ละรายการพิมพ์คำอธิบายของตัวเอง จะไม่สามารถนับจำนวน กรอง หรือกำหนดเกณฑ์ในภายหลังได้ ฟิลด์สี่ฟิลด์นั้นเพียงพอแล้ว
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;
เครื่องมือที่อยู่ดาวน์สตรีมจะกำหนดคีย์ที่ Code โดยไม่ใช้ข้อความ Message ซึ่งมีอิสระที่จะเปลี่ยนแปลงได้ exit-code ของกระบวนการจะเป็นไปตามข้อตกลงสามค่าเช่นเดียวกับบทความแบบแบทช์: 0 หมายถึงไฟล์นั้นไม่พบสิ่งใด, 1 หมายถึงมีสิ่งที่ค้นพบ, และ 2 หมายความว่าการตรวจสอบตัวเองไม่สามารถทำงานได้เนื่องจากไฟล์ล้มเหลวในการแยกวิเคราะห์ (parse) หรือต้องการรหัสผ่าน การแยกโค้ด 2 เอาไว้มีความสำคัญมาก โฟลเดอร์ที่เต็มไปด้วยภาพสแกนที่เสียหายคือเครื่องสแกนเนอร์ที่เสียอยู่ต้นทาง ไม่ใช่การที่ระบบรองรับ (compliance) ล่มสลายลงอย่างกะทันหัน และการรวมทั้งสองอย่างเข้าด้วยกันจะส่งผลให้ผู้คนไปไล่ล่าปัญหาผิดจุด
องค์ประกอบแบบโต้ตอบ: สคริปต์, เป้าหมายการเปิดโปรแกรม, ลิงก์ภายนอก
PDFium จะแยกประเภททุกแอ็คชันที่มันพบด้วยประเภทเลขจำนวนเต็ม และค่าคงที่จาก fpdf_doc.h ก็คุ้มค่าแก่การระบุให้ชัดเจน เพราะค่าที่คัดลอกมาผิดๆ จะทำให้เครื่องสแกนเนอร์บอดสนิทอย่างเงียบๆ การแจงนับที่แท้จริงคือ PDFACTION_UNSUPPORTED = 0, PDFACTION_GOTO = 1, PDFACTION_REMOTEGOTO = 2, PDFACTION_URI = 3, PDFACTION_LAUNCH = 4 และ PDFACTION_EMBEDDEDGOTO = 5 โปรดสังเกตสิ่งที่หายไป: ไม่มีสมาชิก JavaScript เลย สคริปต์ระดับเอกสารไม่ใช่ลิงก์แอ็คชันและจะไม่ปรากฏผ่าน FPDFAction_GetType เลย; พวกมันถูกแจงนับโดยชุดการเรียกใช้งานที่แยกต่างหาก เครื่องมือตรวจสอบที่ทดสอบแอ็คชันตามประเภทกับค่าคงที่ของ JavaScript ที่จินตนาการขึ้นเอง เมื่อคอมไพล์ รัน และก็จะไม่พบอะไรเลยตลอดกาล
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;
การแบ่งระดับความรุนแรงเป็นการเข้ารหัสนโยบาย แอ็คชันประเภท Launch ถือเป็นข้อผิดพลาด เนื่องจากการสั่งรันโปรแกรมแบบสุ่ม (arbitrary program) เป็นสิ่งที่อันตรายที่สุดที่การคลิกภายใน PDF จะสามารถทำได้ และคงไม่มีใบแจ้งหนี้ใบไหนที่จะต้องใช้ฟีเจอร์นี้ URI ภายนอกจะถือเป็นคำเตือน: แม้จะเป็นเรื่องปกติสำหรับเอกสารที่ถูกต้องตามกฎหมาย แต่ผู้ตรวจสอบก็ควรจะได้เห็นเป้าหมายโดยไม่ต้องคลิกก่อน เนื่องจากข้อความลิงก์ที่มองเห็นกับปลายทางที่แท้จริงนั้นไม่จำเป็นต้องตรงกัน การข้าม (GoTo) ภายในเอกสารเป็นเรื่องของโครงสร้าง ไม่ใช่พฤติกรรม และควรเก็บมันเอาไว้นอกรายงานโดยสิ้นเชิง — เครื่องมือพรีไฟลท์ที่คอยร้องเตือนเหมือนเด็กเลี้ยงแกะ (cries wolf) ไปกับทุกๆ เอนทรีของสารบัญ จะฝึกให้ผู้คนคุ้นชินกับการเพิกเฉยมัน สำหรับการอ่านเนื้อหาสคริปต์ที่อยู่เบื้องหลังการนับของ JavaScript รวมถึงระดับลายเซ็น MDP และการตรวจจับ XFA บทความเกี่ยวกับการตรวจสอบความเสี่ยงด้านความปลอดภัย ได้อธิบายเกี่ยวกับพื้นผิวการทำงานเดียวกันผ่านออบเจ็กต์แรปเปอร์ของคอมโพเนนต์นี้ไว้แล้ว
ตัวชี้วัดทรัพยากร: DPI ของรูปภาพที่มีผล
รูปภาพภายในเอกสาร PDF จะไม่มี DPI เป็นของตัวเอง แต่มันมีเพียงพิกเซล จากนั้นหน้าเอกสารก็จะจัดวางพิกเซลเหล่านั้นลงในสี่เหลี่ยมผืนผ้าที่วัดหน่วยเป็นพอยต์ (point) โดยที่ 72 พอยต์จะเท่ากับหนึ่งนิ้ว ความละเอียดนั้นมีอยู่เพียงเพื่อเป็นอัตราส่วนระหว่างสองสิ่งนี้ ซึ่งเป็นเหตุผลที่ว่าทำไมรูปภาพขนาด 600 x 400 ภาพเดียวกันจึงคมชัดกริบเมื่อใช้เป็นรูปภาพขนาดย่อ (thumbnail) แต่กลายเป็นความยุ่งเหยิงที่เบลอเมื่อเป็นภาพหลักเต็มหน้า ดังนั้นการตรวจสอบจึงต้องการตัวเลขทั้งสองตัวสำหรับทุกรูปภาพ: มิติพิกเซลของภาพต้นฉบับจากข้อมูลเมตาของภาพ และสี่เหลี่ยมที่จัดวางจากขอบเขตของออบเจ็กต์
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;
เกณฑ์เหล่านี้คือนโยบาย ไม่ใช่ฟิสิกส์: 150 DPI คือขั้นต่ำที่การพิมพ์ในสำนักงานจะมองเห็นเป็นพิกเซลได้อย่างชัดเจน 300 คือเป้าหมายปกติในเชิงพาณิชย์ และอะไรก็ตามที่สูงกว่า 600 จะไม่ส่งผลดีต่อคุณภาพที่มองเห็นได้ แต่จะทำให้ไฟล์มีขนาดใหญ่ขึ้น ซึ่งเป็นสาเหตุที่มันรายงานข้อมูลว่าบวมมากกว่าเป็นความบกพร่อง ข้อแม้ประการหนึ่งที่ตรงไปตรงมาคือ: FPDFPageObj_GetBounds จะคืนค่ากล่องที่จัดวางตามแกน ดังนั้นสำหรับภาพที่จัดวางโดยมีการหมุน ตัวเลขที่คำนวณได้จะประเมินความหนาแน่นจริงต่ำเกินไป โครงสร้างข้อมูล FPDF_IMAGEOBJ_METADATA จะพกพา horizontal_dpi และ vertical_dpi ซึ่ง PDFium แตกออกมาจากเมทริกซ์การแปลงเต็มรูปแบบ และการเปรียบเทียบผลลัพธ์ทั้งสองวิธีก็เป็นวิธีการที่คุ้มค่าในการตรวจจับการวางตำแหน่งที่มีการหมุน คณิตศาสตร์แบบ points-to-pixels แบบเดียวกันนี้ ขับเคลื่อนการเรนเดอร์ในทิศทางตรงกันข้าม ครอบคลุมไว้ใน บทความเกี่ยวกับการส่งออก JPEG
สถานะความปลอดภัย: การเข้ารหัสและบิตสิทธิ์การเข้าถึง
การเข้ารหัส PDF จะกำหนดรหัสผ่านสองชุดซึ่งทำหน้าที่ต่างกัน รหัสผ่านผู้ใช้ (user password) จะเป็นประตูกั้นการถอดรหัส: หากไม่มีมัน ไฟล์ก็จะไม่เปิดขึ้นมาเลย และ FPDF_LoadDocument จะส่งคืนค่า nil ในขณะที่ FPDF_GetLastError จะรายงาน FPDF_ERR_PASSWORD รหัสผ่านเจ้าของ (owner password) จะเป็นประตูกั้นสิทธิ์การเข้าถึง: ไฟล์ที่ถูกปกป้องด้วยรหัสผ่านของเจ้าของเท่านั้น จะถูกเปิดด้วย credentials ที่ไม่มีสิทธิ์ แต่จะมีบิตข้อจำกัดติดมาด้วยเพื่อให้ตัวอ่านที่รองรับมาตรฐานต้องเคารพตาม ความพยายามในการโหลดจึงเป็นการตรวจสอบความปลอดภัยครั้งแรก และความแตกต่างนี้จะเป็นตัวตัดสิน exit code — ไฟล์ที่ติดรหัสผ่านผู้ใช้จะไม่สามารถตรวจสอบได้ (code 2) ในขณะที่ไฟล์ที่ติดรหัสผ่านเจ้าของจะตรวจสอบได้ตามปกติและจะเก็บข้อมูลที่ค้นพบเอาไว้เพียงอย่างเดียว
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;
หน้ากาก (masks) เหล่านี้มาจากตารางที่ 22 ของมาตรฐาน ISO 32000-1 ซึ่งเรียงลำดับตัวเลขเริ่มจาก 1: บิต 3 ของค่า /P คือ mask 4, บิต 5 คือ 16, และบิต 12 คือ 2048 สิ่งที่ค้นพบแต่ละอย่างจะมีความสำคัญหรือไม่ก็ขึ้นอยู่กับการตัดสินใจในการกำหนดเส้นทาง สำนักพิมพ์ควรจะตีกลับไฟล์ SEC-NOPRINT ไปตั้งแต่ตอนรับเข้า ซึ่งเป็นจุดที่ผู้ส่งจะได้รับข้อความที่ชัดเจน ดีกว่าจะไปตีกลับที่เครื่อง RIP ล่วงหน้าก่อนถึงกำหนดส่งงานเพียงแค่สามชั่วโมง คลังเอกสารควรจะปฏิบัติกับ SEC-ENC ราวกับว่ามันเป็นตัวขัดขวางเสียเอง เนื่องจากการเข้ารหัสและการเก็บรักษาในระยะยาวนั้นไม่สามารถอยู่ร่วมกันได้ — ประเด็นที่การตรวจสอบตามมาตรฐานกำลังจะสร้างขึ้นอย่างเป็นทางการ
เครื่องหมายมาตรฐาน: การอ่านการอ้างสิทธิ์ PDF/A
ไฟล์จะประกาศความสอดคล้องกับมาตรฐาน PDF/A ในแพ็กเก็ตข้อมูลเมตา XMP ผ่านพร็อพเพอร์ตี้ pdfaid:part (1 ถึง 4) และ pdfaid:conformance (อักษรระดับชั้น เช่น b สำหรับความถูกต้องของภาพ (visual fidelity) หรือ a สำหรับการแท็กโครงสร้างทั้งหมด) C API ของ PDFium ไม่มีตัวเข้าถึง XMP เลย; FPDF_GetMetaText จะอ่านเฉพาะพจนานุกรม Info เท่านั้น ซึ่งไม่ใช่ที่ตั้งของการระบุข้อมูลนั้น ช่องทางหนีคือมีกฎอยู่ในตัวมาตรฐานเอง: ISO 19005 กำหนดให้ต้องเก็บสตรีมของข้อมูลเมตา XMP ไว้โดยไม่มีการบีบอัด เพื่อให้เครื่องมือต่างๆ สามารถค้นหามันได้โดยไม่ต้องใช้ตัวแยกวิเคราะห์ PDF เต็มรูปแบบ ดังนั้นการสแกนไบต์ดิบจึงเป็นตัวจับอ้างสิทธิ์ (claim detector) ที่ถูกกฎ — และไฟล์ที่มีการอ้างสิทธิ์ซ่อนอยู่ในสตรีมที่ถูกบีบอัดก็ถือเป็นการละเมิดมาตรฐานที่ตัวมันอ้างสิทธิ์อยู่แล้ว
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;
สิ่งที่ค้นพบเหล่านี้มีจุดมุ่งหมายเพื่อเป็นข้อมูล (informational) เพียงอย่างเดียว เพราะการอ้างสิทธิ์ก็คือการประกาศ (declaration) ไม่ใช่เป็นพร็อพเพอร์ตี้ของไฟล์ การป้อนข้อมูลลง XMP เป็นคำสั่ง XML แบบบรรทัดเดียวที่ผู้ผลิตรายใดก็สามารถเขียนได้ แม้แต่ผู้ผลิตที่มีข้อบกพร่อง; ความสอดคล้อง (conformance) หมายถึงไฟล์นั้นต้องผ่านกฎหลายร้อยข้อเกี่ยวกับฟอนต์ที่ฝังอยู่, การแสดงสีโดยไม่ขึ้นอยู่กับอุปกรณ์ (device-independent color), และฟีเจอร์ที่ต้องห้าม การตรวจจับการอ้างสิทธิ์จะบอกคุณว่าควรส่งต่อไฟล์ใดไปเพื่อตรวจสอบความถูกต้อง (validation) จริงๆ และก็ไม่มีอะไรมากไปกว่านี้ เอนจินพรีไฟลท์ที่ติดตั้งมาพร้อมกับคอมโพเนนต์จะดำเนินการตรวจสอบในโปรไฟล์ PDF/A, PDF/UA, และ PDF/X อย่างครบถ้วน และ บทความเกี่ยวกับแบทช์ CLI จะแสดงให้เห็นถึงวิธีเชื่อมต่อเข้ากับไปป์ไลน์พร้อมด้วยรายงานต่างๆ ที่ผู้ตรวจสอบสามารถเปิดอ่านได้ในภายหลัง
การรันกับไฟล์ที่มีปัญหา
ไดรเวอร์เรียงร้อยการตรวจสอบเข้าด้วยกัน: ความปลอดภัยมาก่อนเป็นอันดับแรก เนื่องจากมันจะเป็นตัวตัดสินว่าจะดำเนินการตรวจสอบทั้งหมดหรือไม่ ถัดมาก็จะเป็นพฤติกรรมระดับเอกสารและการอ้างสิทธิ์ในมาตรฐาน จากนั้นจึงจะเป็นลูปแบบรายหน้าสำหรับแอ็คชันและรูปภาพต่างๆ
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;
เมื่อทดสอบกับโบรชัวร์ที่ส่งกลับมาจากเอเจนซี่ภายนอก ผลลัพธ์ก็จะออกมาหน้าตาแบบนี้
> 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
แต่ละบรรทัดสามารถนำไปใช้งาน (actionable) ได้ด้วยตัวของมันเอง แต่การผสมผสานกันต่างหากที่เป็นคำตัดสินที่แท้จริง ไฟล์นี้อ้างว่าเป็น PDF/A-2 แต่กลับพกเอาพจนานุกรมการเข้ารหัสและ JavaScript แบบไลฟ์มาด้วย และ PDF/A ก็สั่งห้ามทั้งสองสิ่งนี้โดยเด็ดขาด — ดังนั้นการอ้างสิทธิ์จึงสามารถพิสูจน์ได้ว่าเป็นเท็จก่อนที่การตรวจสอบเชิงลึกใดๆ จะเริ่มทำงาน สิ่งนี้คือความขัดแย้งประเภทหนึ่งที่รายการค้นพบแบบแบนสามารถหยิบยกขึ้นมาให้เห็นเด่นชัด ในขณะที่สถานะบูลีน (pass/fail) แบบธรรมดาเลือกที่จะซ่อนมันเอาไว้
สิ่งที่การตรวจสอบนี้ไม่สามารถบอกคุณได้
ความซื่อสัตย์เกี่ยวกับขอบเขต คือสิ่งที่ทำให้เครื่องมือพรีไฟลท์ได้รับความไว้วางใจ ทุกสิ่งที่กล่าวมาข้างต้นคือการอ่านสิ่งที่ไฟล์ได้ประกาศเกี่ยวกับตัวเอง: PDFium แยกวิเคราะห์โครงสร้าง และการตรวจสอบนี้ก็นำมันมาประเมิน (inventory) เครื่องมือนี้ไม่ได้ทำการตรวจสอบมาตรฐาน PDF/A แต่อย่างใด — ไม่มีการตรวจสอบความครอบคลุมของไกลฟ์ (glyph-coverage) กับแบบอักษรที่ฝังอยู่, ไม่มีการวิเคราะห์สเปซสีขัดแย้งกับเจตนาการแสดงผลเอาท์พุต, ไม่มีกฎเกณฑ์ระดับเงื่อนไขหรือระดับข้อย่อยที่แยกการอ้างสิทธิ์ออกจากความสอดคล้องอย่างแท้จริง; สำหรับสิ่งนั้นคุณต้องการเครื่องมือที่สร้างขึ้นมาเพื่อตรวจสอบความถูกต้อง (validator) โดยเฉพาะ เช่น เอนจินพรีไฟลท์ของคอมโพเนนต์ตัวนี้ หรือ veraPDF บิตสิทธิ์การเข้าถึงเป็นการประกาศที่มีให้สำหรับโปรแกรมอ่านที่รองรับเพื่อปฏิบัติตาม ไม่ใช่กำแพงกั้นทางวิทยาการเข้ารหัส ดังนั้น SEC-NOPRINT จึงอธิบายถึงความตั้งใจแทนที่จะเป็นการบังคับ การสแกนแอ็คชัน (action scan) ครอบคลุมถึงคำอธิบายประกอบของลิงก์และสคริปต์ระดับเอกสาร; แต่สำหรับสคริปต์ที่ถูกฝังลึกอยู่ในพจนานุกรมเหตุการณ์ของฟิลด์แบบฟอร์ม (form-field event dictionaries) ก็จำเป็นต้องใช้ form APIs เข้ามาช่วยเพิ่มเติม และการตรวจสอบลายเซ็น หากคุณขยายผลการตรวจสอบด้วยสิ่งนี้ มันจะรายงานเพียงเจตนาที่ประกาศไว้ ไม่ใช่การเข้ารหัสที่ผ่านการรับรอง — การตรวจสอบความถูกต้องของสายโซ่ใบรับรอง (certificate chain validation) เป็นงานที่ต้องทำแยกต่างหาก การทำพรีไฟลท์คือการสัมภาษณ์เพื่อรับเข้าทำงาน ไม่ใช่การพิจารณาคดี: หน้าที่ของมันคือการตัดสินใจในการกำหนดเส้นทางให้ได้รับข้อมูลรวดเร็ว และทำซ้ำได้
หมายเหตุ: API ของออบเจ็กต์เอกสาร หน้าเว็บ คำอธิบายประกอบ และรูปภาพที่ใช้ตลอดการตรวจสอบนี้ เมื่อใช้ร่วมกับ Delphi wrapper ระดับสูงและเอนจินพรีไฟลท์สำหรับตรวจสอบมาตรฐานอย่างสมบูรณ์แบบ จะถูกรวมเข้าไว้ด้วยกันใน PDFium Component