PDF/A ו-PDF/UA עונות על שתי שאלות שאין להן שום קשר זו לזו, וההתייחסות אליהן כאל תיבת סימון (checkbox) אחת של נגישות-וארכוב היא האופן שבו קבצים שבורים מגיעים לארכיון כשהם נושאים תווית של תאימות (compliance). PDF/A שואל האם קובץ עדיין ירונדר בנאמנות בעוד עשרים שנה. PDF/UA שואל האם טכנולוגיה מסייעת (assistive technology) מסוגלת לקרוא אותו היום. מסמך יכול לעבור אחת בקלות ולהיכשל בשנייה, לכן פסק הדין הכנה היחיד מגיע מהרצת שתיהן, ומהרצתן לפני שהקובץ נכתב החוצה, לא לאחר שמערכת במורד הזרם (downstream system) סומכת על מזהה התאימות האפוי לתוך המטא-נתונים שלו. מזהה זה הוא הצהרה עצמית (self-declaration). שום דבר בפורמט אינו מחייב אותו להיות נכון, ויישום שכותב "PDF/A-1b" לתוך ה-XMP מבלי לאמת כנגד התקן מפיק קובץ שנראה תואם לכל צרכן שקורא רק את התווית. losLab PDF Library (PDFlibPas) סוגרת את הפער הזה עבור Delphi ו-C++Builder על ידי בניית שני המאמתים לתוך הספרייה, כך שהבדיקה רצה בתוך-התהליך (in-process) ללא שירות חיצוני שיש להקים
שני תקנים שמכשילים קבצים מסיבות הפוכות
תקן ISO 19005 (PDF/A) הוא חוזה שעתוק (reproduction contract). קובץ תואם חייב להיות מרונדר באופן זהה עשרות שנים מהיום על גבי תוכנה שמעולם לא ראתה את המערכת שהפיקה אותו, לכן הכללים תוקפים תלויות חיצוניות: כל גופן מוטבע (embedded), צבע מעוגן ל-ICC OutputIntent מוטבע או מבוטא במרחב בלתי תלוי במכשיר (device-independent), אין הצפנה ב-PDF/A-1, אין JavaScript, מטא-נתוני XMP שמסכימים עם מילון פרטי המסמך (document information dictionary). לעומת זאת, תקן ISO 14289 (PDF/UA) הוא חוזה סמנטיקה. טכנולוגיה מסייעת חייבת לעבור על המסמך ולצאת עם משמעות, אשר חיה בשכבה שונה לחלוטין: עץ מבנה שלם (structure tree), טקסט חלופי על איורים (figures), כותרת מסמך המוגדרת לתצוגה, רמות כותרת שאינן מדלגות, ויחסי כותרות טבלה ששורדים לאחר שהעמוד יורד מהמסך
מכיוון ששני התקנים אוכפים שכבות שונות, הקבצים שנושכים אותך הם אלו שיושבים ביניהם. מסמך מושלם לארכיון יכול להיות אילם עבור קורא מסך. מסמך מתויג להפליא יכול להתייחס לגופן שולחן עבודה (desktop font) שלא יהיה קיים בעוד עשר שנים. הוצאה לאור במגזר הציבורי היא המקום הרגיל שבו שתי הדרישות נוחתות בבת אחת, וצינור עיבוד (pipeline) שם אינו יכול לקרוס אותן לכדי שער בודד. הממצאים (findings) מגיעים לאנשים שונים. גופנים שאינם מוטבעים הם פגם בקוד שמחולל את ה-PDF, בעוד שטקסט חלופי חסר שייך למי שבבעלותו תבניות התוכן, ודוח שמערבב את שניהם פשוט מועבר הלאה פעמיים
לאיזה חלק של PDF/A אתה מכוון חשוב לא פחות מהשאלה האם אתה פוגע בו. PDF/A-1 קפוא על PDF 1.4 ודוחה שקיפות (transparency) ו-JPEG2000, שניהם דברים שפלט דוחות מודרני שולח אליהם יד מבלי לחשוב. PDF/A-2 (ISO 19005-2, הבנוי על ISO 32000-1) מקבל את שניהם והוא ברירת המחדל השפויה עבור ארכיון חדש. PDF/A-3 מרחיק לכת אף יותר ומתיר קבצים מוטבעים מכל סוג שהוא, ועל כך מסתמכים פורמטים מוסדרים של חשבוניות אלקטרוניות (e-invoicing). צוות שעדיין מקבע תקן (standardizing) על PDF/A-1b בשנת 2026 נושא בדרך כלל דרישה שמישהו כתב לפני חמש עשרה שנים, ומשא ומתן מחדש על חלק היעד לרוב זול יותר מהסרת השקיפות מכל תרשים שהמערכת פולטת
ממצאים מובנים (Structured findings) בזמן הקליטה (ingestion)
נקודת הכניסה בממשק ה-API השטוח (flat-API) היא CheckFileCompliance, עם בורר הבדיקה (test selector) 1 עבור PDF/A ו-2 עבור PDF/UA. היא מחזירה מזהה (handle) של רשימת מחרוזות שפריטיו הם ממצאים פרטניים, אחד בכל שורה, וזה בדיוק הצורה ששער אוטומטי רוצה לעבור עליה:
function GateArchiveUpload(Pdf: TPDFlib; const FileName: string): Boolean;
var
ListId, I: Integer;
begin
ListId := Pdf.CheckFileCompliance(FileName, '', 1, 0); // 1 = PDF/A
if ListId = 0 then
begin
// 0 means "no findings" OR "file unreadable" -- disambiguate before passing
Result := Pdf.LastErrorCode = 0;
Exit;
end;
for I := 0 to Pdf.GetStringListCount(ListId) - 1 do
LogFinding(FileName, Pdf.GetStringListItem(ListId, I));
Pdf.ReleaseStringList(ListId);
Result := False;
end;
שני פרטים קובעים האם זה ירוץ ללא השגחה. הראשון הוא ערך החזרה שמשמעותו שני דברים הפוכים. CheckFileCompliance מחזירה 0 כאשר הקובץ תואם לחלוטין וגם כאשר לא ניתן היה לפתוח את הקובץ כלל, מכיוון שפנימית רשימת תוצאות ריקה קורסת ל-0 בשני המקרים. שער שקורא 0 כאישור מעבר יעביר העלאות מושחתות (corrupt uploads) ישירות אל הארכיון, לכן עליך לפתור את העמימות (disambiguate) בעזרת LastErrorCode לפני שאתה סומך על האפס, כפי שהשער למעלה עושה. השני נוגע לשאלה היכן הקובץ נמצא במחזור החיים שלו. הבודק רץ על קורא הזרמת הנתונים (streaming reader) של הספרייה במקום על מודל המסמך המלא, כשהוא פותח את הקובץ ישירות עם read sharing ומעולם לא קורא ל-LoadFromFile, וזו הסיבה שהוא יכול ללעוס קלט של גיגה-בתים מרובים מבלי לבנות עץ אובייקטים. אותה פתיחה בהזרמה נכשלת בעוד תהליך אחר עדיין מחזיק בקובץ לכתיבה, והעלאה בתהליך היא בדיוק המצב הזה. העבר בשער רק לאחר סיום ההעברה
עיצוב ה-streaming משתלם שוב תחת עומס. כל בדיקה פותחת את הקלט שלה לקריאה בלבד ומשתפת אותו לקריאה, כך שביקורת קורפוס (corpus audit) מתרחבת (scales out) על פני תהליכוני עובד (worker threads) או תהליכים עם מופע TPDFlib אחד לכל עובד וללא מאבק משאבים (contention) ביניהם. המשאב הדורש משמעת הוא המזהה (handle) עצמו. כל תוצאה שאינה אפס מ-CheckFileCompliance נשארת מוקצית עד שתקרא ל-ReleaseStringList, ושער ארוך-ריצה ששוכח לשחרר אותם אינו קורס, הוא פשוט מדמם זיכרון באיטיות עד שמישהו הולך לחפש מדוע
דוחות לבני אדם, הבדלים (diffs) עבור שערי Build
רשימת ממצאים היא הצורה הנכונה עבור שער והצורה השגויה עבור אימייל לצוות התבניות (template team). CreatePreflightReport מרנדרת את אותו ניתוח כטקסט קריא (readable prose), CreatePreflightReportEx מוסיפה בורר של פורמט-דוח, ו-SavePreflightReport כותבת אותו לדיסק כדי שהדוח יוכל לנסוע בתוך חבילת המסמך הנמסרת. חוזי ארכיון רבים הופכים דוח זה לתוצר מסירה (deliverable) בפני עצמו, לא רק ארטיפקט פנימי
החבר במשפחה זו שמרוויח את מקומו בשקט הוא ComparePreflightReports. תאימות היא משטח רגרסיה בדיוק כמו כל פיסת התנהגות אחרת. התאמה קלה של תבנית, גופן תאגידי שעבר רישוי לאחרונה, או שדרוג ספרייה - כל אחד מאלה עשוי להכניס ממצא שלא היה שם בשחרור (release) האחרון, ואף אחד מהם אינו מכריז על עצמו. שמור דוחות זהב (golden reports) עבור קבוצת מסמכים מייצגים תחת בקרת גרסאות (version control), חולל אותם מחדש לאחר כל שינוי, והרץ את ComparePreflightReports כדי לחשב את ההפרש (delta). קובץ הבדלים (diff) ריק הוא ארטיפקט-שחרור שכדאי לשמור. ממצא מפתיע מכשיל את ה-build, שזהו מקום זול בהרבה לגלות אותו מאשר בביקורת (audit)
מחוללים פלט שעובר בריצה הראשונה
בדיקת Preflight מוכיחה את ערכה על קבצים המגיעים ממקומות אחרים. עבור מסמכים שהקוד שלך עצמו מפיק, מציאת הפרות (violations) לאחר המחולל ותיקונן (patching) בחזרה פנימה היא דרך איטית ועקיפה. PDFlibPas נושא מצב בצד-המחולל (generation-side mode) עבור כל תקן, ואתה יכול להדליק את שניהם עבור אותו המסמך:
var
Pdf: TPDFlib;
Diag: WideString;
begin
Pdf := TPDFlib.Create;
try
Pdf.NewDocument;
Pdf.SetPDFAMode(1);
Pdf.LoadOutputIntentProfile('sRGB-IEC61966-2.1.icc', 'RGB');
Pdf.SetPDFUAMode('en-US');
Pdf.SetInformation(1, 'Quarterly Statement'); // /Title: required for PDF/UA
// ... draw tagged content here ...
Diag := Pdf.GetPDFUADiagnostics;
if Diag <> '' then
Writeln('fix before shipping: ', Diag);
Pdf.SaveToFile('statement.pdf');
// the preflight that counts runs on the saved file:
Writeln(Pdf.CreatePreflightReport('statement.pdf', '', 1, 0));
finally
Pdf.Free;
end;
end;
המלכודת מסתתרת בזמן השמירה. אחדים מתיקוני התאימות מתרחשים בזמן שהמסמך עובר סריאליזציה (serialized) ולא כשאתה מפעיל את המצב: אילוץ דגל ההדפסה על הערות (annotations), כתיבת AFRelationship ברירת מחדל עבור קבצים מוטבעים של PDF/A-3, נרמול סדר כרטיסיות (tab order) ותיאורי שדות טופס עבור PDF/UA. המסמך שיושב בזיכרון אינו זהה בבתים (byte-identical) לזה שנוחת על הדיסק, כך שפסק הדין היחיד של ה-preflight שמשמעותי הוא זה המחושב מהקובץ שנשמר. אמת את statement.pdf עצמו. אל תסיק (infer) תאימות מהאובייקט שעדיין בזיכרון, משום שהבתים שהיית שופט אינם הבתים ששלחת
תרחישי חשבוניות (Invoice scenarios) הנושאים XML קריא-מכונה (machine-readable) לצד המסמך הוויזואלי עוקבים אחר התבנית של ZUGFeRD ו-Factur-X, אשר בנויה על PDF/A-3. אלו צריכים להגדיר את יחס הצרופה (attachment relationship) במפורש בעזרת SetPDFA3DefaultAFRelationship, שכן תקן ISO 19005-3 מחייב כל קובץ מוטבע להצהיר על תפקידו ביחס למסמך. השאר זאת לא-מוגדר (unset) וה-XML המוטבע הוא סתם גוש נתונים (blob) ללא מטרה מוצהרת, דבר שהמאמת שם לב אליו
שופטים עצמאיים: veraPDF ו-Acrobat
יצרן אינו צריך להיות השופט היחיד של הפלט שלו עצמו. הבודקים של PDFlibPas נותנים לכם פסקי דין מובנים ומהירים בתוך-התהליך, שזה מה שאתה רוצה על ה"נתיב החם" (hot path), אך שער השחרור (release gate) עבור אצוות ארכיון אמור עדיין להעביר את הפלט דרך מאמת שאף אחד מהצוות שלך לא כתב. veraPDF הוא מימוש הייחוס המתוחזק על ידי הקהילה עבור PDF/A והכלי שרוב הארכיונים מציינים בקריטריוני הקבלה שלהם, לכן אליו צריך להשוות. הפרופילים של Acrobat preflight מהווים שובר-שוויון (tiebreaker) שימושי כאשר veraPDF והבדיקה התוך-תהליכית חלוקים בדעתם. רשום את שם המאמת ואת הגרסה שלו לצד כל דוח מאוחסן. טענה שקובץ עבר את veraPDF אומרת מעט מאוד ללא מספר ה-build שהעביר אותו, שכן הכלי מהדק את הכללים שלו בין שחרורים
מאמתים אכן חלוקים בדעתם בקצוות (edges) של התקנים, וכאשר הם עושים זאת, התשובה היא לא לבחור את הכלי שמוצא חן בעיניך. כווץ את הקובץ לדוגמה מינימלית שעדיין מעוררת את חוסר ההסכמה וקרא אותה אל מול טקסט התקן. שעה כזו של עבודה מציפה בדרך כלל אחד משני דברים: באג אמיתי בכלי ששווה לדווח עליו ל-upstream, או סעיף (clause) שהצוות שלך קרא לא נכון וכדאי לרשום אותו בהערות התאימות כדי שהאדם הבא לא יתדיין על זה מחדש
קלט מוצפן (Encrypted input) מקבל קיצור דרך. שני הבודקים מקבלים ארגומנט של סיסמה, אך קובץ PDF/A-1 עם מילון הצפנה הוא כבר לא-תואם (non-conforming), מכיוון ש-ISO 19005-1 אוסר על הצפנה לחלוטין, לכן ניתן לדחות הגשה מוצפנת לפני שרצה בדיקה עמוקה יותר. הבנה של מה שמילון הצפנה מעניק בפועל היא משימה בפני עצמה, המכוסה בהצפנת PDF וביקורת הרשאות
ממצאי PDF/UA כמעט תמיד מובילים בחזרה לאופן שבו עץ המבנה נכתב (authored) מלכתחילה, וטכניקות התיוג שמאחורי זה חיות בבניית עצי מבנה של PDF מתויג ב-Delphi. ארכיונים שדורשים גם חתימות דיגיטליות צריכים לצמד שער זה עם זרימת העבודה (workflow) בחתימות PAdES ואימותן. מפרט ה-API המלא של ה-preflight נמצא בדף המוצר של Delphi PDF Library