PDFlibPas có thể gắn thẻ một tài liệu ngay khi tài liệu đó đang được vẽ. Bật SetAutoTagMode thì các lệnh DrawText thông thường trở thành đoạn văn, văn bản vẽ ngay sau RegisterHeading trở thành tiêu đề cấp tương ứng, đầu/cuối trang lặp lại trở thành artifact mà trình đọc bỏ qua, hình ảnh trở thành figure và DrawTableRows đưa bảng cùng các dòng và ô của nó vào cây cấu trúc
Phương án thay thế — và cho đến gần đây là lựa chọn duy nhất — là bọc mọi lệnh vẽ bằng BeginTag và EndTag bằng tay. Cách đó hoạt động được, và đối với các tài liệu có cấu trúc bất thường thì nó vẫn là công cụ đúng. Nhưng với một báo cáo, hóa đơn hay sao kê thông thường, điều đó nghĩa là khả năng tiếp cận của đầu ra phụ thuộc vào việc không ai quên một cặp thẻ nào, trên mọi đường dẫn mã vẽ bất cứ thứ gì
Những bit chế độ bao phủ điều gì
SetAutoTagMode nhận một bit mask và trả về chế độ đang có hiệu lực trước đó. AUTOTAG_TEXT (1) gắn thẻ văn bản như một đoạn văn, hoặc như một tiêu đề khi đã đến lúc. AUTOTAG_FURNITURE (2) đánh dấu đầu trang, cuối trang và số trang là artifact. AUTOTAG_FIGURE (4) biến một hình ảnh được vẽ thành figure, hoặc thành artifact khi nó được khai báo là yếu tố trang trí. AUTOTAG_TABLE (8) đưa các bảng được vẽ vào cây cấu trúc. AUTOTAG_DEFAULT là 15, tức là cả bốn giá trị trên
Bật chế độ này cũng đồng thời đánh dấu tài liệu là đã được gắn thẻ, và bước đó quan trọng hơn so với nghe vẻ bề ngoài. Một trình đọc coi một tài liệu là chưa gắn thẻ trừ khi catalog nói điều ngược lại (ISO 32000-1 §14.7.1), nên một file mang theo một cây cấu trúc hoàn chỉnh mà không có khai báo /MarkInfo sẽ được công nghệ hỗ trợ thông báo là không có cấu trúc gì cả. Cây vẫn ở đó; chỉ là không có gì đọc nó
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutoTagMode(AUTOTAG_DEFAULT); // text + furniture + figures + tables
Lib.AddStandardFont(4);
Lib.SetTextSize(18);
Lib.RegisterHeading(1, 'Annual service report');
Lib.DrawText(72, 96, 'Annual service report'); // becomes H1
Lib.SetTextSize(11);
Lib.DrawText(72, 130, 'Every unit installed before 2024 was inspected.');
Lib.SaveToFile('report.pdf');
finally
Lib.Free;
end;
end;
Làm sao một tiêu đề biết văn bản nào nó thuộc về?
RegisterHeading đặt cấp cho văn bản được vẽ tiếp theo, và nó chờ văn bản đó xuất hiện. Nếu một hình ảnh được vẽ xen vào giữa, hình ảnh đó trở thành figure và tiêu đề vẫn treo chờ văn bản đi sau nó. Hành vi đó là có chủ đích: phương án thay thế, nơi hình ảnh nhận cấp tiêu đề, từng tạo ra những tài liệu mà một đường kẻ trang trí nằm dưới nhan đề lại được đọc lên như chính nhan đề đó
Cùng quy tắc "tiêu hao cho một mục" chi phối cả figure. RegisterFigure cung cấp phần mô tả mà hình ảnh kế tiếp mang theo, còn RegisterDecoration khai báo hình ảnh kế tiếp là một đường kẻ, viền hay nền không mang ý nghĩa. Cả hai đều bị tiêu hao bởi một hình ảnh, nên một hình ảnh vẽ sau không bao giờ kế thừa phần mô tả dành cho hình ảnh trước đó — đó chính là cách mà alt text bị gán nhầm cho bức ảnh sai trong mã gắn thẻ thủ công
Phần mô tả quan trọng hơn bất kỳ chuỗi đơn lẻ nào khác trong một tài liệu dễ tiếp cận. Một độc giả khiếm thị nhận được phần mô tả thay cho hình ảnh, và đó là toàn bộ những gì họ có. "Chart" không phải là mô tả; "Quarterly revenue by region, with the eastern region highest in Q3" mới là mô tả
Lib.RegisterFigure('Exploded view of the gearbox assembly');
Lib.AddImageFromFile('gearbox.png', 0); // becomes a tagged Figure
Lib.RegisterDecoration; // meaningless rule
Lib.AddImageFromFile('divider.png', 0); // drawn inside a layout artifact
Bảng, dòng tiêu đề và vị trí của quyết định lặp lại
Với bit bảng bật, DrawTableRows đưa bảng cùng các dòng và ô của nó vào cây cấu trúc, để trình đọc có thể nói giá trị đang nằm ở cột nào thay vì đọc cả bảng như một chuỗi văn bản không liên quan. SetTableHeaderRowCount chỉ định có bao nhiêu dòng đầu là dòng tiêu đề; các dòng đó được ghi là ô tiêu đề mang theo phạm vi cột, và đó là điều cho phép trình đọc đọc lên tiêu đề của giá trị mà người dùng đang ở trên
Các dòng tiêu đề được khai báo theo cách này giữ nguyên vị trí của chúng. Lặp lại chúng ở đầu mỗi trang là một quyết định bố cục, và nó vẫn cứ là quyết định bố cục: DrawTaggedTableRows nhận một tham số RepeatHeaderRows cho chính mục đích đó. Giữ hai thứ tách biệt giúp cây cấu trúc không bị sinh ra thêm một bản sao thứ hai của dòng tiêu đề cho mỗi lần ngắt trang, đó là điều mà một cơ chế lặp tự động sẽ tạo ra
var
TableID: Integer;
begin
TableID := Lib.CreateTable(40, 3);
Lib.SetTableHeaderRowCount(TableID, 1); // row 1 is the header band
Lib.SetTableCellContent(TableID, 1, 1, 'Part');
Lib.SetTableCellContent(TableID, 1, 2, 'Torque');
Lib.SetTableCellContent(TableID, 1, 3, 'Unit');
// ... fill the data rows ...
// Draw rows 1..40 into a 600pt band, repeating one header row per page
Lib.DrawTaggedTableRows(TableID, 72, 150, 600, 1, 40, 1);
end;
Kết hợp gắn thẻ tự động và thủ công
Gắn thẻ tự động nhường bước bên trong một thẻ được mở bằng tay. Một phần của tài liệu có thể được mã của bạn mô tả, phần còn lại để cho thư viện xử lý, mà không có sự lồng nhau vào nhau — đó là sự sắp đặt mà hầu hết các tài liệu thực tế cần. Trang bìa và khối chữ ký có cấu trúc chỉ bạn hiểu; hai trăm trang thân bài ở giữa thì không
Hai quy tắc an toàn giữ cho đầu ra sạch sẽ. Không có gì được gắn thẻ bên trong một artifact, vì nội dung đánh dấu là artifact không được mang theo yếu tố cấu trúc. Và văn bản rỗng không mở bất kỳ phần tử nào, nên một lệnh DrawText lạc vào với chuỗi rỗng không thể tạo ra một phần tử cấu trúc mà trình đọc sẽ đọc lên như là khoảng trắng. Cả hai đều là loại khiếm khuyết mà các tài liệu gắn thẻ thủ công âm thầm tích tụ và mà một trình xác thực chỉ báo cáo hàng loạt nhiều tháng sau đó
Những gì gắn thẻ tự động vẫn không quyết định thay bạn
Thứ tự đọc vượt ra ngoài thứ tự vẽ, vai trò ngữ nghĩa không phải là đoạn văn, tiêu đề, figure hay bảng, và các khai báo ngôn ngữ. Gắn thẻ tự động gán cấu trúc theo thứ tự mà nội dung được vẽ ra — nếu mã bố cục của bạn vẽ thanh bên trước thân bài, đó là thứ tự mà cây ghi lại. Đối với các tài liệu mà thứ tự thị giác và thứ tự đọc thực sự khác biệt, API gắn thẻ thủ công vẫn là công cụ đúng, và bài viết về tagged PDF và cấu trúc khả năng tiếp cận trình bày chi tiết về vai trò, phạm vi và ràng buộc tiêu đề
Khi tài liệu hoàn tất, hãy xác thực thay vì giả định: các ghi chú về PDF/A và PDF/UA preflight cho thấy cách nhận một phán quyết về cấu trúc bạn đã tạo ra, và bài viết về xuất báo cáo hướng dữ liệu trình bày nơi các lệnh gọi này phù hợp trong một công cụ báo cáo sinh bố cục từ dữ liệu
PDFlibPas là thư viện PDF Pascal bản địa cho Delphi, C++Builder và Lazarus, không cần runtime PDF bên ngoài, nên đầu ra dễ tiếp cận được tạo ra bởi cùng mã đang vẽ tài liệu — xem trang sản phẩm PDFlibPas để biết danh sách API và nền tảng đầy đủ