Teknik Makale

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

PDF Library for Delphi 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. PDF Library for Delphi 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

// Bunu yapmayın. Her CopyPageRanges çağrısı içe aktarma haritasını yeniden oluşturur,
// bu yüzden iki kaynağın dahili olarak paylaştığı her şey kaynak başına bir kez
// yerine tur başına bir kez içe aktarılır.
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

PDF Library for Delphi için iki taraflı tarama birleştirme haritası: 1 ile 12 arası sayfaları depolayan ön yığın, ters sırada yakalanan arka yığınla buluşur ve GroupSize 1 ile CollateDocumentsEx bunları sayfa 12'ye kadar F1 B12 F2 B11 okuma sırasına serpiştirirken basit tur başına kopyalama her turda paylaşılan kaynakları yeniden içe aktarır
1'den 12'ye saklanan ön sayfalar, 12'den 1'e yakalanan arka sayfalarla buluşur ve tek bir CollateDocumentsEx çağrısı bunları gerçek okuma sırasına serpiştirir — bunu tur başına kopyalama döngüsü olarak yazmak, paylaşılan yazı tiplerini ve görüntüleri her turda yeniden gömerdi

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

PDF Library for Delphi CollateDocumentsEx hattı: herhangi bir şey değişmeden önce doğrulanan dört bağımsız değişken kapısı, paylaşılan yazı tiplerini tek kopyada tutan kaynak başına bir içe aktarma geçişi, GroupSize ile döndürme sıralı ekleme, MovePage ile sayfa ağacı yeniden sıralama, kopyalama sırasındaki hataya karşı herkese açık DeletePages kurtarması ve sayfa nesne numaraları sabit kalacak şekilde 1 dönüşü
CollateDocumentsEx kopyalamayı sıralamadan ayırır: her kaynak tam bir kez içe aktarılır, böylece paylaşılan kaynaklar tek kopya kalır; ardından MovePage eklenen bloğu yeniden sıralar, geçersiz argümanlar erken döner ve kopyalama sırasındaki hatalar herkese açık DeletePages üzerinden geri alını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);
    // önyüzler 1..12 sırayla, arka yüzler ters taranır: 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
  // Hiçbir şey eklenmedi ve hedef öncekiyle bayt bazında aynı.
  // 412 kopyalama hatasıdır; 0 ise argümanların hiçbir sayfaya
  // dokunulmadan doğrulama sırasında reddedildiği anlamına gelir.
  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

PDF Library for Delphi, 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