Một tệp PDF có khả năng tiếp cận tựa trên một cấu trúc mà trang nhìn thấy được không bao giờ phô ra: cây cấu trúc được định nghĩa trong ISO 32000-1 §14.7. Đó là một hệ phân cấp lô-gic gồm đề mục, đoạn văn, bảng và hình, phủ lên phần nội dung đã vẽ và ánh xạ sang các vai trò chuẩn qua một role map. Trình đọc màn hình đọc cái cây đó, chứ không đọc các nét vẽ trên trang. Không có nó, một hóa đơn sinh ra trông chỉn chu tuyệt đối vẫn rỗng về ngữ nghĩa, vì luồng nội dung chỉ ghi lại thứ tự vẽ chứ không gì khác. Tổng tiền có thể bị đọc trước các dòng hàng hóa, chân trang có thể cắt ngang một đoạn văn, bảng danh mục có thể sụp thành một tràng chữ không phân biệt. Chi phí để ngăn chuyện đó nghiêng hẳn về phía có lợi cho bạn. Phát ra cấu trúc trong lúc vẽ chỉ tốn vài phút viết mã; nhồi nó ngược vào tài liệu đã hoàn thành lại là cả một dự án khắc phục. losLab PDF Library (PDF Library for Delphi) phơi cái cây đó cho Delphi và C++Builder qua một tập nhỏ các lệnh gọi bọc mỗi thao tác vẽ trong vai trò lô-gic của nó
Marked content gắn vào cây cấu trúc ra sao
Hai tầng phối hợp với nhau. Trong luồng nội dung, các thao tác vẽ được đóng ngoặc thành những chuỗi marked-content, mỗi chuỗi mang một số nguyên MCID. Trong catalog của tài liệu, cây cấu trúc ánh xạ những MCID đó vào một hệ phân cấp các phần tử có kiểu (H1, P, Table, Figure) cùng những thuộc tính như văn bản thay thế và ngôn ngữ. Kiểu phần tử tùy chỉnh là hợp lệ, nhưng mỗi kiểu phải phân giải về một vai trò chuẩn qua role map (ISO 32000-1 §14.8.4). Nội dung hoàn toàn không mang ý nghĩa, như đường kẻ, nền và các thành phần lặp lại trên trang, được đánh dấu là artifact để công nghệ hỗ trợ bỏ qua thay vì đọc chen vào giữa câu
PDF Library for Delphi duy trì cả hai tầng sau một cặp ngoặc duy nhất. BeginTag mở một phần tử cấu trúc và khởi đầu chuỗi marked-content, các lệnh vẽ rơi vào bên trong nó, rồi EndTag đóng cả hai. Phần sổ sách vốn làm vấp lối gắn thẻ tự chế, tức các MCID cùng cây cha và các tham chiếu trang, diễn ra bên trong nơi bạn không thể làm sai
Hai công tắc ở cấp tài liệu dựng khung cho công việc trước khi bất kỳ thẻ nào mở ra. SetMarkInfo ghi cờ trong catalog để tuyên bố tài liệu đã gắn thẻ, còn IsTaggedPDF đọc lại cờ đó, và đây là lượt dò đầu tiên rẻ tiền khi quyết định xem một tệp đến từ bên ngoài có cấu trúc nào đáng giữ hay không. Ngôn ngữ có hai điểm vào. SetDocumentLanguage tự nó đặt mặc định cho tài liệu, còn SetPDFUAMode đặt ngôn ngữ như một phần của việc bật đầu ra PDF/UA đầy đủ. Một tệp có thể được gắn thẻ hữu ích mà không tuyên bố tuân thủ PDF/UA, và một đợt triển khai theo giai đoạn thường bắt đầu đúng từ đó
Gắn thẻ trong lúc vẽ, không phải sau đó
Mẫu hình sinh tệp có hiệu quả là coi cặp ngoặc thẻ như một phần chữ ký của mọi lệnh vẽ, không bao giờ coi nó là một lượt quét về sau:
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1); // gốc ở góc trên bên trái
Lib.SetPDFUAMode('en-US'); // nâng phiên bản lúc lưu lên PDF 1.7
Lib.SetInformation(1, 'Service Manual'); // /Title là bắt buộc với PDF/UA
Lib.AddRoleMap('ManualTitle', 'H1'); // kiểu tùy chỉnh -> vai trò chuẩn
Lib.AddStandardFont(4);
Lib.SetTextSize(18);
Lib.BeginTagEx2('ManualTitle', '', '', 'en-US', '', 'h1-cover', '');
Lib.DrawText(72, 96, 'Service Manual');
Lib.EndTag;
Lib.BeginTag('Figure', 'Exploded view of the gearbox assembly', '');
Lib.AddImageFromFile('gearbox.png', 0);
Lib.EndTag;
Lib.BeginArtifact('Layout'); // trang trí trang: loại khỏi luồng đọc
// ... vẽ đường kẻ và nền màu ở đây ...
Lib.EndArtifact;
Lib.SaveToFile('manual.pdf');
finally
Lib.Free;
end;
end;
Ba lệnh gọi trong trình tự đó mang sức nặng về tuân thủ. SetPDFUAMode bật đầu ra PDF/UA và lặng lẽ nâng phiên bản tài liệu lên PDF 1.7, điều này va chạm với việc ghim phiên bản. Một tài liệu bị khóa ở PDF 1.4 bằng LockSaveVersion sẽ từ chối lưu và trả về mã lỗi 602 một khi chế độ UA đang bật, một cú va chạm hay lộ ra khi hồ sơ lưu trữ và yêu cầu tiếp cận do hai đội khác nhau cấu hình. SetInformation(1, ...) ghi tiêu đề tài liệu, thứ mà ISO 14289 mong trình xem hiển thị thay cho tên tệp; việc thiếu nó là một trong những phát hiện PDF/UA phổ biến nhất ngoài thực tế. AddRoleMap đăng ký kiểu tùy chỉnh ManualTitle như một H1, và bỏ qua nó khiến phần chẩn đoán mô tả bên dưới gắn cờ một vai trò chưa ánh xạ
Các cấp đề mục xứng đáng có một chính sách cân nhắc, chứ không phải những lựa chọn tùy hứng đưa ra vì dáng vẻ của trang. Người dùng trình đọc màn hình nhảy giữa các mục bằng phím tắt theo đề mục, nên một mẫu nhảy từ H1 sang H3 chỉ vì cấp ở giữa trông quá to trong thiết kế thị giác sẽ lặng lẽ phá vỡ lối điều hướng đó, và không lượt duyệt bằng mắt nào bắt được. Đó đúng là khiếm khuyết mà chẩn đoán HEADING-LEVEL-SKIP sinh ra để gọi tên. Hãy ánh xạ các kiểu trình bày của từng mẫu vào một thang đề mục cố định, làm một lần, ở một chỗ, thì sự trôi lệch không bao giờ khởi phát
Những bảng mà trình đọc màn hình thật sự điều hướng được
Các đường kẻ ô đã vẽ chẳng có nghĩa gì khi rời màn hình. Thứ trình đọc màn hình điều hướng là các quan hệ cấu trúc: ô nào là tiêu đề, mỗi tiêu đề chi phối những gì, và ô dữ liệu gắn với tiêu đề ra sao trong những bố cục bất quy tắc. Các lệnh gọi đặt thuộc tính cho phần tử cấu trúc lo cả ba việc đó:
Lib.BeginTag('Table', '', '');
Lib.BeginTag('TR', '', '');
Lib.BeginTagEx2('TH', '', '', '', '', 'col-part', '');
Lib.SetStructElemScope('Column'); // chỉ hợp lệ khi TH này còn mở
Lib.DrawText(72, 120, 'Part');
Lib.EndTag;
Lib.BeginTagEx2('TH', '', '', '', '', 'col-torque', '');
Lib.SetStructElemScope('Column');
Lib.SetStructElemColSpan(2); // tiêu đề trải qua cột giá trị và cột đơn vị
Lib.DrawText(200, 120, 'Tightening torque');
Lib.EndTag;
Lib.EndTag;
Lib.BeginTag('TR', '', '');
Lib.BeginTag('TD', '', '');
Lib.SetStructElemHeaders('col-part'); // ràng buộc tường minh cho bảng bất quy tắc
Lib.DrawText(72, 140, 'M8 flange bolt');
Lib.EndTag;
Lib.EndTag;
Lib.EndTag; // Table
Quy tắc về thứ tự thì nghiêm ngặt và được cưỡng chế trong im lặng. Mọi lệnh gọi SetStructElem* đều áp cho cái thẻ đang mở tại thời điểm đó, nằm giữa BeginTag và EndTag của nó, và nó trả về 0 mà chẳng ném ra gì khi không có thẻ nào mở hoặc khi thuộc tính không áp được cho thẻ hiện tại. Một lệnh gọi đặt sai chỗ đơn giản là bốc hơi. Bọc các giá trị trả về trong assertion suốt giai đoạn phát triển sẽ tóm được sự trôi lệch khi bạn còn nhìn thấy nó; để mặc, một scope thiếu chỉ lộ ra khi một cuộc kiểm toán tiếp cận cho trình đọc màn hình thật chạy qua bảng. Các ID phần tử truyền qua BeginTagEx2 nuôi cây ID (ISO 32000-1 §14.7.4), và chính điều đó làm cho ràng buộc SetStructElemHeaders phân giải được ngay từ đầu
Cùng họ thuộc tính đó bao nốt những gì công nghệ hỗ trợ dựa vào. SetStructElemListNumbering khai báo cách các mục danh sách được đánh nhãn, nhờ đó trình đọc màn hình xướng vị trí trong danh sách thay vì đọc lải nhải các ký tự chấm đầu dòng. SetStructElemBBox ghi lại hộp bao của hình và bảng, thứ mà các chế độ xem tự giãn dùng để đặt nội dung. SetStructElemActualText cung cấp văn bản thay thế cho những đoạn có glyph không ánh xạ về ký tự đọc được, chẳng hạn một chữ cái đầu đoạn ghép từ hình vector. Mỗi lệnh đều theo cùng quy tắc: nó gắn vào thẻ đang mở, hoặc nó bốc hơi
Artifact, ngôn ngữ và cửa chẩn đoán trước khi lưu
Các thành phần lặp lại trên trang, tức đầu trang chạy suốt, dấu gấp, hình mờ và nền màu, thuộc về bên trong cặp ngoặc BeginArtifact và EndArtifact để chúng không bao giờ lọt vào luồng đọc. Ngôn ngữ thì kế thừa được. Mặc định của tài liệu đến từ đối số của SetPDFUAMode, còn một đoạn viết bằng ngôn ngữ khác ghi đè mặc định theo từng phần tử qua BeginTagEx hoặc SetStructElemLang. Đó là thứ giữ cho một câu trích tiếng Pháp trong cuốn sổ tay tiếng Anh vẫn phát âm được
Trước khi lưu, GetPDFUADiagnostics chạy các phép kiểm cấu trúc của thư viện trên tài liệu trong bộ nhớ và trả về các phát hiện dưới dạng văn bản, trong đó chuỗi rỗng nghĩa là không tìm thấy gì. Các mã gọi thẳng tên những lỗi soạn thảo kinh điển: FIGURE-NO-ALT cho một ảnh không có văn bản thay thế, HEADING-LEVEL-SKIP cho một H3 đi ngay sau một H1, ROLEMAP-UNMAPPED cho một kiểu tùy chỉnh chưa từng được đăng ký. Hãy đấu nối cái này vào bản build (sinh ra bộ tài liệu, đánh trượt bước đó khi phần chẩn đoán không rỗng) thì các hồi quy về khả năng tiếp cận trở thành lỗi kiểu như lúc biên dịch chứ không phải phát hiện kiểm toán vài tháng sau. Phán quyết tuân thủ đầy đủ vẫn thuộc về preflight chạy trên tệp đã lưu, bàn trong preflight PDF/A và PDF/UA trong Delphi, bởi một số phép chuẩn hóa chỉ được áp trong lúc tuần tự hóa
Việc điều hướng annotation có núm vặn riêng. PDF/UA mong việc duyệt bằng bàn phím qua các trường form và liên kết đi theo thứ tự cấu trúc, và SetTabOrderMode ghi mục thứ tự tab ở cấp trang mà các trình xem tôn trọng, còn GetTabOrderMode có sẵn để kiểm toán các tệp nhận vào. Đó là loại yêu cầu chẳng ai để ý cho tới khi một người dùng chỉ dùng bàn phím gửi báo lỗi, và làm cho đúng chỉ tốn một lệnh gọi mỗi tài liệu
Cây cấu trúc không sống sót qua mọi lượt gộp
Tài liệu đã gắn thẻ chỉ còn gắn thẻ khi mọi bước xử lý về sau đều giữ lại cái cây, và cạnh sắc bên trong PDF Library for Delphi nằm ở họ hàm gộp theo danh sách. MergeFileListFast đánh đổi việc giữ cây cấu trúc lấy tốc độ. Đó là đánh đổi đúng với các lô ảnh quét và là đánh đổi sai với các báo cáo đã gắn thẻ, bởi đầu ra mở lên vẫn ổn, hiển thị y hệt, mà đã lặng lẽ mất đi tầng tiếp cận của nó. Hãy dùng MergeFileList mặc định hoặc biến thể nghiêm ngặt bất cứ khi nào có đầu vào đã gắn thẻ, và đưa IsTaggedPDF vào các assertion sau khi lắp ghép để một lô bị dẹp phẳng không thể xuất xưởng mà không ai hay. Các pipeline lắp ghép cho bộ tài liệu lớn còn nhiều đánh đổi kiểu này, được khai thác trong gộp, tách PDF cỡ lớn và direct access
Vòng kiểm chứng khép lại ở bên ngoài thư viện: mở đầu ra trong Acrobat, soi bảng thẻ, và đọc ít nhất một tài liệu cho mỗi họ mẫu bằng một trình đọc màn hình thật. Phần chẩn đoán bắt được lỗi cấu trúc; chỉ tai người mới bắt được một thứ tự đọc về mặt kỹ thuật thì hợp lệ mà trên thực tế lại khó hiểu. Bản dựng để đánh giá và tài liệu API gắn thẻ đầy đủ nằm trên trang sản phẩm losLab PDF Library for Delphi