PDFlibPas מצמיד קובץ מוטמע לעמוד מסוים אחד ולא למסמך כולו, על ידי כתיבת מערך /AF לתוך מילון העמוד, בזמן שהתוכן עצמו נשאר רשום בעץ השמות EmbeddedFiles של המסמך. הפיצול הזה הוא מה ש-ISO 32000-2 §14.13 מתאר, והוא מה שמאפשר לקורא לענות על השאלה שקובץ מצורף ברמת מסמך לא יכול: לאיזה עמוד הנתונים האלה שייכים
תרחישי השימוש ספציפיים יותר מקיבוצים כלליים. דוח סקר שבו כל עמוד נושא את סדרת המדידות הגולמית שעומדת מאחורי הגרף שלו. אצווה סרוקה שבה כל עמוד שומר את תוצאת ה-OCR שהניבה את שכבת הטקסט שלו. סט שרטוטים שבו כל גיליון נושא את החילוץ מה-CAD שממנו עובד. בכל מקרה מהאלה, רשימת קבצים מצורפים ברמת מסמך תהיה ערימת קבצים ששמותיהם מקודדים מספרי עמודים, וזו מוסכמה ולא מבנה
payload אחד, שני מקומות שמהם יש אליו הפניה
הנקודה המבנית החשובה היא שהשיוך ברמת עמוד לא יוצר עותק שני של שום דבר. הקובץ מוטמע פעם אחת ונרשם בעץ השמות EmbeddedFiles בדיוק כמו קובץ מצורף ברמת מסמך, באמצעות אותו מנגנון של file specification. מה שמשתנה הוא המקום שבו נכתבת ההפניה ומפתח הקשר שלה: לתוך מילון העמוד במקום הקטלוג של המסמך
שתי תוצאות נגזרות מכך. ראשית, קורא שמכיר רק קבצים מצורפים ברמת מסמך עדיין מוצא את התוכן, כי הוא נמצא בעץ השמות שבו הקורא כזה מחפש. שנית, מחיקת השיוך של העמוד מסירה את הקישור, לא את הקובץ. ClearPageAssociatedFiles מנתק את העמוד מהקבצים המשויכים אליו ומשאיר את התכנים נגישים דרך עץ השמות, וזו ההתנהגות השמרנית: פעולה שאומרת נקה את השיוך לא אמורה להשמיד בשקט נתונים שחלק אחר של המסמך עשוי להפנות אליהם
לפונקציה הזו יש תנאי הצלחה מכוון צר, וכדאי להכיר אותו. היא מדווחת הצלחה רק כשהעמוד באמת נשא מפתח /AF. עמוד שמעולם לא היו לו שיוכים מחזיר כישלון ולא אישור עליז, כך שלקורא לפונקציה אין דרך להתבלבל בין no-op לניקוי שהושלם
var
Lib: TPDFlib;
Idx, I: Integer;
begin
Lib := TPDFlib.Create(nil);
try
Lib.LoadFromFile('survey-report.pdf');
// מצמידים את סדרת המדידות שהניבה את הגרף בעמוד 3
Idx := Lib.AddPageAssociatedFileFromFile(3,
'series-03.csv', // קובץ על הדיסק
'measurements.csv', // שם התצוגה בתוך ה-PDF
'text/csv', // סוג MIME
'Raw measurement series for figure 3',
'Data'); // AFRelationship, ISO 32000-2 14.13
if Idx < 0 then
raise Exception.Create('page association refused');
for I := 0 to Lib.GetPageAssociatedFileCount(3) - 1 do
Writeln('page 3 associated file, embedded index ',
Lib.GetPageAssociatedFileEmbeddedIndex(3, I));
Lib.SaveToFile('survey-report-with-data.pdf');
finally
Lib.Free;
end;
end;
מחרוזת הקשר אינה טקסט חופשי בפועל. ISO 32000-2 מגדיר אוצר מילים, Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema ו-Unspecified, והצרכנים נשענים עליו. Data למספרים שעומדים מאחורי גרף, Source למסמך שממנו נוצר עמוד, Alternative לייצוג שקול. בוחרים מאוצר המילים גם כשאף דבר ב-pipeline שלך עדיין לא קורא אותו, כי הכלי הבא בשרשרת עשוי לקרוא
מדוע אותה חיפוש צריך FollowRef בשני הכיוונים?
כי מעקב אחר הפניות עונה על שתי שאלות שונות, והקוד צריך לדעת איזו מהן הוא שואל. חיפוש מפתח שעוקב אחר הפניות עקיפות מחזיר את האובייקט שאליו ההפניה מצביעה. חיפוש שלא עוקב מחזיר את ההפניה עצמה. שניהם נכונים, ושימוש בשגוי מביא להתנהגות שגויה שקטה ולא לשגיאה
קריאת קובץ משויך מדגימה את הכיוון הראשון. כדי לקבל את מספר האובייקט של ה-stream המוטמע שמאחורי מפתחות /EF ו-/F של ה-file specification, החיפוש חייב לא לעקוב, כי מעקב מפענח את ההפניה לתוך אובייקט ה-stream ומספר האובייקט נעלם. הכלל מתכלל: כל נתיב קוד שצריך זהות אובייקט ולא תוכן אובייקט חייב לקחת את ההפניה הגולמית
תוכן אופציונלי מציג את הכיוון ההפוך, וזה עלה ביותר עבודה לגלות. מילון המאפיינים של תוכן אופציונלי נכתב לקטלוג כאובייקט עקיף, ולכן קוד שקורא אותו בחזרה בלי לעקוב מקבל הפניה ולא מילון. בדיקת טיפוס על הערך הזה נכשלת, ואז נכנס לפעולה ענף ה-fallback הטבעי, אין תצורה ואז יוצרים אחת, ודורס את התצורה שכבר הייתה שם. שום דבר לא מרים חריגה. השכבות שמתוארות בקבוצות תוכן אופציונלי ושכבות פשוט מאבדות את מצב הנראות שהוגדר להן
הלקח מתכלל מעבר לשני המקרים. כשחיפוש יכול להחזיר הפניה או אובייקט, בדיקת טיפוס גולמית אינה טיפול בשגיאות: היא ענף שבסופו של דבר ייכנסו אליו מהסיבה הלא נכונה. מחליטים במפורש מה כל אתר קריאה צריך, ומעדיפים API ציבורי שעונה ישירות על השאלה, כמו מאפיין מנייה של תוכן אופציונלי, על פני חדירה ל-accessor מוגן של מילון הקטלוג
// קבצים מצורפים ברמת מסמך ושיוכים ברמת עמוד חיים יחד. גם
// קובץ מוטמע יכול לקבל סימון associated ברמת המסמך
if Lib.IsEmbeddedFileAssociated(0) = 0 then
Lib.SetEmbeddedFileAssociated(0, 1, 'Supplement');
Writeln('document associated files: ', Lib.GetAssociatedFileCount);
Writeln('page 3 associated files : ',
Lib.GetPageAssociatedFileCount(3));
// מחיקה מנתקת את קישור העמוד; ה-payload נשאר בעץ השמות
if Lib.ClearPageAssociatedFiles(3) > 0 then
Writeln('page 3 associations removed, payloads still reachable');
מה מצבי תאימות עושים לקבצים מצורפים
פרופילי ארכוב מגבילים מה מותר להטמיע, וההגבלה נאכפת בנקודת הכניסה ולא בזמן השמירה. PDF/A-1 אוסר קבצים מוטמעים לחלוטין, PDF/A-2 מתיר רק מסמכי PDF/A מוטמעים, ו-PDF/A-3 הוא הפרופיל שפתח את ההטמעה לסוגי קבצים שרירותיים, ובדיוק בגלל זה פורמטי החשבונית ההיברידיים נבנים עליו
PDFlibPas מסרב לצירוף כשמצב התאימות הפעיל לא מתיר אותו, בקריאה עצמה, ולא מאות פעולות אחר כך במהלך הפלט. זו בחירה מכוונת לגבי המקום שבו טעות הכי זול לתקן: סירוב באתר הקריאה קורא בשם את הקובץ שניסית להוסיף, בזמן שסירוב בזמן שמירה קורא בשם מסמך ומשאיר אותך לעבוד איזו מתוך ארבעים קבצים מצורפים גרמה לזה
זו גם הסיבה שקבצים משויכים מופיעים כל כך הרבה בחיוב אלקטרוני. חשבונית היברידית היא PDF שבן אדם קורא, עם payload של XML קריא למכונה מצורף ומסומן בקשר הנכון, וגם פרופיל המיכל וגם מפתח הקשר הם חלק מהמפרט ולא מוסכמות. הבנייה הזו מכוסה בבניית חשבוניות היברידיות Factur-X ו-ZUGFeRD, וצד המטא-נתונים בסכמת ההרחבה של XMP ב-PDF/A-3
מתי השיוך צריך להיות פר-עמוד ולא פר-מסמך?
כשצרכן צריך לדעת לאיזה עמוד הנתונים שייכים, ורק אז. קבצים מצורפים ברמת מסמך פשוטים יותר, נתמכים רחב יותר בצופים, ומספיקים כל פעם שה-payload מתאר את המסמך כולו, XML של חשבונית, מניפסט חתימות, ארכיון מקורות. מגיעים לשיוך ברמת עמוד כשה-payload מוגבל לעמוד באמת וזהות העמוד היא חלק מהמשמעות שלו
התמיכה היא האילוץ המעשי. קבצים משויכים ברמת עמוד הם מבנה של PDF 2.0, והתמיכה בצופים דקה יותר מאשר לקבצים מצורפים ברמת מסמך. כיוון שה-payload יושב בעץ השמות בכל מקרה, צופה שמתעלם מ-/AF בעמודים עדיין מציג את הקובץ ברשימת הקבצים המצורפים שלו, כך שהדירדרון מתון. אבל אם קישור העמוד חיוני לצרכן שלך ולא סתם מטא-נתונים שימושיים, מאמתים את הקורא שאליו באמת מכוונים ולא מניחים
קבצים משויכים ברמת עמוד, קבצים מצורפים ברמת מסמך ושערי פרופילי הארכוב ששולטים בשניהם מגיעים בספריית ה-PDF של PDFlibPas ל-Delphi. אם אתה גם מתקן קבצים ישנים בדרך פנימה, עבודת המטא-נתונים והתאימות בהמרה ל-PDF/A עם תיקון מטא-נתונים היא שמכריעה אילו מנתיבי הצירוף האלה זמינים לך מלכתחילה