Teknisk artikel

Skala PDF-sidor till 70 % med losLab PDF Library

PDF-siddimensioner fastställs vid den tidpunkt då en sida skapas, så du kan inte bara skala om innehåll på plats på det sätt som du kanske ändrar storlek på en bild. Biblioteksmodellen som gör krympning praktisk är fånga-och-rita-om (capture-and-redraw): lyft varje sidas innehåll ut ur dokumentet till ett handtag, skapa en ny tom sida i ursprunglig mediestorlek, och rita sedan in det fångade innehållet igen i en reducerad gränsruta (bounding box). Det omgivande vita utrymmet blir marginalen. Vid 70 % skala på en A4-sida till exempel, faller 15 % av bredden på varje sida och samma andel i toppen och botten, vilket är exakt vad kant-aritmetiken nedan producerar

Hur CapturePage fungerar

CapturePage tar ett sidnummer, befordrar den sidans innehåll in i ett minnesbaserat capture-objekt, och tar bort sidan från dokumentets sidträd. Den borttagningen är avsiktlig och är anledningen till att loopen alltid väljer sida 1 oavsett iterationsindex: när väl sida 1 fångats och raderats blir det som var sida 2 den nya sida 1, och så vidare. Om du ökar sidväljaren tillsammans med loopräknaren kommer du att hoppa över varannan sida och hamna med hälften av den förväntade utmatningen

Capture-handtaget som returneras av CapturePage är inte en sidreferens; det är snarare som en innehålls-snapshot. Den förblir giltig tills du anropar DrawCapturedPage eller släpper den uttryckligen. DrawCapturedPage tar det handtaget plus en destinationsrektangel angiven som vänsterförskjutning (left offset), bottenförskjutning (bottom offset), bredd, och höjd, allt i punkter. Biblioteket skalar det fångade innehållet för att exakt passa den rektangeln, och bevarar endast bildförhållandet (aspect ratio) om din rektangel råkar matcha de ursprungliga proportionerna. För enhetlig skalning vill du att rektangeln ska vara ursprungsstorleken multiplicerad med skalfaktorn, centrerad på sidan

Centreringsmatematiken

Med en skalfaktor på 70 % delas de resterande 30 procenten av varje dimension lika mellan de två sidorna. Så det horisontella insticket (inset) är pageWidth * (1.0 - 0.70) / 2, vilket är 15 % av bredden, och det vertikala insticket följer samma formel utifrån sidhöjden. Destinationsrektangeln för DrawCapturedPage startar då på (horizBorder, vertBorder) och spänner över pageWidth - 2 * horizBorder och pageHeight - 2 * vertBorder. Den aritmetiken är inte biblioteksspecifik; det är bara geometrin i att passa en mindre rektangel symmetriskt inuti en större

En sak värd att notera: SetOrigin(1) sätter koordinatorigin uppe till vänster (top-left) snarare än nere till vänster (bottom-left). Kantvärdena du skickar till DrawCapturedPage mäts från vilket origo du än sätter, så om du byter origo-lägen mellan inläsning och uppritning kommer centreringen att hamna fel

C#-exempel

Följande kod bearbetar varje sida i Pages.pdf genom fånga-och-rita-om-cykeln och skriver resultatet till newpages.pdf. PDFL är ActiveX/COM-wrapper-objektet som lagts till i projektet från 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-exempel

Delphi-versionen använder TPDFlib direkt hellre än genom COM-lagret, men anropssekvensen är identisk. En praktisk skillnad är fil-skyddet (file guard) för utmatningen: FileExists plus DeleteFile istället för File.Delete, eftersom SaveToFile kommer att misslyckas om destinationen är låst av en tidigare körning som fortfarande är öppen i en visare

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;

Vad skalfaktorn faktiskt styr

Värdet 0,70 här betyder att det renderade innehållet upptar 70 % av varje siddimension, inte att filen är 70 % av dess ursprungliga bytestorlek. Filstorlek efter denna operation beror på komplexiteten i originalinnehållet; en sida med stora bilder kommer inte att krympa proportionellt eftersom pixeldatan ritas om vid samma upplösning i ett mindre område. Om komprimering på bytenivå är målet är rätt tillvägagångssätt LinearizeFile eller att spara om med strömkomprimering, inte geometrisk skalning

Siffran 70 % är heller inte en hård gräns. Vilket värde som helst mellan 0,0 och 1,0 fungerar, och värden över 1,0 förstorar innehåll bortom den ursprungliga sidogränsen, vilket klipps av (clips) vid mediaboxens kant såvida du inte också ökar siddimensionerna. Dokument med blandade storlekar hanteras naturligt eftersom PageWidth och PageHeight frågas per sida innan kantberäkningen, så ett dokument där udda sidor är A4 och jämna sidor är A3 kommer att producera korrekt centrerad utmatning på varje sidstorlek utan något specialfall

Där saker kan gå fel

Två fellägen dyker upp i praktiken. Det första är en utmatningsfil som lämnats öppen i en PDF-visare från en tidigare körning: SaveToFile kommer att misslyckas eller skriva noll byte beroende på plattformen, och den nya utmatningen landar aldrig. Filborttagningsskyddet (file-delete guard) överst i funktionen hanterar det för utveckling, men i en produktionspipeline är det säkrare att skriva till en tillfällig sökväg och byta namn (rename) vid framgång

Det andra är obalans i sidantal (page count mismatch). Eftersom CapturePage tar bort sidor från dokumentet i takt med att det bearbetar dem, är antalet du läser från PageCount() innan loopen den korrekta gränsen (bound) att iterera mot. Att anropa PageCount() inuti loopen skulle returnera ett minskande tal vid varje passering och avsluta i förtid, vilket lämnar de sista sidorna obearbetade. Loopvariabeln i exemplen tjänar bara som en räknare för återstående iterationer; den används aldrig för att välja en sida, eftersom sidan att välja alltid är 1 av anledningen som förklarades tidigare

Sidmanipuleringsanropen som visas här, inklusive CapturePage, DrawCapturedPage, och SetPageDimensions, är en del av losLab PDF Library för Delphi, C#, VB.NET och C++