Пакетний інструмент попередньої перевірки – це консольна програма без вікна, що вказує на папку PDF-файлів, перевіряє кожен із них на відповідність стандартам і залишає машинозчитувані докази того, що було знайдено. Ніхто за нею не стежить. Вона запускається о другій ночі під управлінням cron або Windows Task Scheduler, або як шлюз у конвеєрі CI, а наступна людина, яку цікавить її результат, – це або планувальник, що зчитує код виходу, або аудитор, який відкриває звіт через кілька тижнів. Це змінює значення слова "правильний". Рушій попередньої перевірки PDFium Component, бібліотеки PDF із відкритим вихідним кодом для Delphi, C++Builder і Lazarus, робить самі виклики перевірки майже тривіальними. Робота, яка визначає, чи інструмент дійсно корисний, зосереджена навколо цих викликів: який профіль ви перевірили, що код виходу повідомив планувальнику, і чи досі існує звіт, який міг би впіймати помилку, коли хтось шукатиме його
Контракт: що планувальник насправді бачить
Виконавець CI або Windows Task Scheduler бачить від вашого інструменту рівно дві речі: код виходу та будь-які файли, які він залишив. Рядки журналу, кольори консолі, виведення прогресу – все це для людини, що спостерігає наживо, а о другій ночі такої немає. Тому спершу встановіть словник кодів виходу до того, як торкатися API, і зробіть його нудним:
0: кожен файл відповідав кожному запитуваному профілю1: принаймні один файл показав знахідки перевірки2: інструмент дав збій принаймні на одному файлі (пошкоджений вхідний файл, блокування, аварія)
Різниця між кодами 1 і 2 – це те, що команди пропускають і про що потім шкодують. Пошкоджений PDF, який не відкривається, – це не збій перевірки. Об'єднайте його з кодом 1, і вантажівка пошкоджених сканів з'явиться на панелях моніторингу як раптовий обвал відповідності, змусивши когось шукати регресію стандартів, якої ніколи не було, тоді як справжня причина – несправний сканер вище за потоком
До контракту належать ще два пункти. Перший – тайм-аут на файл. Патологічний PDF із тисячами сторінок та глибоко вкладеними структурами об'єктів може тримати один прохід перевірки хвилинами, а нічне вікно не має терпіння для цього. Завершіть завдання цього файлу після дедлайну, зарахуйте це як збій інструменту і продовжуйте пакетну обробку. Другий – каталог карантину: переміщуйте кожен файл із тайм-аутом або неможливий до відкриття файл, замість того щоб залишати його на місці. Протягом кількох місяців цей каталог тихо накопичує найгірші документи, які надсилають ваші реальні клієнти, а цей корпус більш цінний для тестування випусків, ніж будь-який синтетичний зразок, який ви могли б написати вручну
Вибір стандартів і чому рівень відповідності має значення
Перелічення TPdfPreflightStandard охоплює сімейства, які зустрічаються на практиці: ppsPdfA для архівної відповідності ISO 19005, ppsPdfUa для доступності ISO 14289, ppsPdfX для обміну для друку, а також ppsPdfE, ppsPdfR та ppsPdfVT для інженерної, растрової роботи та роботи зі змінними даними. У межах сімейства рушій зчитує рівень відповідності, заявлений документом, і повідомляє про нього для кожного стандарту в полі ConformanceName результату. Вказати лише сімейство рідко буває достатньо, бо саме рівень і є місцем, де живе реальна різниця. PDF/A-2b обіцяє лише візуальну відтворюваність. PDF/A-3a додає вимогу до логічного тегування структури та дозволяє вбудовані вихідні файли, що є набагато вищою планкою для відсканованих матеріалів, у яких немає дерева тегів. Помилитися в будь-якому напрямку означає, що пакет вас обдурює. Якщо ваша політика зберігання насправді хоче PDF/A-2b, але ви відхиляєте файли за відсутні теги структури, звіт заповнюється знахідками, які ніхто ніколи не виправить. Прийміть будь-який ярлик PDF/A без перевірки рівня, і ви затверджуєте документи, що відповідають слабшій планці, ніж ви обіцяли. Вимоги доступності від державних покупців все частіше накладають PDF/UA поверх всього цього, що не додає жодних витрат на виконання, оскільки BuildPdfPreflightReport (з блоку FPdfPreflightReport) приймає набір стандартів:
Report := BuildPdfPreflightReport(Pdf, [ppsPdfA, ppsPdfUa]);
Один виклик оцінює обидва стандарти і повертає єдиний зведений запис звіту
Чому порожній список знахідок – це не успішна перевірка
Звіт перелічує знахідки за стандартами, і порожній список проблем означає лише "проблем не знайдено в стандартах, які дійсно виконувалися". Це вужче твердження, ніж "файл відповідає стандарту, який вас цікавить", і розрив між ними – це місце, де пакетна попередня перевірка тихо руйнується. Помилка конфігурації, яка видаляє ppsPdfA з набору, дає рівно той самий порожній список проблем, що й дійсно чистий файл. Тому ставтеся до мовчанки з підозрою. Пройдіться по Report.Results і підтвердьте дві речі для кожного стандарту, який ви збиралися перевірити: що запис результату для нього взагалі існує, і що його прапорець IsCompliant, підкріплений Status = pfsPass, є true. Нічний пакет, який прирівнює "немає знахідок" до "готово до архівування", не перевіряючи, які стандарти оцінювалися, – це класичний спосіб, яким папка невідповідних файлів непомітно проходить місяцями, доки зовнішній аудитор не відкриє один із них за допомогою veraPDF і весь архів не опиниться під сумнівом
Друга пастка криється в тому, що таке взагалі знахідка. Кожен TPdfPreflightIssue несе Code, Category, Description та Recommendation, і він називає порушене правило, а не сторінку або об'єкт. Це архітектурне рішення з наслідками для зворотного зв'язку. Звіт говорить команді, що виробляє документи, який клас дефекту існує – невбудований шрифт або відсутній ідентифікатор XMP – а знайти конкретний об'єкт-порушник – це завдання інструменту виправлення нижче за потоком, а не валідатора. Будуйте споживачів ваших звітів на основі стабільних значень Code, ніколи – на основі читабельного тексту опису, який може бути переформульований між випусками без попередження
Файли звітів для машин і для чергового
Запис звіту записує ті самі знахідки у п'яти форматах: SaveJsonToFile, SaveCsvToFile, SaveHtmlToFile, SaveTextToFile та SaveMarkdownToFile, кожен з відповідною функцією у стилі ToJson, коли вам потрібен рядок у пам'яті, а не на диску. Не піддавайтеся спокусі вибрати один. Записуйте JSON для конвеєра, щоб CI міг прикріпити його до запису завдання та аналізувати коди проблем і статуси для кожного стандарту без парсингу тексту. Записуйте HTML для людини, яку викличуть, бо він відкривається в будь-якому браузері без жодних інструментів. Разом вони коштують один зайвий рядок на файл і позбавляють вашого чергового інженера єдиного найгіршого завдання в пакетній обробці – зворотного проектування сирого блоку JSON о другій ночі, щоб дізнатися, який файл зламався. Одна дисципліна важливіша за вибір формату: назву кожного звіту виводьте з імені вхідного файлу, ніколи – з часової мітки, інакше два паралельних запуски перемішають звіти, які ви більше не зможете зіставити з їхніми вхідними даними
Порогові значення серйозності належать до конфігурації, а не до коду. Анотація без альтернативного опису – це жорстка помилка для порталу подання PDF/UA і незначна примітка для внутрішнього архіву, але це ідентична знахідка в обох випадках. Задайте рівень помилки для кожного профілю, щоб політика могла змінюватися без перекомпіляції, і зафіксуйте рівень, що діяв, у самому підсумку завдання. Наступного кварталу ніхто не пам'ятатиме, з яким порогом запускався жовтневий пакет, і підсумок – єдине місце, де ця пам'ять зберігається
Ізоляція файлів, щоб один поганий PDF не міг занапастити пакет
procedure RunPreflightBatch(const InputDir, ReportDir: string;
out FilesWithFindings, ToolFailures: Integer);
var
SR: TSearchRec;
Pdf: TPdf;
Report: TPdfPreflightReport;
begin
FilesWithFindings := 0;
ToolFailures := 0;
if FindFirst(InputDir + '*.pdf', faAnyFile, SR) = 0 then
try
repeat
Pdf := TPdf.Create(nil); // fresh instance per file: no state bleed
try
try
Pdf.FileName := InputDir + SR.Name;
Pdf.Active := True;
if not Pdf.Active then // load failures are silent, not raised
raise EPdfError.Create('Cannot open ' + SR.Name);
Report := BuildPdfPreflightReport(Pdf, [ppsPdfA, ppsPdfUa]);
Report.SaveJsonToFile(ReportDir + ChangeFileExt(SR.Name, '.json'));
Report.SaveHtmlToFile(ReportDir + ChangeFileExt(SR.Name, '.html'));
if Report.TotalIssueCount > 0 then
Inc(FilesWithFindings);
except
on E: Exception do
begin
Inc(ToolFailures); // exit-code-2 territory, not a validation verdict
WriteLn(ErrOutput, SR.Name + ': ' + E.Message);
end;
end;
finally
Pdf.Free;
end;
until FindNext(SR) <> 0;
finally
FindClose(SR);
end;
end;
У цьому циклі є три навмисні рішення. Новий TPdf на кожен файл гарантує, що один документ, який пошкодить стан рушія, не отруїть наступні файли. Явна перевірка Active заслуговує свого місця, бо Active := True поглинає помилки завантаження замість того, щоб викидати їх; пропустіть цей захист, і усічений файл дрейфує у виклик перевірки перед тим, як десь нижче дасть збій із незрозумілим повідомленням. Внутрішній блок try..except навмисно знаходиться в межах масштабу на файл, тому єдиний виняток збільшує лічильник збоїв і цикл продовжується. Вам потрібні чисті звіти для 4 999 хороших файлів, навіть коли файл 5 000 пошкоджений. І обидва формати звітів записуються на диск до того, як підраховується вирок, що означає: докази виживають, навіть якщо помилка в логіці підсумку пізніше помилково підраховує
Потім відображення кодів виходу зводиться до кількох рядків у файлі проекту:
begin
RunPreflightBatch(ParamStr(1), ParamStr(2), Findings, Failures);
if Failures > 0 then
Halt(2)
else if Findings > 0 then
Halt(1);
// falling through exits with 0: every file conformed
end.
Що попередня перевірка не зробить за вас
Рушій виявляє; він не виправляє. Знахідка про невбудований шрифт або колірний простір, що залежить від пристрою, – це наряд на роботу для тих, хто виробляє файли, і валідатор не має можливості виправити це на місці. Тому навмисно плануйте цикл зворотного зв'язку. Звіти повинні потрапляти туди, де команда виробників їх дійсно читає, інакше ті самі знахідки з'являтимуться щоночі, доки хтось нарешті не запитає, чому рівень відповідності ніколи не покращується. Також варто перехресно перевірити вибірку вердиктів за допомогою незалежного валідатора – veraPDF для PDF/A або preflight Acrobat для PDF/X – до того, як це зробить зовнішній аудитор замість вас. Коли два рушія не погоджуються з реальним файлом клієнта, цей документ – не незручність; це саме той регресивний тест-кейс, якого не вистачало тестуванню випуску. Зберігайте його, назвіть його і запускайте на кожній збірці
Ще одне поєднання варте уваги. Той самий рушій перевірки керує інтерактивними перевірками в інтерфейсі огляду, тому цей headless CLI і робоча станція огляду прийому PDF для аналітиків можуть спільно використовувати єдиний словник перевірки замість того, щоб розходитися з часом. А оскільки [ppsPdfA, ppsPdfUa] оцінює доступність у тому самому проході, сторона PDF/UA пакета узгоджується з роботою на стороні переглядача, наприклад побудовою доступного засобу читання PDF у Delphi. Профілі, формати звітів і повний API попередньої перевірки задокументовані на сторінці продукту для PDFium Component