Vì sao văn bản trong PDF không thực sự là văn bản
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”Khi bạn đọc một PDF, bạn thấy các từ. Tệp không chứa các từ. Nó chứa các chỉ dẫn để tô các hình tại những tọa độ, và các hình ấy tình cờ trông giống chữ cái. Khoảng cách giữa những gì một PDF vẽ và những gì nó có nghĩa chính là lý do một trang có thể trông không tì vết mà vẫn sao chép ra thành thứ vô nghĩa. Trang này nói về khoảng cách đó, và về tấm bản đồ nhỏ, riêng biệt khép lại nó.
Vì sao điều này quan trọng
Phần tiêu đề “Vì sao điều này quan trọng”Mọi thứ bạn làm với một PDF với tư cách là văn bản — bôi chọn nó, sao chép nó,
tìm kiếm trong nó, lập chỉ mục để truy xuất, đọc to nó bằng trình đọc màn hình —
đều phụ thuộc vào việc khôi phục các ký tự mà tệp chưa bao giờ trực tiếp lưu trữ.
Nếu việc khôi phục đó thất bại, sự thất bại là vô hình. Trang vẫn kết xuất. Không
ai để ý cho đến khi có người sao chép một đoạn vào email và nhận về □□□□, hoặc
tìm trong một hợp đồng 400 trang một điều khoản rõ ràng đang nằm ở đó mà chẳng
tìm thấy gì.
Đó là một lớp lỗi tốn kém chính bởi nó sống sót qua mọi lần soát xét bằng mắt. Chẳng có gì để thấy cả. Tài liệu trông đầy thẩm quyền và, đối với mục đích máy móc, lại câm lặng.
Tóm tắt ngắn gọn
Phần tiêu đề “Tóm tắt ngắn gọn”- Một PDF vẽ các glyph — các hình thị giác được chọn bởi các mã số trỏ vào một font — chứ không phải các ký tự Unicode.
- Cùng một hình có thể mang ý nghĩa của những ký tự khác nhau, và cùng một ký tự có thể được vẽ bằng những glyph khác nhau. Diện mạo và ý nghĩa được tách rời một cách có chủ đích.
- Để lấy lại văn bản, một trình đọc chạy đường dẫn hiển thị theo chiều ngược
lại: mã thành ký tự. CMap
/ToUnicodecủa font là bảng tra cứu cho bước ngược đó (Spec: ISO 32000-2, §9.10ISO 32000-2 §9.10). - Không có
/ToUnicode, hoặc có một bản sai, nghĩa là sao chép-dán rối loạn và tìm kiếm thất bại — trong khi trang vẫn hoàn hảo đến từng điểm ảnh. - Đây là vấn đề ý nghĩa. Việc glyph có trông đúng hay không là vấn đề diện
mạo riêng biệt được trình bày trong
Fonts: the hard part. NextPDF phát ra một
/ToUnicodeđúng để vòng khứ hồi được trung thực.
Cách NextPDF tiếp cận điều này
Phần tiêu đề “Cách NextPDF tiếp cận điều này”Hãy bắt đầu từ chuyện văn bản lên trang ra sao. Một content stream không nói
“viết từ file.” Nó chọn một font, rồi đưa một chuỗi mã cho một toán tử hiển
thị văn bản (Spec: ISO 32000-2, §9ISO 32000-2 §9). Mỗi mã là một chỉ số
— một vị trí trong một chương trình font — và font biến chỉ số đó thành một đường
viền glyph rồi tô nó ra. Mã 70 không phải là ký tự F. Nó là “khe 70 trong
font này,” và khe 70 tình cờ chứa một đường cong hình F. Chọn một font khác
thì khe 70 có thể là một bông tuyết.
Vậy nên một mã chỉ có ý nghĩa tương đối với encoding của font của nó. Với một font đơn giản thì encoding đó đại khái là một byte cho mỗi glyph. Với các hệ chữ cần đến hàng nghìn glyph — CJK, hoặc bất kỳ tài liệu full-Unicode nào — chừng đó không đủ chỗ, và PDF với tới một font tổng hợp (Type 0) (Spec: ISO 32000-2, §9.7ISO 32000-2 §9.7). Một font tổng hợp đọc các mã nhiều byte qua một CMap, biến chúng thành các CID (định danh ký tự), rồi ánh xạ các CID thành glyph. Đó là một phép gián tiếp tinh tế cho phép một font đánh địa chỉ một tập glyph khổng lồ. Nó cũng là thêm một chỗ nữa mà dấu vết từ “cái được hiển thị” đến “cái nó có nghĩa” có thể nguội lạnh.
Giờ hãy chạy nó ngược lại. Để trích xuất văn bản, một trình đọc lấy các mã mà nó tìm thấy trong content stream và phải khôi phục các ký tự (Spec: ISO 32000-2, §9.10ISO 32000-2 §9.10). Encoding đã vẽ glyph đi theo chiều ký tự đến glyph; việc trích xuất cần chiều mã glyph đến ký tự, và chiều đó không được bảo đảm là khả nghịch. Một font subset có thể đã đánh số lại các glyph của nó. Các CID của một font tổng hợp có thể là riêng tư. Hình trên trang không mang ý nghĩa Unicode cố hữu nào.
Cách khắc phục là gửi kèm bản đồ ngược cùng với font. Bản đồ đó là CMap
/ToUnicode: với mỗi mã mà tài liệu sử dụng, nó ghi lại (các) giá trị
Unicode mà mã đó đại diện cho. Có nó, việc trích xuất là một phép tra cứu gọn
gàng. Không có nó, một trình đọc bị bỏ lại để đoán từ các encoding của font và
các phép heuristic — và đoán chính xác là nơi mà fi trở thành một dấu hỏi.
- Your charactersThe Unicode text you set, for example the word 'file'.
- Encoding → codesThe font's encoding turns characters into numeric codes; a composite font routes them through a CMap to CIDs.
- Codes → glyphsEach code selects a glyph outline, which is painted to the page. This is the part you see.
- Extraction reverses itA reader reads the codes back and looks each one up in /ToUnicode to recover Unicode.
- Text againWith a correct /ToUnicode, copy, search, indexing, and screen readers all get the original characters back.
NextPDF viết bản đồ ngược như một lẽ đương nhiên. Khi nó nhúng một font tổng hợp,
nó phát ra hậu duệ CIDFontType2, cha Type0, và một CMap /ToUnicode để các
mã phân giải ngược về Unicode — phần việc encoding được mô tả trong
Fonts: the hard part. Điểm đáng tách bạch ở
đây là vì sao: stream /ToUnicode không phải để làm cho trang trông đúng. Các
glyph đã trông đúng mà không cần nó. Nó là artifact duy nhất giữ cho văn bản
trích xuất được.
Ví dụ thực tế
Phần tiêu đề “Ví dụ thực tế”Nơi nổi tiếng nhất chuyện này lộ ra là ligature fi. Nhiều font vẽ f và
i thành một glyph kết hợp duy nhất, bởi dấu chấm của i va vào móc của f.
Trên trang nó là một hình, được vẽ bởi một mã. Câu hỏi là điều gì xảy ra khi bạn
sao chép nó.
% A /ToUnicode entry that maps the single ligature code to TWO characters,% so selecting the 'fi' glyph copies out as 'f' then 'i' — not one mystery box.1 beginbfchar<0085> <00660069> % code 0x85 -> U+0066 'f' U+0069 'i'endbfcharMột mục đó là sự khác biệt giữa một lần tìm “file” có khớp và một lần tìm “file”
âm thầm trượt mọi lần xuất hiện được kết xuất bằng ligature. Hãy ánh xạ mã
ligature thành chuỗi hai ký tự f + i, và từ đó trở nên tìm được và sao chép
được. Để nó không được ánh xạ, và trang vẫn hiển thị file hoàn hảo trong khi
văn bản lặng lẽ chẳng nói điều gì máy có thể đọc.
Đây là lý do “fi” có thể trích xuất ra thành một ký tự hoặc hai, và vì sao sự
khác biệt không hề ngẫu nhiên — nó là bất cứ điều gì bản đồ /ToUnicode được bảo
phải nói. Một bản đồ đúng phân rã ligature ngược về các ký tự của nó. NextPDF
phát ra chính xác loại mục đó, nên một ligature trên trang là hai chữ cái thông
thường trên clipboard.
Hiểu lầm thường gặp
Phần tiêu đề “Hiểu lầm thường gặp”Cái bẫy, gọi đúng tên: “văn bản kết xuất ra được, vậy văn bản ổn.” Kết xuất
và trích xuất là những cơ chế khác nhau đọc dữ liệu khác nhau. Kết xuất đi theo
đường dẫn mã-đến-glyph và là thứ mắt bạn kiểm tra. Trích xuất đi theo đường dẫn
mã-đến-ký-tự qua /ToUnicode và là thứ mọi bên tiêu thụ là máy móc kiểm tra. Một
tài liệu có thể đạt điểm tuyệt đối ở việc đầu và thất bại hoàn toàn ở việc thứ
hai, bởi vì về mặt cấu trúc chẳng có gì trực quan để lộ ra điều đó.
Một cái bẫy thứ hai, tinh vi hơn: cho rằng một glyph “hiển nhiên” biết nó là ký tự nào. Không phải vậy. Một glyph là một hình với một chỉ số. Phép ánh xạ ngược về một ký tự là thông tin bổ sung mà bên tạo ra phải cung cấp. Nếu bên tạo ra chưa bao giờ viết nó, không trình đọc nào có thể khôi phục nó một cách trung thực — nó chỉ có thể đoán.
Giới hạn và ranh giới
Phần tiêu đề “Giới hạn và ranh giới”| Edition | Availability |
|---|---|
| Core | NextPDF emits a correct /ToUnicode CMap for the fonts it embeds, including the ligature and composite-font cases, so the documents it produces are searchable and copyable. |
| Pro | Not in this edition |
| Enterprise | Not in this edition |
Một bản đồ /ToUnicode chỉ có thể mang thông tin mà font nguồn thực sự bộc lộ. Ở
nơi một glyph không có ký tự xác định được — một dấu thuần túy trang trí, một biểu
tượng vùng dùng riêng không có gán Unicode — không bản đồ nào có thể bịa ra ý
nghĩa vốn chưa từng có. NextPDF phát ra phép ánh xạ trung thực mà nó có thể suy
ra; nó không bịa ra các ký tự để lấp một khoảng trống.
Trang này nói về các tài liệu NextPDF viết ra. Nó không phải một công cụ sửa
chữa cho các PDF đầu vào tùy ý mà /ToUnicode của chúng vốn đã thiếu hoặc sai;
khôi phục văn bản từ những tệp đó là một bài toán riêng, mang tính heuristic. Và
việc trích xuất trung thực là cần thiết cho khả năng tiếp cận nhưng không phải là
toàn bộ của nó — thứ tự đọc, các thẻ, và văn bản thay thế được trình bày trong
What makes a PDF accessible.
Mini-FAQ
Phần tiêu đề “Mini-FAQ”Vì sao cùng một PDF lại sao chép ổn trong trình đọc này mà tệ trong trình đọc
khác?
Các trình đọc khác nhau dự phòng theo những cách khác nhau khi /ToUnicode
thiếu. Một số đoán từ encoding tích hợp của font và gặp may với văn bản Latin phổ
biến; số khác thì không. Một /ToUnicode đúng loại bỏ trò xổ số — mọi trình đọc
tuân thủ chuẩn đều nhận về cùng các ký tự.
Đây có phải là cùng một chuyện với việc font không được nhúng không?
Không. Việc nhúng là về diện mạo — liệu glyph có tô được mà không cần cài font
hay không. /ToUnicode là về ý nghĩa — liệu mã có ánh xạ ngược về một ký tự
hay không. Một font có thể được nhúng hoàn hảo mà vẫn không có /ToUnicode dùng
được, và đó chính là kiểu thất bại đẹp-mà-không-tìm-được.
Việc trích xuất có bao giờ cần nhiều hơn một ký tự cho mỗi mã không?
Có — trường hợp ligature chính là vậy: một mã ánh xạ thành hai ký tự. Một mục
/ToUnicode có thể ánh xạ một mã đơn thành một chuỗi ngắn, và đó là cách các
ligature, cùng một số dạng tổ hợp, trở về thành các chữ cái cấu thành chúng.
Tài liệu liên quan
Phần tiêu đề “Tài liệu liên quan”- Fonts: the hard part — bài đồng hành về phía diện mạo: nhúng, subset, và cách encoding được dựng. Trang này là phía ý nghĩa của cùng một đồng xu.
- What makes a PDF accessible — văn bản trung thực là một thành phần; thứ tự đọc, các thẻ, và văn bản thay thế là phần còn lại.
- What a PDF actually is — mô hình đối
tượng mà các font, encoding và stream
/ToUnicodesống bên trong.
Thuật ngữ
Phần tiêu đề “Thuật ngữ”- Glyph — một hình thị giác trong một font (một đường viền). Thứ mà một PDF thực sự tô ra. Một glyph có một chỉ số nhưng không có ý nghĩa ký tự cố hữu.
- Ký tự — một đơn vị của ngôn ngữ viết, được nhận diện bởi một điểm mã Unicode. Thứ bạn muốn nói, và thứ việc trích xuất cố gắng khôi phục.
- Mã — giá trị số trong một content stream chọn ra một glyph từ font hiện thời. Không phải một ký tự Unicode; chỉ có ý nghĩa tương đối với font.
- CID — một định danh ký tự được một font tổng hợp dùng để đánh địa chỉ một glyph trong một tập lớn; một bước trung gian giữa mã và glyph.
- Font tổng hợp (Type 0) — một font đọc các mã nhiều byte qua một CMap để đến các CID và glyph, được dùng khi một font phải đánh địa chỉ hàng nghìn glyph.
/ToUnicode— stream CMap ánh xạ các mã mà một tài liệu sử dụng ngược về các giá trị Unicode; thứ làm cho văn bản PDF tìm được, sao chép được và tiếp cận được.- Ligature — một glyph đơn vẽ hai chữ cái trở lên thành một hình (ví dụ
fi); mục/ToUnicodecủa nó phân rã nó ngược về các ký tự của nó.