Technical Article

Skaliranje PDF stranica na 70% pomoću losLab PDF biblioteke

Dimenzije PDF stranica fiksiraju se u trenutku stvaranja stranice, pa sadržaj nije moguće jednostavno skalirati na licu mjesta kao što biste promijenili veličinu slike. Model biblioteke koji čini smanjivanje praktičnim jest hvatanje i ponovano iscrtavanje (capture-and-redraw): izvucite sadržaj svake stranice iz dokumenta u ručicu, stvorite novu praznu stranicu s izvornim medijskim dimenzijama, zatim iscrtajte zahvaćeni sadržaj unutar smanjenog okvira. Okolni bijeli prostor postaje margina. Primjerice, pri skaliranju na 70% na A4 stranici, 15% širine pada sa svake strane, a isti udio s gornje i donje strane, što je točno ono što daje matematika granica opisana u nastavku.

Kako radi CapturePage

Metoda CapturePage prima broj stranice, promiče sadržaj te stranice u objekt hvatanja u memoriji i uklanja stranicu iz stabla stranica dokumenta. To uklanjanje je namjerno i razlog je zašto petlja uvijek odabire stranicu 1 bez obzira na indeks iteracije: jednom kada je stranica 1 uhvaćena i izbrisana, ono što je bila stranica 2 postaje nova stranica 1, i tako dalje. Ako povećavate birač stranica zajedno s brojačem petlje, preskočit ćete svaku drugu stranicu i dobiti polovicu očekivanog izlaza.

Ručica hvatanja koju vraća metoda CapturePage nije referenca na stranicu; više je nalik snimci sadržaja. Ostaje valjana dok ne pozovete DrawCapturedPage ili je eksplicitno ne oslobodite. Metoda DrawCapturedPage prima tu ručicu zajedno s odredišnim pravokutnikom zadanim kao lijevi odmak, donji odmak, širina i visina, sve u točkama. Biblioteka skalira uhvaćeni sadržaj da točno stane u taj pravokutnik, čuvajući omjer širine i visine samo ako vaš pravokutnik slučajno odgovara izvornim proporcijama. Za ravnomjerno skaliranje pravokutnik treba biti izvorna veličina pomnožena faktorom skaliranja, centrirana na stranici.

Matematika centriranja

S faktorom skaliranja od 70% preostalih 30% svake dimenzije ravnomjerno se dijeli između dviju strana. Stoga je horizontalni uvlak pageWidth * (1.0 - 0.70) / 2, što je 15% širine, a vertikalni uvlak slijedi istu formulu koristeći visinu stranice. Odredišni pravokutnik za DrawCapturedPage tada počinje na koordinati (horizBorder, vertBorder) i proteže se pageWidth - 2 * horizBorder po pageHeight - 2 * vertBorder. Ta matematika nije specifična za biblioteku; to je jednostavno geometrija smještanja manjeg pravokutnika simetrično unutar većeg.

Vrijedi napomenuti jednu stvar: SetOrigin(1) postavlja ishodište koordinata u gornji lijevi ugao umjesto u donji lijevi. Vrijednosti granica koje prosljeđujete metodi DrawCapturedPage mjere se od ishodišta koje ste postavili, pa ako promijenite način ishodišta između učitavanja i iscrtavanja, centriranje neće biti ispravno.

Primjer u C#

Sljedeći kod obrađuje svaku stranicu datoteke Pages.pdf kroz ciklus hvatanja i ponovnog iscrtavanja te rezultat zapisuje u newpages.pdf. PDFL je ActiveX/COM wrapper objekt dodan projektu iz 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");
}

Primjer u Delphiju

Delphi verzija koristi TPDFlib izravno umjesto kroz COM sloj, ali je redoslijed poziva identičan. Jedna praktična razlika je zaštita izlazne datoteke: FileExists i DeleteFile umjesto File.Delete, jer će SaveToFile zakazati ako je odredište zaključano od prethodnog pokretanja koje je još otvoreno u pregledniku.

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;

Što faktor skaliranja zapravo kontrolira

Vrijednost 0,70 ovdje znači da prikazani sadržaj zauzima 70% svake dimenzije stranice, a ne da je datoteka 70% izvorne veličine u bajtovima. Veličina datoteke nakon ove operacije ovisi o složenosti izvornog sadržaja; stranica s velikim slikama neće se proporcionalno smanjiti jer se podaci piksela ponovno iscrtavaju pri istoj razlučivosti u manjem području. Ako je cilj kompresija na razini bajtova, pravi pristup je LinearizeFile ili ponovno spremanje s kompresijom toka, a ne geometrijsko skaliranje.

Vrijednost od 70% nije ni tvrdo ograničenje. Radi svaka vrijednost između 0,0 i 1,0, a vrijednosti iznad 1,0 povećavaju sadržaj izvan izvorne granice stranice, što se reže na rubu medijskog okvira osim ako ne povećate i dimenzije stranice. Dokumenti miješanih veličina se prirodno obrađuju jer se PageWidth i PageHeight dohvaćaju po stranici prije izračuna granica, pa dokument u kojemu su neparnih stranice A4, a parne A3 daje ispravno centrirani izlaz za svaku veličinu stranice bez ikakvih posebnih slučajeva.

Gdje stvari mogu poći po krivu

U praksi se pojavljuju dva načina kvarova. Prvi je izlazna datoteka ostavljena otvorenom u PDF pregledniku iz prethodnog pokretanja: SaveToFile će zakazati ili upisati nula bajtova ovisno o platformi, a novi izlaz nikada ne dospije na disk. Zaštita brisanjem datoteke na vrhu funkcije rješava to za razvoj, ali u produkcijskom procesu pisanje na privremenu putanju i preimenovanje pri uspjehu je sigurnije.

Drugi je nepodudaranje broja stranica. Budući da metoda CapturePage uklanja stranice iz dokumenta dok ih obrađuje, broj koji pročitate iz PageCount() prije petlje je ispravna granica za iteraciju. Pozivanje PageCount() unutar petlje vraćalo bi sve manji broj u svakom prolazu i izašlo bi prerano, ostavljajući posljednje stranice neobrađene. Varijabla petlje u primjerima služi samo kao brojač preostalih iteracija; nikada se ne koristi za odabir stranice, jer je stranica za odabir uvijek 1 iz razloga objašnjenog ranije.

Pozivi za manipulaciju stranicama prikazani ovdje, uključujući CapturePage, DrawCapturedPage i SetPageDimensions, dio su losLab PDF biblioteke za Delphi, C#, VB.NET i C++.