PDF Library for Delphi render HTML vào một trang PDF với bố cục hai chiều thực sự: display: flex và display: grid được đo đạc và đặt vị trí thay vì bị hạ cấp thành các khối xếp chồng, và chú thích cuối trang được dành sẵn chỗ ở đáy hộp mang tham chiếu của chúng, với đánh số liên tục xuyên suốt các cột và trang. Các điểm vào là những cái quen thuộc, DrawHTMLTextBox cho một hộp đơn và DrawHTMLStory cho luồng nhiều cột
Điều này quan trọng vì HTML hiện là cách phần lớn nội dung báo cáo đến. Mẫu được soạn bởi những người viết CSS, dashboard được thiết kế dưới dạng thẻ (card), và một renderer âm thầm sụp một hàng flex thành bốn khối xếp chồng sẽ tạo ra một tài liệu hoàn toàn không giống với thiết kế. Cho đến khi khả năng này tồn tại, container hai chiều duy nhất mà bộ máy đo đạc được là bảng, nên mọi bố cục dạng thẻ phải được viết lại bằng tay thành bảng
Điều gì đã thay đổi trong mô hình bố cục?
Vòng lặp chính trước đây duy trì một hộp dòng duy nhất và tiến dần xuống trang. Mô hình đó xử lý nội dung inline và các khối xếp chồng hoàn hảo và không thể diễn tả một container mà các phần tử con của nó được định cỡ tương quan với nhau. Bảng là ngoại lệ duy nhất, với phép đo hai lượt riêng của chúng
Flex và grid mỗi thứ đều thêm một lượt đo bị giới hạn qua các phần tử con của một container, và từ quan trọng là bị giới hạn. Một container flex đo tối đa 256 phần tử con trực tiếp vào một mảng cố định. Một grid dùng một ma trận chiếm dụng tối đa 64 nhân 64 ô để đặt tự động có thể xác định trước. Những trần đó tồn tại để một stylesheet mang tính đối kháng hoặc được sinh tự động không thể gây ra đệ quy vô hạn hay bộ nhớ đặt vị trí bậc hai, đây là một mối lo thực sự khi HTML đến từ một mẫu mà khách hàng chỉnh sửa
Các phần tử flex nhận kích thước của chúng như thế nào
Theo hướng hàng, container cộng gộp cơ sở (basis) của mỗi phần tử cùng với trọng số grow và shrink của nó, rồi phân bổ khoảng trống còn lại, dương hoặc âm, theo những trọng số đó. Với flex-wrap, mỗi dòng được giải quyết độc lập, nên một hàng ngắt thành hai dòng sẽ gán khoảng trống tự do theo từng dòng thay vì trên toàn container. Theo hướng cột, cùng phép phân bổ trục chính đó chạy dựa trên hoặc một chiều cao tường minh hoặc chiều cao nội dung
justify-content, align-items, gap và các hướng đảo ngược vận hành trên hình học đã được đo trước đó. Chúng di chuyển các hộp; chúng không bao giờ kích hoạt việc đo lại nội dung phần tử. Sự tách biệt đó chính là điều giữ cho một dashboard phức tạp không phải đo các phần tử con của nó nhiều lần
uses
PDFlibrary;
var
Lib: TPDFlib;
Html, Remainder: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
Lib.SetPageSize('A4');
Lib.NewPage;
Html :=
'<div style="display:flex; gap:12px;">' +
' <div style="flex:2 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Revenue</b><br/>EUR 4,182,300</div>' +
' <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Margin</b><br/>18.4%</div>' +
' <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Backlog</b><br/>92 days</div>' +
'</div>';
Remainder := Lib.DrawHTMLTextBox(40, 40, 515, 120, Html);
if Remainder <> '' then
Log('content did not fit - carry the remainder to the next box');
Lib.SaveToFile('dashboard.pdf');
finally
Lib.Free;
end;
end;
Giá trị trả về là chuỗi tiếp nối, đây là cách mọi điểm vào vẽ HTML báo cáo những gì chưa vừa. Truyền nó cho hộp tiếp theo hoặc trang tiếp theo và luồng tiếp tục từ nơi nó dừng lại
Đặt vị trí grid, và một track có thể là gì
Track của grid chấp nhận độ dài cố định, phần trăm, đơn vị fr, các biểu thức repeat() đơn giản và minmax(). Việc đặt tự động lấp đầy ma trận chiếm dụng một cách xác định, nên cùng một HTML luôn tạo ra cùng một cách sắp xếp. Tọa độ tường minh được phép chồng lấn, đây là điều có chủ đích: một thiết kế xếp một huy hiệu lên trên một thẻ đang thể hiện chủ ý, không phải một lỗi. Khi chỉ một trục được cho tường minh, việc đặt chỉ tìm kiếm trên trục còn lại
Các phần tử trải dài qua nhiều hàng đóng góp chiều cao đã đo của chúng trở lại các hàng mà chúng bao phủ, được lấy trung bình qua các hàng đó, giữ cho một phần tử trải dài cao không ép một hàng đơn lẻ trong khi để các hàng lân cận bị thấp:
Html :=
'<div style="display:grid; grid-template-columns:repeat(3, 1fr); ' +
' gap:10px;">' +
' <div style="grid-row:span 2; background:#eef;">Site plan</div>' +
' <div>Inspector</div>' +
' <div>Date</div>' +
' <div style="grid-column:2 / span 2;">Findings summary</div>' +
'</div>';
Remainder := Lib.DrawHTMLTextBox(40, 180, 515, 260, Html);
Các phần tử con flex và grid được render qua cùng bộ render HTML như mọi thứ khác, đây chính là thuộc tính khiến tính năng này dùng được thay vì trở thành một thế giới riêng biệt. Font, cascade CSS, liên kết, hình ảnh, bảng và các container flex hoặc grid lồng nhau tiếp theo đều hoạt động bên trong một phần tử flex đúng như chúng hoạt động ở cấp cao nhất, và kế hoạch bố cục bên ngoài ghi lại các lệnh văn bản và hình chữ nhật cuối cùng để việc vẽ lặp lại tái sử dụng bộ nhớ đệm đo đạc đã có
Vì sao chú thích cuối trang lại là một bài toán phân trang?
Một chú thích cuối trang không phải nội dung chảy sau đoạn văn chứa tham chiếu của nó; nó là nội dung phải xuất hiện ở đáy của cùng hộp với tham chiếu của nó. Điều đó đảo ngược thứ tự đo lường thông thường, vì khoảng trống dành cho văn bản chính giờ đây phụ thuộc vào nội dung chưa được bố cục
Vì vậy renderer đo chú thích khi gặp tham chiếu, và trừ diện tích chú thích khỏi ngân sách chiều cao phần thân của hộp bị giới hạn hiện tại. Nếu tham chiếu, phần văn bản chính đến thời điểm đó và chú thích không thể cùng vừa, dấu hiệu chú thích và mọi thứ sau nó sẽ cùng chuyển sang chuỗi tiếp nối. Quy tắc đó chính là thứ ngăn hai thất bại kinh điển: một chú thích đè lên văn bản chính, và một chú thích mắc kẹt trên một trang mà tham chiếu của nó nằm ở trang trước
Trong một hộp bị giới hạn, vùng chú thích được ghim ở đáy với một quy tắc phân cách phía trên nó. Trong phép đo không giới hạn, nơi không có chiều cao hộp để ghim vào, vùng chú thích theo ngay sau phần thân. Việc đánh số được mang trong một trường mở rộng trên stack tiếp nối, nên DrawHTMLTextBox và DrawHTMLStory giữ chuỗi đánh số chạy liên tục qua các cột và trang, và một chuỗi tiếp nối được tạo ra trước khi trường đó tồn tại vẫn tiếp tục đúng
// Chú thích cuối trang bên trong một story nhiều cột giữ một
// chuỗi đánh số liên tục duy nhất
Html := LoadTemplate('chapter.html'); // dùng đánh dấu float:footnote
Remainder := Lib.DrawHTMLStory(40, 40, 515, 700,
2, // số cột
16, // rãnh cột tính bằng điểm
20, // số trang tối đa cho story này
Html);
if Remainder <> '' then
Log('story exceeded its page budget');
Hướng dẫn thực tế cho người soạn mẫu
Hãy thiết kế trong phạm vi các trần đã được ghi lại. Một container flex có hơn 256 phần tử con trực tiếp gần như luôn là một bảng dữ liệu khoác lớp áo flex, và đường xử lý bảng đo nó tốt hơn dù sao đi nữa. Một grid lớn hơn 64 nhân 64 là một bảng tính, và cùng lời khuyên đó áp dụng. Với văn bản phần thân nhiều cột, hành vi cột và ngắt từ được mô tả trong ngắt từ và các cột văn bản cân bằng chi phối cách luồng trông ra sao bên trong mỗi cột
Hãy đo trước khi vẽ khi một bố cục phải vừa khít. GetHTMLTextHeight báo cáo chiều cao mà một độ rộng cho trước sẽ cần, đây là cách rẻ để quyết định giữa bố cục này và bố cục khác trước khi cam kết mực in. Và hãy coi một chuỗi tiếp nối không rỗng là bình thường thay vì ngoại lệ: đó là cơ chế nhờ đó nội dung dài được phân trang, không phải một tín hiệu lỗi
Khi HTML đến từ một bộ máy báo cáo thay vì từ mẫu viết tay, tuyến đường theo hướng dữ liệu trong bộ máy báo cáo dataset kết hợp tốt với điều này, tạo ra mã đánh dấu mà flex và grid sau đó sắp xếp. Và khi cùng nội dung cũng phải rời khỏi PDF một lần nữa, đường xuất ngữ nghĩa trong xuất PDF sang Markdown và DOCX khép kín vòng khứ hồi
Bố cục HTML, tạo báo cáo và xuất ngữ nghĩa là một phần của một thư viện cho Delphi, C++Builder và Free Pascal; danh sách tính năng đầy đủ có trên trang PDF Library for Delphi