Teknisk artikel

Skalering af PDF-sider til 70% med losLab PDF Library

PDF-sidedimensioner er faste på det tidspunkt, hvor en side oprettes, så du kan ikke blot omskalere indholdet på plads, som du ville ændre størrelsen på et billede. Den biblioteksmodel, der gør krympning praktisk, er fang-og-tegn-igen: Løft hver sides indhold ud af dokumentet til et håndtag, opret en ny tom side i den originale mediestørrelse, og tegn derefter det fangede indhold tilbage i en reduceret afgrænsningsramme. Det omgivende hvide rum bliver til marginen. Ved 70% skala på en A4-side falder for eksempel 15% af bredden på hver side og den samme brøkdel øverst og nederst, hvilket er præcis, hvad grænsearitmetikken nedenfor producerer

Hvordan CapturePage fungerer

CapturePage tager et sidenummer, fremmer sidens indhold til et indfangningsobjekt i hukommelsen og fjerner siden fra dokumentets sidetræ. Den fjernelse er bevidst og er grunden til, at løkken altid vælger side 1 uanset iterationsindekset: Når side 1 er fanget og slettet, bliver det, der var side 2, den nye side 1 og så videre. Hvis du forøger sidevælgeren sammen med løkketælleren, vil du springe hver anden side over og ende med halvdelen af det forventede output

Fangsthåndtaget, der returneres af CapturePage, er ikke en sidereference; det er mere som et indholdsøjebliksbillede. Det forbliver gyldigt, indtil du kalder DrawCapturedPage eller frigiver det eksplicit. DrawCapturedPage tager det håndtag plus et destinationsrektangel angivet som venstre forskydning, nederste forskydning, bredde og højde, alt sammen i punkter. Biblioteket skalerer det indfangede indhold, så det passer nøjagtigt til det rektangel, og bevarer kun billedformatet, hvis dit rektangel tilfældigvis matcher de oprindelige proportioner. For ensartet skalering ønsker du, at rektanglet skal være den originale størrelse ganget med skaleringsfaktoren, centreret på siden

Centreringsmatematikken

Med en skaleringsfaktor på 70% bliver de resterende 30% af hver dimension delt ligeligt mellem de to sider. Så den vandrette indsætning er pageWidth * (1.0 - 0.70) / 2, hvilket er 15% af bredden, og den lodrette indsætning følger samme formel ved brug af sidehøjden. Destinationsrektanglet for DrawCapturedPage starter derefter ved (horizBorder, vertBorder) og spænder pageWidth - 2 * horizBorder af pageHeight - 2 * vertBorder. Den aritmetik er ikke biblioteksspecifik; det er bare geometrien i at tilpasse et mindre rektangel symmetrisk inde i et større

En ting værd at bemærke: SetOrigin(1) sætter koordinatens oprindelse øverst til venstre i stedet for nederst til venstre. De grænseværdier, du sender til DrawCapturedPage, måles fra den oprindelse, du indstiller, så hvis du skifter oprindelsestilstand mellem indlæsning og tegning, vil centreringen være forkert

C#-eksempel

Følgende kode behandler hver side af Pages.pdf gennem fang-og-tegn-igen cyklussen og skriver resultatet til newpages.pdf. PDFL er ActiveX/COM-indpakningsobjektet føjet til projektet fra 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");
}

Delphi-eksempel

Delphi-versionen bruger TPDFlib direkte i stedet for gennem COM-laget, men opkaldssekvensen er identisk. En praktisk forskel er outputfilbeskyttelsen: FileExists plus DeleteFile i stedet for File.Delete, fordi SaveToFile vil mislykkes, hvis destinationen er låst af en tidligere kørsel, der stadig er åben i en fremviser

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;

Hvad skaleringsfaktoren faktisk styrer

Værdien 0,70 her betyder, at det gengivne indhold optager 70% af hver sidedimension, ikke at filen er 70% af dens oprindelige bytestørrelse. Filstørrelse efter denne handling afhænger af kompleksiteten af det originale indhold; en side med store billeder vil ikke krympe proportionalt, fordi pixeldataene tegnes igen med samme opløsning på et mindre område. Hvis komprimering på byteniveau er målet, er den rigtige tilgang LinearizeFile eller gengemning med strømkomprimering, ikke geometrisk skalering

Tallet 70% er heller ikke en hård grænse. Enhver værdi mellem 0,0 og 1,0 fungerer, og værdier over 1,0 forstørrer indholdet ud over den originale sidegrænse, som klippes ved mediebokskanten, medmindre du også øger sidedimensionerne. Dokumenter i blandede størrelser håndteres naturligt, fordi der forespørges om PageWidth og PageHeight pr. side før grænseberegningen, så et dokument, hvor ulige sider er A4, og lige sider er A3, vil producere korrekt centreret output på hver sidestørrelse uden særlige tilfælde

Hvor tingene kan gå galt

To fejltilstande dukker op i praksis. Den første er en outputfil, der efterlades åben i en PDF-fremviser fra en tidligere kørsel: SaveToFile vil mislykkes eller skrive nul bytes afhængigt af platformen, og det nye output lander aldrig. Slettefilbeskyttelsen øverst i funktionen håndterer det til udvikling, men i en produktionspipeline er det mere sikkert at skrive til en midlertidig sti og omdøbe ved succes

Den anden er manglende overensstemmelse i sideantal. Fordi CapturePage fjerner sider fra dokumentet, efterhånden som det behandler dem, er antallet, du læser fra PageCount() før løkken, den korrekte grænse at iterere imod. At kalde PageCount() inde i løkken ville returnere et faldende tal ved hvert gennemløb og afslutte tidligt, hvilket efterlader de sidste sider ubehandlede. Løkkevariablen i eksemplerne fungerer kun som en tæller for resterende iterationer; den bruges aldrig til at vælge en side, fordi den side, der skal vælges, altid er 1 af den årsag, der er forklaret tidligere

De kald til sidemanipulation, der er vist her, inklusive CapturePage, DrawCapturedPage og SetPageDimensions, er en del af losLab PDF Library til Delphi, C#, VB.NET og C++