Teknik Makale

Delphi'de Duplex Taramaları Harmanlama: PDF Serpiştirme

PDFlibPas Delphi PDF kütüphanesindeki CollateDocumentsEx, birkaç açık belgeyi tek bir serpiştirilmiş belgede birleştirir. Her turda her kaynaktan GroupSize kadar sayfa ekler, kaynak başına bir sayfa aralığı listesi kabul eder ve 3-1 gibi azalan bir aralığı o kaynağın ters çevrilmesi olarak ele alır. Tek bir çağrı, önyüz yığınını ve ters çevrilmiş arka yüz yığınını okuma sırasına dönüştürür

Bu API'nin arkasındaki senaryo sıradan ve son derece yaygındır. Tek taraflı yola sahip bir kağıt beslemeli tarayıcı tüm yığını yüzü aşağı bakacak şekilde tarar, ardından operatör yığını çevirip yeniden tarar. Sonuçta iki PDF ortaya çıkar: sıralı önyüzler, ters sıralı arka yüzler. Kullanıcının istediği çıktı tek bir dosyadır — sayfa 1 ön, sayfa 1 arka, sayfa 2 ön, ve böyle devam eder. Bu makale sıralama problemi ve altında yatan kaynak çoğaltma tuzağı hakkındadır. Derdiniz ham birleştirme verimi ise byte seviyesinde ref kaydırmayla hızlı PDF birleştirme yazısına bakın; girdiler belleğe sığmayacak kadar büyükse doğrudan erişimle gigabayt boyutlu PDF birleştirme ve bölme yazısına bakın

Tarayıcı iki yığın üretir, biri ters

Harmanlama birleştirme değildir. Bir birleştirme sayfa aralıklarını art arda ekler; bir harmanlama ise onları serpiştirir ve serpiştirme deseni girdiyi üreten fiziksel cihazın bir özelliğidir. Deseni yanlış alırsanız dosya biraz yanlış değil, okunamaz olur: her ikinci sayfa farklı bir sayfaya aittir. Neredeyse her gerçek durumu üç değişken tanımlar: rotasyonda kaç kaynak var, tur başına her kaynaktan kaç sayfa geliyor ve herhangi bir kaynağın tersten okunması gerekiyor mu. CollateDocuments ilk ikisini düz bir belge tutamacı dizisi ve bir GroupSize tamsayısıyla kapsar. CollateDocumentsEx ise üçüncüsünü, kaynak başına bir segment olmak üzere noktalı virgülle ayrılmış bir sayfa aralığı listesi kabul ederek ekler; boş bir segment o kaynağın tüm sayfaları anlamına gelir, azalan bir aralık ise onu ters çevirir. Her iki fonksiyon da o an seçili olan belgenin sonuna ekleme yapar ve başarıda 1, herhangi bir reddedilmede 0 döndürür

Saf harmanlama neden dosya boyutunu katlıyor?

Çünkü kaynak nesne numaralarını hedef nesne numaralarına eşleyen içe aktarma haritası her kopyalama çağrısında yeniden inşa edilir ve birden fazla parçadan erişilebilen her şey parça başına bir kez içe aktarılır. PDFlibPas içinde TPDFDocument.CopyPagesFromDoc, her çağrının başında NewIndObjList değerini sıfırlar. Bu liste, kopyalayıcının daha önce neyi aktardığına dair sahip olduğu tek hafızadır. On sayfalık bir aralıkla bir kez çağırın, on sayfanın tamamı tarafından paylaşılan bir font bir kez gömülür. Aynı fonksiyonu her seferinde bir sayfayla on kez çağırın, aynı font on kez gömülür. Bu, tarama belgeleri için metin belgelerinden çok daha fazla önem taşır, çünkü taranmış bir sayfa tek büyük bir görüntü XObject'idir ve gerçek ağırlığı olan nesneler paylaşılanlardır: gömülü bir ICC profili, paylaşılan bir /DecodeParms zinciri, her sayfaya uygulanan bir damga veya filigran form XObject'i, OCR metin katmanı fontu. Döngüsel bir harmanlama yazmanın bariz yolu turlar üzerinde bir döngüdür ve bu döngü tam olarak patolojik durumdur

// Do not do this. Each CopyPageRanges call rebuilds the import map,
// so anything the two sources share internally is imported once per
// round instead of once per source.
var
  RoundIndex: Integer;
begin
  for RoundIndex := 1 to 12 do
  begin
    PDF.CopyPageRanges(Fronts, IntToStr(RoundIndex));
    PDF.CopyPageRanges(Backs, IntToStr(13 - RoundIndex));
  end;
end;

On iki tur, iki kaynak, yirmi dört içe aktarma haritası. Hiçbir şey sizi uyarmaz. Sayfa sırası doğrudur, her sayfa doğru render edilir ve tek belirti dosyanın girdilerinin toplamından kat kat büyük olmasıdır. 300 sayfalık bir toplu işte bu çarpan bir yuvarlama hatası değildir, saklama bütçesine sığan bir arşiv ile sığmayan bir arşiv arasındaki farktır

Bir kez içe aktar, sonra sayfa ağacını yeniden sırala

Çözüm, saf döngünün birbirine kaynaştırdığı iki kaygıyı ayırmaktır. Kopyalama, hedefte hangi nesnelerin bulunacağına karar verir; sıralama ise sayfaların sayfa ağacında nerede oturacağına karar verir. CollateDocumentsEx her kaynağı tam olarak bir kez, o kaynağın tüm aralığıyla tek bir CopyPagesFromDoc çağrısında kopyalar, böylece her kaynak bir içe aktarma haritası alır ve paylaşılan kaynaklar bir kez yazılır. Serpiştirme ancak her kaynak yerleştikten sonra gerçekleşir ve tamamen TPDFPageTree.MovePage üzerinden işler

Sayfa taşımaları burada önemli olan anlamda bedavadır. ISO 32000-1 §7.7.3, sayfa ağacını, /Kids dizileri dolaylı referanslar tutan ve her düğümde /Count yaprak toplamını taşıyan, dengeli bir düğüm sözlükleri yapısı olarak tanımlar. Bir sayfayı yeniden konumlandırmak, bir dolaylı referansı bir /Kids dizisinden kaldırıp başka birine eklemek, her iki /Count değerini ayarlamak ve sayfa /Parent'ını yeniden yönlendirmek anlamına gelir. Hiçbir içerik akışına dokunulmaz, hiçbir kaynak çoğaltılmaz, hiçbir nesne oluşturulmaz. Sayfa nesnesi kendi nesne numarasını korur, bu da nesne numaralarının nesne numaralarını koruyan sayfa değiştirme yazısındaki gibi neden sabit kaldığının bir sebebidir. Saf bir sayfa taşımasının yanlış yaptığı ve MovePage'in doğru yaptığı bir ayrıntı daha var. ISO 32000-1 §7.7.3.4, /Resources, /MediaBox, /CropBox ve /Rotate özelliklerinin sayfada belirtilmek yerine bir üst düğümden miras alınmasına izin verir. Kaynaklarını A düğümünden miras alan ve ardından B düğümü altına taşınan bir sayfa, sessizce farklı bir şey miras alır, ya da hiçbir şey miras almaz. Bu yüzden MovePage, miras alınan değeri taşımadan önce çözer ve sayfa sözlüğüne yazar, böylece sayfa kendi özniteliklerini taşıma boyunca yanında taşır

Yeniden sıralama geçişi tam olarak ne yapar?

Insert-at semantiğine karşı bir seçmeli sıralama çalıştırır. İstenen blok göreli sıra önce hesaplanır: kaynakları rotasyonda dolaş, her birinden en fazla GroupSize kadar indeks al, tükenmiş bir kaynağı atla, her sayfa yerleştirilene kadar tekrarla. Bu, eklenen blok üzerinde bir permütasyon üretir. Uygulaması can sıkıcı kısımdır, çünkü MovePage bir takas değil, bir ekleme işlemidir, dolayısıyla her taşıma eski ve yeni konum arasındaki her şeyi bir kaydırır

Uygulama, eklenen her sayfanın şu anda nerede oturduğunu modelleyen bir Current dizisi tutar, K konumundan ileriye doğru tarayarak K'ya ait olan sayfayı bulur, taşımayı verir, sonra taşımanın ağaçta yaptığını yansıtacak şekilde dizi girdilerini kaydırır. Dizi işlemlerinde O(n kare), nesne kopyalarında sıfırdır ve bu iş yükü için doğru takas budur: 500 sayfalık bir harmanlama, çeyrek milyon tamsayı karıştırmasıdır ve bir bayt bile çoğaltılmış görüntü verisi yoktur. Azalan aralıklar ve tekrar eden sayfalar bu geçişte özel bir işlem gerektirmez, çünkü PLParsePageRangeList sıralama devre dışı ve tekrarlara izin verilerek çağrılır, dolayısıyla istenen sıra ayrıştırmadan bozulmadan çıkar

Ters çevrilmiş aralıklar ve tek çağrılık duplex birleştirme

Ters çevirme bir aralık olarak ifade edildiğinde, düz yataklı çift geçiş durumu tek bir çağrıya indirgenir. Önyüzler doğal sırasını ister, arka yüzler 12-1'i ister ve noktalı virgülden önceki boş ilk segment, ilk kaynağın tüm sayfalarını katkıda bulunduğunu belirtir

var
  PDF: TPDFlib;
  Target, Fronts, Backs: Integer;
begin
  PDF := TPDFlib.Create;
  try
    Target := PDF.NewDocument;
    if PDF.LoadFromFile('fronts.pdf', '') <> 1 then
      Exit;
    Fronts := PDF.SelectedDocument;
    if PDF.LoadFromFile('backs.pdf', '') <> 1 then
      Exit;
    Backs := PDF.SelectedDocument;
    PDF.SelectDocument(Target);
    // fronts 1..12 in order, backs scanned in reverse: F1 B12 F2 B11 ...
    if PDF.CollateDocumentsEx([Fronts, Backs], ';12-1', 1) = 1 then
      PDF.SaveToFile('duplex.pdf');
  finally
    PDF.Free;
  end;
end;

Bu kod parçasındaki iki davranışın açıkça belirtilmesi gerekir. Harmanlanmış sayfalar seçili belgeye eklenir, dolayısıyla NewDocument ile oluşturulan bir belge onlardan önce kendi başlangıç boş sayfasını katkıda bulunur ve istemiyorsanız onu silmeniz gerekir. Kaynaklar eşit olmayan sayıda olabilir: üç sayfalık ve beş sayfalık bir kaynak üzerinde GroupSize 2 ile turlar A1 A2 B1 B2, ardından A neredeyse tükendiğinde A3 B3 B4, ardından tek başına B5 şeklinde gelir, çünkü tükenmiş bir kaynak dolgulanmak yerine basitçe atlanır

Geri alma, form alanları ve neyin gelmediği

Hedef dokunulmadan önce her argüman doğrulanır. Eksik bir belge tutamacı, kendi kaynağı olarak listelenen seçili belge, birden düşük bir GroupSize, kaynak sayısıyla eşleşmeyen bir segment sayısı, kaynağın sahip olmadığı bir sayfayı adlandıran bir aralık: bunların hepsi hedef değişmeden 0 döndürür. Kopyalama sırasındaki hata daha zor bir durumdur ve ham PageTree.DeletePages yerine genel DeletePages üzerinden işlenir. Bunun belirli bir nedeni vardır. Kopyalama MergeFormData etkinken çalışır, dolayısıyla sonraki bir kaynak başarısız olduğunda kaynak form alanları zaten hedefin /AcroForm /Fields dizisine eklenmiştir. Sayfaları sayfa ağacı düzeyinde silmek widget sayfalarını çıkarır ve bu alan referanslarını sarkık bırakır; genel yol ise alan, ana hat ve makale iş parçacığı referanslarını sayfaların yanında bağlantısını keser

if PDF.CollateDocumentsEx([Fronts, Backs], ';12-1', 1) = 0 then
  // Nothing was appended and the target is byte-identical to before.
  // 412 is the copy failure; 0 means the arguments were rejected
  // during validation, before any page was touched.
  Log(Format('collate rejected, LastErrorCode=%d', [PDF.LastErrorCode]));

Kullanıcılarınıza sınırlar konusunda dürüst olun. Harmanlama sayfaları, açıklamalarını ve form alanlarını taşır ve AcroForm alan listesini, hesaplama sıra dizisini ve varsayılan kaynaklar sözlüğünü birleştirir. Kaynak yer imlerini taşımaz: taranmış bir önyüz yığınının ana hat ağacı neredeyse her zaman boştur, bu yüzden duplex durumunda hiçbir şey kaybolmaz, ama iki hazırlanmış belgeyi harmanlarsanız ana hatları geride kalır ve navigasyonu kendiniz yeniden inşa etmeniz gerekir. Yalnızca kaynak katalogda bulunan adlandırılmış hedefler de aynı durumdadır. Bir müşteriye kayıpsız bir harmanlama vaat etmeden önce bunu planlayın

PDFlibPas, harmanlama fonksiyonlarını sayfa montaj yüzeyinin geri kalanıyla birlikte sunar, dolayısıyla tarayıcı iş akışı, aralık tabanlı çıkarma ve büyük dosya yolları Delphi ve C++Builder'da tek bir bileşenin arkasında yer alır. Tam API referansı ve deneme sürümü losLab Delphi PDF kütüphanesi ürün sayfasındadır