Kiểm tra độ rộng cho thấy chuỗi an toàn để cắt. Nhưng nó lại làm tách đôi một chữ Kanji.
Một cái tên được nhập vào bảng trên terminal và kết quả hiển thị bị lỗi. Họ của người đó là 𠮷田.
Ký tự đầu tiên không phải là chữ 吉 thông thường. Nó là 𠮷 (U+20BB7). Đây là một dạng hiếm được sử dụng trong các họ thực tế của Nhật Bản. Bảng đã cắt bớt ô để vừa với cột, để lại một ký tự bị lỗi.
Lỗi nằm trong chỉ một dòng mã. Đó là một phần tối ưu hóa quyết định rằng một chuỗi là an toàn để cắt theo index.
Một chuỗi JavaScript có ba loại độ dài khác nhau: • Code units (.length): "𠮷".length là 2. • Code points: [..."𠮷"].length là 1. • Display width (Độ rộng hiển thị): 𠮷 chiếm 2 cột.
Đối với văn bản tiếng Anh tiêu chuẩn, các con số này đều giống nhau. Sự trùng hợp này khiến mã nguồn trông có vẻ an toàn.
Ký tự 𠮷 phá vỡ quy tắc này. Nó có 2 code units vì nó là một surrogate pair. Nó chiếm 2 cột vì nó là một ký tự rộng (wide character). Các con số khớp nhau (2 = 2), nhưng vì những lý do khác nhau.
Thư viện cli-table3 đã sử dụng một fast path:
Nếu độ dài code unit bằng với display width, thì cắt chuỗi theo index.
Điều này đã hoạt động tốt trong nhiều năm vì các ký tự tiếng Nhật thông thường như 漢 có độ dài là 1 và độ rộng là 2. Chúng không bao giờ rơi vào fast path.
Fast path chỉ được kích hoạt đối với các ký tự hiếm như 𠮷 hoặc emoji. Những ký tự này có độ dài là 2 và độ rộng là 2. Mã nguồn nghĩ rằng chúng là các ký tự đơn vị một (one-unit characters) đơn giản. Nó cắt chúng làm đôi theo index. Điều này để lại một surrogate đơn độc. Đó là lý do tại sao terminal hiển thị một ô lỗi.
Để khắc phục điều này, bạn phải:
- Bảo vệ fast path để loại trừ các surrogate pairs.
- Trim theo code points thay vì code units.
Sử dụng Array.from(str) sẽ giúp ích vì nó lặp qua từng code point. Điều này đảm bảo bạn không bao giờ cắt đôi một ký tự.
Bài học rất đơn giản: Đừng bao giờ đo bằng một đơn vị rồi lại cắt bằng một đơn vị khác. Nếu bạn đo bằng display width hoặc code points, bạn phải cắt bằng chính những đơn vị đó.
Hãy kiểm tra mã của bạn với một ký tự CJK Extension B hoặc một emoji. ASCII sẽ không bao giờ làm lộ ra lỗi này.
Cộng đồng học tập tùy chọn: https://greymoth-jp.github.io/cjk-failure-corpus/
