רכיב PDFium עבור דלפי מאמת מסמכי PDF/X המוכנים לדפוס באמצעות TPdf.ValidatePdfX, המממש בדיקת תאימות לתקן ISO 15930 בשתי שכבות: שמונה בדיקות תוכן ברמת הבתים (איסור דחיסת LZW, JavaScript, שדות טופס, הפניות OPI, היעדר TrimBox, מפתח Trapped לא מוגדר ועוד) בתוספת מעבר על מודל האובייקטים של PDFium המשתמש ב-FPDFFont_GetIsEmbedded כדי לוודא הטמעת גופנים בכל אובייקט טקסט בכל עמוד. התוצאה היא רשומת TPdfXValidationResult המציינת את רמת התאימות שזוהתה ומפרטת כל הפרה כ-enum מוגדר, כך שאפליקציית הדלפי שלכם יכולה לספר ללקוח בדיוק מדוע הקובץ יידחה בבית הדפוס לפני שמישהו יתחיל לעבוד
אם אי פעם שלחתם עבודה לבית דפוס מסחרי וקיבלתם אותה בחזרה עם דחייה של שורה אחת — "אין TrimBox", "הגופנים אינם מוטמעים", "Trapped לא מוגדר" — אתם מכירים את המחיר של גילוי מאוחר. PDF/X הוא המקביל ל-prepress של PDF/A: בעוד ש-PDF/A לארכיונאות מבטיח שמסמך יוצג באופן זהה בעוד עשרות שנים, PDF/X מבטיח שהמסמך יופרד לצבעים, יוקרן וייחתך באופן זהה ב-RIP של מישהו אחר מחר בבוקר. שני התקנים חולקים מנגנונים דומים (זיהוי XMP, ערכי OutputIntents, פרופילי ICC מוטמעים) אך עונים על שאלות שונות, ולכן הרכיב מספק מאמתים נפרדים לכל אחד מהם — צד ה-PDF/A מכוסה באימות קדם-טיסה (preflight) של PDF/A עם רכיב PDFium
מה תקן ISO 15930 בעצם דורש מ-PDF מוכן לדפוס?
תקן ISO 15930 קיים כדי לאפשר החלפה עיוורת (blind exchange): מעצב מוסר קובץ למדפיס שמעולם לא דיבר איתו, והמדפיס יכול להפיק פלט נכון ללא שיחת טלפון, ללא דוא"ל על גופן חסר וללא תמונה מקושרת שנשארה מאחור על המחשב הנייד של המעצב. כל כלל בתקן משרת מטרה זו. גופנים חייבים להיות מוטמעים מכיוון שלא ניתן להניח שה-RIP המקבל מחזיק בהם. הפניות חיצוניות אסורות מכיוון שהקובץ חייב להיות שלם בפני עצמו. תכונות אינטראקטיביות אסורות מכיוון שלדיו אין מנהל אירועים של onclick
רכיב PDFium מזהה שלוש משפחות תאימות ומדווח עליהן באמצעות ה-enum של TPdfXConformance בתוצאת האימות: pxc1a עבור PDF/X-1a:2001 (תקן ISO 15930-1, בסיס ה-CMYK וצבעי הספוט המחמיר על גבי PDF 1.3/1.4), pxc3 עבור PDF/X-3:2002 (תקן ISO 15930-3, המקבל צבעי RGB, Lab וניהול צבע מבוסס ICC), ו-pxc4 עבור PDF/X-4:2010 (תקן ISO 15930-7, המאפשר סוף סוף שקיפות חיה ושכבות על גבי בסיס של PDF 1.6). קובץ שאינו נושא זיהוי PDF/X כלל מוחזר כ-pxcNone, וזו כשלעצמה תשובה שימושית: המסמך מעולם לא הצהיר שהוא מוכן לדפוס, וכל יתר הדברים שהמאמת מדווח עליהם מסבירים מה יידרש כדי להביא אותו לשם
האיסורים הגיוניים ברגע שחושבים כמו ספק RIP. ה-/LZWDecode אסור בכל גרסאות PDF/X כך שצרכן תואם לעולם אינו תלוי במסנן בעל היסטוריה של תאימות ורישוי; Flate עושה את אותה העבודה ללא המטען. קוד JavaScript, שדות AcroForm ומילוני פעולות נוספות /AA אסורים כולם מכיוון שקובץ דפוס חייב להיות תיאור קבוע של סימנים על נייר — כל דבר שיכול לשנות את המראה בזמן הפתיחה מפר את הערובה לכך שמה שנבדק הוא מה שמודפס. מצייני מקום של OPI (Open Prepress Interface) אסורים מכיוון שהם, מעצם עיצובם, הפניות לתמונות ברזולוציה גבוהה המאוחסנות במקום אחר, ו"מקום אחר" הוא בדיוק מה שהחלפה עיוורת אוסרת
מדוע בתי דפוס פוסלים קובצי PDF ללא TrimBox?
ה-TrimBox הוא העמוד המוגמר — המלבן שנשאר לאחר חיתוך הגיליוטינה. ה-MediaBox, הקיים בכל עמוד PDF, הוא פשוט הגיליון: הוא כולל גלישה (bleed), סימני חיתוך, מטרות רישום ופסי צבע. תוכנות סידור עמודים (imposition) מציבות עמודים על גיליון הדפסה לפי ה-TrimBox שלהם; ללא תיבה זו, המפעיל צריך לנחש היכן כרטיס הביקור שלכם באמת מסתיים, וניחוש שגוי חותך את הגלישה או מותיר קו לבן דק בקצה אחד. זו הסיבה שתקן ISO 15930 דורש TrimBox (או ArtBox) בכל עמוד, ומדוע הפונקציה ValidatePdfX מעלה את השגיאה pvxiMissingTrimBox כאשר לא נמצא מפתח /TrimBox באף עמוד של המסמך
המפתח /Trapped עונה על שאלת ייצור אחרת. trapping (השמנה) היא טכניקת prepress של חפיפה קלה של צבעים סמוכים כך שסטיית רישום קלה במכונת הדפוס לא תיצור מרווחים לבנים ביניהם. המדפיס צריך לדעת האם עבודה זו כבר בוצעה: ביצוע trapping לקובץ שכבר עבר trapping מכפיל את החפיפות, ודילוג על כך בקובץ שלא עבר trapping מסתכן במרווחים גלויים לעין. לפיכך, תקן PDF/X דורש ממילון ה-Info לציין במפורש /Trapped /True או /Trapped /False — מפתח חסר או ערך /Unknown מאלץ אדם לבדוק את הקובץ פיזית, וזהו בדיוק שיח שהחלפה עיוורת נועדה לחסוך. הרכיב מסמן זאת כ-pvxiTrappedNotSet
הרצת האימות הדו-שכבתי באמצעות TPdf.ValidatePdfX
הפונקציה TPdf.ValidatePdfX אינה מקבלת ארגומנטים ומחזירה רשומת TPdfXValidationResult בעלת שלושה חברים: Conformance (גרסת ה-PDF/X שזוהתה), Issues (קבוצת פסקל של ערכי TPdfXValidationIssue), ועזר IsCompliant. פנימית היא מבצעת סריאליזציה של המסמך הטעון לזרם בזיכרון, מריצה עליו את מפקח רמת הבתים, ואז עוברת על מודל האובייקטים של PDFium לצורך בדיקת הטמעת הגופנים. שער preflight מינימלי נראה כך:
uses PDFium, FPdfPdfx;
procedure CheckPrintReadiness(const FileName: string);
var
Pdf: TPdf;
Res: TPdfXValidationResult;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
Pdf.Active := True;
Res := Pdf.ValidatePdfX;
Writeln('Detected conformance: ',
Ord(Res.Conformance)); // pxc1a, pxc3, pxc4, pxcNone...
if Res.IsCompliant then
Writeln('PDF/X checks passed')
else
begin
if pvxiMissingTrimBox in Res.Issues then
Writeln('REJECT: no /TrimBox on the pages');
if pvxiTrappedNotSet in Res.Issues then
Writeln('REJECT: /Trapped missing or /Unknown');
if pvxiPdfiumFontNotEmbedded in Res.Issues then
Writeln('REJECT: a page uses a non-embedded font');
if pvxiLzwForbidden in Res.Issues then
Writeln('REJECT: LZWDecode filter present');
end;
finally
Pdf.Free;
end;
end;
מכיוון ש-Issues הוא קבוצת פסקל רגילה, אתם יכולים לחלק אותו בהתאם לצרכי זרימת העבודה שלכם — להתייחס לבעיות מבניות כדחייה מוחלטת, להתייחס ל-pvxiMissingTitle (המלצה בתקן ולא חובה מוחלטת) כאזהרה, ולתעד את השאר. אותו סוג רשומה מזין גם את מחולל הדוחות של הרכיב, כך שאם אתם מעדיפים להפיק מסמך קריא לבני אדם במקום להסתעף על גבי enums, התבנית בבניית ממשק שורת פקודה לדוחות preflight באצווה עם רכיב PDFium חלה על PDF/X ללא שינוי
מה שכבת רמת הבתים תופסת — ומה היא מחמיצה
שכבת רמת הבתים היא סריקת אסימונים על גבי בתי המבנה של המסמך כאשר גופי הזרמים מרוקנים, כך שקובץ JPEG שבמקרה מכיל את תבנית הבתים /JavaScript אינו יכול להפעיל false positive. מעבר לבדיקות הסימונים (ערך ה-XMP pdfxid:GTS_PDFXVersion, ה-OutputIntent עם פרופיל ICC מוטמע, ה-/ID של הטריילר, ואיסור ההצפנה), מעבר התוכן מוסיף שמונה בדיקות, שלכל אחת מהן ערך enum משלה:
pvxiLzwForbidden— מסנן/LZWDecodeמופיע במקום כלשהו בקובץ (אסור בכל גרסאות PDF/X)pvxiJavaScriptForbidden— פעולת/JavaScriptאו עץ שמות נוכחים בקובץpvxiFormFieldsForbidden— מילון/AcroFormאו ערך/XFAקיימים בקובץpvxiAdditionalActions— מילון פעולות נוספות/AAקיים בקובץpvxiEmbeddedFilesForbidden— ערך/EmbeddedFilesאו הערת/FileAttachmentנוכחים בקובץpvxiOpiForbidden— ערך/OPIאו/Alternatesמפנה לתוכן תמונה שניתן להחלפהpvxiMissingTrimBox— לא נמצא/TrimBoxבאף עמודpvxiTrappedNotSet— המפתח/Trappedחסר או מוגדר כ-/Unknown
סריקת בתים היא מהירה ואינה דורשת מנוע תצוגה, אך יש לה נקודה עיוורת מובנית עם גופנים: ברמה זו המפקח יכול להחיל רק יוריסטיקה גסה — הוא מסמן מסמך כאשר הוא אינו מוצא שום תוכנית גופן מוטמעת כלל. קובץ עם תשעה גופנים מוטמעים וגופן מערכת אחד שהושחל פנימה ייראה תקין לחלוטין לסריקת בתים. פער יחיד זה הוא הסיבה לקיומה של השכבה השנייה
הטמעת גופנים דרך מודל האובייקטים של PDFium
שכבת מודל האובייקטים של רכיב PDFium עונה על שאלת הגופנים במדויק. לאחר המעבר ברמת הבתים, TPdf.ValidatePdfX עובר על כל עמוד, מבקש מ-FPDFPage_CountObjects את רשימת האובייקטים, ועבור כל אובייקט טקסט פותר את מזהה הגופן דרך FPDFTextObj_GetFont ושואל את FPDFFont_GetIsEmbedded. גופן בודד שאינו מוטמע במקום כלשהו במסמך מוסיף את pvxiPdfiumFontNotEmbedded לקבוצת הבעיות. המעבר מתקצר (short-circuits) בשתי רמות — הוא עוצר את סריקת האובייקטים בעמוד, ועוצר טעינת עמודים נוספים ברגע שהבעיה מאומתת — כך שבקטלוג בן 300 עמודים המפר את התקן, פסק הדין מגיע לרוב כבר אחרי העמוד הראשון
שתי הערות גבול שכדאי לדעל. ראשית, שכבה זו דורשת שספריית PDFium תהיה טעונה ומחייבת בניות המייצאות את FPDFFont_GetIsEmbedded; כאשר הייצוא חסר הבדיקה מדולגת ולא נכשלת, כך שקובץ DLL ישן יותר לעולם אינו מייצר דחיות שווא. שנית, הבדיקה עונה על השאלה "מוטמע או לא" ותו לא — היא אינה מבחינה בין הטמעה מלאה לבין הטמעת תת-קבוצה (subsetting), ואינה בודקת כיסוי תווים. כאשר קובץ נכשל ואתם צריכים לדעת איזה גופן באיזה עמוד נכשל, טכניקות המעבר המכוסות בניתוח מאפייני גופני PDF עם PDFium בדלפי ממשיכות בדיוק מהמקום שבו הערך הבוליאני של המאמת נעצר
אימות זרמים ללא טעינת מסמך — או ה-DLL
מפקח רמת הבתים נחשף גם כפונקציה עצמאית, ValidatePdfXCompliance(Source: TStream) ביחידת FPdfPdfx, והיא כתובה בפסקל טהור ללא תלות ב-DLL של PDFium. זה הופך אותה לניתנת לפריסה במקומות שבהם מנוע תצוגה אינו רצוי: שער העלאות קל משקל בשרת אינטרנט, משימת CI הבודקת קבצי גרפיקה מיוצרים, או שירות Lazarus בפלטפורמה שבה אינכם רוצים לשלוח קבצים בינאריים מקוריים. הזינו לה כל זרם התומך בחיפוש (seekable):
uses Classes, FPdfPdfx;
function QuickPdfXGate(const FileName: string): Boolean;
var
Fs: TFileStream;
Res: TPdfXValidationResult;
begin
Fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Res := ValidatePdfXCompliance(Fs);
Result := Res.IsCompliant and (Res.Conformance <> pxcNone);
finally
Fs.Free;
end;
end;
הפשרה ברורה: הנתיב העצמאי מריץ את בדיקות הסימונים ואת כל שמונת בדיקות התוכן, אך לא את שכבת ה-PDFium לכל גופן, ולכן פסק דין הגופנים שלו נסוג ליוריסטיקה הגסה. ארכיטקטורה נבונה משתמשת ב-ValidatePdfXCompliance כשער ראשון וזול ושומרת את TPdf.ValidatePdfX המלא לקבצים שעוברים אותו
היכן המאמת הזה נעצר והיכן מתחיל preflight מלא
כנות חשובה בכלי preflight, ולכן להלן הגבול. ValidatePdfX מאמת סימוני זיהוי, איסורים מבניים, מפתחות גאומטריית עמוד, את הצהרת ה-Trapped והטמעת גופנים עד לרמת אובייקט טקסט בודד. הוא אינו מודד כיסוי דיו כולל, אינו מאמת שכל מרחב צבע חוקי עבור הגרסה המוצהרת (כלל CMYK בלבד של X-1a, למשל), אינו בודק רזולוציית תמונה מול רשת ההדפסה, ואינו מעריך התנהגות מילוי יתר (overprint) או ששטוח שקיפויות — אלו דורשים מנוע preflight מנוהל-צבע, ותיעוד היחידה עצמה ממליץ לשלב אותה עם מנוע כזה לאישור סופי. מה שהבדיקה הדו-שכבתית מעניקה לכם הוא 80% מהדחיות שהן מבניות וניתנות לזיהוי מוקדם, הנתפסות במילי-שניות בתוך קוד הדלפי שלכם במקום בדוא"ל של מחר בבוקר מבית הדפוס
שתי שכבות האימות, ממשקי ה-API להזרקת סימוני PDF/X להפקת פלט תואם, ומאמתי ה-PDF/A, PDF/UA, PDF/E ו-PDF/VT החולקים את אותה ארכיטקטורה מסופקים ב-רכיב PDFium עבור דלפי ו-C++Builder — רכיב אחד, מתצוגה ועד שמירה על סף הדפוס