Một nhóm giải quyết khiếu nại đã có ba mươi năm hồ sơ giấy chạy qua một máy quét nạp tờ. Máy quét nhả ra một JPEG cho mỗi trang vào một thư mục, có tên 0001.jpg, 0002.jpg, v.v. Điều mà kho lưu trữ thực sự cần là một PDF cho mỗi tệp hồ sơ, với các trang theo thứ tự, để người đánh giá có thể mở một tài liệu duy nhất thay vì nhấp qua một trăm hình thu nhỏ. Bước cuối cùng đó, biến một đống ảnh quét được đánh số thành một tệp PDF duy nhất có thứ tự, là công việc ở đây
Component PDFium xử lý việc này một cách trực tiếp. Ngoài việc kết xuất và trích xuất văn bản, component này có thể xây dựng một tệp PDF từ đầu: tạo một tài liệu trống, thêm một trang trống có kích thước tùy ý bạn, thả một hình ảnh lên trang đó theo tọa độ không gian người dùng, sau đó lưu lại. Toàn bộ quy trình này nằm trên component TPdf, vì vậy một trình chuyển đổi hàng loạt là một vòng lặp qua các tên tệp cộng với một số ít các lệnh gọi
Hình dạng của quá trình chuyển đổi
Ba điều phải xảy ra cho mỗi bản quét. Bạn quyết định kích thước trang, bạn đặt hình ảnh vào trong trang để lại một lề và bạn chuyển sang trang tiếp theo. Component PDFium cung cấp cho bạn một phương thức cho mỗi công việc: AddPage tạo ra một trang trống ở một kích thước nhất định, AddImage (hoặc AddPicture nếu bạn đã giữ một TPicture) vẽ bitmap vào trang hiện tại và PageNumber cho component biết lệnh vẽ tiếp theo sẽ nhắm mục tiêu vào trang nào
Chi tiết duy nhất khiến mọi người nhầm lẫn là hệ tọa độ. Không gian người dùng PDF đặt gốc ở góc dưới cùng bên trái của trang, với Y tăng dần lên trên, ngược lại với tọa độ màn hình mà các nhà phát triển Delphi thường phản xạ với. X, Y mà bạn truyền cho AddImage là góc dưới bên trái của hình chữ nhật hình ảnh và Width, Height là kích thước vị trí tính bằng điểm, không phải kích thước pixel của tệp nguồn. Làm ngược lại điều này và các bản quét của bạn sẽ rơi ra khỏi trang hoặc bị lộn ngược so với vị trí bạn mong đợi chúng
Tạo tài liệu và mỗi trang cho một bản quét
Bắt đầu với một tài liệu trống. CreateDocument cấp phát một tệp PDF mới và để component hoạt động, vì vậy không có bước mở riêng biệt. Từ đó, bạn duyệt qua danh sách các tệp được quét và đối với mỗi tệp, bạn thêm một trang, đặt nó làm trang hiện tại và đặt hình ảnh. Kích thước trang ở đây là A4 tính bằng điểm (595 × 842 hướng dọc), kích thước tờ chuẩn cho thư từ lưu trữ
procedure TArchiveForm.ScansToPdf(const Files: TStrings; const OutputPath: string);
const
PageW = 595.0; // A4 width in points
PageH = 842.0; // A4 height in points
Margin = 36.0; // half-inch border around each scan
var
I: Integer;
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument; // new, empty, already active
for I := 0 to Files.Count - 1 do
begin
Pdf.AddPage(I + 1, PageW, PageH); // 1-based page index
Pdf.PageNumber := I + 1; // make the new page current
PlaceScan(Pdf, Files[I], PageW, PageH, Margin);
end;
Pdf.SaveAs(OutputPath);
finally
Pdf.Free;
end;
end;
Mỗi lần lặp lại tạo ra một trang và ngay lập tức đặt PageNumber thành trang đó. Dòng thứ hai đó rất quan trọng: AddPage chèn trang nhưng các phương thức vẽ sẽ tác động lên bất kỳ trang nào hiện tại, do đó, đặt PageNumber là thao tác nhắm AddImage vào trang bạn vừa tạo. Bỏ qua nó và hình ảnh của bạn sẽ xếp chồng lên bất kỳ trang nào tình cờ được tải trước đó
Một giả định ẩn trong vòng lặp đó: thứ tự của Files. Máy quét đặt tên các trang từ 0001.jpg đến 0100.jpg, nhưng việc liệt kê thư mục không phải lúc nào cũng trả về chúng theo thứ tự đã sắp xếp và ngay khi bạn gặp page9.jpg bên cạnh page10.jpg, một sắp xếp chuỗi thuần túy sẽ đặt trang 10 trước trang 9. Sắp xếp danh sách rõ ràng trước vòng lặp và ưu tiên các tên được đệm số không tại thời điểm quét để thứ tự từ vựng khớp với thứ tự trang. Trình tự trang là điều duy nhất mà người đánh giá nhận thấy ngay lập tức và đây là sai lầm rẻ nhất có thể ngăn chặn
Đặt một bản quét và giữ tỷ lệ khung hình của nó
Một bản quét hiếm khi có cùng hình dạng với trang. Nếu bạn kéo căng nó để lấp đầy tờ giấy, bạn sẽ làm biến dạng văn bản; nếu bạn đặt nó ở kích thước pixel đầy đủ, nó sẽ tràn ra ngoài. Cách khắc phục là chia tỷ lệ theo tỷ lệ nhỏ hơn của hai tỷ lệ, vừa chiều rộng hoặc vừa chiều cao và căn giữa phần còn lại. Bởi vì gốc tọa độ nằm ở phía dưới cùng bên trái, việc căn giữa có nghĩa là chia đều không gian thừa và cộng nó vào cả X và Y
procedure TArchiveForm.PlaceScan(Pdf: TPdf; const FileName: string;
PageW, PageH, Margin: Double);
var
Pic: TPicture;
AvailW, AvailH, Scale, DrawW, DrawH, X, Y: Double;
begin
Pic := TPicture.Create;
try
Pic.LoadFromFile(FileName); // BMP, JPG, PNG, etc. via the VCL graphics units
AvailW := PageW - 2 * Margin;
AvailH := PageH - 2 * Margin;
// Fit inside the margins without distorting the scan.
Scale := Min(AvailW / Pic.Width, AvailH / Pic.Height);
DrawW := Pic.Width * Scale;
DrawH := Pic.Height * Scale;
// Center: leftover space split evenly. Y measured from the page bottom.
X := (PageW - DrawW) / 2;
Y := (PageH - DrawH) / 2;
Pdf.AddImage(FileName, X, Y, DrawW, DrawH);
finally
Pic.Free;
end;
end;
Điều này tải tệp một lần để đọc kích thước pixel của nó, tính toán một tỷ lệ đồng nhất duy nhất và truyền hình chữ nhật vị trí cho AddImage. AddImage chấp nhận trực tiếp một đường dẫn tệp và định tuyến nó qua cùng một đường ống hình ảnh như AddPicture, do đó, bất kỳ định dạng nào mà các đơn vị đồ họa VCL nhận ra đều hoạt động mà không cần trường hợp đặc biệt. Nếu bạn đã có hình ảnh được giải mã trong TPicture từ một ngăn xem trước, hãy gọi AddPicture(Pic, X, Y, DrawW, DrawH) với cùng một hình chữ nhật và bỏ qua lần đọc tệp thứ hai
Bỏ qua giải mã đối với các bản quét JPEG
Máy quét hầu như luôn phát ra định dạng JPEG. Tải một tệp JPEG vào TPicture sẽ giải mã nó thành một bitmap, và sau đó PDFium mã hóa lại nó khi lưu, hai chuyến đi khứ hồi có tổn hao mà bạn không cần. AddJpegImage nhúng trực tiếp các byte nén gốc vào trang từ một luồng, vừa nhanh hơn vừa sạch hơn về mặt hình ảnh đối với một lô khối lượng lớn
var
Stream: TFileStream;
begin
// ... after AddPage + PageNumber for the current page ...
Stream := TFileStream.Create(FileName, fmOpenRead);
try
// Embeds the JPEG bytes as-is; no decode/re-encode cycle.
Pdf.AddJpegImage(Stream, X, Y, DrawW, DrawH);
finally
Stream.Free;
end;
end;
Bạn vẫn tính toán X, Y, DrawW và DrawH theo cùng một cách, vì bạn cần kích thước pixel để chia tỷ lệ. Đọc những dữ liệu đó từ tệp hoặc phân tích cú pháp tiêu đề nhanh, sau đó chuyển luồng thô cho AddJpegImage. Đối với các bản quét PNG hoặc TIFF, đường dẫn AddImage là đường dẫn phù hợp; hãy dành phím tắt JPEG cho định dạng mà nó thực sự áp dụng
Gắn nhãn cho từng trang
Các bản quét lưu trữ dễ kiểm tra hơn khi mỗi trang mang tên tệp nguồn của nó. AddText vẽ một chuỗi ở tọa độ không gian người dùng, vì vậy chú thích nằm ngay dưới hình ảnh. Hãy nhớ trục Y bị đảo ngược: để đặt nhãn bên dưới bản quét, bạn trừ đi cạnh dưới của hình ảnh chứ không phải thêm vào
// Caption below the scan: Y decreases toward the page bottom.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
X, Y - 14, clGray);
Một điểm cuối cùng về việc lưu. SaveAs là một hàm trả về giá trị Boolean, do đó, trong mã sản xuất, hãy kiểm tra kết quả của nó thay vì giả định rằng quá trình ghi đã thành công; nếu không, một ổ đĩa đầy hoặc đường dẫn đầu ra bị khóa sẽ không thành công trong im lặng. Khi vòng lặp kết thúc và tệp được ghi, bạn có chính xác những gì kho lưu trữ cần: một tệp PDF có thứ tự cho mỗi tệp hồ sơ, các trang được chia tỷ lệ cho vừa vặn, sẵn sàng để đọc trong bất kỳ trình xem nào
Các khối xây dựng tương tự bao gồm các công việc liên quan. Hoán đổi quy tắc định cỡ trên mỗi trang và bạn có một sổ ảnh với một ảnh trên mỗi tờ; giữ vòng lặp nhưng đọc từ một nguồn nhiều trang TIFF và bạn có một trình chuyển đổi lưu trữ fax. Nếu bạn muốn bức tranh rộng hơn về cách lập trình tạo các tệp PDF, hãy xem phần tạo tài liệu PDF từ đầu bằng Component PDFium; để kết xuất kết quả trở lại màn hình sau này, hãy xem phần chuyển đổi trang PDF sang hình ảnh JPEG bằng Component PDFium
PDFium Component từ loslab.com kết hợp các API tạo tài liệu, kết xuất và văn bản được sử dụng trong suốt loạt bài này