Technical Article

PDFium ile N-up Sayfa Düzeni ve Sayfa Yeniden Sıralama

Birleştirme ve bölme, herkesin ilk ulaştığı iki sayfa işlemidir ve çok fazla alanı kapsarlar. Her şeyi kapsamazlar. Sayfaları taşımak yerine yeniden düzenleyen ayrı bir çalışma ailesi vardır: bir dinleyici notu için tek bir sayfaya dört slayt yerleştirmek, bir sayfayı belgenin arkasından önüne sürüklemek veya geri kalanına dokunmadan 3, 7 ve 12. sayfaları kısa bir alıntıya çekmek. PDFium tam olarak bunun için üç yöntem sunar ve her biri zaten bildiğiniz birleştirme ve bölme işlemlerinden farklı davranır. Bu makale ne yaptıklarını, çıktı noktalarının nerede yaşadığını ve sahada çökmeye neden olan bir mülkiyet detayını incelemektedir

Bu üçü; N-up düzeni için ImportNPagesToOne, yerinde yeniden sıralama için MovePages ve alt küme çıkarma için ImportPagesByIndex'tir. Birleştirme, belgeleri uç uca yığar ve sayfa sayısını girdilerin toplamına eşit bırakır. Bölme, bir girdiden birkaç çıktı dosyası yazar. Buradaki üç işlem arada yer alır: bunlardan biri kaç kaynak sayfanın bir sayfayı paylaşacağını değiştirir, biri tek bir belgenin içindeki sırayı değiştirir ve biri seçilen bir avuç sayfayı başka bir belgeye kopyalar. Hangisinin ne olduğunu bilmek, tek bir çağrının yeterli olacağı durumlarda sizi bir birleştir ve sil dansına zorlamaktan kurtarır

N-up sayfa düzeni gerçekte ne yapar

Montaj (imposition), basılmış ve katlanmış sonucun doğru sırada okunması için birkaç kaynak sayfanın daha büyük tek bir sayfaya yerleştirilmesine yönelik baskı öncesi terimdir. Günlük sürümü, 2-up dinleyici notu, 4-up kitapçık imzası veya bir sayfaya bir düzine küçük resim sığdıran dizgi sayfasıdır. PDFium geometriyi tek bir çağrıyla işler:

function ImportNPagesToOne(
  OutputWidth, OutputHeight: Single;
  NumX, NumY               : Cardinal): TPdf;

NumX ve NumY ızgarayı tanımlar. 2, 1 değeri iki kaynak sayfayı yan yana yerleştirir; 2, 2 dört sayfayı bir çeyrek düzene paketler; 4, 3 on iki-up bir dizgi sayfası oluşturur. PDFium kaynak sayfaları sırayla okur, her birini hücresine sığacak şekilde ölçeklendirir ve mevcut ızgara dolduğunda yeni bir çıktı sayfası başlatarak ızgarayı soldan sağa, yukarıdan aşağıya doldurur. Kaynak sayfalar değiştirilmez. Geri aldığınız şey, sayfaları kompozit olan yeni bir belgedir

Çıktı boyutu piksel değil, nokta (point) cinsindendir

OutputWidth and OutputHeight PDF kullanıcı birimleridir ve bir PDF kullanıcı birimi, bir inçin yetmiş ikide biri olan bir noktadır (point). Birim, çıktı sayfasının fiziksel boyutunu bildirir ve ekran pikselleri veya işleme DPI'ı ile hiçbir ilgisi yoktur. Bu, montajı yanlış yapmanın en yaygın tek yeridir, çünkü bit eşlemlere alışmış bir geliştirici bir piksel sayısına ulaşır ve sonunda bir posta pulu veya reklam panosu boyutunda bir sayfayla karşılaşır

Ezberlemeye değer sayılar, en çok kullanacağınız iki sayfa boyutudur. US Letter 612'ye 792 noktadır, çünkü 8.5 inç çarpı 72 eşittir 612 ve 11 inç çarpı 72 eşittir 792'dir. A4, 210'a 297 milimetre boyutlarından dolayı kabaca 595'e 842 noktadır. Bağlamanın kendi başlığı kuralı açıkça belirtir: bir birim bir inçin yetmiş ikide biridir ve birim, kod içinde inç cinsinden bir boyut hesaplamayı değişmez değeri yazmaya tercih ederseniz, 72'ye eşit bir PointsPerInch sabiti gönderir

const
  LetterW = 612.0;   // 8.5 in * 72
  LetterH = 792.0;   // 11  in * 72
var
  Source, Composite: TPdf;
begin
  Source := TPdf.Create(nil);
  Composite := nil;
  try
    Source.FileName := 'slides.pdf';
    Source.Active := True;

    // Four source pages per Letter sheet, 2 by 2 grid.
    Composite := Source.ImportNPagesToOne(LetterW, LetterH, 2, 2);
    if Composite = nil then
      raise Exception.Create('PDFium rejected the imposition arguments');

    Composite.SaveAs('slides-4up.pdf');
  finally
    Composite.Free;   // see the next section: this is mandatory
    Source.Free;
  end;
end;

Döndürülen tutamaç serbest bırakılmak üzere sizindir

İmzayı tekrar okuyun. ImportNPagesToOne, bir Boole değil, bir TPdf döndürür. Bu dönüş değeri, kaynaktan ayrı olarak tahsis edilmiş yepyeni bir belge tutamacıdır ve çağıran ona sahiptir. Yöntemi çağırdığınız kaynak TPdf dokunulmamıştır ve hala kendi tutamacına sahiptir; kompozit ikinci, bağımsız bir nesnedir. Döndürülen TPdf'i serbest bırakmadan kapsam dışına çıkmasına izin verirseniz, tüm bir PDFium belgesini sızdırırsınız

Daha tehlikeli olan hata diğer yönde çalışır. Altta, yöntem PDFium'dan FPDF_ImportNPagesToOne aracılığıyla taze bir FPDF_DOCUMENT ister, ardından sarmalayıcının ömrü tutamacınkini yönetecek şekilde bu ham tutamacı döndürülen TPdf içine sarmalar. O andan itibaren tutamacın tam olarak tek bir sahibi ve kapatılması gereken tam olarak tek bir yer vardır: döndürülen nesneyi Free ettiğinizde. Hem sarmalayıcıyı serbest bırakan hem de yakaladığı ham tutamaçta FPDF_CloseDocument'ı çağıran dikkatsiz bir hata yolu aynı PDFium belgesini iki kez kapatır. Bu bir çift serbest bırakmadır ve burada bir arayanı bir kez ısıran özel hatadır. Bunu önleyen kural kısadır. Belgeyi yalnızca tek bir yolda kapatın; yöntemin size teslim ettiği TPdf'i serbest bırakarak ve zaten benimsediği tutamacı kapatmak için asla sarmalayıcının ötesine geçmeyin

Bundan iki sonuç çıkar. İlk olarak, PDFium ızgara eksenlerinden herhangi birinde sıfır veya tahsis başarısızlığı gibi bağımsız değişkenleri reddettiğinde yöntem nil döndürür, bu nedenle sonuca dokunmadan önce bir nil kontrolü yapılmalıdır. İkinci olarak, yukarıdaki örnekte olduğu gibi try'dan önce çıktı değişkeninizi nil olarak başlatın ve finally içinde serbest bırakın, böylece yarıdaki bir başarısızlık sizi tanımsız bir referansı serbest bırakmak zorunda bırakmaz veya serbest bırakmayı tamamen atlamaz

Sayfaları yeniden yazmadan yeniden sıralama

Montaj yeni bir belge oluşturur. Yeniden sıralama bir belgeyi yerinde değiştirir. MovePages bir dizi sayfayı mevcut konumlarından kaldırır ve bir hedefe bırakır, taşınan bloğun etrafındaki diğer her şeyi kaydırır, böylece sayfa sayısı aynı kalır:

function MovePages(
  const PageIndices: array of Integer;
  DestPageIndex    : Integer): Boolean;

Dizinler sıfır tabanlıdır. PageIndices, taşınacak sayfaları nihayetinde olmaları gereken sırada listeler ve DestPageIndex, taşınan ilk sayfanın taşıma gerçekleştikten sonra yerleştiği dizindir. PDFium, sayfaların içeriğini kopyalayıp yeniden sıkıştırmak yerine sayfaların yerini değiştirdiği için işlem ucuz ve kayıpsızdır: sayfa nesneleri akışlarını, kaynaklarını ve doğruluklarını korur. Bu, kullanıcının bir küçük resmi yeni bir yuvaya çektiği ve sizin yeni sırayı tek bir hareketle taahhüt ettiğiniz bir sürükle-yeniden sırala sayfa panelinin arkasındaki çağrıdır. Bir dizin aralık dışındayken False döndürür, bu nedenle yeniden düzenlemenin başarılı olduğunu varsaymak yerine sonucu doğrulayın

var
  Doc: TPdf;
begin
  Doc := TPdf.Create(nil);
  try
    Doc.FileName := 'report.pdf';
    Doc.Active := True;

    // Move the last page (index 4 in a 5-page file) to the very front.
    if not Doc.MovePages([4], 0) then
      raise Exception.Create('MovePages rejected the index');

    Doc.SaveAs('report-reordered.pdf');
  finally
    Doc.Free;
  end;
end;

Dizine göre bir alt küme çekme

Üçüncü işlem, bir belgeden diğerine açık bir sayfa kümesi kopyalar. ImportPagesByIndex kaynak belgeyi ve sıfır tabanlı bir dizin dizisini alır ve bu sayfaları hedefte seçilen bir konuma ekler:

function ImportPagesByIndex(
  Source           : TPdf;
  const PageIndices: array of Integer;
  InsertAt         : Integer= 0): Boolean;

Bunu hedef belge üzerinde çağırırsınız ve ilk bağımsız değişken olarak kaynağı geçersiniz. PageIndices, çekmek istediğiniz kaynak sayfaları istediğiniz sırada adlandırır; InsertAt, ithal edilen ilk sayfanın gideceği hedefteki sıfır tabanlı yuvadır; bu nedenle 0 bunları mevcut ilk sayfanın önüne yerleştirir ve hedefin mevcut sayfa sayısı eklenir. Boş bir dizi her sayfayı ithal eder, bu da ihtiyacınız olduğunda çağrıyı tam bir kopyaya dönüştürür. Kaynakta herhangi bir dizin aralık dışındaysa False döndürür

Bölme (split) ile karşıtlığın önemli olduğu yer burasıdır. Bölme ayrı dosyalar yazar, tek bir işlem diskte birçok çıktı üretir. ImportPagesByIndex işin ters şeklini yapar: seçilen bir sayfa kümesini bellekte tek bir hedef belgede toplar, bunu daha sonra bir kez kaydedersiniz. İş "bana 3, 7 ve 12. sayfaları tek bir kısa PDF olarak ver" olduğunda, bu doğrudan yoldur ve altta FPDF_ImportPagesByIndex'i sarmalar

var
  Source, Excerpt: TPdf;
begin
  Source := TPdf.Create(nil);
  Excerpt := TPdf.Create(nil);
  try
    Source.FileName := 'manual.pdf';
    Source.Active := True;
    Excerpt.CreateDocument;   // start an empty target

    // Pull pages 3, 7 and 12 (zero-based 2, 6, 11) into the excerpt.
    if not Excerpt.ImportPagesByIndex(Source, [2, 6, 11], 0) then
      raise Exception.Create('A requested page index is out of range');

    Excerpt.SaveAs('manual-excerpt.pdf');
  finally
    Excerpt.Free;
    Source.Free;
  end;
end;

Temiz bir şekilde bir araya getirme

Uçtan uca şekil üçünde de aynıdır: FileName'i ayarlayıp Active'i True konumuna getirerek kaynağı açın, işlemi gerçekleştirin, SaveAs ile kaydedin ve sahip olduğunuzu serbest bırakın. Dikkat gerektiren tek dal, hangi çağrıların yeni bir belge tahsis ettiğidir. MovePages zaten sahip olduğunuz belgeyi değiştirir, bu nedenle serbest bırakılacak tek bir nesne vardır. ImportPagesByIndex kendi oluşturduğunuz bir hedefe yazar, bu nedenle açtığınız kaynağı ve hedefi serbest bırakırsınız. ImportNPagesToOne aykırı değerdir, çünkü yeni belge oluşturduğunuz bir şeyden ziyade yöntemin dönüş değeridir ve bunun ayrı, çağıranın sahip olduğu bir tutamaç olduğunu unutmak, hem sızıntının hem de çift serbest bırakmanın meydana gelme şeklidir. Sonucu nil olarak başlatın, çağrıdan sonra kontrol edin ve tek bir yolda serbest bırakın

Gerçekte sahip olduğunuz iş sayfaları yeniden düzenlemek yerine tüm dosyaları birleştirmekse, birden fazla PDF dosyasını tek bir belgede birleştirme konusuna bakın. Tam tersi ise, bir belgeyi birkaç dosyaya bölmekse, PDF belgelerini birden fazla dosyaya bölme konusuna bakın. Burada açıklanan montaj ve yeniden sıralama yöntemleri, bu blogun başka yerlerinde ele alınan yükleme, işleme ve düzenleme API'lerinin yanı sıra Delphi ve C++Builder için PDFium Bileşeni'nin bir parçası olarak sunulmaktadır