4 điểm bởi GN⁺ 23 ngày trước | 1 bình luận | Chia sẻ qua WhatsApp
  • Pretextthư 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

  • Pretextthư 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
    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 corporatrang kiểm tra độ chính xác để đối chiếu với trình duyệt thực

    • Tôi cũng từng làm thứ tương tự. Đó là một phiên bản đơn giản 2KB tên uWrap.js, được viết mà không dùng AI
      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
    • Engine layout văn bản thực sự nổi tiếng là cực khó
      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
    • Đọc phần mô tả thì có vẻ thực ra nó render segment lên canvas để đo
      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ói cho đúng thì đây không hẳn là tự triển khai thuật toán render văn bản 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
    • Khi làm phụ đề động cho video Remotion, tôi từng rất vất vả vì phải tính chiều cao văn bản; thư viện này có vẻ sẽ giúp ích rất nhiều
  • Đâ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

    • Accordion responsive giờ đã có thể làm bằng CSS, nhưng kiểu API này vẫn rất cần thiết
      Đ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-size của CSS
    Xem 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 | pretty

    • Nhưng balance hay pretty vẫn không giải quyết được hoàn toàn
      Nhiều khi ta không hề muốn các dòng có độ dài đồng đều
      Issue CSSWG liên quan: #191
    • text-wrap có 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

    • Khác biệt nằm ở sự đơn giản. pretext không phải wasm
    • Skia là một engine render khổng lồ. Nó cung cấp API render độc lập thiết bị như Flutter
      Đ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ếu tác giả nói đúng, thì điều này sẽ tạo ra thay đổi lớn cho framework GUI web và trình soạn thảo rich text
  • 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

    • Điều đó cho thấy loại dự án này rốt cuộc là chuỗi truy đuổi các edge case không hồi kết
  • 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

    • Đã có đề xuất Font Metrics API
      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

    • Đã từng có Virtual Scroller API, nhưng hầu như không có tiến triển
      Các dự án liên quan: Display Locking, tài liệu MDN
    • Tìm kiếm native chỉ duyệt các node đang tồn tại trong DOM
      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