Teknik Makale

Delphi'de PDF/raster Taranmış Belgeleri Doğrulama

Bir tarama toplu işi sayfa başına tek JPEG'ten ibaret olabilir; font yok, açıklama yok, yer imi yok — ve yine de PDF/raster olarak başarısız olabilir. ISO 23504-1, yalnızca raster sayfalardan fazlasını ister: dosya, trailer alanındaki bir %PDF-raster-1.0 yorumuyla kendini tanımlamalı, XMP meta verisi ve trailer /ID taşımalı ve katalog ile Info sözlüğünü kısa, izinli listelerde tutmalı. Bir yakalama toplu işini denetlemek bu yüzden iki farklı okuma gerektirir: biri ham baytlar üzerinden, biri her sayfanın gerçekte çizdiği şey üzerinden

Aynı dosyanın iki okuması

PDF/R doğrulaması ile SaveAsPdfR, PDFium Component'in Small Business ve Enterprise sürümleriyle gelir ve doğrulamayı o hat boyunca böler. FPdfPdfr birimindeki ValidatePdfRCompliance bir akış okur ve bayt düzeyi kuralları denetler: başlık sürümü, raster işaretçisi, object stream'ler, katalog ve Info girdileri, XMP, trailer /ID, MediaBox kaynakları, font kaynakları, yasak filtre adları ve görüntü nesnesi sayısının sayfa sayısıyla eşleşip eşleşmediği. ValidatePdfRPageFacts ise bir TPdfRPageFacts kaydından birer sayfa denetler: sayfanın kaç metin, yol, form ve shading nesnesi çizdiği, tek şerit görüntünün nerede durduğu, piksel boyutu, bit derinliği, renk alanı ve filtre zinciri

İki okuma da tek bir numaralandırma olan TPdfRValidationIssue üzerinden rapor verir ve birleşik giriş noktaları bunları tek bir TPdfRValidationResult içinde birleştirir. Conformance alanı yalnızca raster işaretçisi bulunduğunda prc1 olur ve IsCompliant, düzey prc1 iken ve sorun kümesi boşken True döndürür. Her başka yönden kusursuz ama işaretçisi olmayan bir dosya prcNone raporlar; insanlar temiz bir taramayı ilk kez içinden geçirdiklerinde buna şaşırır

Soru, sayfa bilgilerinin nereden geldiğidir. TPdf.ValidatePdfR bunları yüklenmiş belgeden toplar ama önce o belgeyi geçici bir depoya serileştirir ve bayt denetimlerini yeniden kaydedilen kopya üzerinde çalıştırır; dolayısıyla işaretçi ve katalog kararları taze bir kaydı anlatır ve diskteki dosyadan farklı olabilir. FPdfBusinessWorkflows içindeki ValidatePdfRasterSource, giriş akışının tek bir anlık görüntüsünü alır ve iki okumayı da ona karşı çalıştırır. Arşive girmek üzere olan bir dosya için kullanılacak çağrı budur: karar, o zaman arşivlenen baytları anlatır

uses
  System.Classes, System.SysUtils, System.TypInfo,
  FPdfPdfr, FPdfReport, FPdfBusinessWorkflows;

procedure CheckDeliveredScan(const FileName: string; Log: TStrings);
var
  Src: TFileStream;
  Options: TPdfRasterSourceValidationOptions;
  Report: TPdfRasterSourceValidationReport;
  Issue: TPdfRValidationIssue;
  I: Integer;
begin
  Options := TPdfRasterSourceValidationOptions.Default;
  Options.MaxInputBytes := 512 * 1024 * 1024;     // varsayılan bütçe 64 MiB
  Options.Validation.MinimumResolutionDpi := 200; // yakalama sözleşmemiz
  Src := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    if not ValidatePdfRasterSource(Src, Options, Report) then
    begin
      Log.Add(Format('Check stopped (%s): %s',
        [GetEnumName(TypeInfo(TPdfRasterSourceStatus), Ord(Report.Status)),
         Report.ErrorMessage]));
      Exit;
    end;
  finally
    Src.Free;
  end;

  if Report.IsCompliant then
    Log.Add('PDF/R-1 conformant')
  else
  begin
    // Validation.Issues, bayt düzeyi sorunları ve her sayfa sorununu tutar
    for Issue := Low(TPdfRValidationIssue) to High(TPdfRValidationIssue) do
      if Issue in Report.Validation.Issues then
        Log.Add('File: ' + GetEnumName(TypeInfo(TPdfRValidationIssue), Ord(Issue)));
    // PageIssues hangi sayfaların yeniden taranacağını söyler
    for I := 0 to Report.ProcessedPageCount - 1 do
      for Issue := Low(TPdfRValidationIssue) to High(TPdfRValidationIssue) do
        if Issue in Report.PageIssues[I] then
          Log.Add(Format('Page %d: %s', [Report.Pages[I].PageNumber,
            GetEnumName(TypeInfo(TPdfRValidationIssue), Ord(Issue))]));
  end;

  // Makine tarafından okunabilir kararı arşivlenen dosyanın yanına koyun
  Log.Add(Report.ToJson(TPdfReportLimits.Default));
end;

TPdfRasterSourceValidationOptions'un tarama toplu işleri için önemli iki varsayılanı vardır. MaxInputBytes 64 MiB ve MaxPages 1000'dir; uzun bir renkli tarama toplu işi bayt bütçesini aşabilir ve denetim o zaman prssBudgetExceeded durumuyla, Complete False olarak biter. MemoryLimitBytes 8 MiB'dir; onun ötesinde anlık görüntü TemporaryDirectory'ye taşar ve rapor SnapshotSpilled ayarlar. Report.IsCompliant ayrıca Complete ister; yarıda kalmış bir denetim asla geçmiş gibi okunmaz

Delphi için PDFium Component'te PDF/R doğrulamasının haritası: ValidatePdfRCompliance bir akışı TPdfRValidationResult'a okur; onun sorun kümesi pvriFontForbidden ve pvriBadMediaBox gibi sayfa kurallarını, ISO 23504-1 görsel filtre beyaz listesini ve pvriObjStmPresent gibi kapsayıcı kurallarını kapsar
Tek bir çağrı uygunluk düzeyini artı somut bir sorun kümesi döndürür; kontroller sayfa kurallarına, dört filtreli bir görüntü beyaz listesine ve herhangi bir sayfa okunmadan önce ateşlenen kapsayıcı kurallarına bölünür

Bir OCR metin katmanı PDF/R'yi neden düşürür?

Çünkü bir raster sayfa taranan görüntüyü çizer ve başka hiçbir şey. Görünmez bir OCR katmanı, PDF/A akışında sıradan bir ektir ve PDF/R'nin dışladığı iki şeyi dosyaya sokar: bayt okumasının pvriFontForbidden olarak raporladığı bir font kaynağı ve sayfa okumasının pvriNonRasterPageContent olarak raporladığı sayfa üzerindeki metin nesneleri. Aynı sayfa sorunu vektör yolları, shading'leri ve form XObject'leri için de ateşlenir; damgalı bir sayfa numarası ya da çizilmiş bir kenarlık kuralı da gizli metin gibi aynı biçimde başarısız olur

Sayfa-görüntü ilişkisi de katıdır. Her sayfa tam olarak bir şerit görüntü taşımalıdır; pvriPageImageMismatch, görüntüsü olmayan bir sayfada da taraması iki görüntü olarak yan yana yazılmış bir sayfada da ateşlenir. Bayt tarafında sayım, /Subtype'ı /Image olan her nesnenin üzerinden alınır; dolayısıyla fazladan her görüntü nesnesi — örneğin şeride eklenmiş bir soft mask — toplamı sayfa sayısının ötesine iter

Geometri iki denetim alır. pvriBadMediaBox, [0 0 ...] ile başlamayan bir MediaBox raporlar; bayt okuması satır içi sayısal dizilere bakar. Sayfa okuması sonra şeridin çizilmiş sınırlarını MediaBox ile karşılaştırır ve herhangi bir kenar GeometryTolerancePoints'ten — varsayılanı 0.5 punto — fazlasıyla kaymışsa pvriStripGeometryInvalid raporlar. Görüntüyü sayfanın yüzde 98'ine ölçekleyerek beyaz bir kenar bırakan bir tarayıcı sürücüsü burada başarısız olur; sayfa ekranda düzgün görünse bile. Kenar kabul edilebilir iç dosyalar için RequireFullPageStrip'i False yapmak kapsam testini kapatır

Hangi görüntü kodlamaları geçer

Doğrulayıcı dört şerit filtresi kabul eder: /CCITTFaxDecode, /DCTDecode, /JPXDecode ve /FlateDecode. Bayt okuması öteki standart filtreleri adlarıyla pvriForbiddenImageFilter olarak işaretler: /LZWDecode, /RunLengthDecode, /ASCII85Decode, /ASCIIHexDecode, /JBIG2Decode ve /Crypt

JBIG2, yakalama ekiplerinin ilk çarptığı reddediliştir. PDF/A'da izinlidir ve iki düzeyli sayfaları çok iyi sıkıştırır; dolayısıyla PDF/A için kurulmuş bir yakalama profili onu çoktan üretiyor olabilir. Kayıplı örüntü eşleştirme kipinin taranmış bir glifi benzer görünümlü biriyle değiştirdiği bilinir — taranan formlardaki rakamlar için belgelenmiş bir sorun — ve bir raster arşivi tam olarak kâğıtta olanın dosyada olması için vardır. O sayfaları CCITT Group 4 ile yeniden kodlamak hem iki düzeyli hem kayıpsız tutar

Sayfa okuması bir adım öteye gider ve filtre zincirini görüntünün bit derinliği ve renk alanıyla birlikte inceler; uyuşmazlıkları pvriImageCompressionParametersInvalid olarak raporlar:

  • Birden fazla filtreden oluşan bir zincir reddedilir; dolayısıyla ASCII85'e sarılmış bir Flate görüntüsü iki kez düşer, bir kez bayt tarafında bir kez burada
  • /CCITTFaxDecode 1 bitlik bir DeviceGray şeridinde durmalıdır
  • /DCTDecode, DeviceGray ya da DeviceRGB'de 8 bitlik ya da 24 bitlik bir şeritte durmalıdır
  • /FlateDecode 1 bitlik şeritlerde kabul edilir; 24 bitlik bir Flate sayfa bu denetimi düşürür
  • /JPXDecode piksel başına 1, 8 ya da 24 bittede kabul edilir

Filtreden bağımsız olarak, renk alanı DeviceGray de DeviceRGB de olmayan bir şerit pvriImageColorInvalid alır. ICC tabanlı ya da CMYK bir tarama bu yüzden sarılmadan önce dönüştürülmelidir ve bunun en ucuz yeri, hiçbir PDF yazılmadan önce tarayıcı profilidir

Delphi doğrulaması için PDF/R görsel filtre beyaz listesi: Şerit görsellerinde CCITTFaxDecode, DCTDecode, JPXDecode ve FlateDecode'e izin verilir; LZW, RunLength, ASCII85, ASCIIHex, JBIG2 ve Crypt filtreleri pvriForbiddenImageFilter yükseltir
Doğrulayıcı tam olarak dört şerit kodlamasını kabul eder ve diğer her standart filtreyi, JBIG2 ile ASCII filtreleri dahil, pvriForbiddenImageFilter olarak raporlar

Şerit çözünürlüğü nasıl hesaplanır

Görüntü kendi çözünürlüğünü taşıyorsa sayfa okuması onu kullanır. Taşımıyorsa etkin çözünürlük piksellerden ve sayfa boyutundan türetilir: piksel genişliği çapı 72 bölü punto cinsinden MediaBox genişliği, yükseklik için aynı. Bir tarayıcının 612 ile 792 punto arası bir US Letter sayfasına 2550 ile 3300 piksel şerit yazdığını düşünün. Bu her iki yönde tam olarak 300 dpi eder. Aynı sayfa 600 ile 776 pikselde yatayda kabaca 70.6 dpi eder; bu varsayılan en düşüğün 72'nin altındadır ve sayfa pvriImageResolutionInvalid alır

TPdfRValidationOptions içindeki varsayılan aralık 72 ile 2400 dpi'dir. O sınırlar yalnızca bariz biçimde bozuk sayfaları yakalar. Bir yakalama sözleşmesi genellikle belirli bir en düşük değer vaat eder — çoğunlukla 200 ya da 300 dpi — ve o değer MinimumResolutionDpi'ye aittir: 150 dpi'lik bir sayfa varsayılan aralığı geçer ve tam olarak sözleşmenin dışlamak için yazıldığı sayfadır. Sınırlardan birini 0 yapmak o tarafın denetimini kapatır

ValidatePdfRPageFacts herkese açıktır; dolayısıyla aynı kurallar, hiçbir PDF var olmadan, doğrudan tarayıcının her görüntü hakkında raporladıkları üzerinden çalışabilir:

uses
  FPdfPdfr;

// Sayfa bilgilerini tarayıcı çıktısından kurar; bu yardımcı sizindir, kitaplığın parçası değildir
function ScannerStripFacts(PixelW, PixelH: Integer;
  PageWidthPt, PageHeightPt: Single; const Filter: string;
  BitsPerPixel: LongWord; ColorSpace: Integer): TPdfRPageFacts;
begin
  Result := Default(TPdfRPageFacts);
  Result.PageNumber := 1;
  Result.MediaWidth := PageWidthPt;
  Result.MediaHeight := PageHeightPt;
  Result.ImageCount := 1;
  Result.ImageRight := PageWidthPt;   // şerit bütün sayfayı kapsar
  Result.ImageTop := PageHeightPt;
  Result.PixelWidth := PixelW;        // HorizontalDpi 0'da bırakıldı: türetilir
  Result.PixelHeight := PixelH;
  Result.BitsPerPixel := BitsPerPixel;
  Result.ColorSpace := ColorSpace;    // 1 = DeviceGray, 2 = DeviceRGB
  SetLength(Result.Filters, 1);
  Result.Filters[0] := Filter;
end;

var
  Opts: TPdfRValidationOptions;
  Issues: TPdfRValidationIssues;
begin
  Opts := TPdfRValidationOptions.Default;
  Opts.MinimumResolutionDpi := 300;

  // Letter üzerinde 300 dpi renkli JPEG: boş küme
  Issues := ValidatePdfRPageFacts(
    ScannerStripFacts(2550, 3300, 612, 792, 'DCTDecode', 24, 2), Opts);

  // Aynı sayfa 24 bitle Flate ile sıkıştırılmış:
  // [pvriImageCompressionParametersInvalid]
  Issues := ValidatePdfRPageFacts(
    ScannerStripFacts(2550, 3300, 612, 792, 'FlateDecode', 24, 2), Opts);
end;

Katalog, Info sözlüğü ve XMP

Zorunlu /Type ile /Pages'in yanında ISO 23504-1, katalogda yalnızca Version, ViewerPreferences, PageLayout, PageMode, AcroForm ve Metadata'ya izin verir. Doğrulayıcı katalogu, o kümenin dışındaki 21 standart anahtara karşı denetler ve herhangi biri göründüğünde pvriProhibitedCatalogEntry yükseltir; aralarında Outlines, Names, PageLabels, OpenAction, StructTreeRoot, MarkInfo, Lang, OCProperties ve OutputIntents var. Sonuncusu pratikte önemlidir: bir PDF/A dosyası her zaman bir çıktı amacı taşır, dolayısıyla tek bir dosya iki profili de karşılayamaz

Info sözlüğü Creator, Producer, CreationDate ve ModDate tutabilir; Title, Author, Subject, Keywords ve Trapped pvriProhibitedInfoEntry yükseltir. /Metadata akışı olmayan bir katalog pvriMissingXmpMetadata alır. Akış yerindeyken XMP'inde xmp:CreateDate, xmp:ModifyDate ve xmp:MetadataDate'den herhangi biri eksikse bulgu bu kez pvriMetadataInvalid olur. pvriInfoXmpMismatch, Producer'ı pdf:Producer ile ve Creator'ı xmp:CreatorTool ile karşılaştırır. Tarihler o karşılaştırmanın dışında bırakılır, çünkü iki kodlama meşru biçimde farklıdır; hex dizgeleri olarak saklanan Info değerleri atlanır. Tipik tetikleyici, XMP'ye dokunmadan Info Producer'ını yeniden yazan sonradan bir araçtır

Kalan kapsayıcı kuralları kısadır. Başlık %PDF-1.4 ile %PDF-1.7 arası ya da %PDF-2.0 olmalıdır (pvriBadHeader); herhangi bir /Type /ObjStm akışı pvriObjStmPresent yükseltir ve şifreli bir dosya %PDF-2.0 başlığı kullanmalıdır (pvriEncryptVersionMismatch). RequireEncryptedPdf20Profile açık bırakıldıysa şifreleme sözlüğü ayrıca /V 5 ya da 6 ile /R 5 ve üzeri bildirmelidir; aksi hâlde dosya pvriEncryptedPdf20ProfileInvalid alır

İşaretçileri yazmak

TPdfRSaveOptions Creator, Producer, CreationDate, ModDate, DocumentId ve InstanceId taşır ve Title, Author, Subject ya da Keywords için alanı yoktur; çünkü profil onları yasaklar. StripInfoOptionalEntries ile StripCatalogOptionalEntries varsayılanda True'dur ve dışlanan girdileri kaynaktan siler; ya birini False yapın, girdiler kalır — uyumluluk pahasına. TPdf.SaveAsPdfR, boş seçenek alanlarını yüklenmiş belgenin kendi meta verisi ve dosya kimliklerinden doldurur, güvenliği kaldırır, sonra işaretçiyi, kırpılmış sözlükleri, XMP paketini ve trailer /ID'yi yazar

uses
  System.Classes, System.SysUtils, PDFium, FPdfPdfr;

procedure WrapScanAsPdfR(const InFile, OutFile: string; Log: TStrings);
var
  Pdf: TPdf;
  Opts: TPdfRSaveOptions;
  Written: TFileStream;
  Res: TPdfRValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := InFile;
    Pdf.Active := True;
    Opts := TPdfRSaveOptions.Default;
    Opts.Creator := 'Capture Station 4';   // Producer, tarihler ve kimlikler kaynaktan gelir
    if not Pdf.SaveAsPdfR(OutFile, Opts) then
      raise Exception.Create('PDF/R save failed: ' + InFile);
  finally
    Pdf.Free;
  end;

  // Bellek içi belgeyi değil, yazılan baytları denetleyin
  Written := TFileStream.Create(OutFile, fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfRCompliance(Written);
    if pvriPageImageMismatch in Res.Issues then
      Log.Add('A source page still has no single strip image');
    if pvriFontForbidden in Res.Issues then
      Log.Add('The source carries fonts; rescan or drop the text layer');
  finally
    Written.Free;
  end;
end;

Sarma yalnızca meta veriyi ve tanımlamayı değiştirir. Vektör metin çizen ya da şerit görüntüsü olmayan bir kaynak sayfa, SaveAsPdfR'den aynı sayfa içeriğiyle çıkar ve aynı sayfa denetimlerinde düşer. Sayfa içeriği bilerek dokunulmadan bırakılır; çünkü bir taramayı sessizce yeniden render etmek, arşivin korumak üzere kurulduğu piksellerin ta kendisini değiştirirdi. İlk listedeki CheckDeliveredScan'i OutFile üzerinde çalıştırmak, sayfa okumaları dahil tam kararı verir

PDF/R bir yakalama hattında nereye oturur

PDF/R, taramanın kendisinin kayıt olduğu ve doğruluğun bütün sözleşme olduğu çıktılar için uygundur: kanıt niteliğinde görüntüleme, çek ve havale yakalama, geniş formatlı bir tarayıcıdan çizim arşivleri. Bir belge aranabilir metin, etiketleme, ekler ya da yer imleri gerektirdiği an PDF/A'ya aittir; çünkü o özelliklerin her biri yukarıdaki dışlanan listelerine düşer

Pek çok ekip ikisini de tutar. Bir PDF/R özgünü bir kez yazılır ve hiç değişmez; OCR katmanlı bir PDF/A türevi erişimi karşılar. Doğrulayıcılar birbirinden bağımsızdır; dolayısıyla tek bir toplu iş her dosyayı iddia ettiği profile karşı denetleyebilir. Arşiv tarafı PDF/A arşiv uyumluluğu ile PDF/A preflight doğrulamasında, baskı çıktısı ise baskıya hazır PDF/X belgelerini doğrulamada işlenir

Burada gösterilen PDF/R denetimleri, Delphi, C++Builder ve Lazarus için PDFium Component'in Small Business ve Enterprise sürümlerinin parçasıdır; PDF/A, PDF/X, PDF/E ve PDF/UA doğrulayıcılarının yanında dururlar. Sürümler ve desteklenen IDE sürümleri PDFium Component ürün sayfasında