מאמר טכני

אוטומציה של בדיקות Preflight ל-PDF ב-Delphi עם HotPDF

הקובץ נפתח תקין במחשב שלך. Acrobat מציג אותו, תצוגת ההדפסה נראית נכונה, כל הדפים קיימים. לאחר מכן הוא מגיע לבית הדפוס, או נכנס לתוך מערכת הארכיון שבולעת את הנתוח החודשי שלך, וחוזר דחוי: תמונות RGB בעבודת CMYK, ללא מפתח /Trapped, כוונת פלט שאינה תואמת את הדפוס. לא היה שום פגם גלוי במסמך. הוא היה שגוי ביחס לפרופיל, והפרופיל נבדק במקום שבו לא היית. Preflight הוא השם לפני-הדפוס לאותה בדיקה, והשאלה האמיתית היא היכן היא שייכת כאשר קבצי ה-PDF יוצאים מקוד ה-Delphi שלך עצמו ולא מהמחשב של מעצב גרפי

HotPDF אינה מעניקה לך פונקציית preflight לקריאה. לרכיב יש חלון דוח preflight בהדגמת ה-GUI שלו, אך אין API מאחוריו ששירות או סקריפט בנייה יכולים להפעיל, ולהעמיד פנים אחרת היה שולח אותך לחפש שיטה שאינה קיימת. זה נשמע כמו חוסר עד שמבחינים שעבור קבצים שאתה מייצר בעצמך, קריאה לאמת על הפלט שלך עצמו הוא בכלל הצורה הלא נכונה. אתה כבר שולט בכל מאפיין שאמת יבדוק. הפיצול השימושי הוא להפוך את המחולל לכזה שאינו מסוגל לפלוט קובץ פגום, ואז להוכיח זאת עם כלי שלא כתבת

מדוע בודקים את הפלט שלך עצמך אחרת

Preflight מסורתי מניח קובץ של זר. מעצב כלשהו, יישום כלשהו, שרשרת עריכות לא ידועה כלשהי ייצרו אותו, ואתה בוחן אותו מפני שאין לך מושג מה יש בפנים. מסמך שהקוד שלך ייצר אינו זר. הטמעת גופנים, מרחב צבע, כוונת פלט, בלוק המטאדטה: התוכנית שלך החליטה על כל אלה כמה אלפיות שנייה לפני שהקובץ נגע בדיסק. לבדוק אותו לאחר מכן כדי לגלות בחירות שזה עתה עשית זו עבודת שווא. המהלך הזול יותר הוא להגביל את הבחירות האלה כדי שקובץ לא תואם לעולם לא יתקיים כדי שייתפס

יש גם סיבת אמינות לשמור על האימות חיצוני. ספרייה שמברכת את הפלט שלה עצמה בוחנת את הבחינה שלה עצמה. כאשר מערכת הארכיון של לקוח או ה-RIP של בית דפוס דוחים את הקובץ שלך, "הרכיב שלנו אומר שהוא תקין" אינו נושא משקל. פסק דין מ-veraPDF או Acrobat כן, מפני שהצד השני מפעיל את אותם הכלים

הפוך עמידה בתקן להגדרה, לא לרשימת בדיקה

שכבת המניעה היא פשוט תצורה. הגדר PDFACompliance או PDFXCompliance לפני BeginDoc ו-HotPDF יחזיק את הכללים המתאימים לכל מעבר היצירה: הוא מטמיע גופנים, עוקב אחר שימוש ב-DeviceRGB וב-DeviceCMYK ביחס לכוונת הפלט שהצהרת, וסירב לתכונות שהפרופיל אוסר. הסתירות עולות לפני השטח ב-EndDoc, שבו שערי עמידה בתקן מעלים חריגות במקום לשלוח בשקט משהו שייכשל במורד הזרם. לאחר שמירת הקובץ, אותן מאפיינים קוראים בחזרה מה נאכף בפועל, שזה הנתון היחיד שיומן הצינור שלך הכי זקוק לו:

// After EndDoc: record the enforced profiles with the run metadata
if Pdf.PDFACompliance <> '' then
  Log('Generated as PDF/A level ' + Pdf.PDFACompliance);
if Pdf.PDFXCompliance <> '' then
  Log('Generated as PDF/X profile ' + Pdf.PDFXCompliance);

שים את הדגלים האלה באותה שורת יומן כמו hash נתוני הקלט וגרסת HotPDF. ביום שבו אמת ומחולל לא מסכימים על קובץ, אותה שורה אומרת לך איזה תבנית ייצרה אותו ואיזה build של הספרייה נטען, והויכוח שהיה אוכל אחר הצהריים הופך לחיפוש grep. כוונות הפלט, פרופילי ICC והתיוג שמאחורי הדגלים האלה מפורטים במדריך לפלט PDF/A, PDF/X ו-PDF/UA עם HotPDF

שער ראשון זול לקבצים שלא יצרת

לא כל צינור הוא גנרטיבי בלבד. לקוחות מעלים קבצי PDF, סורקים מפילים אותם בתיקייה, שותפים מצרפים אותם לדוא"ל. דחיפת כל אחד מהם דרך אמת מבני מלא מבזבזת זמן תור על קבצים שלא ייפתחו בכלל. ה-Direct File API של HotPDF קורא מספיק ממבנה הקובץ כדי לענות על "האם זה PDF שמיש בכלל" מבלי לטעון את עץ האובייקטים כולו, מה שהופך אותו למקום טוב לכישלון מהיר:

function TriagePdf(Pdf: THotPDF; const FileName: string): Boolean;
var
  Handle, Pages: Integer;
begin
  Result := False;
  Handle := Pdf.DAOpenFileReadOnly(FileName, '');
  if Handle <= 0 then
    Exit;  // structurally unreadable: quarantine, do not validate
  try
    Pages := Pdf.DAGetPageCount(Handle);
    Result := Pages > 0;
  finally
    Pdf.DACloseFile(Handle);
  end;
end;

שני עובדות על ה-API הזה מחליטות כיצד תעטוף אותו. קיצור הדרך לזיכרון שטוח פועל רק עבור קלט לא מוצפן; מסור ל-DAOpenFileReadOnly סיסמה והוא נסוג בשקט לפרסינג מלא, כך שקובץ שידוע לך שהוא מוצפן צריך לעבור דרך DecryptFile לעותק עבודה רגיל לפני הסינון הראשוני. ו-DAGetPageCount לא אומר כלום על ידית שלא נפתחה כראוי, לכן בדיקת הידית נשארת מחמירה ותוצאה לא-חיובית היא דחייה, לא ניסיון חוזר. עוד מהתבניות האלה נמצאות במאמר ה-Direct File API לזרימות עבודה של PDF גדול

veraPDF, הפעל כחלק מהבנייה

עבור כל דבר שאתה טוען כ-PDF/A או PDF/UA, veraPDF הוא האמת לחיבור. הוא פועל ללא ראש, מקבל אצווה, פולט XML או JSON, ומציין כל כישלון לפי סעיף ה-ISO שלו, כך שכישלון כלל ביחס לסעיף 6.2.2 של ISO 19005-1 מצביע ישירות בחזרה על הגדרת מחולל במקום להשאיר אותך מנחש. הנהגתו מ-Delphi היא שליטת תהליכים פשוטה:

function RunVeraPdf(const PdfFile, ReportFile: string): Cardinal;
var
  Cmd: string;
  SI: TStartupInfo;
  PI: TProcessInformation;
begin
  Cmd := Format('cmd /c verapdf.bat --format xml "%s" > "%s"',
    [PdfFile, ReportFile]);
  FillChar(SI, SizeOf(SI), 0);
  SI.cb := SizeOf(SI);
  if not CreateProcess(nil, PChar(Cmd), nil, nil, False,
      CREATE_NO_WINDOW, nil, nil, SI, PI) then
    RaiseLastOSError;
  try
    WaitForSingleObject(PI.hProcess, 120000);  // bound the wait per file
    GetExitCodeProcess(PI.hProcess, Result);
  finally
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

פסק הזמן הזה מרוויח את מקומו. קובץ בעל פורמט שגוי יכול להכניס כל מנתח לפינה שממנה לעולם לא יצא, והמתנה פתוחה בתוך עובד תור גוררת את שאר התור איתה. הגבל את ההמתנה, תן לפסק זמן קוד כישלון משלו, והנח את הקובץ בצד לבן אדם. כאשר אתה קורא את התוצאה, נתח את ה-XML לאיתור מזהי כלל, לא לטקסט הקריא לאדם. מזהי כלל שורדים שדרוגי אמת; הניסוח של ההודעות לא שורד, וקוד יציב הוא משהו שמהנדס תמיכה יכול לחפש בכרטיסים ישנים

כיצד אתה מפעיל את האצווה חשוב לא פחות מאם כל קובץ עובר. תהליך אחד לכל קובץ, לא אחד לכל אצווה, כך שקלט רעיל עולה לך פסק הזמן של אותו קובץ ולא יותר. הגבל את מספר תהליכי האמת לספירת הליבות, מפני שבניית דוח ה-XML היא מוגבלת ב-CPU ועומס יתר רק יוצר בעיות. ושים תקרת גודל בקלט, מפני שספר סרוק של שני גיגה יבעל את התור לא משנה כמה המנתח סבלני. אף אחד מאלה אינו preflight במובן המחמיר. זו ההבדל בין שער שחי דרך נפח סוף החודש לאחד שמכבים אותו הלילה הראשון שהוא מעכב את הצינור בשעה 2 לפנות בוקר

PDF/X הוא המקום שבו זה נופל. veraPDF לא מאמת אותו, אז הבדיקה הפעילה עדיין היא Preflight של Acrobat עם פרופיל ISO 15930 שבית הדפוס שלך ציין. Acrobat רוצה אדם, מה שאומר דגימה ולא כיסוי מלא: הקובץ הראשון מתבנית חדשה, ועוד שאיבה אקראית קטנה מכל אצווה, בעוד השער האוטומטי מטפל בכל מה שניתן לטפל בלי אחד. בדיקת דגימה שאכן רצה עדיפה על אוטומציה מלאה שנשארת חצי גמורה לנצח

דוח שתרצה עוד בעוד שנה

שער preflight משתלם פעמיים. פעם אחת כאשר הוא עוצר קובץ רע בדלת, ושוב הרבה יותר מאוחר כאשר מישהו שואל מדוע קובץ מסוים הורשה לעבור. הרגע השני הוא זה שצריך להכתיב את הפורמט, מפני שזה הרגע שבו דוח דק משאיר אותך תקוע. עבור כל קובץ שנבדק, שמור את hash הקלט, דגלי עמידת המחולל בתקן וגרסת הספרייה משורת היומן לעיל, שם ה-validator וגרסתו, הפרופיל שנבדק לפיו, עבר או נכשל, ומזהי הכלל שנכשלו עם מספרי עמודים בכל מקום שה-validator נותן אותם. אחסן את הדוח הזה לצד הקובץ שהוא מתאר. שמור אותו במערכת נפרדת ואותה מערכת תיסגר לפני הארכיון שהיא מתעדת

יש לתעד גם חריגים. כאשר לקוח מתעקש לשלוח קובץ שהשער לא אוהב, התשובה אינה להרפות את הכלל עבור כולם. רשום מי אישר את הקובץ הזה, על איזה בסיס, ועד איזה תאריך, ואז צרף את הפטור הזה לדוח שלו. פטור עם שם ותאריך תפוגה הוא החלטה שמישהו אחראי עליה. בדיקה מוגנה בהערה "זמנית" היא תקרית שממתינה לתאריכה

עוד הרגל אחד משתלם בעצמו: כאשר קובץ נכשל, העתק אותו לתיקיית רגרסיה מוגדרת לפני שמישהו נוגע בו. כמעט כל בעיית preflight ששווה לאבחן מתחקה חזרה לקלט ספציפי אחד, והצוותים ששומרים על אותם קלטים מתקנים את הרצות-מחדש בשעה במקום להמתין שיצוץ שוב בייצור. מאפייני העמידה בתקן וה-Direct File API המוצגים כאן הם חלק מHotPDF Component עבור Delphi ו-C++Builder, שתיעודו מכסה כל קריאה במלואה