Technical Article

Zmena mierky stránok PDF na 70% pomocou knižnice losLab PDF

Rozmery stránok PDF sú fixné v čase vytvorenia stránky, takže nemôžete jednoducho zmeniť mierku obsahu na mieste tak, ako by ste zmenili veľkosť obrázka. Model knižnice, ktorý robí zmenšovanie praktickým, je zachytenie a prekreslenie: vyzdvihnete obsah každej stránky z dokumentu do identifikátora, vytvoríte novú prázdnu stránku s pôvodnými mediálnymi rozmermi a potom nakreslíte zachytený obsah späť s menšou ohraničovacou obálkou. Okolité biele miesto sa stáva okrajom. Pri 70% mierke na stránke A4 napríklad 15% šírky padá na každú stranu a rovnaký zlomok hore a dole, čo je presne to, čo produkuje nižšie uvedená aritmetika ohraničenia.

Ako funguje CapturePage

CapturePage vezme číslo stránky, povýši obsah tejto stránky na zachytávaný objekt v pamäti a odstráni stránku zo stromu stránok dokumentu. Toto odstránenie je zámerné a je dôvodom, prečo slučka vždy vyberá stránku 1 bez ohľadu na index iterácie: keď je stránka 1 zachytená a vymazaná, to, čo bolo stránkou 2, sa stane novou stránkou 1 atď. Ak zvyšujete selektor stránok spolu s počítadlom slučky, preskočíte každú druhú stránku a skončíte s polovicou očakávaného výstupu.

Identifikátor zachytenia vrátený od CapturePage nie je odkazom na stránku; je skôr ako snímka obsahu. Zostane platný, kým nezavoláte DrawCapturedPage alebo ho explicitne neuvoľníte. DrawCapturedPage vezme tento identifikátor plus cieľový obdĺžnik zadaný ako ľavý posun, spodný posun, šírka a výška, všetko v bodoch. Knižnica zmení mierku zachyteného obsahu tak, aby presne zodpovedal tomuto obdĺžniku, pričom zachováva pomer strán iba vtedy, ak váš obdĺžnik náhodou zodpovedá pôvodným proporciám. Pre jednotnú zmenu mierky chcete, aby bol obdĺžnik pôvodná veľkosť vynásobená faktorom mierky, vycentrovaná na stránke.

Matematika centrovania

Pri faktore mierky 70% sa zostatok 30% každého rozmeru rozdelí rovnako medzi dve strany. Horizontálne vnútorné odsadenie je teda pageWidth * (1.0 - 0.70) / 2, čo je 15% šírky, a vertikálne vnútorné odsadenie nasleduje rovnaký vzorec s výškou stránky. Cieľový obdĺžnik pre DrawCapturedPage potom začína na (horizBorder, vertBorder) a rozkladá sa cez pageWidth - 2 * horizBorder krát pageHeight - 2 * vertBorder. Táto aritmetika nie je špecifická pre knižnicu; je to len geometria symetrického umiestenia menšieho obdĺžnika do väčšieho.

Jedna vec stojí za zmienku: SetOrigin(1) umiestni súradnicový počiatok vľavo hore namiesto vľavo dole. Hodnoty ohraničenia, ktoré odovzdávate do DrawCapturedPage, sú merané od nastaveného počiatku, takže ak prepnete režimy počiatku medzi načítaním a kreslením, centrovanie bude nesprávne.

Príklad v C#

Nasledujúci kód spracuje každú stránku súboru Pages.pdf cez cyklus zachytenia a prekreslenia a výsledok zapíše do newpages.pdf. PDFL je objekt obálky ActiveX/COM pridaný do projektu zo súboru PDFlibDLL64.dll.

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++)
    {
        // Always select page 1: CapturePage removes the page, so page 2
        // becomes page 1 on the next iteration.
        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);

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

        ret = PDFL.DrawCapturedPage(
            capturedPageId,
            horizBorder, vertBorder,
            pageWidth  - 2 * horizBorder,
            pageHeight - 2 * vertBorder);
    }

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

Príklad v Delphi

Verzia Delphi používa TPDFlib priamo namiesto cez vrstvu COM, ale sekvencia volaní je rovnaká. Jeden praktický rozdiel je ochrana výstupného súboru: FileExists plus DeleteFile namiesto File.Delete, pretože SaveToFile zlyhá, ak je cieľ uzamknutý predchádzajúcim spustením stále otvoreným v prehliadači.

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);

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

      ret := PDFLib.DrawCapturedPage(
        capturedPageId,
        horizBorder, vertBorder,
        pageWidth  - 2 * horizBorder,
        pageHeight - 2 * vertBorder);
    end;

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

Čo faktor mierky skutočne riadi

Hodnota 0,70 tu znamená, že vykreslený obsah zaberá 70% každého rozmeru stránky, nie že súbor má 70% svojej pôvodnej veľkosti v bajtoch. Veľkosť súboru po tejto operácii závisí od zložitosti pôvodného obsahu; stránka s veľkými obrázkami sa nezmení proporcionálne, pretože pixelové dáta sú prekreslené pri rovnakom rozlíšení do menšej plochy. Ak je cieľom kompresie na úrovni bajtov, správnym prístupom je LinearizeFile alebo opätovné uloženie so streamovou kompresiou, nie geometrická zmena mierky.

Číslo 70% tiež nie je pevnou hranicou. Funguje akákoľvek hodnota medzi 0,0 a 1,0 a hodnoty nad 1,0 zväčšia obsah nad pôvodnú hranicu stránky, čo sa oreže na okraji mediálneho boxu, pokiaľ nezvýšite aj rozmery stránky. Dokumenty so zmiešanými veľkosťami sú riešené prirodzene, pretože PageWidth a PageHeight sú dotazované za každú stránku pred výpočtom ohraničenia, takže dokument, kde nepárne stránky sú A4 a párne A3, vytvorí správne centrovaný výstup pre každú veľkosť stránky bez akéhokoľvek špeciálneho spracovania.

Kde môžu nastať problémy

V praxi sa vyskytujú dva režimy zlyhaní. Prvý je výstupný súbor ponechaný otvorený v prehliadači PDF z predchádzajúceho spustenia: SaveToFile zlyhá alebo zapíše nula bajtov v závislosti od platformy, a nový výstup nikdy nedorazí. Ochrana mazaním súboru na začiatku funkcie to rieši pre vývoj, ale v produkčnom kanáli je bezpečnejšie písať do dočasnej cesty a premenovať po úspechu.

Druhý je nesúlad počtu stránok. Pretože CapturePage odstraňuje stránky z dokumentu počas spracovania, počet, ktorý ste načítali z PageCount() pred slučkou, je správna hranica iterácie. Volanie PageCount() vo vnútri slučky by vracalo klesajúce číslo pri každom prechode a skončilo by predčasne, pričom posledné stránky zostanú nespracované. Premenná slučky v príkladoch slúži len ako počítadlo zostatok iterácií; nikdy sa nepoužíva na výber stránky, pretože stránka na výber je vždy 1 z dôvodu vysvetleného skôr.

Tu zobrazené volania manipulácie stránok vrátane CapturePage, DrawCapturedPage a SetPageDimensions sú súčasťou knižnice losLab PDF pre Delphi, C#, VB.NET a C++.