PDF/E-1 הוא פרופיל הארכוב למסמכי הנדסה, ו-PDFlibPas מממש אותו כמצב כתיבה שמפעילים עם SetPDFEMode בתוספת preflight מוגבל שקורא content stream אופרטור אחר אופרטור. הפרופיל הוא לא PDF/A עם תווית אחרת: יש לו מרחב זיהוי משלו, דרישת מטא-נתונים משלו למחזור החיים, וכלל אחד שהופך את אימות התוכן למחמיר יותר מכל פרופיל ארכוב שפגשת
תוצרי הנדסה הם הסיבה לקיומו של הפרופיל. סט שרטוטים שצריך להישאר קריא והוכח כבלתי שונה גם בעוד עשרים שנה, עם היסטוריית מהדורות ששורדת, ועם צבע שמשמעותו זהה גם על ה-plotter בבניין האחר. הדרישות האלה מייצרות מפרט שרוב דרישותיו יושבות מחוץ לתוכן העמוד, במטא-נתונים ובניהול צבע, וזה בדיוק המקום שבו כותב PDF גנרי טועה
זיהוי משלו, לא וריאציה על PDF/A
הדבר הראשון ליישר הוא שזיהוי PDF/E-1 לא ניתן להפקה על ידי התאמת התבנית של PDF/A או PDF/X. הוא משתמש במרחב שמות XMP נפרד, http://www.aim.org/pdfe/ns/id/, וערך הגרסה חייב להופיע בשני מקומות: כרשומה במידע המסמך וכמאפיין XMP ממורחב שם מרחב. הפקה של מאפיין ה-XMP בלבד, או של רשומת המידע בלבד, מניבה קובץ שנושא את הכוונה ונכשל באימות
ל-output intent יש צורה מפורטת לא פחות. PDF/E-1 דורש פרופיל ICC מוטמע עם מזהה המשנה ISO_PDFE1, ומספר הרכיבים של הפרופיל חייב להתאים למשפחת הצבע של ההתקן שהמסמך באמת משתמש בה. זה הסעיף האחרון שבו מימושים טועים בשקט, כי המשמעות היא שאת ה-intent אי אפשר לבחור מראש ואז לשכוח ממנו
מדוע צבע התקן צריך סריקה של המסמך כולו?
כי מרחבי צבע מסתתרים במילוני משאבים שסריקה ברמת עמוד לעולם לא מגיעה אליהם. PDF/E-1 מתייחס אל DeviceRGB ו-DeviceCMYK כמשפחות הדדית בלעדיות למסמך, ולכן אימות הפרופיל מחייב לדעת את כל מרחבי הצבע של ההתקן שכל דבר בקובץ משתמש בהם. ל-form XObject יש משאבים משלו. כך גם ל-pattern, וכך גם לתמונה. tiling pattern בתוך form XObject בתוך עמוד הוא בעומק של שלוש רמות, ומאמת שבודק רק את משאבי העמוד העליונים יעביר מסמך שמשתמש בשתי המשפחות
הסריקה לכן רושמת מרחבי צבע תוך הליכה על עמודים, טפסים, תמונות ו-patterns כמעבר אחד, ורק אז מחליטה אם המסמך קוהרנטי ואם ה-output intent תואם. אותו היגיון מניע את ארכיטקטורת ה-preflight בכלל: חצי מעבר מייצר מעברי שווא, ומעבר שווא על בדיקת תאימות גרוע מאשר אין בדיקה, כי הוא נרשם כראיה
var
Lib: TPDFlib;
Diag: WideString;
begin
Lib := TPDFlib.Create(nil);
try
Lib.LoadFromFile('assembly-drawings.pdf');
if Lib.SetPDFEMode(1) = 0 then
raise Exception.Create('PDF/E author mode was refused');
// מצב הכתיבה שומר את מטא-נתוני מחזור החיים מסונכרנים בכל שמירה.
// שואלים לפני השמירה אם המסמך יעבור את השער שלו עצמו
if not Lib.PDFEReadyForSave then
begin
Diag := Lib.GetPDFEDiagnostics;
Writeln('PDF/E blockers: ', Diag);
Exit;
end;
Lib.SaveToFile('assembly-drawings-pdfe.pdf');
finally
Lib.Free;
end;
end;
מטא-נתוני מחזור החיים הם חובה של כל שמירה
PDF/E-1 מבקש יותר ממזהה מסמך. הסט המינימלי כולל את מזהה המסמך של ניהול המדיה, מזהה גרסה, rendition class, זמן יצירה, זמן שינוי, זמן מטא-נתונים וכותרת. זה אוצר מילים למעקב מהדורות, והוא קיים כי מצפים שתוצר הנדסה יונפק מחדש ולא ייכתב פעם אחת
התוצאה למימוש היא שהשדות האלה לא יכולים להיקבע בעת יצירת המסמך. אם זמן השינוי נכתב כשמפעילים את המצב והמסמך נערך אחר כך, צילום ה-XMP ומצב המסמך בפועל התפצלו, ומאמת שמשווה ביניהם ידווח על אי-עקביות שאף אחד לא התכוון אליה. מצב הכתיבה לכן מסנכרן את השדות מיד לפני כל שמירה, כך שהמטא-נתונים מתארים את הבייטים שעומדים להיכתב ולא את הבייטים שהתקיימו כשהמצב הופעל
זה עיקרון כללי למטא-נתוני תאימות ושווה לנסח אותו בנפרד מ-PDF/E: מטא-נתונים נגזרים שייכים לנתיב השמירה, לא לנתיב העריכה. כל שדה שמחושב ממצב המסמך חייב להימחש מחדש ברגע שהמצב נקפא, או שהוא cache בלי אינוולידציה
הכלל שהופך את אימות התוכן למחמיר
PDF/E-1 לא מתיר לאופרטורים של מקטעי התאימות לבלוע תוכן לא מוכר. ב-PDF רגיל, BX ו-EX תוחמים אזור שבו הצרכן חייב להתעלם מאופרטורים שאינו מזהה, וזו דלת המילוט שמאפשרת ליצרן להפיק מבנים חדשים בלי לשבור קוראים ישנים. תחת PDF/E-1 המילוט הזה סגור, ולכן כל אופרטור שה-preflight לא מזהה מדווח ללא תנאי, בין אם הוא יושב בתוך מקטע תאימות ובין אם לאו
ההשפעה על מאמת משמעותית. אי אפשר לדלג על אזורים שלא מובנים, וזה אומר שמנתח ה-operand חייב לנתח באמת כל אופרטור בכל content stream. כאן נכנסים התחומים. המעבר מוגבל ל-128 רמות קינון, מיליון אובייקטים ו-64 MiB של תוכן, והמגבלות האלה הן לא כיוונון ביצועים. קובץ עוין או סתם שבור יכול להציג גרף אובייקטים עם מעגלים או עומק קינון שהופך מאמת רקורסיבי לגלישת מחסנית, והמגבלות הן מה שמונע ממעבר אימות להפוך לווקטור של מניעת שירות. אותה גישה הגנתית מתוארת בניתוח בטוח של PDF לא מהימן
// אימות עצמאי של קובץ שלא אתה יצרת, בלי לטעון אותו
// לתוך מופע מסמך
var
Issues: TStringList;
Stream: TFileStream;
I: Integer;
begin
Issues := TStringList.Create;
Stream := TFileStream.Create('incoming.pdf', fmOpenRead or fmShareDenyWrite);
try
if CheckCompliancePDFE(Stream, '', 0, Issues) = 0 then
for I := 0 to Issues.Count - 1 do
Writeln('PDF/E: ', Issues[I]);
finally
Stream.Free;
Issues.Free;
end;
end;
מה שער השמירה מתקן ומה שהוא מסרב
השער מחלק את עבודתו לשני שלבים, והחלוקה עצמה רעיון עיצובי שמיש. תחילה הוא מנרמל את הדברים שבטוחים לתיקון: דגלי הדפסה של הערות, דגלי איסור-זום ואיסור-סיבוב על הערות טקסט, ודגל יצירת המראה במילון הטופס. אלה הגדרות עם ערך אחד נכון תחת הפרופיל ובלי שום תוכן מידעי, ולכן תיקון שקט שלהן נכון, וסירוב עליהן היה פדנטיות
אחר כך הוא בודק את האילוצים שלא ניתנים לתיקון בלי לשנות את מה שהמסמך אומר: גרסה, זיהוי, הצפנה, output intent, קוהרנטיות צבע התקן ונוכחות תוכן טפסים דינמי. מסמך שנכשל באחד מהם נדחה, כי להמציא output intent או לבחור משפחת צבע בשם הכותב יניב קובץ שעובר אימות ומציג את התוכן באור שגוי
קריאת האבחון חזרה דרך GetPDFEDiagnostics לפני השמירה הופכת את הסירוב לרשימה שאפשר לפעול לפיה במקום פעולה שנכשלה. ב-pipeline אצווה, קוראים אותו לכל מסמך, רושמים את החוסמים לכל קובץ, ומפנים את הכישלונות לתור שבן אדם מסתכל עליו. זה שימושי הרבה יותר משמירה שמרימה חריגה, כי החוסמים נוטים להצטבר: ארבעים מסמכים שנכשלים על אותו output intent חסר הם תיקון אחד, לא ארבעים
בחירה בין פרופילי הארכוב
PDF/E-1 הוא היעד הנכון כשהתוצר הוא תיעוד הנדסה עם מחזור מהדורות, ובמיוחד כשקוהרנטיות צבע ההתקן חשובה כי הפלט הולך ל-plotters ולמדפסות פורמט גדול. PDF/A הוא היעד הנכון כשהמטרה היא קריאות ארוכת טווח למסמכים בכלל, והוא הפרופיל עם התמיכה הרחבה ביותר בכלי אימות. השניים לא ניתנים להחלפה, ומסמך יכול לעמוד באחד ולהיכשל בשני
אם בחרת, מתחילים ממי מאמת את הקובץ בקצה הרחוק. כלי אימות PDF/A הם בכל מקום, וה-preflight המקביל ב-PDFlibPas מתואר בpreflight של PDF/A ו-PDF/UA. אימות PDF/E מתמחה יותר ובדרך כלל הוא דרישה חוזית ולא ברירת מחדל. כשארכיון קיים צריך להימשך אל פרופיל שמעולם לא נכתב עבורו, נתיב תיקון המטא-נתונים בהמרה ל-PDF/A עם תיקון מטא-נתונים הוא התבנית ללכת לפיה, ואותה צורה חלה כאן: לזהות, לתקן מה שבטוח, לסרב לשאר עם רשימה
מצב הכתיבה, ה-preflight המוגבל לתוכן ובדיקת התאימות העצמאית מגיעים כולם עם ספריית ה-PDF של PDFlibPas ל-Delphi, כך שמסמך יכול להיות מופק תחת הפרופיל ולהיבדק באופן בלתי תלוי אחר כך דרך נתיב קוד נפרד, וזה המערך היחיד ששווה לסמוך עליו לתביעת תאימות