Технічна стаття

Інженерні документи PDF/E-1 у Delphi з PDFlibPas

PDF/E-1 — архівний профіль для інженерних документів, і PDFlibPas реалізує його як режим авторства, який ви вмикаєте через SetPDFEMode, плюс обмежений preflight, що читає потоки вмісту оператор за оператором. Це не PDF/A з іншою наклейкою: у профілю власний простір імен ідентифікації, власна вимога метаданих життєвого циклу і одне правило, яке робить валідацію вмісту суворішою, ніж у будь-якому архівному профілі, який ви зустрічали

Інженерні результати — ось чому існує цей профіль. Набір креслень, який має бути читабельним і доведено незміненим через двадцять років, з історією ревізій, що переживає, і з кольором, який означає те саме на плотері в іншій будівлі. Ці вимоги дають специфікацію, чиї вимоги сидять переважно за межами вмісту сторінок — у метаданих і керуванні кольором, а це рівно те місце, де генеричний 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 всередині сторінки — це три рівні вглиб, і валідатор, який перевіряє лише ресурси верхнього рівня сторінки, пропустить документ, що використовує обидва сімейства

Тому прочісування реєструє кольорові простори, обходячи сторінки, форми, зображення та патерни як один прохід, і лише потім вирішує, чи документ когерентний і чи відповідає 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 просить більше, ніж ідентифікатор документа. Мінімальний набір включає ідентифікатор документа медіаменеджменту, ідентифікатор версії, клас рендерингу, час створення, час модифікації, час метаданих і заголовок. Це словник відстеження ревізій, і він існує тому, що інженерний результат очікує перевидань, а не одноразового написання

Наслідок для реалізації: ці поля не можна встановлювати при створенні документа. Якщо час модифікації записано, коли ви увімкнули режим, а документ потім редаговано, знімок XMP і фактичний стан документа роз'їхалися, і валідатор, який їх порівнює, повідомить неузгодженість, якої ніхто не хотів. Тому режим авторства синхронізує поля безпосередньо перед кожним збереженням, тож метадані описують байти, які зараз будуть записані, а не ті, що існували в момент увімкнення режиму

Це загальний принцип для метаданих відповідності, і його варто сформулювати окремо від PDF/E: похідні метадані живуть на шляху збереження, а не на шляху редагування. Будь-яке поле, обчислене зі стану документа, має бути перераховане в момент, коли стан заморожується, інакше це кеш без інвалідації

Діаграма PDFlibPas PDF/E-1: прочісування кольору пристрою по всьому документу, яке обходить словники ресурсів сторінок, form XObject, tiling pattern і зображень, збираючи сімейства DeviceRGB і DeviceCMYK перед вердиктом про когерентність, поруч із полями метаданих життєвого циклу, які режим авторства ресинхронізує безпосередньо перед кожним збереженням, тож знімок XMP збігається з байтами, що будуть записані
Когерентність кольору можна оцінити лише після одного проходу, який досягає кожного словника ресурсів, а похідні метадані життєвого циклу перераховуються в момент заморозження стану документа, а не при увімкненні режиму

Правило, яке робить валідацію вмісту суворою

PDF/E-1 не дозволяє операторам секції сумісності поглинати невідомий вміст. У звичайному PDF BX і EX беруть у дужки регіон, у якому споживач мусить ігнорувати оператори, які не розпізнає, — це аварійний вихід, що дозволяє продуценту випускати новіші конструкції, не ламаючи старіших читачів. Під PDF/E-1 цей вихід зачинено, тож будь-який оператор, який preflight не розпізнає, повідомляється безумовно, незалежно від того, чи сидить він у секції сумісності

Ефект на валідатора значний. Він не може пропускати регіони, яких не розуміє, а отже парсер операндів має справді розібрати кожен оператор у кожному потоці вмісту. Ось тут і з'являються межі. Обхід обмежено 128 рівнями вкладення, мільйоном об'єктів і 64 МіБ вмісту, і ці ліміти — не тюнінг продуктивності. Вороже чи просто побите файл може подати граф об'єктів із циклами або глибиною вкладення, яка перетворює рекурсивний валідатор на переповнення стека, і ліміти — це те, що не дає проходу валідації стати вектором denial-of-service. Та сама оборонна постава описана в безпечному розборі недовірених 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;

Що ворота збереження лагодять, а що відхиляють

Ворота ділять роботу на два етапи, і сам цей поділ — придатна дизайнерська ідея. Спершу вони нормалізують те, що безпечно ремонтоване: прапорці друку анотацій, прапорці no-zoom і no-rotate на текстових анотаціях та прапорець генерації вигляду в словнику форми. Це налаштування з одним правильним значенням за профілем і без жодного інформаційного вмісту, тож мовчазне виправлення — правильно, а відмова через них була б педантизмом

Потім вони перевіряють обмеження, які не ремонтуються без зміни того, що документ означає: версія, ідентифікація, шифрування, output intent, когерентність кольору пристрою і присутність динамічного вмісту форм. Документ, що провалює будь-яке з них, відхиляється, бо вигадати output intent чи обрати сімейство кольорів за автора — це продукувати файл, який пройде валідацію і спотворить вміст

Діаграма воріт збереження PDF/E-1 у PDFlibPas для Delphi: обмежений preflight сканує кожен оператор потоку вмісту при стелях 128 рівнів вкладення, мільйона об'єктів і 64 МіБ, мовчки лагодить прапорці друку, зуму й обертання анотацій, відхиляє неправильну версію, ідентифікацію, шифрування, output intent, колір пристрою чи динамічний вміст форм і повідомляє блокери через GetPDFEDiagnostics
Ворота мовчки лагодять лише те, що не несе інформації, відхиляють кожне обмеження, яке ремонт спотворив би, і перетворюють відмову на список блокерів через GetPDFEDiagnostics до того, як хоч один байт сягне диска

Прочитати діагностику назад через GetPDFEDiagnostics перед збереженням — і відмова перетворюється на придатний до дій список, а не на невдалу операцію. У пакетному конвеєрі викликайте його на кожному документі, логуйте блокери по файлах і скеровуйте відмови в чергу, яку дивиться людина. Це куди корисніше за збереження, що кидає виключення, бо блокери зазвичай кластеризуються: сорок документів, які провалилися через той самий відсутній output intent, — це один фікс, а не сорок

Вибір між архівними профілями

PDF/E-1 — правильна ціль, коли результат — інженерна документація з життєвим циклом ревізій, і особливо коли когерентність кольору пристрою важить, бо вивід іде на плотери та широкосформатні принтери. PDF/A — правильна ціль, коли мета — довгострокова читабельність документів загалом, і це профіль із найширшою підтримкою валідаторів. Вони не взаємозамінні, і документ може задовольняти одному та провалювати інший

Діаграма рішень PDFlibPas, що порівнює архівні профілі PDF/E-1 і PDF/A для Delphi: PDF/E-1 для інженерних результатів із життєвими циклами ревізій, кольором на плотер і договірною валідацією у власному просторі імен XMP з output intent ISO_PDFE1; PDF/A для загальної довгострокової читабельності з найширшою підтримкою валідаторів
Стартуйте з того, хто валідує файл на далекому кінці: профілі вимагають різних ідентифікації, метаданих і кольорових гарантій, і документ може задовольняти одному, провалюючи інший

Якщо ви обираєте, стартуйте з того, хто валідує файл на далекому кінці. Інструменти валідації PDF/A всюдисущі, і відповідний preflight у PDFlibPas описано в preflight PDF/A і PDF/UA. Валідація PDF/E спеціалізованіша і зазвичай є договірною вимогою, а не дефолтом. Коли наявний архів треба привести до профілю, під який він ніколи не писався, патерн — шлях ремонту метаданих із конвертації в PDF/A з ремонтом метаданих, і та сама форма застосовується тут: ідентифікуй, відремонтуй безпечне, відхили решту зі списком

Режим авторства, обмежений preflight вмісту й окрема перевірка відповідності постачаються з бібліотекою PDFlibPas для Delphi, тож документ можна продукувати під профілем і незалежно верифікувати потім окремим кодовим шляхом — і це єдина схема, якому варто довіряти для заяви про відповідність