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

Валидиране на PDF/X в Delphi с компонента PDFium

Компонентът PDFium за Delphi валидира готови за печат PDF/X документи чрез TPdf.ValidatePdfX, който имплементира проверка по ISO 15930 в два слоя: осем проверки на съдържанието на ниво байтове (забрана на LZW компресия, JavaScript, формулярни полета, OPI препратки, липсващ TrimBox, незададен Trapped ключ и др.) плюс преминаване през обективния модел на PDFium, което използва FPDFFont_GetIsEmbedded за проверка на вграждането на шрифтове за всеки текстов обект на всяка страница; Резултатът е запис TPdfXValidationResult, който посочва откритото ниво на съответствие и изброява всяко нарушение като типизирано изброяване (enum), така че вашето приложение на Delphi може да каже на клиента точно защо файлът ще бъде отхвърлен в печатницата, преди някой да е изложил пластините

Ако някога сте изпращали поръчка на търговска печатница и сте я получавали обратно с отказ от един ред — „няма TrimBox“, „шрифтовете не са вградени“, „Trapped не е зададен“ — знаете каква е цената да разберете твърде късно; PDF/X е съответствието за предпечатната подготовка на PDF/A: докато архивният PDF/A гарантира, че документът ще се рендерира идентично след десетилетия, PDF/X гарантира, че документът ще се раздели на цветове, ще се експонира и ще се обреже идентично на RIP на някой друг утре сутрин; Двата стандарта споделят сходни механизми (XMP идентификация, OutputIntents, вградени ICC профили), но отговарят на различни въпроси, поради което компонентът доставя отделни валидатори за всеки — страната на PDF/A е разгледана в PDF/A предварителна проверка с компонента PDFium

Какво всъщност изисква ISO 15930 от готов за печат PDF?

ISO 15930 съществува, за да направи възможен обмена на сляпо: дизайнерът предава файл на печатница, с която никога не е разговарял, и печатницата може да произведе правилен резултат без телефонно обаждане, без имейл за липсващ шрифт и без свързано изображение, останало на лаптопа на дизайнера; Всяко правило в стандарта служи на тази цел; Шрифтовете трябва да бъдат вградени, тъй като не може да се предполага, че получаващият RIP ги притежава; Външните препратки са забранени, тъй като файлът трябва да бъде напълно самостоятелен; Интерактивните функции са забранени, тъй като мастилото няма манипулатор за кликване

Компонентът PDFium разпознава три семейства съответствие и ги докладва чрез изброяването TPdfXConformance в резултата от валидирането: pxc1a за PDF/X-1a:2001 (ISO 15930-1, строгата CMYK-плюс-spot база върху PDF 1.3/1.4), pxc3 за PDF/X-3:2002 (ISO 15930-3, който допуска RGB, Lab и управлявани от ICC цветове) и pxc4 за PDF/X-4:2010 (ISO 15930-7, който най-накрая позволява прозрачност на живо и слоеве върху PDF 1.6 база); Файл, който изобщо не носи PDF/X идентификация, се връща като pxcNone, което само по себе си е полезен отговор: документът никога не е твърдял, че е готов за печат, а всичко останало, което валидаторът докладва, обяснява какво е необходимо, за да се стигне дотам

Забраните имат смисъл, след като започнете да мислите като доставчик на RIP; /LZWDecode е забранен във всички варианти на PDF/X, така че съвместимият потребител никога да не зависи от филтър с история на съвместимост и лицензиране; Flate върши същата работа без този товар; JavaScript, AcroForm полета и речници с допълнителни действия /AA са забранени, тъй като файлът за печат трябва да бъде фиксирано описание на маркировки върху хартия — всичко, което може да промени външния вид при отваряне, нарушава гаранцията, че това, което е одобрено, е това, което се печата; OPI (Open Prepress Interface) маркерите са забранени, тъй като по дизайн те са препратки към изображения с висока разделителна способност, съхранявани другаде, а „другаде“ е точно това, което обменът на сляпо забранява

Защо печатниците отхвърлят PDF файлове без TrimBox?

TrimBox е готовият за рязане лист — правоъгълникът, който остава след срязването на гилотината; MediaBox, който всяка PDF страница има, е просто листът: той включва наддаването за обрязване (bleed), маркировки за рязане, регистрационни мишени и цветни скали; Софтуерът за монтаж позиционира страниците върху печатния лист по техните TrimBoxes; без такъв оператор трябва да гадае къде всъщност свършва вашата визитка, а грешното предположение отрязва наддаването или оставя бяла ивица на единия ръб; Ето защо ISO 15930 изисква TrimBox (или ArtBox) на всяка страница и защо ValidatePdfX докладва pvxiMissingTrimBox, когато не е намерен ключ /TrimBox на нито една страница от документа

Ключът /Trapped отговаря на друг производствен въпрос; Трапингът (trapping) е предпечатна техника за леко застъпване на съседни цветове, така че минимално несъответствие при печат да не отваря бели празнини между тях; Печатницата трябва да знае дали тази работа вече е свършена: трапирането на вече трапиран файл удвоява застъпванията, а пропускането на трапинг при нетрапиран файл рискува видими празнини; Поради това PDF/X изисква речникът Info да декларира /Trapped /True или /Trapped /False изрично — липсващ ключ или /Unknown принуждава оператор да инспектира файла, което е точно разговорът, който обменът на сляпо трябваше да елиминира; Компонентът маркира това като pvxiTrappedNotSet

Изпълнение на двуслойната валидация с TPdf.ValidatePdfX

Методът TPdf.ValidatePdfX не приема аргументи и връща запис TPdfXValidationResult с три члена: Conformance (откритият PDF/X вариант), Issues (Pascal набор от стойности TPdfXValidationIssue) и помощна функция IsCompliant; Вътрешно той сериализира заредения документ в поток от паметта, изпълнява инспектора на ниво байтове над него и след това обхожда обективния модел на PDFium за проверка на вграждането на всеки шрифт; Една минимална проверка преди печат изглежда така:

uses PDFium, FPdfPdfx;

procedure CheckPrintReadiness(const FileName: string);
var
  Pdf: TPdf;
  Res: TPdfXValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Active := True;

    Res := Pdf.ValidatePdfX;
    Writeln('Detected conformance: ',
      Ord(Res.Conformance)); // pxc1a, pxc3, pxc4, pxcNone...

    if Res.IsCompliant then
      Writeln('PDF/X checks passed')
    else
    begin
      if pvxiMissingTrimBox in Res.Issues then
        Writeln('REJECT: no /TrimBox on the pages');
      if pvxiTrappedNotSet in Res.Issues then
        Writeln('REJECT: /Trapped missing or /Unknown');
      if pvxiPdfiumFontNotEmbedded in Res.Issues then
        Writeln('REJECT: a page uses a non-embedded font');
      if pvxiLzwForbidden in Res.Issues then
        Writeln('REJECT: LZWDecode filter present');
    end;
  finally
    Pdf.Free;
  end;
end;

Тъй като Issues е обикновен Pascal набор, можете да го разделите според нуждите на вашия работен процес — третирайте структурните проблеми като твърди откази, третирайте pvxiMissingTitle (препоръка в стандарта, а не задължение) като предупреждение и записвайте останалото в лога; Същият тип запис захранва и генератора на отчети на компонента, така че ако предпочитате да издадете четим от човек документ, вместо да се разклонявате по enums, моделът в изграждането на CLI за пакетен отчет преди печат с компонента PDFium се прилага към PDF/X без промяна

Какво улавя слоят на ниво байтове и какво пропуска

Слоят на ниво байтове е сканиране на токени в структурните байтове на документа с празни тела на потоците, така че JPEG файл, който случайно съдържа байтовата последователност /JavaScript, не може да задейства фалшиво положителен резултат; В допълнение към проверките на маркери (XMP pdfxid:GTS_PDFXVersion, OutputIntent с вграден ICC профил, трейлър /ID, забрана на шифроване), преминаването през съдържанието добавя осем проверки, всяка със своя стойност на enum:

  • pvxiLzwForbidden — филтър /LZWDecode се появява някъде във файла (забранен във всички варианти на PDF/X)
  • pvxiJavaScriptForbidden — налично е действие /JavaScript или дърво с имена
  • pvxiFormFieldsForbidden — съществува речник /AcroForm или запис /XFA
  • pvxiAdditionalActions — наличен е речник за допълнителни действия /AA
  • pvxiEmbeddedFilesForbidden — налично е /EmbeddedFiles или анотация /FileAttachment
  • pvxiOpiForbidden — запис /OPI или /Alternates препраща към сменяемо съдържание на изображение
  • pvxiMissingTrimBox — няма намерен /TrimBox на нито една страница
  • pvxiTrappedNotSet/Trapped липсва или е зададен на /Unknown

Сканирането на ниво байтове е бързо и не се нуждае от рендериращ двигател, но има вродено сляпо петно с шрифтовете: на това ниво инспекторът може да приложи само груба евристика — той маркира документа като грешен, когато не открие вградена програма за шрифт изобщо; Файл с девет вградени шрифта и един вмъкнат системен шрифт изглежда добре при сканиране по байтове; Тази единствена празнина е причината да съществува вторият слой

Вграждане на шрифтове чрез обективния модел на PDFium

Слоят на обективния модел на компонента PDFium отговаря точно на въпроса за шрифтовете; След преминаването на ниво байтове, TPdf.ValidatePdfX обхожда всяка страница, извиква FPDFPage_CountObjects за списъка с обекти и за всеки текстов обект извлича дескриптора на шрифта чрез FPDFTextObj_GetFont и прави запитване към FPDFFont_GetIsEmbedded; Един-единствен невграден шрифт някъде в документа добавя pvxiPdfiumFontNotEmbedded към набора от проблеми; Обхождането се прекратява на две нива — спира сканирането на обекти на дадена страница и спира зареждането на следващи страници в момента, в който проблемът бъде потвърден — така че за каталог с грешки от 300 страници присъдата често пристига още след страница първа

Две подробности, които си струва да знаете; Първо, този слой изисква заредената библиотека PDFium и се нуждае от компилации, които изнасят FPDFFont_GetIsEmbedded; когато този експорт липсва, проверката се пропуска, а не се счита за неуспешна, така че по-стара DLL никога не създава фалшиви откази; Второ, проверката отговаря на „вграден или не“ и нищо повече — тя не разграничава пълното вграждане от подмножествата (subsetting), нито проверява покритието на глифовете; Когато даден файл се провали и трябва да разберете кой шрифт на коя страница е виновен, техниките за изброяване в анализирането на свойствата на шрифтовете в PDF с PDFium в Delphi продължават точно оттам, където булевата стойност на валидатора спира

Валидиране на потоци без зареждане на документ — или на DLL

Инспекторът на ниво байтове също е изложен като самостоятелна функция, ValidatePdfXCompliance(Source: TStream) в модула FPdfPdfx, и е чист Object Pascal без зависимост от PDFium DLL; Това го прави лесен за внедряване на места, където рендериращ двигател не е желан: лека проверка при качване на уеб сървър, CI задача, която проверява генерирани графики, или Lazarus услуга на платформа, където предпочитате да не доставяте компилирани двоични файлове; Подайте му всеки поток с поддръжка на позициониране:

uses Classes, FPdfPdfx;

function QuickPdfXGate(const FileName: string): Boolean;
var
  Fs: TFileStream;
  Res: TPdfXValidationResult;
begin
  Fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfXCompliance(Fs);
    Result := Res.IsCompliant and (Res.Conformance <> pxcNone);
  finally
    Fs.Free;
  end;
end;

Компромисът е ясен: самостоятелният път изпълнява проверките на маркерите и всичките осем проверки на съдържанието, но не и слоя на PDFium за всеки шрифт, така че неговата присъда за шрифтовете се връща към грубата евристика; Една разумна архитектура използва ValidatePdfXCompliance като бърза първа проверка и запазва пълния TPdf.ValidatePdfX за файлове, които я преминават успешно

Къде свършва този валидатор и къде започва пълната проверка

Честността е важна при предпечатните инструменти, така че ето я и границата; ValidatePdfX проверява маркери за идентификация, структурни забрани, ключове за геометрия на страниците, декларацията Trapped и вграждането на шрифтове до отделни текстови обекти; Той обаче не измерва общото покритие на мастилото (total ink coverage), не проверява дали всяко цветово пространство е законно за заявения вариант (например CMYK правилото на X-1a), не проверява разделителната способност на изображенията или поведението при надпечатване (overprint) и изглаждане на прозрачността — те се нуждаят от предпечатен двигател с управление на цветовете, а собствената документация на модула препоръчва сдвояването му с такъв за крайна сертификация; Това, че двуслойната проверка ви дава, са 80% от отказите, които са структурни и могат да бъдат открити рано, уловени за милисекунди в рамките на собствения ви Delphi код, вместо в утрешния имейл от печатницата

Двата слоя за валидация, приложните програмни интерфейсове (APIs) за вграждане на PDF/X маркери за генериране на съвместим изход, както и валидаторите за PDF/A, PDF/UA, PDF/E и PDF/VT, които споделят същата архитектура, се доставят в PDFium Component за Delphi и C++Builder — един компонент, от рендериране до предпечатни проверки