3 điểm bởi GN⁺ 2024-03-22 | 3 bình luận | Chia sẻ qua WhatsApp
  • Dropflow là một công cụ layout CSS được tạo ra để khám phá các tiêu chuẩn CSS nền tảng như inline, block, float, positioning và trong tương lai là table; flexbox và grid không nằm trong phạm vi mục tiêu
  • Có thể dùng với Node và node-canvas để tạo PDF·hình ảnh ở backend, và trong trình duyệt có thể render rich text có xuống dòng lên canvas
  • Layout văn bản hỗ trợ văn bản hai chiều·RTL, font fallback theo đơn vị grapheme, dấu phụ có màu, đăng ký font OpenType/TrueType và shaping dựa trên HarfBuzz
  • Phạm vi hỗ trợ CSS gồm hơn 30 thuộc tính; các mục như float, clear, display: inline-block, position: relative, overflow, z-index, zoom hoạt động, còn table, absolute, fixed, transform đang ở giai đoạn lên kế hoạch
  • Hiệu năng cao nhất đạt được với API hyperscript bỏ qua việc parse HTML/CSS và các đối tượng style có thể tái sử dụng; trên MacBook Pro 2019, có thể chuyển The Little Prince với hơn 500 đoạn văn từ HTML sang hình ảnh trong dưới 160ms

Phạm vi layout CSS mà Dropflow xử lý

  • Dropflow là một công cụ layout CSS, được tạo ra để khám phá phạm vi của các tiêu chuẩn CSS nền tảng
    • Mục tiêu là inline, block, float, positioning và trong tương lai là tables
    • flexbox và grid không nằm trong phạm vi mục tiêu
  • Có triển khai layout văn bản chất lượng cao và có thể hiển thị nhiều ngôn ngữ trên thế giới
  • Hai trường hợp sử dụng được nêu ra
    • Tạo PDF hoặc hình ảnh ở backend bằng Node và node-canvas
    • Render rich text có xuống dòng lên canvas trong trình duyệt

Tính năng chính

  • Hỗ trợ hơn 30 thuộc tính CSS, bao gồm cả những thuộc tính phức tạp như float
  • Các tính năng liên quan đến văn bản rất phong phú
    • Văn bản hai chiều và văn bản RTL
    • Font fallback theo đơn vị grapheme
    • Dấu phụ có màu
    • Xử lý xuống dòng hợp lý, ví dụ đưa padding ở đầu sang dòng tiếp theo
    • Shaping được tối ưu hóa
  • Ngoài HTML/CSS, còn hỗ trợ API hyperscript h() để truyền style dưới dạng object
  • Có thể đăng ký buffer OpenType/TrueType, và việc đăng ký là bắt buộc
  • <img> hỗ trợ JPEG, BMP, PNG, GIF nhưng mức hỗ trợ có thể khác nhau tùy backend
  • Không tính toán lại style kế thừa·cascade hai lần
  • Được trang bị đầy đủ type, có nhiều test và hướng tới tốc độ cao

Trạng thái hỗ trợ CSS

  • Trong inline formatting, các mục hoạt động gồm color, direction, font-family, font-size, font-stretch, font-style, font-weight, line-height, text-align, vertical-align, white-space, word-break, overflow-wrap, word-wrap, word-spacing v.v.
  • Trong inline formatting, font-variant, letter-spacing, tab-size, text-decoration, unicode-bidi đang ở trạng thái được lên kế hoạch
  • Trong block formatting, clearfloat hoạt động
    • horizontal-tb, vertical-lr, vertical-rl của writing-mode đang được triển khai một phần
    • Đã triển khai trong BFC nhưng chưa triển khai trong IFC
  • Trong boxes and positioning, nhiều thuộc tính box và bố trí hoạt động
    • background-clip, background-color, border-color, border-style, border-width
    • top, right, bottom, left
    • box-sizing
    • display: block, inline, inline-block, flow-root, none
    • height, margin, padding, overflow, width, z-index, zoom
    • position: relative
  • Trong boxes and positioning, display: table, min/max-height, min/max-width, position: absolute, position: fixed, transform đang ở trạng thái được lên kế hoạch

Quy trình sử dụng cơ bản

  • Dropflow hoạt động dựa trên DOM có style kế thừa·đã tính toán, tương tự như trình duyệt
  • Quy trình thông thường như sau
    • Đăng ký font bằng FontFace trước khi layout
    • Tạo object style bằng flow.style()
    • Tạo DOM bằng flow.h()
    • Chuẩn bị cây để render bằng flow.dom()
    • Thực hiện layout và paint toàn bộ lên canvas bằng renderToCanvas()
  • Cũng cung cấp HTML API, nhưng chỉ khuyến nghị khi hiệu năng không quan trọng hoặc để học tập
    • Việc parse làm tăng thêm thời gian và làm bundle lớn hơn đáng kể
    • Có ghi rằng HTML parsing hiện nhanh nhờ @fb55
    • Hiện chỉ hỗ trợ thuộc tính HTML style, còn class vẫn chưa hoạt động

Đặc tính hiệu năng

  • Hiệu năng là mục tiêu hàng đầu, chỉ đứng sau độ chính xác
  • Có đưa ra các số liệu hiệu năng theo ví dụ
    • Chuyển 8 đoạn văn với nhiều inline span và font khác nhau từ HTML sang hình ảnh mất 9ms trên MacBook Pro 2019 và 13ms trên MacBook Pro 2012
    • Chuyển The Little Prince với hơn 500 đoạn văn từ HTML sang hình ảnh mất dưới 160ms trên MacBook Pro 2019 và dưới 250ms trên MacBook Pro 2012
    • Tạo từ dài 10 ký tự và chỉ thực hiện layout mất dưới 25µs trên MacBook Pro 2019 và dưới 50µs trên MacBook Pro 2012
  • Hiệu năng nhanh nhất đạt được khi dùng API hyperscript để tạo DOM trực tiếp và bỏ qua bước parse HTML/CSS thông thường
  • Tái sử dụng object style còn mang lại lợi ích lớn hơn
  • Việc reflow với chiều rộng khác nhanh hơn so với tạo lại toàn bộ cây layout

Cấu trúc API

  • Có hai bước cơ bản
    • Đăng ký font
    • Tạo DOM bằng Hyperscript API hoặc Parse API
  • Trong cách dùng đơn giản, có thể render DOM trực tiếp lên canvas
    • renderToCanvas(el, canvas) render toàn bộ layout bằng cách dùng width và height của canvas làm kích thước viewport
  • Nếu dùng API cấp thấp hơn thì có thể giữ lại layout
    • Tải tài nguyên phụ thuộc
    • Tạo layout của DOM
    • Reflow layout
    • Paint lên đích như HTML5 canvas
  • Cách này có thể dùng để reflow ở kích thước khác, không paint phần layout không nhìn thấy, hoặc lấy intrinsics

Xử lý font

  • Bước đầu tiên của chương trình Dropflow là đăng ký các font sẽ được chọn bởi thuộc tính font của CSS
  • Vì Dropflow không tìm kiếm font hệ thống, cần tạo và thêm ít nhất một FontFace
  • API đăng ký font triển khai một tập con của CSS Font Loading API và thêm phương thức phi chuẩn loadSync
  • Ở phía server, có thể đồng bộ tải URL file:/// bằng readFileSync
  • ArrayBuffer được tải ngay trong constructor như trên trình duyệt
  • registerNotoFonts đăng ký toàn bộ họ font Noto Sans
    • Font được phân phối bởi FontSource và được host bởi jsDelivr
    • Có hơn 200 font Noto Sans và chuỗi unicodeRange của font CJK rất lớn nên đây là một import nặng
    • Có ghi rằng khi dùng production trên trình duyệt, nên đăng ký từng font riêng lẻ sẽ tốt hơn
    • Với Latin, font italic được đăng ký; với mọi script, normal 400 và bold 700 được đăng ký
  • Tiếng Trung·Hàn·Nhật chia sẻ các mã Unicode chung nhưng ký tự có thể được render khác nhau, nên nếu có thể thì nên dùng font dành riêng cho từng ngôn ngữ

Layout, reflow, paint

  • layout(el) tạo ra layout gồm box tree, fragmentation tree và glyph
    • Box tree nhìn chung tương ứng với DOM tree, nhưng có thể có nhiều box hơn do nội dung văn bản ẩn danh hoặc ít hơn do display: none
  • reflow(layout, width = 640, height = 480) sắp xếp box, chia văn bản thành dòng và đưa layout vào trạng thái có thể paint
    • Margin collapsing của block box
    • Truyền văn bản vào HarfBuzz
    • Lặp font fallback
    • Reshaping theo xuống dòng và break point
    • Bố trí float và xử lý clear
    • Bố trí shaped text span và background theo direction và hướng văn bản
    • Tính intrinsics của nội dung float, inline-block, absolute
    • Xử lý position sau normal flow
  • Đích paint hiện hỗ trợ Canvas và SVG
    • paintToCanvas paint lên browser canvas, node-canvas hoặc context tương thích tiêu chuẩn tương tự
    • paintToSvg tạo chuỗi SVG và bao gồm quy tắc @font-face tham chiếu đến URL đã truyền vào FontFace
    • paintToSvgElements không thêm <svg> và quy tắc @font-face để dùng khi vẽ vào SVG hiện có
    • paintToHtml tạo danh sách phẳng các phần tử định vị tuyệt đối; không được khuyến nghị sử dụng nhưng có thể hữu ích trong quá trình phát triển

DOM API và hook môi trường

  • HTMLElement gốc nhận được từ Hyperscript và Parse API cung cấp các phương thức tìm phần tử theo tag name, id, class giống họ querySelector của trình duyệt
    • query(selector) trả về một HTMLElement hoặc null
    • queryAll(selector) trả về HTMLElement[]
  • HTMLElement có thể có render box được liên kết
    • Thông thường là một, nhưng có thể là nhiều khi nội dung inline và block trộn lẫn
    • BlockContainer được tạo cho absolute positioned element, floated element, inline-block, block-level element
    • ReplacedBox được tạo cho hình ảnh
  • Dropflow được thiết kế để có thể điều chỉnh cho nhiều môi trường khác nhau
    • Trong trình duyệt, dùng fetch để tải font và hình ảnh, rồi đăng ký buffer font vào document.fonts
    • Trong Nodejs, có thể đồng bộ tải font bằng fs.readFileSync
    • Nếu có canvas backend và node-canvas, sẽ gọi registerFont của node-canvas
    • node-canvas không hỗ trợ font buffer nên phải dùng URL file://
  • Để dùng @napi-rs/canvas hoặc skia-canvas, cần vài dòng code nối flow.environment.registerFont với API đăng ký font tương ứng
  • Môi trường có 6 hook
    • wasmLocator
    • registerFont
    • resolveUrl
    • resolveUrlSync
    • createDecodedImage
    • destroyDecodedImage

Text shaping dựa trên HarfBuzz

  • Layout glyph được thực hiện bởi HarfBuzz được biên dịch sang WebAssembly
  • Mục tiêu là đạt được độ chính xác khó có được với cách xác định vị trí span văn bản bằng API measureText
  • Ví dụ, trong Google Sheets nếu chỉ tô màu khác cho chữ "V" trong "AV", kerning sẽ biến mất và khoảng cách giữa các chữ sẽ xa hơn ban đầu
    • Vì hai lần gọi measureTextfillText xảy ra theo từng ký tự, làm mất contextual glyph advance
  • Dropflow dùng HarfBuzz ở các ranh giới shaping thô hơn thay vì tại điểm đổi màu để hỗ trợ font chính xác hơn
  • HarfBuzz biên dịch sang WebAssembly có thể cho chỉ số hiệu năng tương tự measureText của CanvasRenderingContext2D
    • Không nhanh bằng measureText, nhưng cũng không chậm hơn quá nhiều
    • Cả hai đều được ghi là không phải nút thắt cổ chai chi phối trong text layout stack
  • Text layout dựa trên measureText cần word cache để chạy nhanh, và có ghi rằng các ứng dụng GSuite dùng cách này
    • Word cache không thể hỗ trợ font có hiệu ứng vượt qua khoảng trắng
    • Để hỗ trợ các font như vậy, cần tìm kiếm nhị phân trên break index của cả đoạn, và điều đó chậm hơn nhiều so với việc đưa toàn bộ đoạn vào HarfBuzz
    • Dấu phụ có màu là điều không thể làm bằng measureText

Các dự án phụ thuộc

  • Dropflow không có dependency trong package.json, nhưng tận dụng công sức của nhiều dự án khác
  • Các dependency JavaScript được check in trực tiếp vào dự án, và được chỉnh sửa ở nhiều mức độ để giữ trọng tâm và tránh vấn đề dependency-of-dependency
  • Các dự án chính gồm

3 bình luận

 
winterjung 2024-03-23

Tiêu đề gốc là "Show HN: Dropflow, a CSS layout engine for node or <canvas>". Hiện tại nó lại đang được đưa vào thành "GN⁺: HN giới thiệu: Dropflow, công cụ dựng bố cục CSS cho node hoặc <canvas></canvas>".

 
dlehals2 2024-03-22

Trong tiêu đề có thẻ nên phần tiêu đề ở trang chi tiết bị vỡ rồi.. haha, phải escape thôi..

 
GN⁺ 2024-03-22
Ý kiến trên Hacker News
  • Dạo này cách làm PDF đẹp mặc định ở backend là chạy trình duyệt headless rồi dùng API trình duyệt để chuyển HTML/CSS thành PDF, nhưng chi phí vận hành các instance trình duyệt trên server và mở rộng theo khối lượng công việc lớn là khá cao
    Đây là công cụ có thể thay đổi cuộc chơi, vì giờ có thể thiết kế và tạo PDF bằng HTML/CSS mà không phải chịu overhead của trình duyệt

    • Thật đáng kinh ngạc khi web đã đi đến mức này. Cách tốt nhất để tạo tài liệu PDF đẹp lại là chạy nguyên một trình duyệt web trên server, điều mà vào thập niên 90 hay 2000 hẳn rất khó tưởng tượng
    • Dùng trình duyệt có ưu điểm là PDF tạo ra sử dụng vector và font. Trong khi đó, cách làm dựa trên Canvas có lẽ sẽ khiến phần lớn nội dung trong PDF trở thành hình ảnh, dù với đa số mục đích sử dụng thì đó có thể không phải vấn đề lớn
    • Tôi đã dùng https://ekoopmans.github.io/html2pdf.js/ và nó hoạt động khá ổn
    • Tôi hơi bối rối. Tôi đã dùng thư viện Prawn để tạo PDF cho backend của một side project trong khá lâu: https://github.com/prawnpdf/prawn
      Nhưng PDF tôi tạo ra thì rõ ràng không đẹp lắm, nên có lẽ khác biệt nằm ở đó
    • Tôi đã làm trình render PDF cho một số khách hàng, và yêu cầu lớn nhất với PDF là khả năng truy cập
      Tất cả đều cần tuân thủ ADA, nên rất khó chuyển sang renderer dựa trên Canvas. Làm vậy sẽ đánh mất tính truy cập
  • Trông thật sự rất hay. Ở công ty trước tôi từng phát triển một nền tảng học trực tuyến nhập vai, dùng Oculus Quest 2 để đào tạo ngoại ngữ cho nhân sự Bộ Quốc phòng, và có dùng WebXR, Three.js cùng những công nghệ khác
    Unity3D một lần là đủ, và tôi cũng không muốn đi qua quy trình duyệt của app store. Chúng tôi có một nhóm thiết bị riêng nên điều đó không thành vấn đề
    Một trong những bài toán khó nhất là xây dựng workflow cho nội dung giáo trình, và gần như tôi phải tự làm một mình. Các giảng viên ngôn ngữ thực tế tạo PDF bằng PowerPoint, rồi tôi dùng trình biên tập chuyên dụng do mình làm để đưa PDF lên cơ sở dữ liệu nội dung và triển khai vào môi trường huấn luyện, sau đó render chúng bằng PDFJS vào phần tử Canvas để dùng làm texture cho các hình chữ nhật 3D
    Nếu có công cụ như thế này, mọi người đã không cần phải đi đường vòng qua PowerPoint để tạo tài liệu. Cách làm bằng PowerPoint đã cải thiện đáng kể tốc độ workflow so với lần thử trước đó là bắt mọi người làm hình ảnh bằng Photoshop, nhưng sẽ còn tốt hơn nếu có thể tạo trình biên tập biển báo ngay trong ứng dụng, để loại bỏ vòng lặp “đoán xem nó sẽ trông thế nào trong môi trường → xuất ra PDF → tải lên DB → xem hình dạng thực tế”
    Dù sao thì cũng không có nhiều ý nghĩa lắm vì chẳng có đội ngũ phát triển kinh doanh hay marketing nào biết bán dịch vụ thay vì sản phẩm

  • Tôi đã tìm thứ như thế này cho dự án của mình https://htwins.net/scale2 và cho các công việc khác dùng SVG hoặc Canvas

    • Tôi rất muốn đọc bài viết hậu trường phát triển về cách họ đã làm ra thứ này
  • Nếu bạn đang khổ sở với Flexbox, có thể dùng một công cụ giúp đơn giản hóa quá trình để không phải bận tâm đến nhiều thuộc tính khi tạo layout responsive: https://flexboxcss.com

    • Đây thực sự là lần đầu tôi thấy một website dùng thiết kế neumorphic. Nhìn đẹp và tôi chưa từng thấy nó trong thực tế
  • Tuyệt vời. Những công cụ như thế này rất quan trọng vì chúng giúp chiếc hộp ma thuật là engine render của trình duyệt trở nên dễ hiểu hơn
    Nếu có thể tạo ra một đặc tả đầy đủ, máy có thể đọc được cho việc render HTML và CSS, ta có thể sinh ra renderer từ đó. Những hành vi đặc thù theo từng trình duyệt có thể được đặt thành phần mở rộng bên trên. Tôi rất muốn thấy thứ như https://github.com/tawesoft/html5spec trở thành dạng được dùng trong engine thực tế

    • Điều này khiến tôi nhớ đến việc Ladybird là một trình duyệt đang được viết từ đầu, và nó cũng khá hữu ích trong việc kiểm tra xem đặc tả có lỗi hay không
  • Khá gần với điều tôi vẫn đang tò mò gần đây. Tôi đã nghĩ về việc liệu có thể dùng CSS và SVG như một lớp trừu tượng nằm trên các thư viện đồ họa và UI hay không
    Đây là lần đầu tôi nghe đến node-canvas, nhưng có vẻ nó lấp vào phần vẽ trong số đó. Công cụ này có vẻ có thể đảm nhận phần layout là tất cả những gì tôi cần từ một thư viện UI
    Tôi cũng tò mò không biết việc hiện thực CSS khó đến mức nào. Tôi nghe nói nó khá phức tạp

    • Cũng có một dự án khác tên là Sciter nhắm CSS vào các thư viện đồ họa native: https://sciter.com
      Việc hiện thực CSS là khó, nhưng rào cản lớn nhất là kiến thức không được trình bày rõ ràng
      Phần khó nhất là text layout. Việc xử lý glyph và duyệt ngược để hỗ trợ RTL rất đau đầu, và xuống dòng cũng trở nên thực sự phức tạp. Mọi thứ càng khó hơn vì kiến thức cần thiết không được gom lại ở một nơi
      Sau khi hoàn thành block layout ban đầu, trong vài năm tôi chỉ làm thêm vài giờ mỗi tuần, đồng thời phải học các chi tiết về text shaping và itemization, những gì nên làm và nên tránh. Tôi học được nhiều điều bằng cách đọc mã nguồn Pango [1], và phần còn lại chắp vá qua Google Search
      Ngoài ra, đặc tả của W3C bao quát gần như mọi thứ. Chuẩn CSS2 [2] là một trong những tài liệu đẹp nhất tôi từng đọc. Nó nhất quán về nội tại, súc tích, và là kết quả của nhiều năm suy ngẫm cùng thử nghiệm sai-sửa. CSS3 cũng rất tuyệt, nhưng CSS2 là nền tảng của mọi thứ
      [1] https://gitlab.gnome.org/GNOME/pango/
      [2] https://www.w3.org/TR/CSS22/
  • Đây là một đóng góp lớn cho thế giới. Trông giống kiểu trường hợp điển hình mà ai cũng nghĩ “cần có người làm $X” nhưng chẳng ai làm
    Là người thích dùng CSS cho layout, dạo này tôi chủ yếu dựa vào Flexbox và Grid. Tôi hoàn toàn hiểu việc chưa hỗ trợ chúng, nhưng vẫn tò mò liệu có kế hoạch hỗ trợ vào một lúc nào đó hay không. Nếu có thì người khác có thể giúp như thế nào?

  • Thật sự quá ấn tượng. Có lẽ đa số mọi người không nhận ra việc chuyển HTML sang PNG bằng code khó đến mức nào
    Do sự khác biệt giữa Node và trình duyệt, hoặc sự khác biệt giữa HTML và Canvas, bạn sẽ vấp phải vô số vấn đề nhỏ

  • Có vẻ hữu ích. Thật khó tưởng tượng cần bao nhiêu công sức để trước hết hiểu CSS, rồi xây dựng layout engine trên nền đó