Artykuł techniczny

Walidacja zeskanowanych dokumentów PDF/raster w Delphi

PDF/R, standaryzowany jako ISO 23504-1, to profil PDF dla dokumentów skanowanych: każda strona niesie dokładnie jeden obraz paskowy i nic więcej. PDFium Component waliduje go z Delphi, Lazarusa i C++Builder przez ValidatePdfRCompliance, który czyta strumień i zwraca poziom zgodności oraz zbiór konkretnych kwestii

Profil istnieje, ponieważ skanery i systemy przechwytywania dokumentów potrzebowały celu węższego niż PDF/A. Archiwalny PDF może zawierać wszystko, co dopuszcza część; PDF rastrowy jest celowo zubożały, tak by każdy zgodny czytnik wyświetlił go identycznie, a każdy zgodny zapisywacz wyprodukował go ze skanu bez silnika autorskiego

Czego PDF/R zabrania, a PDF/A dopuszcza?

Tekst, w praktyce. Strona rastrowa niesie zeskanowany obraz i nic więcej, więc zasób czcionki na stronie to naruszenie — raportowane jako pvriFontForbidden pod ISO 23504-1 §6.5.2. To zaskakuje ludzi, którzy dodają niewidzialną warstwę tekstu OCR dla przeszukiwalności, co jest normalną i pożyteczną rzeczą w przepływie PDF/A, a po prostu nie jest PDF/R

Relacja strony do obrazu jest równie surowa. §6.5.1 czyni każdą stronę dokładnie jednym obrazem paskowym, więc pvriPageImageMismatch odampa się, gdy liczba obrazów nie zgadza się z liczbą stron — strona bez obrazu i strona z dwoma są obie niezgodne. A pvriBadMediaBox raportuje stronę, której MediaBox nie ma postaci [0 0 w h] (§6.5.3), bo skan nie ma powodu siedzieć na przesuniętym początku układu

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;

Jakie kodowania obrazów są dopuszczone

Cztery, a biała lista jest krótka nie bez powodu. §6.6 dopuszcza /CCITTFaxDecode, /DCTDecode, /JPXDecode i /FlateDecode — faks dwupoziomowy, JPEG, JPEG 2000 i bezstratny deflate, które razem obejmują każde wyjście skanera mające znaczenie. Wszystko inne jest raportowane jako pvriForbiddenImageFilter, w tym /LZWDecode, /RunLengthDecode, /ASCII85Decode, /ASCIIHexDecode, /JBIG2Decode i /Crypt

Dwa z tych odrzuceń warto zrozumieć zamiast zapamiętać. /JBIG2Decode kompresuje skany dwupoziomowe skrajnie dobrze i jest doskonale legalny w PDF/A, ale jego rekonstrukcja słownika symboli może podstawić wizualnie podobne glify — udokumentowany tryb porażki dla zeskanowanych cyfr — a profil, którego całą celem jest wierne odtworzenie rastrowe, nie może dopuścić tego ryzyka. Filtry ASCII są wykluczone z przeciwnego powodu: pompują plik bez dodawania czegokolwiek, czego profil rastrowy potrzebuje

Reguły struktury, które odpalają, zanim jakakolwiek strona zostanie przeczytana

PDF/R ogranicza także kontener. pvriObjStmPresent raportuje strumień /Type /ObjStm, którego profil zabrania wprost — strumienie obiektów komplikują prosty, sekwencyjny parse, jaki czytnik rastrowy ma móc wykonać. pvriBadHeader raportuje nagłówek poza %PDF-1.4 do 1.7 oraz %PDF-2.0, a pvriEncryptVersionMismatch raportuje zaszyfrowany plik, którego nagłówek to nie %PDF-2.0, zgodnie z §6.2.3

Katalog i słownik Info są na białej liście, nie tylko sprawdzane. pvriProhibitedCatalogEntry i pvriProhibitedInfoEntry odampają się dla wpisów poza dozwolonym zestawem, a pvriInfoXmpMismatch, gdy wpis Info nie zgadza się ze swoim odpowiednikiem XMP. Brakujący strumień /Metadata katalogu, brakujący /ID w trailerze i nieobecny znacznik stopki %PDF-raster-1.0 mają też każdy własną kwestię

Dlaczego rekord opcji zapisu pomija Title i Author

TPdfRSaveOptions niesie Creator, Producer, CreationDate, ModDate, DocumentId i InstanceId, a celowo nie ma pola na Title, Author, Subject ani Keywords. Te cztery to wpisy, których §6.4.3 zabrania, więc rekord, który by je wystawiał, zapraszałby wywołujących do zapisu niezgodnego pliku przez zgodne API

Dwie opcje boolowskie kontrolują sprzątanie przy konwersji istniejącego PDF. StripInfoOptionalEntries domyślnie jest True i usuwa Title, Author, Subject, Keywords i Trapped ze źródłowego słownika Info. StripCatalogOptionalEntries też domyślnie jest True i usuwa Names, Outlines, StructTreeRoot, OutputIntents, Lang i resztę, zostawiając tylko białą listę §6.3. Ustaw którekolwiek na False, a zatrzymujesz wpisy — i tracisz zgodność, co czasem jest właśnie tym, czego wywołujący faktycznie chce dla pliku wewnętrznego

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;

Zauważ, czego wstrzykiwanie znaczników nie robi: dodaje metadane i identyfikację, a nie potrafi dostarczyć treści strony. Strona źródłowa, która nie niesie obrazu paskowego, wciąż zawiedzie pvriPageImageMismatch po wstrzyknięciu, bo brakujący obraz nigdy nie był problemem metadanych

Gdzie PDF/R się mieści w potoku przechwytywania

Używaj go tam, gdzie wynikiem jest sam skan, a wierność jest całym kontraktem — obrazowanie dowodowe, przechwytywanie czeków i wpłat, archiwa rysunków inżynieryjnych ze skanera wielkoformatowego. Używaj PDF/A zamiast tego w momencie, gdy dokument potrzebuje przeszukiwalnego tekstu, tagowania, osadzonych załączników albo czegokolwiek innego, co profil rastrowy obdiera

Typowym i wykonalnym układem jest produkcja obu: oryginału PDF/R, który nigdy się nie zmienia, i pochodnego PDF/A z warstwą OCR do pobierania. Walidatory są niezależne, więc to samo zadanie wsadowe może sprawdzić każdy artefakt wobec profilu, który faktycznie rości. Dla archiwalnej strony tej pary zobacz notatki o zgodności archiwalnej PDF/A i walidacji preflighitu PDF/A, a dla wyjścia zorientowanego na druk przewodnik po walidacji dokumentów PDF/X gotowych do druku

PDFium Component przynosi silnik PDFium do Delphi, C++Builder i Lazarus z API VCL i walidatorami zgodności dla PDF/A, PDF/X, PDF/E, PDF/UA i PDF/R — strona produktu PDFium Component wymienia obsługiwane normy i wersje IDE