Розміри сторінки PDF фіксуються під час її створення, тому ви не можете просто змінити масштаб вмісту на місці так, як це робиться при зміні розміру зображення. Модель бібліотеки, яка робить зменшення практичним, - це захоплення та перемальовування: витягніть вміст кожної сторінки з документа в дескриптор, створіть нову порожню сторінку оригінального розміру медіа, а потім намалюйте захоплений вміст назад у зменшеному обмежувальному прямокутнику. Навколишній порожній простір стає полем. Наприклад, при масштабі 70% на сторінці A4 по 15% ширини припадає на кожну сторону, і така ж частка залишається зверху і знизу, що точно відповідає арифметиці меж, наведеній нижче
Як працює CapturePage
CapturePage приймає номер сторінки, переводить вміст цієї сторінки в об'єкт захоплення в пам'яті та видаляє сторінку з дерева сторінок документа. Це видалення є навмисним і є причиною того, що цикл завжди вибирає сторінку 1 незалежно від індексу ітерації: як тільки сторінка 1 захоплена і видалена, те, що було сторінкою 2, стає новою сторінкою 1, і так далі. Якщо ви збільшуватимете селектор сторінки разом з лічильником циклу, ви пропустите кожну другу сторінку і в результаті отримаєте лише половину очікуваного результату
Дескриптор захоплення, що повертається CapturePage, не є посиланням на сторінку; це більше схоже на знімок вмісту. Він залишається дійсним, доки ви не викличете DrawCapturedPage або не звільните його явно. 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 збільшують вміст за межі оригінальної сторінки, що обрізається по краю медіа-боксу, якщо ви також не збільшите розміри сторінки. Документи зі змішаними розмірами обробляються природно, оскільки PageWidth і PageHeight запитуються для кожної сторінки перед розрахунком меж, тому документ, у якому непарні сторінки мають формат A4, а парні - A3, створить правильно відцентрований результат на кожному розмірі сторінки без жодних спеціальних умов
Де щось може піти не так
На практиці виникають два типи збоїв. Перший - це вихідний файл, залишений відкритим у програмі перегляду PDF з попереднього запуску: SaveToFile завершиться помилкою або запише нуль байтів залежно від платформи, і новий результат ніколи не буде збережено. Захист із видаленням файлу на початку функції вирішує це під час розробки, але у виробничому процесі безпечнішим є запис у тимчасовий шлях і перейменування у разі успіху
Другий - це невідповідність кількості сторінок. Оскільки CapturePage видаляє сторінки з документа під час їхньої обробки, кількість, яку ви зчитуєте з PageCount() перед циклом, є правильною межею для ітерації. Виклик PageCount() всередині циклу повертатиме число, що зменшується при кожному проході, і призведе до передчасного виходу, залишаючи останні сторінки необробленими. Змінна циклу в прикладах слугує лише як лічильник ітерацій, що залишилися; вона ніколи не використовується для вибору сторінки, тому що сторінка для вибору завжди 1 з причини, поясненої раніше
Показані тут виклики для маніпуляцій зі сторінками, включно з CapturePage, DrawCapturedPage та SetPageDimensions, є частиною losLab PDF Library для Delphi, C#, VB.NET та C++