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

Партидни PDF preflight отчети в Delphi с PDFium Component CLI

Партиден preflight инструмент е конзолна програма без прозорец, насочена към папка с PDF файлове, която валидира всеки от тях спрямо посочените стандарти за съответствие и оставя машинночетими доказателства за откритото. Никой не я гледа. Тя работи в два сутринта под cron или Windows Task Scheduler, или като порта в CI конвейер, и следващият човек, интересуващ се от резултата й, е или планировчик, четящ изходен код, или одитор, отварящ отчет седмици по-късно. Това променя значението на „правилен". Preflight машината на PDFium Component, PDF библиотека с изходен код за Delphi, C++Builder и Lazarus, прави самите валидационни извиквания почти тривиални. Работата, решаваща дали инструментът е стойностен, живее около тези извиквания: кой профил е проверен, какво е казал изходният код на планировчика и дали отчетът, който би хванал грешката, все още съществува, когато някой го потърси

Договорът: какво може действително да вижда планировчикът

CI runner или 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]);

Едно извикване оценява и двата стандарта и предава единичен консолидиран запис от отчет

Защо празен списък с находки не е одобрение

Отчетът изброява находките по стандарти, а празен списък с проблеми означава само „не са намерени проблеми в стандартите, действително изпълнени." Това е по-тясно твърдение от „файлът отговаря на стандарта, за който ви е грижа", и пропастта между двете е там, където партидният preflight тихо се разпада. Грешка в конфигурацията, изпускаща ppsPdfA от набора, произвежда точно същия празен списък с находки като действително чист файл. Затова третирайте тишината като подозрителна. Обходете Report.Results и потвърдете две неща за всеки стандарт, който сте имали намерение да проверите: че запис за него съществува изобщо, и че неговият флаг IsCompliant, подкрепен от Status = pfsPass, е верен. Нощна задача, приравняваща „без находки" с „готово за архивиране" без никога да потвърди кои стандарти са оценени, е класическият начин папка с несъответстващи файлове да преминава месеци, докато zewnętrzен одитор не отвори един с veraPDF и целият архив не дойде под въпрос

Втори капан се крие в това, какво всъщност е една находка. Всеки TPdfPreflightIssue носи Code, Category, Description и Recommendation, и назовава нарушеното правило, а не страница или обект. Това е дизайнерски избор с последствия за цикъла на обратна връзка. Отчетът казва на екипа, произвеждащ файловете, какъв клас дефект съществува, невграден шрифт или липсващ XMP идентификатор, и намирането на конкретния нарушаващ обект е задача на инструмента за поправка по-надолу, не на валидатора. Изграждайте потребителите на вашите отчети спрямо стабилните стойности на Code, никога спрямо текста за четене от хора на описанието, който може да бъде преформулиран между версиите без предупреждение

Файлове с отчети за машини и за дежурния специалист

Записът на отчета записва едни и същи находки в пет формата: SaveJsonToFile, SaveCsvToFile, SaveHtmlToFile, SaveTextToFile и SaveMarkdownToFile, всеки с подходяща функция тип ToJson, когато искате низа в паметта, а не на диска. Устоете на желанието да изберете само един. Пишете JSON за конвейера, така че CI може да го прикачи към записа на задачата и да анализира кодовете на находките и статусите за всеки стандарт без сканиране на текст. Пишете HTML за човека, който бива извикан, защото се отваря в произволен браузър без никакви инструменти. Двете заедно струват един допълнителен ред за файл и спестяват на дежурния инженер задачата по декодиране на суров JSON blob в два сутринта, за да разберат кой файл е счупен. Едно дисциплиниращо правило има значение повече от избора на формат: извличайте всяко име на отчет от името на входния файл, никога от клеймо за дата и час, или два паралелни изпълнения ще преплетат отчети, които вече не можете да свържете обратно с техните входни данни

Праговете за тежест спадат към конфигурацията, а не към кода. Анотация без алтернативно описание е твърд неуспех за портал за изпращане на 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 живее в обхвата за всеки файл нарочно, така че единично изключение увеличава брояча за грешки и цикълът продължава. Искате чисти отчети за 4999-те добри файла, дори когато файл 5000 е нечетлив. И двата формата на отчета се записват на диска преди вердиктът да бъде отчетен, което означава, че доказателствата оцеляват, дори ако грешка по-надолу в логиката за обобщение изброи неправилно

Тогава съпоставянето на изходния код се свежда до няколко реда в проектния файл:

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.

Какво preflight няма да направи вместо вас

Машината открива; не поправя. Находка за невграден шрифт или зависимо от устройство цветово пространство е нареждане за работа за онзи, който произвежда файловете, и валидаторът няма начин да го поправи на място. Затова планирайте цикъла на обратна връзка умишлено. Отчетите трябва да стигат там, където производственият екип действително ги чете, или едни и същи находки ще се появяват всяка нощ, докато някой накрая не попита защо нормата на съответствие никога не се подобрява. Изплаща се и да кръстосано проверите извадка от вердикти срещу независим валидатор, veraPDF за PDF/A или Acrobat preflight за PDF/X, преди zewnętrzен одитор да го направи вместо вас. Когато две машини не се съгласят за реален клиентски файл, този документ не е неприятност; той е именно регресионният случай, липсващ на вашето тестване на версии. Запазете го, именувайте го и го изпълнявайте при всяка сборка

Още нещо заслужава внимание. Същата валидационна машина задвижва интерактивните проверки в потребителски интерфейс за преглед, така че тази headless CLI и аналитично насоченото работно пространство за преглед на PDF в Delphi могат да споделят единен речник за валидация вместо да се отдалечават с времето. И тъй като [ppsPdfA, ppsPdfUa] оценява достъпността в същия проход, PDF/UA страната на партидата се наредва чисто с работата от страна на прегледателя, като изграждане на достъпен PDF четец в Delphi. Профили, формати на отчети и пълният preflight API са документирани на продуктовата страница на PDFium Component