Техническа статия

PDF/E-1 в PDFlibPas: авторски режим и bounded preflight

PDF/E-1 е архивният профил за инженерни документи, а PDFlibPas го реализира като авторски режим, включван с SetPDFEMode, плюс bounded preflight, който чете content streams оператор по оператор. Профилът не е PDF/A с друг етикет: той има собствено identification namespace, собствено изискване за lifecycle метаданни и едно правило, което прави валидирането на съдържанието по-строго от всеки архивен профил, който сте срещали

Инженерните резултати са причината профилът да съществува. Комплект чертежи, който трябва да бъде четим и доказуемо непроменен след двадесет години, с ревизионна история, която оцелява, и с цвят, който значи същото на плотера в другата сграда. Тези изисквания раждат спецификация, чиито искания стоят най-вече извън съдържанието на страницата – в метаданни и управление на цвета, точно там, където generic PDF writer се обърква

Собствена идентификация, не вариация върху PDF/A

Първото нещо, което трябва да се схване право, е, че идентификацията на PDF/E-1 не може да бъде произведена по шаблона на PDF/A или PDF/X. Тя ползва отделно XMP namespace, http://www.aim.org/pdfe/ns/id/, а стойността на версията трябва да се появи на две места: като запис в document information и като namespace-квалифицирано XMP свойство. Излъчване само на XMP свойството, или само на информационния запис, ражда файл, който носи намерението и се проваля на валидация

Output intent-ът има също толкова конкретна форма. PDF/E-1 изисква вграден ICC profile с subtype идентификатор ISO_PDFE1, а профилът трябва да има брой компоненти, съвпадащ с device цветовото семейство, което документът реално ползва. Последната клауза е мястото, където имплементациите тихо се провалят, защото тя значи, че intent-ът не може да бъде избран предварително и после забравен

Защо device цветът се нуждае от обхождане на целия документ?

Защото цветовите пространства се крият в resource речници, до които сканиране на ниво страница никога не стига. PDF/E-1 третира DeviceRGB и DeviceCMYK като взаимно изключващи се семейства за един документ, така че валидирането на профила значи да знаеш всяко device цветово пространство, което каквото и да е в файла ползва. Form XObject има собствени ресурси. Също така pattern, и също изображение. Tiling pattern в form XObject в страница е на три нива навътре, а валидатор, проверяващ само ресурсите на най-горното ниво на страницата, ще пропусне документ, ползващ и двете семейства

Затова обхождането регистрира цветовите пространства, докато върви по страници, форми, изображения и шарки като едно прохождане, и чак тогава решава дали документът е кохерентен и дали output intent-ът пасва. Същото разсъждение движи общо архитектурата на preflight: частично обхождане ражда фалшиви минавания, а фалшиво минаване на conformance проверка е по-лошо от липса на проверка, защото се записва като доказателство

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');

    // Авторският режим държи lifecycle метаданните в крак при всеки запис.
    // Запитай преди запис дали документът би минал през собствения си порт
    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;

Lifecycle метаданните са задължение при всеки запис

PDF/E-1 иска повече от идентификатор на документ. Минималният набор включва media management идентификатора на документа, идентификатор на версия, rendition class, време на създаване, време на промяна, време на метаданните и заглавие. Това е речник за проследяване на ревизии и съществува, защото от инженерния резултат се очаква да бъде преиздаден, а не написан веднъж

Последицата за имплементацията е, че тези полета не могат да бъдат зададени при създаване на документа. Ако времето на промяна е записано, когато включвате режима, а документът после се редактира, XMP snapshot-ът и реалното състояние на документа са се разминали, а валидатор, който ги сравнява, докладва несъответствие, което никой не е искал. Авторският режим затова синхронизира полетата непосредствено преди всеки запис, така че метаданните описват байтовете, предстоящи да бъдат записани, а не байтовете, съществували при включването на режима

Това е общ принцип за conformance метаданните и си струва да бъде изречен отделно от PDF/E: производните метаданни принадлежат на пътя на записа, не на пътя на редакция. Всяко поле, изчислено от състоянието на документа, трябва да бъде преизчислено в момента, в който състоянието е замразено, иначе е кеш без валидация

PDFlibPas PDF/E-1 диаграма на цялодокументовото обхождане на device цвят, което минава през resource речниците на страница, form XObject, tiling pattern и изображение, събирайки семействата DeviceRGB и DeviceCMYK, преди да отсъди кохерентност, до полетата на lifecycle метаданните, които авторският режим ресинхронизира непосредствено преди всеки запис, така че XMP snapshot-ът да пасва на байтовете, предстоящи да бъдат записани
Цветовата кохерентност може да бъде отсъдена само след едно обхождане, стигнало всеки resource речник, а производните lifecycle метаданни се преизчисляват в момента, в който състоянието на документа е замразено, а не когато режимът е включен

Правилото, което прави валидирането на съдържанието строго

PDF/E-1 не позволява операторите на compatibility секцията да поглъщат непознато съдържание. В обикновен PDF BX и EX заграждат регион, в който консуматорът трябва да игнорира оператори, които не разпознава – страничната вратичка, позволяваща на производител да излъчва по-нови конструкции, без да чупи по-стари читатели. Под PDF/E-1 тази вратичка е затворена, така че всеки оператор, който preflight-ът не разпознава, се докладва безусловно, независимо дали стои в compatibility секция

Ефектът върху валидатора е значителен. Той не може да прескача региони, които не разбира, което значи, че operand парсерът трябва реално да парсва всеки оператор във всеки content stream. Тук се наместват границите. Обхождането е ограничено до 128 нива на влагане, един милион обекта и 64 MiB съдържание, и тези лимити не са настройка за производителност. Враждебен или просто счупен файл може да подаде граф от обекти с цикли или дълбочина на влагане, превръщаща рекурсивен валидатор в stack overflow, а лимитите са това, което спира валидационен пас да се превърне в 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;

Какво поправя save портата и какво отказва

Портата разделя работата си на две стъпки, а разделянето само по себе си е годна дизайнерска идея. Първо нормализира нещата, които са безопасно поправими: print флаговете на анотациите, no-zoom и no-rotate флаговете на текстовите анотации и appearance-generation флага на form речника. Това са настройки с една-единствена правилна стойност под профила и без никакво информационно съдържание, така че безшумната им поправка е правилната, а отказът заради тях би бил педантизъм

После портата проверява ограниченията, които не могат да бъдат поправени без промяна на значението на документа: версия, идентификация, криптиране, output intent, device цветова кохерентност и наличието на динамично form съдържание. Документ, провалящ някое от тях, се отказва, защото измислянето на output intent или избирането на цветово семейство от името на автора би произвело файл, който минава валидация и не представя съдържанието честно

PDFlibPas PDF/E-1 диаграма на save портата за Delphi, показваща bounded preflight, който сканира всеки оператор в content streams под ограничения от 128 нива влагане, един милион обекта и 64 MiB, безшумно поправя print, zoom и rotate флаговете на анотациите, отказва грешна версия, идентификация, криптиране, output intent, device цвят или динамично form съдържание, и докладва блокерите чрез GetPDFEDiagnostics
Портата поправя безшумно само това, което не носи информация, отказва всяко ограничение, което поправката би изкривила, и превръща отказа в списък с блокери чрез GetPDFEDiagnostics, преди някой байт да стигне диска

Четенето на диагностиката обратно чрез GetPDFEDiagnostics преди запис превръща отказа в списък за действие, а не в провалена операция. В batch pipeline – викайте го за всеки документ, логвайте блокерите по файл и насочвайте провалите към опашка, която човек поглежда. Това е далеч по-полезно от запис, който вдига изключение, защото блокерите обикновено се групират: четиридесет документа, провалящи се заради един и същ липсващ output intent, са една поправка, а не четиридесет

Избор между архивните профили

PDF/E-1 е правилната цел, когато резултатът е инженерна документация с ревизионен жизнен цикъл, и по-конкретно когато device цветовата кохерентност има значение, защото изходът отива на плотери и широкоформатни принтери. PDF/A е правилната цел, когато целта е дългосрочна четимост на документи изобщо, и е профилът с най-широката поддръжка от валидатори. Двата не са взаимозаменяеми, а един документ може да удовлетвори единия и да провали другия

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

Ако избирате, започнете от това кой валидира файла в далечния край. Инструментите за PDF/A валидация са навсякъде, а съответният preflight в PDFlibPas е описан в PDF/A и PDF/UA preflight. PDF/E валидацията е по-специализирана и обикновено е договорно изискване, а не подразбиращо се. Когато съществуващ архив трябва да бъде доведен до профил, за който никога не е писан, пътят за ремонт на метаданни в конвертиране към PDF/A с ремонт на метаданни е моделът за следване, а същата форма важи и тук: идентифицирай, поправи това, което е безопасно, откажи останалото със списък

Авторският режим, bounded content preflight-ът и самостоятелната compliance проверка идват всички с PDFlibPas Delphi PDF библиотеката, така че документ може да бъде произведен под профила и независимо проверен после през отделен кодов път – единствената уредба, заслужаваща доверие за conformance твърдение