Kích thước trang PDF được cố định vào thời điểm một trang được tạo ra, do đó bạn không thể đơn giản là thay đổi tỷ lệ nội dung tại chỗ theo cái cách bạn có thể thay đổi kích thước của một hình ảnh. Mô hình thư viện giúp cho việc thu nhỏ trở nên thực tế là chụp-và-vẽ-lại (capture-and-redraw): nhấc nội dung của mỗi trang ra khỏi tài liệu vào một handle, tạo ra một trang trống mới theo kích thước phương tiện (media) ban đầu, sau đó vẽ lại nội dung đã chụp vào một bounding box (hộp giới hạn) bị thu nhỏ. Khoảng trắng xung quanh trở thành lề. Ví dụ, ở tỷ lệ 70% trên trang A4, 15% chiều rộng nằm ở mỗi bên và một tỷ lệ tương tự ở trên cùng và dưới cùng, điều này chính xác là những gì mà số học về viền (border arithmetic) ở dưới đây tạo ra
CapturePage hoạt động như thế nào
CapturePage lấy một số trang, thăng cấp nội dung của trang đó vào một đối tượng chụp trong bộ nhớ, và xóa trang khỏi cây trang của tài liệu. Việc xóa bỏ đó là có chủ ý và là lý do mà vòng lặp luôn luôn chọn trang 1 bất kể chỉ số vòng lặp là gì: một khi trang 1 bị chụp và bị xóa, thứ từng là trang 2 sẽ trở thành trang 1 mới, và cứ thế. Nếu bạn tăng dần bộ chọn trang cùng với bộ đếm của vòng lặp, bạn sẽ bỏ qua cứ mỗi một trang đan xen (skip every other page) và kết thúc với một nửa kết quả đầu ra như mong đợi
Handle chụp được CapturePage trả về không phải là một tham chiếu trang; nó giống như một bản chụp nhanh nội dung (content snapshot). Nó duy trì trạng thái hợp lệ cho đến khi bạn gọi DrawCapturedPage hoặc nhả nó ra một cách tường minh. DrawCapturedPage nhận handle đó cộng với một hình chữ nhật đích (destination rectangle) được cung cấp dưới dạng độ lệch trái, độ lệch dưới cùng, chiều rộng, và chiều cao, tất cả tính bằng point. Thư viện thu phóng nội dung đã chụp để khớp chính xác với hình chữ nhật đó, chỉ bảo toàn tỷ lệ khung hình (aspect ratio) nếu như hình chữ nhật của bạn tình cờ khớp với các tỷ lệ ban đầu. Đối với việc thu phóng đồng đều, bạn muốn hình chữ nhật có kích thước bằng kích thước ban đầu nhân với hệ số tỷ lệ, nằm ở giữa trang
Phép toán căn giữa
Với hệ số tỷ lệ 70%, 30% còn lại của mỗi kích thước được chia đều cho hai bên. Do đó phần bù ngang (horizontal inset) là pageWidth * (1.0 - 0.70) / 2, tức là 15% của chiều rộng, và phần bù dọc cũng tuân theo cùng một công thức sử dụng chiều cao của trang. Hình chữ nhật đích cho DrawCapturedPage khi đó sẽ bắt đầu tại (horizBorder, vertBorder) và kéo dài pageWidth - 2 * horizBorder với pageHeight - 2 * vertBorder. Số học đó không mang tính cụ thể của thư viện; nó chỉ là đặc tính hình học để đưa một hình chữ nhật nhỏ hơn nằm đối xứng vào bên trong một hình chữ nhật lớn hơn
Có một điều đáng lưu ý: SetOrigin(1) đặt gốc tọa độ ở góc trên cùng bên trái thay vì góc dưới cùng bên trái. Các giá trị viền (border) mà bạn truyền cho DrawCapturedPage được đo lường từ bất kỳ gốc tọa độ nào bạn thiết lập, vì vậy nếu bạn chuyển đổi các chế độ gốc tọa độ ở giữa lúc tải (loading) và vẽ (drawing), tính năng căn giữa sẽ bị tắt
Ví dụ C#
Đoạn mã sau đây xử lý từng trang của Pages.pdf thông qua chu trình chụp-và-vẽ-lại và ghi kết quả ra newpages.pdf. PDFL là đối tượng bao bọc ActiveX/COM (wrapper object) được thêm vào dự án từ 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");
}
Ví dụ Delphi
Phiên bản Delphi sử dụng TPDFlib trực tiếp thay vì đi qua lớp COM, nhưng trình tự gọi là hoàn toàn giống nhau. Một khác biệt trên thực tế là biện pháp bảo vệ tệp đầu ra: FileExists cộng với DeleteFile thay vì File.Delete, bởi vì SaveToFile sẽ bị lỗi nếu điểm đích bị khóa bởi một lần chạy trước đó vẫn đang mở trong một trình xem
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;
Hệ số tỷ lệ thực sự điều khiển cái gì
Giá trị 0,70 ở đây có nghĩa là nội dung được kết xuất sẽ chiếm 70% của mỗi kích thước trang, chứ không phải tệp chỉ bằng 70% kích thước byte ban đầu của nó. Kích thước tệp sau thao tác này phụ thuộc vào độ phức tạp của nội dung gốc; một trang chứa các hình ảnh lớn sẽ không thu nhỏ lại theo tỷ lệ tương xứng vì dữ liệu pixel được vẽ lại ở cùng độ phân giải vào một khu vực nhỏ hơn. Nếu mục tiêu là nén ở cấp độ byte, cách tiếp cận đúng là LinearizeFile hoặc lưu lại với tính năng nén luồng, chứ không phải là thay đổi tỷ lệ hình học
Con số 70% cũng không phải là một giới hạn cứng. Bất kỳ giá trị nào trong khoảng từ 0,0 đến 1,0 đều hoạt động được, và các giá trị lớn hơn 1,0 sẽ phóng to nội dung vượt quá ranh giới trang ban đầu, nó sẽ cắt xén ở mép hộp phương tiện (media box edge) trừ khi bạn cũng làm tăng kích thước của trang. Các tài liệu có kích thước hỗn hợp được xử lý một cách tự nhiên vì PageWidth và PageHeight được truy vấn theo mỗi trang trước khi tính toán viền, do đó một tài liệu nơi các trang lẻ là A4 và các trang chẵn là A3 sẽ tạo ra một kết quả đầu ra được căn giữa một cách chính xác trên mỗi kích thước trang mà không cần trường hợp đặc biệt nào
Mọi thứ có thể sai ở đâu
Có hai chế độ thất bại xảy ra trong thực tế. Thứ nhất là một tệp đầu ra vẫn đang mở trong một trình xem PDF từ một lần chạy trước: SaveToFile sẽ báo lỗi hoặc ghi không (0) byte tùy thuộc vào nền tảng, và đầu ra mới không bao giờ đáp xuống. Lính canh xóa-tệp (file-delete guard) ở phần đầu của hàm xử lý điều đó cho mục đích phát triển, nhưng trong một pipeline sản xuất, việc ghi vào một đường dẫn tạm thời và đổi tên khi thành công thì an toàn hơn
Thứ hai là sự không khớp về số lượng trang. Vì CapturePage loại bỏ các trang khỏi tài liệu khi nó xử lý chúng, số đếm bạn đọc được từ PageCount() trước vòng lặp là giới hạn đúng để làm vòng lặp đi đến đó. Nếu gọi PageCount() bên trong vòng lặp thì sẽ trả về một con số giảm dần sau mỗi lần duyệt và thoát sớm, để lại các trang cuối cùng không được xử lý. Biến vòng lặp trong các ví dụ chỉ đóng vai trò như một bộ đếm số lần duyệt còn lại; nó không bao giờ được dùng để chọn trang, vì trang cần chọn luôn là số 1 bởi lý do đã được giải thích trước đó
Các lệnh gọi thao tác trang được hiển thị ở đây, bao gồm CapturePage, DrawCapturedPage, và SetPageDimensions, là một phần của losLab PDF Library cho Delphi, C#, VB.NET, và C++