- Pretext là thư viện JavaScript/TypeScript thuần để tính toán chiều cao và cách ngắt dòng của văn bản nhiều dòng mà không cần truy cập DOM, hỗ trợ cả môi trường trình duyệt lẫn máy chủ
- Không sử dụng các API đo đạc DOM như getBoundingClientRect, nên loại bỏ chi phí reflow bố cục và đảm bảo độ chính xác bằng logic đo đạc riêng dựa trên font engine
- Thông qua API prepare() / layout(), có thể tiền xử lý văn bản và thực hiện tính toán chiều cao nhanh bằng phép toán thuần túy nhờ dữ liệu độ rộng được cache
- Hỗ trợ emoji, văn bản đa hướng trộn lẫn (bidi), nhiều ngôn ngữ, đồng thời cho kết quả nhất quán trên Canvas·SVG·WebGL·server rendering
- Đây là text engine hiệu năng cao có thể dùng để triển khai các bố cục UI chính xác như virtualized scrolling, kiểm tra tràn văn bản, bố trí văn bản nổi
Tổng quan
- Pretext là thư viện JavaScript/TypeScript thuần dành cho đo lường và dàn trang văn bản nhiều dòng, hỗ trợ DOM, Canvas, SVG và cả server-side rendering
- Không sử dụng API đo đạc DOM (
getBoundingClientRect, offsetHeight, v.v.) nên loại bỏ chi phí reflow bố cục
- Cung cấp độ chính xác và hiệu năng cao nhờ logic đo đạc riêng lấy font engine của trình duyệt làm chuẩn
- Hỗ trợ mọi ngôn ngữ, emoji, văn bản đa hướng trộn lẫn (bidi) và cũng xử lý khác biệt giữa các trình duyệt
Cài đặt và demo
Tính năng chính
- Pretext cung cấp hai cách sử dụng chính
-
1. Đo chiều cao đoạn văn mà không cần truy cập DOM
prepare() tiền xử lý văn bản, chuẩn hóa khoảng trắng, tách segment, áp dụng quy tắc glue và thực hiện đo đạc dựa trên canvas để trả về opaque handle
layout() dùng dữ liệu độ rộng đã cache để tính toán chiều cao và số dòng bằng phép toán thuần túy
- Với cùng văn bản và cấu hình, không cần gọi lặp lại
prepare(), khi resize chỉ cần chạy lại layout()
- Tùy chọn
{ whiteSpace: 'pre-wrap' } giúp giữ nguyên khoảng trắng, tab (\t) và xuống dòng (\n)
- Kết quả benchmark:
prepare() khoảng 19ms (với 500 văn bản), layout() khoảng 0.09ms
- Giá trị chiều cao trả về có thể dùng cho các tính năng UI như sau
- Tính toán chiều cao chính xác trong virtualization và xử lý occlusion
- Hệ thống bố cục dựa trên JS (ví dụ: masonry, cấu trúc tương tự flexbox)
- Kiểm tra tràn văn bản bằng AI
- Giữ nguyên vị trí cuộn khi văn bản được tải
-
2. Tự cấu thành dàn trang đoạn văn thủ công
- Tạo dữ liệu theo đơn vị segment bằng
prepareWithSegments()
layoutWithLines() trả về văn bản và thông tin độ rộng của từng dòng trong độ rộng cố định
walkLineRanges() duyệt độ rộng và phạm vi con trỏ của từng dòng mà không tạo chuỗi văn bản
- Ví dụ: có thể điều chỉnh bố cục kiểu tìm kiếm nhị phân để tìm số dòng và chiều cao phù hợp bằng cách thử nhiều độ rộng
layoutNextLine() dàn trang tuần tự từng dòng khi mỗi dòng có độ rộng khác nhau
- Ví dụ: bố trí văn bản nổi để văn bản chảy quanh hình ảnh
- Cách này cũng có thể áp dụng giống hệt trên Canvas, SVG, WebGL, server-side rendering
Tóm tắt API
-
API cho đo đạc cơ bản
prepare(text, font, options?): phân tích và đo văn bản, trả về handle để truyền cho layout()
layout(prepared, maxWidth, lineHeight): tính chiều cao và số dòng của văn bản theo độ rộng và line-height đã cho
-
API cho dàn trang thủ công
prepareWithSegments(text, font, options?): trả về dữ liệu theo đơn vị segment
layoutWithLines(prepared, maxWidth, lineHeight): bao gồm văn bản, độ rộng và thông tin con trỏ của từng dòng
walkLineRanges(prepared, maxWidth, onLine): truyền độ rộng và phạm vi con trỏ của từng dòng qua callback
layoutNextLine(prepared, start, maxWidth): thực hiện dàn trang dưới dạng iterator theo từng dòng
- Bao gồm định nghĩa kiểu
LayoutLine, LayoutLineRange, LayoutCursor
-
Tiện ích khác
clearCache(): xóa cache nội bộ
setLocale(locale?): đặt locale và xóa cache (không ảnh hưởng tới trạng thái hiện có)
Giới hạn và lưu ý
- Pretext không phải là font rendering engine hoàn chỉnh
- Các thuộc tính CSS mục tiêu mặc định
white-space: normal
word-break: normal
overflow-wrap: break-word
line-break: auto
- Khi dùng
{ whiteSpace: 'pre-wrap' }, khoảng trắng, tab và xuống dòng được giữ nguyên, đồng thời áp dụng tab-size: 8
- Trên macOS, font
system-ui không phù hợp cho độ chính xác của layout(), nên khuyến nghị dùng tên font tường minh
- Do
overflow-wrap: break-word, ở độ rộng rất hẹp vẫn có thể xuống dòng trong lòng từ, nhưng chỉ tách theo đơn vị grapheme
Liên quan đến phát triển
- Xem
DEVELOPMENT.md để biết môi trường phát triển và các lệnh
Đóng góp và bối cảnh
- Kế thừa ý tưởng từ dự án text-layout của Sebastian Markbage
- Phát triển tiếp cấu trúc kế thừa từ shaping dựa trên canvas
measureText, xử lý bidi của pdf.js và thiết kế streaming line breaking
1 bình luận
Ý kiến trên Hacker News
Dự án này thực sự ấn tượng
Nó giải quyết bài toán tính toán hiệu quả chiều cao của văn bản xuống dòng mà không cần thực sự render văn bản trên trang web
Nó cache chiều rộng và chiều cao của các segment được tách theo từng từ, rồi tự triển khai thuật toán xuống dòng của trình duyệt
Đây là công việc rất khó vì phải xử lý nhiều loại ký tự như dấu gạch nối, emoji, tiếng Trung, cũng như khác biệt render giữa các trình duyệt (bao gồm Safari)
Nó dùng bộ dữ liệu corpora và trang kiểm tra độ chính xác để đối chiếu với trình duyệt thực
Với văn bản ASCII, code của tôi mất 80ms, còn pretext mất 2200ms
Tôi vẫn chưa kiểm tra độ chính xác, nhưng dự định tối nay sẽ thử
Các PR cải thiện hiệu năng đã được mở sẵn trong issue #18
Trước đây tôi cũng từng khổ sở khi cố render văn bản nhiều dòng trên canvas
Dự án này hữu ích hơn nhiều vì nó gắn trực tiếp với DOM
Ví dụ: demo Scrawl
Nó có thể chậm hơn API native, và cũng không thể đảm bảo dùng cùng logic với cơ chế render không-phải-canvas của trình duyệt
Nó là cách render lên canvas rồi đo đạc, và chủ yếu cung cấp API để phân tích layout văn bản
Đây đúng là tính năng được chờ đợi từ rất lâu
Từ trước tới nay luôn rất khó triển khai đúng kiểu những thứ như accordion responsive
Mô thức phát triển của web lúc nào cũng là ① xuất hiện yêu cầu phức tạp → ② hack bằng JS/CSS → ③ được chuẩn hóa
Lần này có vẻ là bước 2 đúng nghĩa, chứ không chỉ là mẹo vặt
Xem RESEARCH.md thì thấy họ còn nghiên cứu rất chi tiết cả khác biệt khi đo emoji giữa các trình duyệt
Việc bảo trì chắc sẽ rất vất vả, nhưng có cảm giác đây có thể là một bước ngoặt lớn cho sự phát triển của web
Điều thú vị lần này là AI được tận dụng tích cực trong quá trình phát triển. Có vẻ phần lớn được hiện thực bằng agent của Cursor
Theo tác giả thư viện, họ đã cho Claude Code và Codex học dữ liệu ground truth của trình duyệt rồi lặp lại quá trình đo đạc trong nhiều tuần
Xem tweet liên quan
Có vẻ cũng đã dùng một phần Autoresearch
Tôi đặc biệt thích ví dụ reflow dựa trên shape
Tôi đã muốn áp dụng nó vào Ensō(enso.sonnet.io), nhưng cố kiềm lại để giữ mọi thứ đơn giản
Ví dụ accordion cũng có thể triển khai bằng
interpolate-sizecủa CSSXem bài viết của Josh Comeau
Ví dụ text bubble cũng có thể được làm tương tự bằng
text-wrap: balance | prettybalancehayprettyvẫn không giải quyết được hoàn toànNhiều khi ta không hề muốn các dòng có độ dài đồng đều
Issue CSSWG liên quan: #191
text-wrapcó giúp cân số từ trên mỗi dòng, nhưng vấn đề khoảng trống bên phải vẫn còn đópretext không dùng trực tiếp canvas.measureText; bạn chỉ cần truyền văn bản và thuộc tính qua JS API là nó sẽ tự tính layout
Trước đây либо phải tự gọi measureText, либо phải port harfbuzz sang trình duyệt
Đây có vẻ không hẳn là đột phá kỹ thuật, mà là kết quả của việc kết hợp khéo các thành phần sẵn có
Nhưng tôi vẫn tò mò nó khác Skia-wasm / Canvaskit thế nào
Điểm khác của pretext là dùng AI để hiện thực render glyph thuần TypeScript
Cảm giác như sự khác biệt giữa tự viết ffmpeg bằng C và gọi nó từ Dart
Những thử nghiệm như thế này cho thấy khả năng mới của FOSS phía client
Năm ngoái tôi đã làm một hệ thống dàn trang brochure in ấn bằng HTML, và để xử lý xuống dòng cùng tránh orphan line, tôi đã phải dùng Selection API để lặp đi lặp lại việc tính biên của box
Đến giờ nó vẫn chạy tốt, nhưng có một đoạn hack off-by-one mà tôi cũng không hiểu vì sao nó cần thiết
Tính năng tạo dòng lặp của pretext đúng là rất đáng mừng
Trên Fedora + Firefox, tất cả demo đều bị vỡ giao diện
Ví dụ: ảnh chụp màn hình
Kiểu tính năng này thực ra nên được cung cấp dưới dạng API chuẩn của trình duyệt
Tôi tò mò không biết phải làm thế nào để gửi feature request lên W3C, hay liệu có kiểu bỏ phiếu cộng đồng nào không
Nhưng các browser vendor không đặt ưu tiên cho nó
Hiện tại Chrome dường như tập trung hơn vào các API liên quan tới AI
Engine Sciter đã có sẵn tính năng Graphics.Text
Đó là một phần tử render văn bản dựa trên canvas có thể áp dụng trực tiếp style CSS
Tìm kiếm văn bản của trình duyệt (Ctrl+F) không hoạt động đúng với danh sách cuộn ảo
Để giải quyết kiểu vấn đề này, có lẽ cần một API “Search” mới thay vì JS
Các dự án liên quan: Display Locking, tài liệu MDN
Các mục ngoài màn hình của danh sách ảo không có trong DOM nên không thể tìm kiếm
Muốn giải quyết chuyện này sẽ cần một hợp đồng mới của trình duyệt tích hợp cả selection, focus, vị trí cuộn và điều hướng giữa các kết quả khớp
Nhưng khả năng các website dùng nó một cách nhất quán là không cao