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

Валідація растрових відсканованих документів PDF у Delphi

PDF/R, стандартизований як ISO 23504-1, — це профіль PDF для відсканованих документів: кожна сторінка несе рівно одне смугове зображення й нічого більше. PDFium Component валідує його з Delphi, Lazarus та C++Builder через ValidatePdfRCompliance, що читає потік і повертає рівень відповідності плюс набір конкретних проблем

Профіль існує, бо сканерам та системам захоплення документів потрібна ціль вужча за PDF/A. Архівний PDF може містити все, що дозволяє частина; растровий PDF навмисно збіднений, тож будь-який відповідний читач може показати його ідентично, а будь-який відповідний письменник — створити зі скану без рушія авторинга

Що PDF/R забороняє, а PDF/A дозволяє?

Текст, на практиці. Растрова сторінка несе відскановане зображення й нічого більше, тож шрифтовий ресурс на сторінці є порушенням — про нього звітується як pvriFontForbidden згідно з ISO 23504-1 §6.5.2. Це дивує тих, хто додає невидимий шар тексту OCR для пошуку, що є нормальною та корисною справою в робочому процесі PDF/A й просто не є PDF/R

Зв'язок сторінки із зображенням такий само суворий. §6.5.1 робить кожну сторінку рівно одним смуговим зображенням, тож pvriPageImageMismatch спрацьовує, коли кількість зображень не відповідає кількості сторінок — сторінка без зображення і сторінка з двома обидві невідповідні. А pvriBadMediaBox звітує про сторінку, чиї MediaBox не мають форми [0 0 w h] (§6.5.3), бо скану немає причини сидіти зі зміщеним початком координат

uses FPdfPdfr;

var
  Src: TFileStream;
  Res: TPdfRValidationResult;
begin
  Src := TFileStream.Create('scan-batch-0142.pdf', fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfRCompliance(Src);
    if Res.IsCompliant then
      Memo1.Lines.Add('PDF/R-1 conformant')
    else
    begin
      if pvriFontForbidden in Res.Issues then
        Memo1.Lines.Add('A page names a font resource; a raster page carries no text');
      if pvriPageImageMismatch in Res.Issues then
        Memo1.Lines.Add('Image count does not match page count');
      if pvriForbiddenImageFilter in Res.Issues then
        Memo1.Lines.Add('A strip image uses an encoding outside the white list');
    end;
  finally
    Src.Free;
  end;
end;

Які кодування зображень дозволені

Чотири, і білий список короткий не без причини. §6.6 визнає /CCITTFaxDecode, /DCTDecode, /JPXDecode та /FlateDecode — бі-level факс, JPEG, JPEG 2000 та стиснення без втрат deflate, що між собою охоплюють усі виходи сканерів, що мають значення. Усе інше звітується як pvriForbiddenImageFilter, включно з /LZWDecode, /RunLengthDecode, /ASCII85Decode, /ASCIIHexDecode, /JBIG2Decode та /Crypt

Дві з цих відмов варто зрозуміти, а не завчити. /JBIG2Decode стискає бі-level скани надзвичайно добре й цілком правомісний у PDF/A, але його реконструкція словника символів може підміняти візуально подібні гліфи — документована вада для відсканованих цифр — а профіль, чиє все призначення — точне растрове відтворення, не може припустити цього ризику. ASCII-фільтри виключені з протилежної причини: вони роздувають файл, не додаючи нічого, що потрібен растровому профілю

Структурні правила, що спрацьовують до читання будь-якої сторінки

PDF/R обмежує й контейнер. pvriObjStmPresent звітує про потік /Type /ObjStm, що профіль забороняє категорично — потоки об'єктів ускладнюють простий, послідовний розбір, який растровий читач має бути здатним виконати. pvriBadHeader звітує про заголовок поза %PDF-1.4 до 1.7 та %PDF-2.0, а pvriEncryptVersionMismatch — про зашифрований файл, чий заголовок не %PDF-2.0, згідно з §6.2.3

Каталог та словник Info не просто перевіряються, а вносяться до білого списку. pvriProhibitedCatalogEntry та pvriProhibitedInfoEntry спрацьовують для записів поза дозволеним набором, а pvriInfoXmpMismatch — коли запис Info не погоджується зі своїм XMP-еквівалентом. Відсутній потік /Metadata каталога, відсутній /ID у трейлері та відсутній кінцевий маркер %PDF-raster-1.0 мають власні проблеми також

Чому запис опцій збереження оминає Title та Author

TPdfRSaveOptions несе Creator, Producer, CreationDate, ModDate, DocumentId та InstanceId і навмисно не має поля для Title, Author, Subject або Keywords. Ці чотири — записи, які §6.4.3 забороняє, тож запис, що їх виставляв би, запросив би викличний код писати невідповідний файл через відповідний API

Два булеві параметри керують очищенням під час перетворення наявного PDF. StripInfoOptionalEntries за замовчуванням True та прибирає Title, Author, Subject, Keywords і Trapped із вихідного словника Info. StripCatalogOptionalEntries також за замовчуванням True та прибирає Names, Outlines, StructTreeRoot, OutputIntents, Lang і решту, залишаючи лише білий список §6.3. Установіть будь-яке в False — і ви збережете записи, і втратите відповідність, що часом і є тим, чого викличний код справді хоче для внутрішнього файла

var
  Opts: TPdfRSaveOptions;
  Src, Dest: TFileStream;
begin
  Opts := TPdfRSaveOptions.Default;
  Opts.Creator := 'Capture Station 4';
  Opts.Producer := 'PDFium Component';
  Src := TFileStream.Create('scan-in.pdf', fmOpenRead or fmShareDenyWrite);
  try
    Dest := TFileStream.Create('scan-pdfr.pdf', fmCreate);
    try
      InjectPdfRMarkers(Src, Dest, Opts);   // markers + metadata, not page content
    finally
      Dest.Free;
    end;
  finally
    Src.Free;
  end;
end;

Зауважте, чого вставлення маркерів не робить: воно додає метадані та ідентифікацію і не може постачати вміст сторінок. Вихідна сторінка, що не несе смугового зображення, усе одно не пройде pvriPageImageMismatch після вставлення, бо відсутнє зображення ніколи не було проблемою метаданих

Куди PDF/R вписується в конвеєр захоплення

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

Поширене і працездатне рішення — виробляти обидва: оригінал PDF/R, що ніколи не змінюється, і похідний PDF/A зі шаром OCR для пошуку. Валідатори незалежні, тож те саме пакетне завдання може перевірити кожен артефакт проти профілю, який він фактично заявляє. Щодо архівної сторони цієї пари, дивіться нотатки про архівну відповідність PDF/A та префлайт валідацію PDF/A, а щодо виводу, орієнтованого на друк, нарис валідації документів PDF/X, готових до друку

PDFium Component приносить рушій PDFium у Delphi, C++Builder та Lazarus із VCL API та валідаторами відповідності для PDF/A, PDF/X, PDF/E, PDF/UA та PDF/R — сторінка продукту PDFium Component перераховує підтримувані стандарти та версії IDE