Giới thiệu HN: Dropflow, công cụ layout CSS cho Node hoặc `<canvas>`
(github.com/chearon)- 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,zoomhoạt động, còntable,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-spacingv.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,
clearvàfloathoạt độnghorizontal-tb,vertical-lr,vertical-rlcủawriting-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-widthtop,right,bottom,leftbox-sizingdisplay: block,inline,inline-block,flow-root,noneheight,margin,padding,overflow,width,z-index,zoomposition: 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
FontFacetrướ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()
- Đăng ký font bằng
- 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ònclassvẫ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ằngreadFileSync ArrayBufferđược tải ngay trong constructor như trên trình duyệtregisterNotoFontsđă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
unicodeRangecủ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
- 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
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
directionvà hướng văn bản - Tính intrinsics của nội dung
float,inline-block,absolute - Xử lý
positionsau normal flow
- Đích paint hiện hỗ trợ Canvas và SVG
paintToCanvaspaint lên browser canvas, node-canvas hoặc context tương thích tiêu chuẩn tương tựpaintToSvgtạo chuỗi SVG và bao gồm quy tắc@font-facetham chiếu đến URL đã truyền vàoFontFacepaintToSvgElementskhông thêm<svg>và quy tắc@font-faceđể dùng khi vẽ vào SVG hiện cópaintToHtmltạ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
HTMLElementgố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,classgiống họquerySelectorcủa trình duyệtquery(selector)trả về mộtHTMLElementhoặcnullqueryAll(selector)trả vềHTMLElement[]
HTMLElementcó 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 elementReplacedBoxđượ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àodocument.fonts - Trong Nodejs, có thể đồng bộ tải font bằng
fs.readFileSync - Nếu có canvas backend và
node-canvas, sẽ gọiregisterFontcủanode-canvas - Vì
node-canvaskhông hỗ trợ font buffer nên phải dùng URLfile://
- Trong trình duyệt, dùng
- Để dùng
@napi-rs/canvashoặcskia-canvas, cần vài dòng code nốiflow.environment.registerFontvới API đăng ký font tương ứng - Môi trường có 6 hook
wasmLocatorregisterFontresolveUrlresolveUrlSynccreateDecodedImagedestroyDecodedImage
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
measureTextvàfillTextxảy ra theo từng ký tự, làm mất contextual glyph advance
- Vì hai lần gọi
- 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ự
measureTextcủaCanvasRenderingContext2D- 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
- Không nhanh bằng
- Text layout dựa trên
measureTextcầ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
- harfbuzz: cung cấp font shaping và API font thiết yếu
- Tehreer/SheenBidi: tính toán bidi boundary
- foliojs/linebreak: cung cấp Unicode break index
- peggyjs/peggy: tạo CSS parser
- fb55/htmlparser2: HTML parsing
- google/emoji-segmenter: phân đoạn emoji
- foliojs/grapheme-breaker: cung cấp Unicode grapheme boundary, đã được chỉnh sửa lớn cho Unicode 15
- foliojs/unicode-trie: dùng cho dữ liệu Unicode tốc độ cao, đã được chỉnh sửa lớn để loại bỏ phần không dùng
3 bình luận
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>".
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..
Ý 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
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ấ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
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
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ế
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
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 đó