Technical Article

Мащабиране на PDF страници до 70% с PDF библиотеката на losLab

Размерите на страниците на PDF са фиксирани в момента на създаване на страницата, така че не можете просто да преоразмерите съдържанието на място, както бихте направили с изображение. Моделът на библиотеката, който прави смаляването практично, е „улавянÐ?и повторно изчертаванеâ€?(capture-and-redraw): извличате съдържанието на всяка страница от документа в манипулатор, създавате нова празна страница с оригиналния размер на носителя и след това изчертавате обратно уловеното съдържание в намалена ограничителна кутия. Околното празно пространство се превръща в поле. При мащаб от 70% на страница А4 например, 15% от ширината остава от всяка страна и същата част се пада отгоре и отдолу, което е точно това, което аритметиката за рамката по-долу изчислява.

Как работи CapturePage

Методът CapturePage приема номер на страница, прехвърля съдържанието на тази страница в обект за улавяне в паметта и премахва страницата от дървото на страниците на документа. Това премахване е умишлено и е причината цикълът винаги да избира страница 1, независимо от индекса на итерацията: след като страница 1 е уловена и изтрита, това, което е било страница 2, става новата страница 1 и т.н. Ако увеличавате селектора на страници заедно с брояча на цикъла, ще прескочите всяка втора страница и ще получите наполовина по-малко страници от очакваното.

Манипулаторът за улавяне, върнат от CapturePage, не е референция към страница; той е по-скоро моментна снимка на съдържанието. Той остава валиден, докато не извикате DrawCapturedPage or не го освободите изрично. DrawCapturedPage приема този манипулатор плюс целеви правоъгълник, зададен като ляво отместване, долно отместване, ширина и височина, всички в точки. Библиотеката мащабира уловеното съдържание, за да пасне точно на този правоъгълник, като запазва пропорциите само ако правоъгълникът ви съвпада с оригиналните пропорции. За равномерно мащабиране целевият правоъгълник трябва да бъде оригиналният размер, умножен по коефициента на мащабиране, центриран на страницата.

Математиката зад центрирането

При мащаб от 70%, останалите 30% от всяко измерение се разделят поравно между двете страни. Така хоризонталното отместване е pageWidth * (1.0 - 0.70) / 2, което е 15% от ширината, а вертикалното отместване следва същата формула, като се използва височината на страницата. Тогава целевият правоъгълник за DrawCapturedPage започва от (horizBorder, vertBorder) и се разпростира на pageWidth - 2 * horizBorder по pageHeight - 2 * vertBorder. Тази аритметика не е специфична за библиотеката; тя е просто геометрия на симетричното вписване на по-малък правоъгълник в по-голям.

Едно нещо, което си струва да се отбележи: SetOrigin(1) поставя началото на координатната система в горния ляв ъгъл вместо в долния ляв. Стойностите за границата, които предавате на DrawCapturedPage, се измерват от това начало, което сте задали, така че ако превключите режимите на координатното начало между зареждането и изчертаването, центрирането ще се измести.

Пример на C#

Следният код обработва всяка страница от Pages.pdf чрез цикъла на улавяне и повторно изчертаване и записва резултата в newpages.pdf. PDFL е ActiveX/COM обвиващият обект, добавен към проекта от 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

Версията за Delphi използва директно TPDFlib вместо през COM слоя, но последователността на извикванията е идентична. Една практическа разлика е защитата на изходния файл: използване на FileExists плюс DeleteFile вместо File.Delete, тъй като SaveToFile ще се провали, ако целевият файл е заключен от предишно стартиране и е отворен в четец.

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;

Какво всъщност контролира коефициентът на мащабиране

Стойността 0.70 тук означава, че изобразеното съдържание заема 70% от всяко измерение на страницата, а не че файлът става 70% от оригиналния си размер в байтове. Размерът на файла след тази операция зависи от сложността на оригиналното съдържание; страница с големи изображения няма да се смали пропорционално, тъй като данните за пикселите се преначертават със същата резолюция в по-малка област. Ако целта е компресия на ниво байтове, правилният подход е LinearizeFile или повторно запазване с компресия на потока, а не геометрично мащабиране.

Стойността от 70% също така не е твърдо ограничение. Всяка стойност между 0.0 и 1.0 работи, а стойности над 1.0 увеличават съдържанието извън първоначалните граници на страницата, което го изрязва на ръба на медийната кутия (media box), освен ако не увеличите и размерите на страницата. Документи с различен размер на страниците се обработват естествено, тъй като PageWidth и PageHeight се запитват за всяка страница преди изчисляването на границата, така че документ, в който нечетните страници са A4, а четните â€?A3, ще генерира правилно центриран изходен резултат за всеки размер на страницата без нужда от специална обработка.

Къде нещата могат да се объркат

В практиката се срещат два режима на срив. Първият е изходният файл да бъде оставен отворен в PDF четец от предишно стартиране: тогава SaveToFile ще се провали или ще запише нула байта в зависимост от платформата, и новият резултат никога няма да бъде записан. Защитата за изтриване на файл в началото на функцията се справя с това по време на разработка, но в производствена среда е по-безопасно да се пише във временен път и файлът да се преименува при успех.

Вторият е несъответствие в броя на страниците. Тъй като CapturePage премахва страници от документа, докато ги обработва, броят, който четете от PageCount() преди цикъла, е правилната граница, спрямо която да извършвате итерациите. Извикването на PageCount() вътре в цикъла би върнало намаляващ брой при всяко преминаване и цикълът ще завърши преждевременно, оставяйки последните страници необработени. Променливата на цикъла в примерите служи само като брояч на оставащите итерации; тя никога не се използва за избор на страница, тъй като страницата за избор винаги е 1 поради обяснената по-горе причина.

Извикванията за манипулиране на страници, показани тук, включително CapturePage, DrawCapturedPage и SetPageDimensions, са част от PDF библиотеката на losLab за Delphi, C#, VB.NET и C++.