PDFium Component, Delphi tarafından yeni PDF katmanlarını TPdf.SaveAsOptionalContentCreated ile oluşturur. Tek bir doğrulanmış kaydetmede metot, isteğe bağlı içerik gruplarını (OCG), isteğe bağlı içerik üyelik sözlüklerini (OCMD) ve /OC girdisi yeni bir gruba işaret eden Form XObject'lere sarılmış, çağıran tarafından verilen çizimleri ekler; katman anahtarlandığında PDFium ve diğer görüntüleyiciler o çizimi gösterir ya da gizler
İstek nadiren soyut olur. Bir tesis mühendisliği ekibi, aynı çizim sayfası üzerinde metrik ve emperyal ölçüleri, tam olarak bir kümesi görünür olacak biçimde ister. Bir matbaa, ekranda görünen ama prova öncesi kapatılabilen bir kesme çizgisi katmanı ister. İki dosyada da henüz katman yoktur; anahtarlanacak bir şey yoktur. Mevcut katmanları anahtarlamak farklı bir iştir ve SaveAsOptionalContentConfigured ile mevcut PDF isteğe bağlı içerik katmanlarını anahtarlamak makalesinde ele alınır; bu makale katmanları baştan yaratmaktan söz eder
PDFium ile bir sayfa nesnesine neden doğrudan /OC ayarlayamazsınız?
PDFium, mevcut bir sayfa nesnesine isteğe bağlı içerik ekleyen genel bir fonksiyona sahip değildir. Genel başlıklarda FPDFPageObj_SetOC ya da eşdeğeri bir şey yoktur; o yüzden alışılmış düzenle-sonra-FPDFPage_GenerateContent yolu geçitli içerik üretemez. Geçit dosya yapısına yazılmak zorundadır ve tek soru, PDFium'un render ederken hangi yapıya riayet edeceğidir
ISO 32000-1 §8.11.3, grafikleri isteğe bağlı yapmanın üç yolunu verir ve PDFium onlara farklı davranır:
/OC /Name BDC...EMCişaretli içeriğine sarılmış sayfa içerik akışı bölümleri; render sırasında sayfa nesnesi başına kontrol edilir- Kendi sözlüğünde bir
/OCgirdisi taşıyan Form XObject'ler ile görsel XObject'leri; XObject boyandığında kontrol edilir - Açıklama sözlüğünde bir
/OCgirdisi olan açıklamalar; PDFium render yolu bunlara hiç başvurmaz, geçitli bir açıklama katman durumundan bağımsız görünür kalır
İşaretli içeriğe sarmak, mevcut içerik akışlarını operatör operatör yeniden yazmak demektir ve açıklamaları renderer eler. Geriye Form XObject yolu kalır ve PDFium Component'in kullandığı da odur: çiziminiz, /OC'si gruba işaret eden yeni bir Form XObject'e girer; form, sayfa /Resources /XObject sözlüğüne kaydedilir ve tek bir /Name Do tutan yeni bir içerik akışı sayfa /Contents'ine eklenir. Mevcut sayfa içeriği asla ayrıştırılmaz ya da yeniden yazılmaz
Grupları ve geçitli formları Delphi'de tanımlamak
TPdf.SaveAsOptionalContentCreated üç dizi alır ve bir rapor döndürür. Bir TStream'e, öteki bir dosya adına yazan her iki aşırı yükleme aynı parametre listesini paylaşır: Groups: TPdfOptionalContentCreateGroups, Memberships: TPdfOptionalContentCreateMemberships, Forms: TPdfOptionalContentCreateForms, out Report: TPdfOptionalContentCreateReport ve isteğe bağlı AllowSignedDocument: Boolean = False. Metot PDFium Component v3.154.0 ile geldi
Her TPdfOptionalContentCreateGroup bir Name, isteğe bağlı bir Creator, pociView ya da pociDesign olan bir Intent, bir InitialStateVisible bayrağı, bir Locked bayrağı ve bir RadioTag taşır. Aynı RadioTag'i paylaşan gruplar tek bir /RBGroups dizisine düşer; görüntüleyicinin birini açmanın diğerlerini kapattığını bilmesi budur. Her TPdfOptionalContentCreateForm, geçit grubunu GroupIndex (sizin Groups dizinize sıfır tabanlı bir indeks), 1 tabanlı bir PageNumber, ham içerik akışı baytları, bir sınırlayıcı kutu, isteğe bağlı bir matris ve isteğe bağlı bir ResourceName ile adlandırır
var
Pdf: TPdf;
Groups: TPdfOptionalContentCreateGroups;
Forms: TPdfOptionalContentCreateForms;
Report: TPdfOptionalContentCreateReport;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'sheet-a1.pdf';
Pdf.Active := True;
SetLength(Groups, 2); // SetLength kayıtları sıfırla doldurur
Groups[0].Name := 'Dimensions (mm)';
Groups[0].Intent := pociView;
Groups[0].InitialStateVisible := True;
Groups[0].RadioTag := 'Units';
Groups[1].Name := 'Dimensions (in)';
Groups[1].Intent := pociView;
Groups[1].InitialStateVisible := False;
Groups[1].RadioTag := 'Units';
SetLength(Forms, 2);
Forms[0].GroupIndex := 0;
Forms[0].PageNumber := 1;
Forms[0].Content := TEncoding.ASCII.GetBytes(
'0 0 1 RG 1.5 w 72 120 m 540 120 l S');
Forms[0].BBoxRight := 612;
Forms[0].BBoxTop := 792;
Forms[0].ResourceName := 'DimMetric';
Forms[1] := Forms[0];
Forms[1].GroupIndex := 1;
Forms[1].Content := TEncoding.ASCII.GetBytes(
'1 0 0 RG 1.5 w 72 140 m 540 140 l S');
Forms[1].ResourceName := 'DimImperial';
if not Pdf.SaveAsOptionalContentCreated('sheet-a1-layers.pdf',
Groups, nil, Forms, Report) then
raise Exception.Create(Report.ErrorMessage);
finally
Pdf.Free;
end;
end;
Form içeriğinin saf vektör çizimi olduğuna dikkat edin. Bileşen her forma yalnızca boş bir /Resources sözlüğü yazar; içerik akışı bu yüzden kendi kendine yetmelidir: aygıt renkleri, yollar, kırpma ve dönüşümler çalışır ama adlandırılmış bir kaynak gerektiren her şey (Tf için bir font, Do için bir görsel, gs için bir ExtGState) çözümleyecek hiçbir şey bulamaz. ResourceName'i boş bırakırsanız formlar FmOc1, FmOc2 diye adlandırılır; özel bir ad yalnızca harf, rakam, alt çizgi, eksi ve nokta içerebilir
Bir OCMD, düz bir grubun üstüne ne katar?
Bir isteğe bağlı içerik üyelik sözlüğü, görünürlüğü aynı anda birkaç gruba bağlar. ISO 32000-1 §8.11.2.2 iki biçim tanımlar: /AnyOn ya da /AllOn gibi bir /P politikasıyla bir /OCs listesi ve spesifikasyonda /And, /Or ile /Not'tan kurulan bir /VE görünürlük ifadesi. PDFium Component, MemberGroupIndices, bir Policy ile bir Expression taşıyan bir TPdfOptionalContentCreateMembership alır; Expression.GroupIndices boş olmadığında ifade kazanır ve /VE, /OCs'in yanına yazılır
procedure AddUnitMemberships(var Memberships: TPdfOptionalContentCreateMemberships);
begin
SetLength(Memberships, 2);
// Birim katmanlarından biri açıkken görünür: /OCs [...] /P /AnyOn
SetLength(Memberships[0].MemberGroupIndices, 2);
Memberships[0].MemberGroupIndices[0] := 0;
Memberships[0].MemberGroupIndices[1] := 1;
Memberships[0].Policy := ocmpAnyOn;
// Yalnızca emperyal katman kapalıyken görünür: /VE [ /Not ref ]
SetLength(Memberships[1].MemberGroupIndices, 1);
Memberships[1].MemberGroupIndices[0] := 1;
Memberships[1].Expression.Kind := poceNot;
SetLength(Memberships[1].Expression.GroupIndices, 1);
Memberships[1].Expression.GroupIndices[0] := 1;
end;
Desteklenen alt küme bilinçli olarak dardır. Politikalar ocmpAllOn ile ocmpAnyOn'a sınırlıdır; ocmpAnyOff ile ocmpAllOff reddedilir. İfadeler bir düzey derindir; oluşturulan gruplar üzerinde poceAll, poceAny ya da poceNot ve /Not tam olarak bir grup indeksi ister. poceAll /And olarak, poceAny /Or olarak ve poceNot /Not olarak serileşir; §8.11.2.2'nin tanımladığı üç operatör budur. 3.181.2 öncesi sürümler /All ile /Any yazıyordu; PDFium bunları gizli olarak değerlendirir, eski bir derlemeyle OCMD oluşturduysanız ifadeye güvenmek yerine onları yeniden üretin. Her üyelik ayrıca, bir ifade varken bile, MemberGroupIndices içinde en az bir girdi ister; çünkü o liste /OCs dizisi olur. Yeni sözlüklerin nesne numaraları Report.CreatedMembershipObjectNumbers'ta geri döner
OCMD'lerin üzerine inşa etmeden önce bilinmesi gereken iki davranış var. Birincisi, aynı çağrıdaki geçitli formlar yalnızca gruplara referans verir: TPdfOptionalContentCreateForm.GroupIndex Memberships'i değil Groups'u indeksler; bir form bu API üzerinden yeni oluşturulmuş bir OCMD ile geçite alınamaz. İkincisi, PDFium Component her yeni OCMD'yi katalog /OCProperties /OCGs dizisine ekler; /ON, /OFF ile /Order yalnızca gruba özgü kalır. ISO 32000-1 §8.11.4.2, /OCGs'yi grupların listesi olarak anlatır ama o kayıt olmadan TPdf.InspectOptionalContent üyeliği hiç saymaz. Yan etki, çıktının envanterinin her OCMD'yi Inventory.Groups içinde adsız bir ek girdi olarak listelemesidir; grupları konuma göre değil Name'e göre eşleştirin
Bileşen mevcut bir /OCProperties'i nasıl yamalar?
TPdf.SaveAsOptionalContentCreated, /OCProperties'i ya sıfırdan kurar ya da katalogdaki mevcudu genişletir ve genişletmeyi ekleyerek yapar; asla yeniden kurarak değil. Katmansız bir belge, ISO 32000-1 §8.11.4.3'ün dizdiği gibi /OCGs ile, /ON, /OFF ve /Order taşıyan bir varsayılan yapılandırma /D, herhangi bir grup isterse /Locked ile /RBGroups içeren yeni bir sözlük alır
Hâlihazırda katmanı olan bir belge için her hedef dizi, yeni referansları kapanış köşeli parantezinin hemen önüne eklenerek yamalanır. Bu titiz görünür ve bir nedeni vardır. Bir /Order dizisi, bir görüntüleyicinin gösterdiği katman ağacını şekillendiren iç içe diziler ile metin etiketleri tutabilir; diziyi düzleştirip yeniden kuran önceki bir yaklaşım o etiketleri kaybetti. Sonda ekleme, mevcut ağacı sağlam tutar ve yeni grupları arkasına yalnızca listeler. Kendi dolaylı nesnelerinde yaşayan diziler çözülür ve yerinde yamalanır
v3.175.0'dan bu yana varsayılan yapılandırmanın kendisi de dolaylı olabilir. Bazı üreticiler /OCProperties << /OCGs [...] /D 12 0 R >> yazar; PDFium Component artık o referansı izler ve bozuk girdi hatasıyla başarısız olmak yerine hedef sözlüğü yamar. Dolaylı bir /XObject alt sözlüğü dahil, doğrudan ile dolaylı sayfa /Resources sözlükleri aynı biçimde işlenir
Yeni katmanların gerçekten anahtarlandığını kanıtlamak
/OCProperties'te var olan ama hiçbir şeyi geçite almayan bir katman üretmesi kolay, fark etmesi zor bir şeydir. Geliştirme sırasında form yazıcısının ilk derlemesi, kendi N 0 obj başlığı ile endobj kuyruğunu hâlihazırda taşıyan bir akış nesnesini ikinci bir başlığın içine sarmıştı. PDFium ikiye katlanmış nesneyi ayrıştıramadı ve hata bildirmek yerine Do operatörünü sessizce attı. Dosya açılıyordu, katman paneli doğru görünüyordu ve geçitli çizim hiç görünmüyordu. Onu ortaya çıkaran, katmanı anahtarlayıp render edilmiş pikselleri karşılaştıran bir sondaydı: fark yoktu, yani bir şeyler yanlıştı
Metot bu yüzden teslim etmeden önce doğrular. Çıktı, çapraz referans tablosunu kontrol eden katı bir yüklemeyle yeniden açılır ve istenen her grup, adını, ilk durumunu ve kilitli kaydını teyit etmek için InspectOptionalContent üzerinden geri okunur. Ancak ondan sonra çıktı akışınıza kopyalanır; dosya aşırı yüklemesi geçici bir dosya yazar ve hedefi atomik olarak değiştirir. Aynı anahtar kontrolünü kendiniz de çalıştırabilirsiniz: çıktıyı yeniden açın, grubu adıyla bulun ve ters durumla bir kopya kaydedin
function SaveImperialPreset(const SourceName, TargetName: string): Boolean;
var
Pdf: TPdf;
Inventory: TPdfOptionalContentInventory;
Changes: TPdfOptionalContentStateChanges;
Options: TPdfOptionalContentConfigureOptions;
CfgReport: TPdfOptionalContentConfigureReport;
I: Integer;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := SourceName;
Pdf.Active := True;
if not Pdf.InspectOptionalContent(Inventory) then
raise Exception.Create(Inventory.ErrorMessage);
for I := 0 to High(Inventory.Groups) do
if (Inventory.Groups[I].Name = 'Dimensions (mm)') or
(Inventory.Groups[I].Name = 'Dimensions (in)') then
begin
SetLength(Changes, Length(Changes) + 1);
Changes[High(Changes)].GroupObjectNumber := Inventory.Groups[I].ObjectNumber;
Changes[High(Changes)].Visible := Inventory.Groups[I].Name = 'Dimensions (in)';
end;
Options := TPdfOptionalContentConfigureOptions.Default;
Result := Pdf.SaveAsOptionalContentConfigured(TargetName, Changes,
Options, CfgReport);
finally
Pdf.Free;
end;
end;
İki dosyanın da birinci sayfasını render edin; mavi ile kırmızı çizgiler yer değiştirmeli. Değişmiyorsa önce envanterde grubun EffectiveVisible değerine bakın; yapılandırma dizileri ile kullanım kuralları uygulandıktan sonra PDFium'un gerçekte render edeceği durumu yansıtır
Planında göze almanız gereken sınırlar ve tuzaklar
TPdf.SaveAsOptionalContentCreated sınırlı bir profildir ve sürprizlerin çoğu onu genel bir katman editörü saymaktan gelir. Aşağıdaki kısıtlar baştan uygulanır; ihlal eden bir çağrı Report.ErrorMessage ayarlı olarak başarısız olur ve hiçbir çıktı yazmaz:
- En az bir grup gerekir; gruplar, üyelikler ve formların her biri 4096 girdiyle sınırlıdır; grup adları eşsiz ve 1 ile 255 karakter uzunluğunda olmalıdır ve her form en fazla 16 MiB içerik baytı kabul eder
- Şifreli belgeler reddedilir ve imzalı bir belge
AllowSignedDocument = Trueister; çıktı eksiksiz bir yeniden yazımdır, mevcut imzalar artık doğrulanmaz - Her
Formsgirdisi kendi Form XObject'i olur; aynı katmanı 200 sayfaya koymak içeriği bir kez değil 200 kez saklar - Yeni
Doakışı mevcut sayfa içeriğinden sonra eklenir; önceki akışların bıraktığı hangi grafik durumdaysa öyle çalışır. Düzgün kurulmuş sayfalar durumlarını geri yükler ama dengesizq/Q'lu bir sayfa katmanınızı kaydırabilir - Mevcut sayfa içeriği asla bir katmana taşınmaz;
Forms'ta verdiğiniz çizim geçite alınır
Tekrar noktası, büyük belgeler için tartılacak olandır. Katman ağır gidiyor ve her sayfada yineliyorsa Form XObject'lere dayalı yeniden kullanılabilir sayfa damgalarındaki paylaşımlı nesne yaklaşımı dosyada tek kopya tutar; bedeli, katman geçidini kendinizin yönetmesidir
SSS
PDFium Component, mevcut sayfa içeriğini yeni bir katmana koyabilir mi?
Hayır. TPdf.SaveAsOptionalContentCreated yalnızca Forms'ta verdiğiniz içeriği geçite alır; mevcut operatörleri işaretli içeriğe sarmaz ya da bir XObject'e taşımaz. Mevcut grafiği katmanlamak için onu kendi kendine yeten form içeriği olarak yeniden üretin ve özgün çizimi ayrı bir adımda kaldırın
Katman kapalıyken geçitli açıklamalarım neden hâlâ görünür?
PDFium, render yolunda açıklama sözlüklerinin /OC girdisini kontrol etmez; açıklama düzeyi geçit PDFium tabanlı görüntüleyicilerde etkisizdir. PDFium Component'in katman içeriğini Form XObject'ler üzerinden eklemesinin nedeni budur; PDFium onları geçite alır
Yeni katmanları sonra anahtarlarken hangi nesne numaralarını kullanmalıyım?
Report.CreatedGroupObjectNumbers, az önce yazılan dosyadaki numaraları tutar. O dosyayı yeniden açıp yapılandırdığınızda ObjectNumber'ı TPdf.InspectOptionalContent'ten alın ve Name'e göre eşleştirin; sonraki bir kaydetme nesneleri yeniden numaralandırsa bile bu doğru kalır
PDFium Component, SaveAsOptionalContentCreated, SaveAsOptionalContentConfigured ile InspectOptionalContent'i, PDFium renderer'ı üzerine kurulu tek bir isteğe bağlı içerik araç seti olarak Delphi ve C++Builder için sunar. Ayrıntılar ve indirmeler PDFium Component ürün sayfasında