Bài viết kỹ thuật

Tuyến tính hóa PDF và Fast Web View: Cách thức hoạt động

Đặt một báo cáo quét dung lượng 80 MB sau một liên kết, mở nó bằng trình duyệt và theo dõi điều gì sẽ xảy ra: trình xem nằm trên một bảng điều khiển trống cho đến khi phần lớn các byte đó đã tới, rồi sơn trang một tất cả cùng lúc. Chuyển sang trang 40 và trên một tệp được xây dựng kém, toàn bộ quá trình tải xuống có thể bắt đầu lại. Phần gây bực bội là người đọc chỉ muốn xem trang đầu tiên. Tuyến tính hóa là cấu trúc trả lời cho vấn đề đó. Nó sắp xếp lại tệp PDF để một trình xem có thể render trang mở đầu từ một phần tiền tố nhỏ của tệp và lấy phần còn lại theo nhu cầu, đó là lý do tại sao Adobe tiếp thị tính năng này là "Fast Web View."

Không có thứ nào trong số này là một định dạng tệp khác biệt. Một tệp PDF được tuyến tính hóa là một PDF bình thường mà trình đọc tuân thủ sẽ mở mà không cần xử lý đặc biệt. Bí quyết hoàn toàn nằm ở cách sắp xếp các byte theo thứ tự và ở hai cấu trúc phụ mà tệp mang theo. ISO 32000-1 chỉ định toàn bộ sự sắp xếp trong Phụ lục F, và một khi bạn đã nhìn thấy bố cục, hành vi đó sẽ không còn giống như phép màu mà giống như một sự đánh đổi có chủ ý của thứ tự tệp lấy độ trễ paint đầu tiên (first-paint latency)

Tuyến tính hóa thực sự sắp xếp lại cái gì

Một PDF bình thường có thể phân tán các đối tượng của nó theo hầu hết mọi thứ tự. Bảng tham chiếu chéo (cross-reference) ở cuối tệp là thứ làm cho điều đó hoạt động: trình đọc tìm đến cuối, đọc con trỏ startxref, tải xref và từ đó có thể xác định vị trí của mọi đối tượng qua offset của nó. Thiết kế đó là xuất sắc cho các tệp cục bộ, nơi tìm kiếm đến cuối không tốn kém, và kém đối với tệp luân chuyển qua mạng, nơi đuôi tệp chính là phần đến sau cùng. Để render trang một, một trình đọc thông thường cần có đối tượng trang, luồng nội dung của nó, font chữ mà nó tham chiếu, và bất kỳ hình ảnh nào nó vẽ ra, và trong một tệp không có thứ tự, chúng có thể nằm ở bất cứ đâu, kể cả trong megabyte cuối cùng

Tuyến tính hóa cố định trật tự này. Các đối tượng cần thiết để hiển thị trang đầu tiên được gom vào một khối liền kề gần phía trước, ngay sau phần tiêu đề nhỏ, vì vậy chúng đến sớm trong luồng byte. Mọi thứ khác, các trang còn lại và tài nguyên chúng chia sẻ, tiếp nối theo một chuỗi có thể đoán trước. Một bảng tham chiếu chéo thứ hai, hoàn chỉnh vẫn nằm ở phần cuối dành cho các trình đọc bỏ qua việc tối ưu hóa, nhưng tệp tuyến tính hóa cũng đặt tham chiếu chéo trang đầu tiên và các tham số mà trình đọc dạng luồng (streaming) cần lên phía trước. Trình đọc không còn phải chờ đạt đến đuôi trước khi có thể vẽ bất cứ thứ gì

Tập hợp đối tượng trang đầu tiên và từ điển tham số tuyến tính hóa

Đối tượng đầu tiên trong tệp tuyến tính hóa, sau tiêu đề %PDF, là từ điển tham số tuyến tính hóa. Nó là thứ mà một trình đọc streaming tìm kiếm để quyết định liệu có sự hiện diện của tối ưu hóa và cách sử dụng nó. Từ điển ghi lại độ dài của toàn bộ tệp, byte offset nơi phần tham chiếu chéo chính bắt đầu, số lượng đối tượng của trang đầu tiên, và vị trí cũng như chiều dài của luồng gợi ý (hint stream) tiếp nối. Cùng với những con số đó, một trình đọc đã biết từ những kilobyte mở đầu rằng nó phải lấy bao nhiêu byte để hiển thị trang một và nơi tìm chỉ mục cho phép nó nhảy đến bất kỳ vị trí nào khác

Phụ lục F rất nghiêm ngặt về ý nghĩa của "trang đầu tiên" ở đây. Phần trang đầu tiên phải chứa bản thân đối tượng trang, các luồng nội dung của nó và các tài nguyên mà các luồng đó tham chiếu, sao cho trang tự chủ được một khi tiền tố đó đã được tải xuống. Các tài nguyên chia sẻ, một font chữ được sử dụng trên mọi trang, một biểu trưng được lặp lại trong phần đầu trang, được xử lý đặc biệt: chúng xuất hiện đủ sớm để phục vụ trang đầu tiên nhưng được cắm cờ là chia sẻ để trình đọc không lấy lại chúng khi sau đó render trang 30. Sự khác biệt giữa các đối tượng riêng tư của trang và chia sẻ là phần mà hầu hết các trình "tối ưu hóa" cây nhà lá vườn hiểu sai, và hiểu sai điều này là những gì tạo ra một tệp tuyên bố đã được tuyến tính hóa nhưng vẫn bị đình trệ

Các luồng gợi ý: chỉ mục khiến các bước nhảy trang trở nên rẻ

Hiển thị trang một cách nhanh chóng mới chỉ là một nửa giá trị. Một nửa khác là nhảy đến trang tùy ý mà không tải xuống mọi thứ ở giữa, và đó là điều mà các luồng gợi ý cung cấp. Một tệp tuyến tính hóa mang một bảng gợi ý (hint) cho trang offset và một bảng gợi ý đối tượng được chia sẻ, được lưu trữ dưới dạng một luồng tham chiếu từ từ điển tham số. Bảng gợi ý trang offset ghi lại với mỗi trang xem đối tượng của chúng bắt đầu ở đâu trong tệp và chạy trong khoảng cách dài như thế nào. Bảng đối tượng chia sẻ làm tương tự cho các tài nguyên được sử dụng trên nhiều trang

Đưa ra những bảng đó, một trình đọc muốn xem trang 40 không phân tích tệp theo trình tự. Nó tham khảo bảng gợi ý để biết phạm vi byte mà trang 40 chiếm, yêu cầu máy chủ lấy chính xác phạm vi đó và render trang một khi các byte đó tới, đồng thời kéo bất kỳ tài nguyên chia sẻ nào mà nó chưa có thông qua cùng một cơ chế. Thực tế, luồng gợi ý là một bản đồ truy cập ngẫu nhiên nằm trên tài liệu, và đây là lý do một tệp tuyến tính hóa tốt dài 500 trang lại thấy phản hồi nhanh trên liên kết chậm trong khi một tệp không được tối ưu có kích thước tương tự thì không

Tại sao máy chủ cần phải hợp tác

Tuyến tính hóa giả định rằng đường truyền có thể cung cấp các phần cắt tùy ý của tệp, và giả định đó đáng để kiểm tra trước khi bạn ghi nhận định dạng cho kết quả kém. Cơ chế này là phục vụ byte HTTP: trình đọc đưa ra yêu cầu phạm vi, và máy chủ trả lời bằng các phản hồi 206 Partial Content. Nếu máy chủ không quảng cáo Accept-Ranges: bytes, hoặc nếu một proxy hoặc CDN phía trước nó gộp các yêu cầu phạm vi lại thành các bản truyền đầy đủ, trình đọc không có cách nào lấy trang 40 cô lập và bị rơi trở lại việc tải xuống toàn bộ tệp. Khi đó cấu trúc bên trong PDF là hoàn toàn đúng đắn nhưng lại bị lãng phí hoàn toàn

Đây là lỗi thường xuyên bị chẩn đoán nhầm thành "tuyến tính hóa không hoạt động." Tệp vẫn tốt; đường dẫn phân phối mới là thứ không tốt. Trước khi xây dựng lại một tài liệu, hãy xác nhận bằng một yêu cầu có điều kiện rằng máy chủ thực sự trả lại nội dung một phần cho URL mà trình đọc truy cập. Nhiều máy chủ tĩnh thực hiện việc này theo mặc định, trong khi nhiều máy chủ ứng dụng bị cấu hình sai và các lớp bộ đệm (caching) thì không

Các cập nhật tăng dần âm thầm làm hỏng sự tuyến tính hóa

Đây là rào cản làm ngạc nhiên những người tạo tệp được tuyến tính hóa một cách chính xác rồi tự hỏi tại sao khả năng tối ưu hóa biến mất. Tuyến tính hóa phụ thuộc vào một bố cục được đặt hàng cẩn thận với chỉ mục nằm ở phía trước. Một cập nhật tăng dần vi phạm thiết kế đó. Khi một công cụ thêm một chữ ký, điền vào một trường biểu mẫu hoặc bổ sung một annotation qua lần lưu tăng dần (incremental save), nó không viết lại tệp. Nó đính kèm các đối tượng bị thay đổi, phần tham chiếu chéo mới và một trailer mới vào đuôi, để lại các byte ban đầu không bị ảnh hưởng. Việc chèn vào đuôi (append) này là mục tiêu cốt lõi của các cập nhật tăng dần: nó nhanh, và nó bảo tồn các phiên bản cũ hơn để có thể theo dõi, đánh giá hoặc xác nhận chữ ký

Tác dụng phụ là tệp hiện có dữ liệu tham chiếu chéo mới nhất nằm ở đuôi, sau khối trang đầu tiên được đặt một cách thận trọng, và từ điển thông số tuyến tính hóa phía trước mô tả một bố cục không còn khớp với tệp. Một trình đọc tuân thủ phát hiện sự không phù hợp và coi tài liệu là tệp PDF bình thường, phi tuyến tính. Fast Web View đã biến mất, mặc dù cấu trúc tuyến tính hóa ban đầu vẫn đang ngồi ở nửa đầu tệp. Nếu bạn đính kèm vài lần cập nhật, mỗi lần cập nhật lại xếp một bản sửa đổi nữa ở đuôi và khoảng cách giữa chỉ mục cũ phía trước và trạng thái thực tế càng được mở rộng

Nếu quy trình làm việc của bạn vừa cần chỉnh sửa vừa cần Fast Web View, quy tắc tuân theo trực tiếp từ cấu trúc: chỉnh sửa tăng dần trong khi tài liệu đang thay đổi, rồi tuyến tính hóa lại một lần vào lúc cuối cùng. Bản viết lại hoàn toàn là thứ sẽ khôi phục bố cục. Theo thuật ngữ HotPDF, điều đó có nghĩa là một lần chỉnh sửa đang tiến hành thông qua BeginIncrementalUpdateSaveIncrementalUpdate, sẽ thêm phần khác biệt (delta), trong khi bước hoàn thiện sẽ tải toàn bộ tài liệu và định dạng lại (serialize) mới với LoadFromFile theo sau là SaveLoadedDocument, loại bỏ các phiên bản cũ tích lũy và phát ra một bố cục sạch sẽ duy nhất. Sự đánh đổi tương tự xuất hiện với các luồng đối tượng: cho phép UseObjectStreams cùng với UseXRefStream nén tham chiếu chéo và đóng gói các đối tượng chặt chẽ, điều này giúp thu nhỏ kích thước tệp nhưng, cũng giống như bất kỳ sự lựa chọn cấu trúc nào, cần áp dụng ở lần ghi lại cuối cùng này chứ không thể tháo lắp bu lông (bolted onto) lên một bản sửa đổi được phụ thêm (appended)

// In-flight edits: append a delta, keep prior revisions intact.
// This leaves the file NOT linearized.
Pdf.BeginIncrementalUpdate('report.pdf');
Pdf.AddPage;
Pdf.CurrentPage.TextOut(72, 760, 0, 'Addendum');
Pdf.SaveIncrementalUpdate('report.pdf');

// Finishing step: full re-serialization produces one clean layout,
// dropping the stacked revisions. Re-run your linearizer on the output.
Pdf.LoadFromFile('report.pdf');
Pdf.SaveLoadedDocument('report-final.pdf');

HotPDF không phơi bày thủ tục "linearize" một lần gọi, vì vậy mô hình thực tế là tạo ra một tệp được ghi lại đầy đủ và sạch sẽ và cho chạy trình tối ưu hóa chuyên biệt đi qua nó. Các công cụ dòng lệnh (command-line) có thể xử lý việc tái tổ chức lại một cách trực tiếp. qpdf viết lại tệp thành dạng tuyến tính với một cờ cắm đơn giản:

qpdf --linearize report-final.pdf report-web.pdf

Làm thế nào để phân biệt tệp có tuyến tính hóa hay không

Đừng tin vào tên tệp hoặc công cụ đã tuyên bố tạo ra nó; hãy xác minh các byte. Kiểm tra trực tiếp nhất là đoạn đầu của tệp: mở nó ra và tìm kiếm từ điển tham số tuyến tính hóa vì nó là đối tượng đầu tiên ngay sau header, mang theo từ khóa /Linearized. Một phím tắt dành cho người đọc là hộp thoại Thuộc tính Tài liệu của Acrobat, chỉ báo cáo "Fast Web View: Yes" khi cấu trúc này có hiện diện xác thực và hiện hành

Đối với việc dùng lệnh cho mã kịch bản (scripted checks), qpdf báo cáo về cả sự hiện diện và tính toàn vẹn của cấu trúc, điều đáng quan tâm vì một tệp có thể mang một từ điển tuyến tính hóa không phản ánh được bố cục của nó nữa, đó chính là trạng thái mà phiên bản chỉnh sửa cập nhật (incremental update) để lại:

# Reports "File is linearized" and validates hint tables against the layout
qpdf --check report-web.pdf

# Dumps the linearization parameters and hint data in detail
qpdf --show-linearization report-web.pdf

Bước xác thực là bước xứng đáng với số tiền kiếm được. Một lần xác thực chỉ xác nhận từ điển tồn tại cũng sẽ vui vẻ chúc phước một tệp mà các chỉ mục của nó trỏ về các số offset không đúng; trong khi đó kiểm tra xem đối chiếu từ bảng gợi ý so với những vị trí cấu trúc của bảng đối tượng thực là điều nói cho bạn biết liệu sự tối ưu đó có thể đứng vững được dưới truy vấn khoảng trống thực tế của trình xem không

Tuyến tính hóa vẫn đáng được áp dụng cho mọi tài liệu dung lượng lớn được phân phối thông qua Web, đặc biệt dành riêng cho kết nối các trình đọc từ di động với mạng di động với tốc độ không ồn định (uneven connections), và nó chỉ tốn vài phần trăm dung lượng bộ nhớ dành để lập chỉ mục được dồn tại đoạn mở đầu (front-loaded). Hai vấn đề bạn cần quan tâm rõ ràng đó là, một tệp có đúng cấu trúc dữ liệu PDF bên trong và phân phát định dạng byte bên ngoài phải là hợp lệ với các cấu hình có thể hoạt động hoàn toàn chính xác cùng nhau, và vấn đề thứ hai là bất kỳ sự thay đổi (edit) nào diễn ra sau đó đều sẽ xoá bỏ hiệu quả ưu hoá đó tới tận lúc khi bạn tái xuất tập tin một lần nữa. Xin hãy nhớ việc đánh dấu tái khởi chạy tuyến tính (re-linearization) lại là những bước làm cuối cùng nhất trong quy trình biên dịch (pipeline), chỉ có sau mọi sửa đổi khác thực thi để được kết tinh vững chắc. Dữ kiện thông chiếu ngang tham chiếu (cross-reference), dòng sự kiện của đối tượng dữ liệu (object stream), xử lý thay đổi thêm thắt (incremental-update) được miêu tả kể trên là một phần của sơ đồ kết cấu dữ liệu chung trong đó trình HotPDF Component cho phép nhà lập trình Delphi và C++Builder tự tiến hành thực thi và điều phối; nếu bạn muốn khám phá chi tiết của dạng tổ chức tệp theo mặt bao quát tổng, mời đọc bố cục của PDF được cơ cấu ra sao, nếu muốn áp dụng quá trình tăng cấp các bước thêm cập nhật ở mức tệp với mã code lập trình xem thử bài tiến trình cho ứng dụng kích cỡ tệp tải PDF dung lượng quá lớn xử lí ở Delphi