Teknik Makale

losLab PDF Kütüphanesi ile PDF Sayfalarını %70 Oranında

PDF sayfa (page) boyutları bir sayfanın (page) oluşturulduğu sırada sabitlenir (fixed), bu nedenle bir resmi (image) yeniden boyutlandırdığınız (resize) gibi içeriği basitçe (simply) yerinde (in place) yeniden ölçeklendiremezsiniz. Küçültmeyi pratik (practical) hale getiren kitaplık modeli yakalama (capture) ve yeniden çizmektir (redraw): her sayfanın (page) içeriğini belgeden bir işleyiciye (handle) çıkarın (lift), orijinal (original) medya (media) boyutunda yeni (new) boş (blank) bir sayfa (page) oluşturun, ardından yakalanan içeriği daha küçük (reduced) bir sınırlayıcı kutu (bounding box) içinde geri (back) çizin (draw). Çevredeki boşluklar marj (margin) haline gelir. Örneğin bir A4 sayfasında %70 (70%) ölçekte (scale), genişliğin %15'i (15%) her iki tarafa, aynı oranda üst (top) ve alt (bottom) kısımlara düşer; bu da tam (exactly) olarak aşağıdaki (below) sınır (border) aritmetiğinin (arithmetic) ürettiği (produces) şeydir

PDF: Yakala-ve-yeniden-çiz hattı: her PDF sayfası bir tanıtıcıya yakalanır ve özgün medya boyutunda tutulan taze boş bir sayfaya geri çizilir
Yakala-ve-yeniden-çiz, her sayfanın içeriğini bir tanıtıcıya kaldırır ve özgün boyutlarda taze bir sayfada küçültülmüş boyutta yeniden çizer

CapturePage nasıl çalışır

CapturePage bir sayfa (page) numarası alır, o sayfanın (page) içeriğini (content) bellek içi (in-memory) bir yakalama nesnesine (capture object) yükseltir (promotes) ve sayfayı (page) belgenin sayfa (page) ağacından kaldırır (removes). Bu kaldırma işlemi kasıtlıdır (intentional) ve döngünün yinelenme indeksine (iteration index) bakılmaksızın her zaman sayfa (page) 1'i (1) seçmesinin (selects) nedeni budur: sayfa (page) 1 yakalanıp silindiğinde, sayfa (page) 2 olan (was) sayfa yeni (new) sayfa (page) 1 olur ve bu (so) böyle (on) devam eder. Sayfa seçiciyi (page selector) döngü sayacıyla (loop counter) birlikte artırırsanız (increment), her iki sayfadan (page) birini atlarsınız (skip) ve beklenen (expected) çıktının yarısını elde edersiniz (end up)

CapturePage'in döndürdüğü yakalama tutamacı bir sayfa referansı değildir; daha çok bir içerik anlık görüntüsüne benzer. Çizimden sonra da geçerli kalır; böylece aynı yakalamayı, ReleaseCapturedPage ile serbest bırakana ya da belge ortadan kalkana kadar birden fazla kez yerleştirebilirsiniz. DrawCapturedPage bu tutamacı artı bir hedef dikdörtgeni alır; dikdörtgen sol kenar, üst kenar, genişlik ve yükseklik olarak verilir ve varsayılan olarak tümü punto cinsindendir. Kütüphane, yakalanan içeriği bu dikdörtgene tam olarak sığacak şekilde ölçekler; en-boy oranını yalnızca dikdörtgeniniz tesadüfen özgün orantılarla eşleşiyorsa korur. Eşit ölçekleme için dikdörtgenin, sayfada ortalanmış, ölçek çarpanıyla çarpılmış özgün boyut olmasını istersiniz

Ortalama matematiği

%70'lik (70%) bir ölçek çarpanıyla (scale factor), her bir boyutun (dimension) kalan %30'u (30%) iki taraf (sides) arasında (between) eşit olarak paylaştırılır. Dolayısıyla (So) yatay içe doğru (horizontal inset) yerleşim pageWidth * (1.0 - 0.70) / 2'dir, bu da (which) genişliğin %15'idir (15%) ve dikey (vertical) iç yerleşim (inset), sayfa yüksekliğini (page height) kullanarak aynı formülü (formula) izler. Daha (Then) sonra (Then) DrawCapturedPage için (for) hedef dikdörtgeni (destination rectangle) (horizBorder, vertBorder) konumundan (at) başlar ve pageWidth - 2 * horizBorder boyutundan (by) pageHeight - 2 * vertBorder boyutuna yayılır (spans). Bu aritmetik, kütüphaneye (library) özgü değildir; sadece daha küçük bir dikdörtgeni daha (larger) büyük (larger) bir (one) dikdörtgenin (rectangle) içine simetrik (symmetrically) olarak yerleştirmenin (fitting) geometrisidir

PDF: Yüzde 70 ölçekte hedef dikdörtgeni özgün sayfada ortalanmış durur ve her yan için yüzde 15 kenar hesaplanır
%70'e ölçeklemek her kenarda yüzde 15 kenar bırakır ve DrawCapturedPage o ortalanmış dikdörtgeni punto cinsinden alır

Dikkat çeken bir şey: SetOrigin(1), koordinat başlangıç noktasını sol alt yerine sol üste koyar. DrawCapturedPage'e geçtiğiniz ikinci değer, dikdörtgenin üst kenarıdır ve ayarladığınız orijinden itibaren ölçülür. Örneklerin önce SetOrigin(1) çağırmasının nedeni budur: varsayılan sol alt orijinle aynı vertBorder, üst kenarı sayfanın dibine yakın bir yere koyar ve içeriğin çoğunu sayfanın dışına iter

C# örneği

Aşağıdaki kod, Pages.pdf'in her sayfasını yakala-ve-yeniden-çiz döngüsünden geçirir ve sonucu newpages.pdf dosyasına yazar. PDFL, DLL sürümüyle gelen PDFlibrary import sınıfının bir örneğidir ve new PDFlibrary("PDFlibDLL64.dll") ile oluşturulur; ActiveX sürümü aynı metotları PDFlibAX.PDFlibrary üzerinden açar

private void ScalePages_Click(object sender, EventArgs e)
{
    File.Delete("newpages.pdf");

    double pageWidth, pageHeight, horizBorder, vertBorder;
    double scaleFactor = 0.70;
    int capturedPageId, ret;

    PDFL.LoadFromFile("Pages.pdf", "");
    PDFL.SetOrigin(1);

    int numPages = PDFL.PageCount();

    for (int i = 1; i <= numPages; i++)
    {
        // Her zaman 1. sayfayı seçin: CapturePage sayfayı kaldırır, böylece 2. sayfa
        // sonraki yinelemede 1. sayfa olur.
        PDFL.SelectPage(1);

        pageWidth  = PDFL.PageWidth();
        pageHeight = PDFL.PageHeight();

        horizBorder = pageWidth  * (1.0 - scaleFactor) / 2;
        vertBorder  = pageHeight * (1.0 - scaleFactor) / 2;

        capturedPageId = PDFL.CapturePage(1);
        // 0 = hiçbir şey yakalanmadı, örneğin tek sayfalık kaynak: tek sayfa kaldırılamaz
        if (capturedPageId == 0)
            throw new InvalidOperationException("CapturePage failed on source page " + i);

        PDFL.NewPage();
        PDFL.SetPageDimensions(pageWidth, pageHeight);

        ret = PDFL.DrawCapturedPage(
            capturedPageId,
            horizBorder, vertBorder,
            pageWidth  - 2 * horizBorder,
            pageHeight - 2 * vertBorder);
        if (ret != 1)
            throw new InvalidOperationException("DrawCapturedPage failed on source page " + i);
    }

    PDFL.SaveToFile("newpages.pdf");
}

Delphi örneği

Delphi sürümü, TPDFlib'i DLL import sınıfı üzerinden değil doğrudan kullanır, ama çağrı sırası aynıdır. Pratik bir fark, çıktı dosyası korumasıdır: File.Delete yerine FileExists artı DeleteFile; çünkü hedef, önceki bir çalıştırmadan görüntüleyicide hâlâ açık kalmışsa SaveToFile başarısız olur

procedure TForm1.ScalePagesClick(Sender: TObject);
var
  PDFLib: TPDFlib;
  pageWidth, pageHeight, horizBorder, vertBorder: Double;
  scaleFactor: Double;
  capturedPageId, ret, numPages, i: Integer;
begin
  if FileExists('newpages.pdf') then
    DeleteFile('newpages.pdf');

  scaleFactor := 0.70;

  PDFLib := TPDFlib.Create;
  try
    PDFLib.LoadFromFile('Pages.pdf', '');
    PDFLib.SetOrigin(1);

    numPages := PDFLib.PageCount();

    for i := 1 to numPages do
    begin
      PDFLib.SelectPage(1);

      pageWidth  := PDFLib.PageWidth();
      pageHeight := PDFLib.PageHeight();

      horizBorder := pageWidth  * (1.0 - scaleFactor) / 2;
      vertBorder  := pageHeight * (1.0 - scaleFactor) / 2;

      capturedPageId := PDFLib.CapturePage(1);
      // 0 = hiçbir şey yakalanmadı, örneğin tek sayfalık kaynak: tek sayfa kaldırılamaz
      if capturedPageId = 0 then
        raise Exception.CreateFmt('CapturePage failed on source page %d', [i]);

      PDFLib.NewPage();
      PDFLib.SetPageDimensions(pageWidth, pageHeight);

      ret := PDFLib.DrawCapturedPage(
        capturedPageId,
        horizBorder, vertBorder,
        pageWidth  - 2 * horizBorder,
        pageHeight - 2 * vertBorder);
      if ret <> 1 then
        raise Exception.CreateFmt('DrawCapturedPage failed on source page %d', [i]);
    end;

    PDFLib.SaveToFile('newpages.pdf');
  finally
    PDFLib.Free;
  end;
end;

Ölçek çarpanı (scale factor) aslında (actually) neyi kontrol (controls) eder (controls)

Buradaki 0.70 değeri, işlenen içeriğin her sayfa boyutunun %70'ini kapladığı anlamına gelir; dosyanın özgün bayt boyutunun %70'i olduğu anlamına değil. Bu işlemden sonraki dosya boyutu, özgün içeriğin karmaşıklığına bağlıdır; büyük resimler içeren bir sayfa orantılı olarak küçülmez, çünkü piksel verisi aynı çözünürlükte daha küçük bir alana yeniden çizilir. Amaç daha küçük bir dosyaysa bakılacak yer geometrik ölçekleme değil, sıkıştırılmadan çıkan sayfa içerik akışları için CompressContent ve resim yeniden sıkıştırmadır (Delphi sürümünde OptimizeImages). LinearizeFile başka bir araçtır: dosyayı sayfa-sayfa yükleme (Fast Web View) için yeniden sıralar; görüntüleyicilerin ağ üzerinden büyük dosyaları açmasına yardım eder ama küçültmez

%70 (70%) rakamı da kesin (hard) bir sınır (limit) değildir. 0.0 (0.0) ile 1.0 (1.0) arasındaki (between) herhangi (Any) bir değer (value) işe yarar ve 1.0'ın üzerindeki değerler (values), sayfa (page) boyutlarını da artırmadığınız (increase) sürece (unless) ortam kutusu (media box) kenarında (edge) kırpılan (clips) içeriği (content) orijinal sayfa (page) sınırının (boundary) ötesine (beyond) büyütür (enlarge). Karışık (Mixed-size) boyutlu (Mixed-size) belgeler (documents) doğal (naturally) olarak işlenir çünkü PageWidth ve PageHeight, kenarlık hesaplamasından (calculation) önce her sayfa için sorgulanır (queried), bu nedenle (so) tek (odd) sayfaların A4 ve çift (even) sayfaların A3 olduğu bir (a) belge, herhangi bir özel duruma (casing) gerek kalmadan her sayfa (page) boyutunda doğru (correctly) şekilde ortalanmış çıktı üretecektir (produce)

İşlerin nerede ters (wrong) gidebileceği (go)

Uygulamada (practice) iki başarısızlık (failure) modu (modes) ortaya çıkar (come up). Birincisi (first), önceki bir (a) çalıştırmadan (run) dolayı (from) PDF görüntüleyicide açık (open) bırakılan bir çıktı dosyasıdır: SaveToFile platforma bağlı (depending) olarak (depending) başarısız (fail) olacak (will) veya (or) sıfır bayt yazacaktır ve yeni (new) çıktı (output) asla inmeyecektir (lands). İşlevin (function) en üstündeki (top) dosya (file-delete) silme (file-delete) koruması (guard) geliştirme (development) için bunu (that) işler (handles), ancak bir üretim boru hattında (pipeline) geçici (temporary) bir (a) yola (path) yazmak (writing) ve (and) başarıyla yeniden adlandırmak (renaming) daha (safer) güvenlidir (safer)

İkincisi, sayfa sayısı uyumsuzluğudur. CapturePage, sayfaları işlerken belgeden kaldırdığı için döngüden önce PageCount()'ten okuduğunuz sayı, yineleme yapılacak doğru sınırdır. Döngünün içinde PageCount() çağırmak da işe yaramaz: her geçiş bir sayfa kaldırıp bir sayfa ekler; sayı yerinde sayar ve kaç kaynak sayfa kaldığı hakkında hiçbir şey söylemez. Örneklerdeki döngü değişkeni yalnızca kalan yineleme sayacı olarak görev yapar; sayfa seçmek için asla kullanılmaz, çünkü seçilecek sayfa, daha önce açıklanan nedenle daima 1'dir

PDF: Döngü şeması: her geçişin neden SelectPage(1) çağırması gerektiğini gösterir; yakalanan sayfalar ağaçtan kaldırılır ve kalan sayfalar ileri kayar
CapturePage işlenen her sayfayı kaldırır; böylece kuyruk kendini yeniden adlandırır ve bu, 1. sayfayı tek doğru seçici değere yapar

Burada (here) gösterilen (shown) CapturePage, DrawCapturedPage ve SetPageDimensions dahil (including) sayfa (page) işleme (manipulation) çağrıları (calls), Delphi, C#, VB.NET ve C++ için losLab PDF Kütüphanesinin bir parçasıdır