Teknik Makale

PDF Optional Content Katmanlarını PDFium ile Açıp Kapatmak

PDFium Component, PDF optional content katmanlarını (OCG'leri) Delphi'de iki TPdf yöntemiyle kontrol eder: InspectOptionalContent her katmanı, PDFium'un gerçekte render edeceği görünürlükle birlikte listeler; SaveAsOptionalContentConfigured ise seçtiğiniz katmanların açılıp kapatıldığı doğrulanmış bir kopya yazar. İkinci yöntem ayrıca, aksi hâlde düzenlemenizi sessizce geri alan Usage ve /AS kurallarını etkisizleştirir. İkisi de TPdf içinde zaten açık olan belge üzerinde çalışır; görüntüleyicinin gösterdiğiyle eşit tutulacak ikinci bir parser yoktur

Talep genellikle bir CAD ya da GIS atölyesinden gelir: çizim takımı, ölçüler, notlar ve başlık bloğu ayrı katmanlarda dağıtılır ve müşteri, tedarikçiye gitmeden önce ölçüleri gizlenmiş bir kopya ister. PDFium optional content'i doğru render eder ama genel ABI'sinde OCG'leri listelemeye, bir konfigürasyon seçmeye ya da katman durumunu çevirmeye yarayan bir fonksiyon yoktur. O yüzden nesne düzeyine iner, /OCProperties'i düzenler, kaydeder, yeniden yüklersiniz ve katman hâlâ oradadır. Neden, PDFium'un görünürlük mantığıdır ve hiçbir bayta dokunmadan önce anlamaya değer

PDFium'un render ettiğini değiştirmek için /ON ve /OFF'u düzenlemek neden yetmez?

Konfigürasyon sözlüğünün /ON ve /OFF dizilerini düzenlemek yetmez, çünkü PDFium, OCG'nin kendi /Usage sözlüğünün içindeki açık bir durumun o dizileri geçmesine izin verir ve bir /AS otomatik-durum kuralı ardından ikisini de ezebilir. ISO 32000-1 §8.11.4, konfigürasyonları ve usage sözlüklerini ayrı mekanizmalar olarak tanımlar; PDFium'un renderer'ı onları tek bir karara katlar ve InspectOptionalContent bu kararı şu sırayla yeniden üretir:

  • Konfigürasyonun /BaseState'inden başlayın; /ON ile /Unchanged görünür sayılır, yalnızca /OFF gizler
  • Konfigürasyonun /ON dizisini, sonra /OFF dizisini uygulayın; ikisinde de listelenen bir grup gizlenmiş kalır
  • İstenen kullanım için grubun açık Usage durumunu uygulayın — örneğin /Usage << /View << /ViewState /OFF >> >> — bu, yukarıdakilerin hepsini ezer
  • /Intent'inde ne /View ne de /All bulunan bir grubu görünür sayın; view-intent görünürlüğüne katılmaz
  • Son olarak seçili konfigürasyonun /AS dizisini işletin; eşleşen event için girdileri, listeledikleri grupların durumunu belirler
PDFium Component'ın Delphi'de her PDF optional content grubu için yeniden oynattığı beş adımlı görünürlük kararı: BaseState başlangıcı belirler, konfigürasyonun ON ve OFF dizileri sırayla uygulanır, açık bir Usage ViewState ya da PrintState girdisi ikisini de ezer, Intent katılımı olmama görünür sayılır ve AS dizisi en son çalışır
ON ve OFF dizilerini düzenlemek yetmez, çünkü PDFium BaseState'i, iki diziyi, grup Usage durumunu ve son olarak AS otomatik-durum kurallarını tek bir hükme katlar; InspectOptionalContent bu hükmü adım adım yeniden üretir

Üçüncü adım, insanları yakan adımdır. Bir layout aracının kaydettiği dosya çoğu zaman her OCG üzerinde /ViewState /ON taşır ve PDFium o zaman özenle düzenlediğiniz /OFF dizisini yok sayar: kaydetme başarılı olur, dosya temizce yeniden açılır ve katman yine çizilir. Print ve Export için OcExplicitUsageState önce PrintState'i ya da ExportState'i okur ve ilgili girdi yoksa ViewState'e düşer; tek başına bir ViewState /ON katmanı yazdırmak için de sabitler. Bir OCMD'ye (§8.11.2.2) başvuran marked content sonra bu grup-başı sonuçlara karşı, /P politikası üzerinden ya da varsa /VE görünürlük ifadesiyle çözümlenir

PDFium'un gerçekte göstereceği katmanları nasıl listelersiniz?

TPdf.InspectOptionalContent, Groups dizisinde her OCG'nin nesne numarasını, adını, intent'lerini, üç Usage durumunu, dilini, zoom aralığını, Locked bayrağını, radio-grup indeksini ve hesaplanmış EffectiveVisible'ı taşıyan bir TPdfOptionalContentInventory döndürür. Yöntem önce PDFium'a bellekteki güncel belgeyi kaydettirir, object stream'leri açar ve sonucu tarar; oturumda daha önce yapılan düzenlemeler yansır. Konfigürasyon indeksi 0 her zaman varsayılan /D sözlüğüdür ve /Configs girdileri 1'den itibaren gelir; -1 varsayılan argümanı indeks 0'ı seçer. /OCProperties'i olmayan bir belge, yöntemin exception fırlatmak yerine ErrorMessage'de gerekçesiyle False dönmesini sağlar

procedure TFormMain.ListLayers;
var
  Inv: TPdfOptionalContentInventory;
  G: TPdfOptionalContentGroup;
begin
  // Usage varsayılanı ocuView; -1 konfigürasyon 0'ı, yani /D sözlüğünü seçer
  if not Pdf.InspectOptionalContent(Inv) then
  begin
    Memo1.Lines.Add('No usable layers: ' + Inv.ErrorMessage);
    Exit;
  end;
  Memo1.Lines.Add(Format('Configuration %d: %s',
    [Inv.SelectedConfigurationIndex,
     string(Inv.Configurations[Inv.SelectedConfigurationIndex].Name)]));
  for G in Inv.Groups do
    Memo1.Lines.Add(Format('obj %d  %s  visible=%s  locked=%s  radio=%d',
      [G.ObjectNumber, string(G.Name),
       BoolToStr(G.EffectiveVisible, True),
       BoolToStr(G.Locked, True), G.RadioGroupIndex]));
end;

Memberships dizisi her OCMD'yi Policy'siyle (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), ham VisibilityExpression metniyle ve kendi EffectiveVisible'ıyla raporlar. Birkaç uç kural bilinçlidir. /P varsayılanı /AnyOn'dur ve grupsuz bir OCMD görünür sayılır. Bilinen bir OCG olmayan bir nesne numarasına referans, tüm ifadeyi düşürmek yerine görünür sayılır. /VE değerlendirmesi 32 iç içe geçme derinliğinde durur ve daha derin her şeyi gizli sayar; bu, düşmanca ya da kendine referans veren bir ifadenin incelemeyi stack overflow'a çevirmesini önler

SaveAsOptionalContentConfigured ile yeni bir katman durumu yazmak

TPdf.SaveAsOptionalContentConfigured, TPdfOptionalContentStateChange kayıtlarından oluşan bir dizi alır (grup nesne numarası artı Visible) ve seçili konfigürasyonun tam olarak o durumu ürettiği bir belge yazar. Seçili konfigürasyon, her grubu kapsayan eksiksiz /ON ve /OFF dizileriyle birlikte /BaseState /ON alır; hâlihazırda Usage sözlüğü olan her OCG ise yeni durumuna uyan bir açık ViewState (ya da Options.Usage'e uyarak PrintState / ExportState) alır. TPdfOptionalContentConfigureOptions.Default ile seçili konfigürasyonun /AS anahtarı kaldırılır; böylece bir open, print ya da export event'i katmanları geri çeviremez

procedure TFormMain.SaveWithoutDimensions(DimensionsObj, NotesObj: Integer);
var
  Changes: TPdfOptionalContentStateChanges;
  Options: TPdfOptionalContentConfigureOptions;
  Report: TPdfOptionalContentConfigureReport;
begin
  SetLength(Changes, 2);
  Changes[0].GroupObjectNumber := DimensionsObj;
  Changes[0].Visible := False;
  Changes[1].GroupObjectNumber := NotesObj;
  Changes[1].Visible := True;

  // Konfigürasyon 0, ocuView, DisableAutomaticState ve EnforceRadioGroups True
  Options := TPdfOptionalContentConfigureOptions.Default;

  if not Pdf.SaveAsOptionalContentConfigured('C:\Out\Drawing-NoDims.pdf',
    Changes, Options, Report) then
    raise Exception.Create('Layer update rejected: ' + Report.ErrorMessage);

  Log(Format('%d of %d groups changed, %d Usage states rewritten, /AS removed: %s',
    [Report.ChangedGroupCount, Report.GroupCount,
     Report.UpdatedUsageStateCount,
     BoolToStr(Report.RemovedAutomaticState, True)]));
end;

Yazma yolu, PDFium'un kendi kaydetme çıktısını bayt-bayt bir önek olarak tutar ve yalnızca yeniden yazılan konfigürasyon sahibini ile Usage sözlüğü taşıyan OCG nesnelerini, ardından yeni bir xref bölümü ve trailer ekler. Tek bir bayt hedefinize ulaşmadan önce sonuç, sıkı yükleme politikası altında ayrı bir TPdf içinde yeniden açılır ve çapraz-referans tablosu doğrulanmazsa yöntem başarısız olur. Dosya overload'u bir adım daha ileri gider: hedefin yanındaki geçici bir dosyaya yazar ve hedefi yalnızca doğrulama başarılı olduktan sonra değiştirir; reddedilen bir güncelleme yarım yazılmış bir çizim geride bırakmaz. PDFium Component'taki PDF name tree ve number tree editörünün kullandığı doğrulanmış artımlı revizyon yaklaşımı da budur

PDFium Component'taki SaveAsOptionalContentConfigured katmanları açılıp kapatılmış bir Delphi PDF'ini nasıl yazar: durum değişiklikleri ve seçenekler girer, seçili konfigürasyon eksiksiz ON ve OFF dizileriyle Usage durumlarıyla yeniden yazılır, doğrulanmış artımlı revizyon eklenir ve sıkı bir yeniden açma, hiçbir şey yazılmadan önce doğrulamak zorundadır
Konfigüre kaydetme, PDFium'un kendi yeniden kaydını bayt önek olarak tutar, yeniden yazılan konfigürasyon sahibini artı yeni bir xref bölümünü ekler ve hedefe dokunulmadan önce sonucu ayrı bir TPdf içinde yeniden açar

Konfigüre kaydetme neyi yapmayı reddediyor?

Konfigüre kaydetme, belgenin kendisinin yasakladığı ya da güvenle temsil edemeyeceği her değişikliği reddeder ve her ret, hedefe dokunulmadan önce gerçekleşir. /OCGs içinde olmayan bir nesne numarası düpedüz başarısız olur. Konfigürasyonun /Locked dizisinde listelenen bir grubu değiştirmek başarısız olur; mevcut değerini yeniden belirtmeye ise izin verilir. EnforceRadioGroups açıkken, birden fazla görünür üyeyle kalacak her /RBGroups kümesi, diğerlerini sessizce kapatmak yerine reddedilir. Şifreli belgeler reddedilir, çünkü düz metin artımlı nesneler etkin security handler'ı taşıyamaz. İmzalı belgeler AllowSignedDocument = True geçmediğiniz sürece EPdfError fırlatır; bir sayfanın gösterdiğini değiştirmek, imza kapsamını ya da bir sertifikalandırma politikasını bozabilir

SaveAsOptionalContentConfigured'ın PDFium Component'ta konfigüre bir Delphi PDF'ini yazmadan önce uyguladığı ret kapıları: OCG'ler dışındaki bir nesne numarası başarısız olur, kilitli gruplar başarısız olur, birden fazla görünür üyeli RBGroups kümeleri reddedilir, şifreli belgeler düz metin artımlı nesneleri taşıyamaz ve imzalı dosyalar AllowSignedDocument ister
Her ret, hedefe dokunulmadan önce gerçekleşir ve başarısızlık gerekçesi, yarım yazılmış bir çizim geride bırakmak yerine Report.ErrorMessage'e düşer
function TFormMain.SavePrintPreset(Target: TStream;
  const Changes: TPdfOptionalContentStateChanges): Boolean;
var
  Options: TPdfOptionalContentConfigureOptions;
  Report: TPdfOptionalContentConfigureReport;
begin
  Options := TPdfOptionalContentConfigureOptions.Default;
  Options.Usage := ocuPrint;          // /Print << /PrintState ... >> yazar
  Options.ConfigurationIndex := 1;    // /Configs'in ilk girdisi, /D değil
  try
    Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
      Report);                        // AllowSignedDocument False kalır
    if not Result then
      ShowMessage(Report.ErrorMessage);
  except
    on E: EPdfError do
    begin
      ShowMessage(E.Message);         // imzalı dosya: Target'a hiçbir şey yazılmadı
      Result := False;
    end;
  end;
end;

Bunu bir toplu işe bağlamadan önce takasları bilin. Eklenen revizyon, özgün dosya baytlarınızın değil PDFium'un tam yeniden kaydının üstüne oturur; imzalı girdinin açık onay istemesinin nedeni tam olarak budur. Yeniden yazım ayrıca seçili konfigürasyonu /BaseState /ON'a normalize eder; yazarın /Unchanged ya da /OFF taban çizgisi, aynı sonucu veren görünürlükle açık dizilerle değiştirilir. /AS'yi düşürmek, yalnızca kâğıtta görünen filigran katmanı gibi yalnız-print hilelerini kaldırır; o kuralları tutmak için DisableAutomaticState'i False yapın, o event için istediğiniz durumu ezebileceklerini kabullenerek. Artı tarafta, PDF/A-2 (ISO 19005-2 clause 6.9) ile PDF/UA (ISO 14289-1 clause 7.10) konfigürasyon sözlüklerinde /AS'yi yasaklar; varsayılan çıktı, PDFium Component ile PDF/A preflight doğrulamasının aksi hâlde raporlayacağı bir sorunu daha baştan kaldırır

Katman kontrolünün bir Delphi PDF görüntüleyicideki yeri

Bir görüntüleyicide katman kontrolü, envanterin sürdüğü ve kaydedilen sonucun yeniden yüklenmesiyle tamamlanan bir checklist'tir. Checklist'i Groups'tan doldurun, Locked olan girdileri devre dışı bırakın, aynı RadioGroupIndex'i paylaşan üyeleri karşılıklı dışlayıcı sayın ve uygula'da bir TMemoryStream'e yazıp o stream'i TPdf'e geri yükleyin; görünüm yeni durumu çizsin. TPdf ile TPdfView arasındaki bağlantı Delphi'de PDFium VCL ile özellik dolu bir PDF görüntüleyici kurmak yazısında işleniyor. Lisanslama, deneme indirmeleri ve özellik setinin kalanı Delphi için PDFium Component ürün sayfasında