3 ポイント 投稿者 GN⁺ 2024-03-22 | 3件のコメント | WhatsAppで共有
  • Dropflowは基礎的なCSS標準である inline、block、float、positioning、将来的には table を探究するために作られたCSSレイアウトエンジンであり、flexbox と grid は対象外
  • Node と node-canvas を使ってバックエンドでPDF・画像生成に利用でき、ブラウザでは canvas に改行を含むリッチテキストをレンダリングできる
  • テキストレイアウトは双方向・RTL テキスト、書記素単位のフォントフォールバック、色付きのダイアクリティカルマーク、OpenType/TrueType フォント登録、HarfBuzz ベースの shaping をサポートする
  • CSS の対応範囲は30以上のプロパティで、floatcleardisplay: inline-blockposition: relativeoverflowz-indexzoom などが動作し、tableabsolutefixedtransform などは計画段階
  • 最高性能は HTML/CSS パースを省くhyperscript APIと再利用可能な style オブジェクトで得られ、2019 MacBook Pro 基準で500超の段落からなる The Little Prince を HTML から画像へ 160ms 未満で変換する

Dropflowが扱うCSSレイアウトの範囲

  • DropflowはCSSレイアウトエンジンで、基礎的な CSS standards の範囲を探究するために作られている
    • 対象は inline、block、float、positioning、将来的には tables
    • flexbox と grid は対象外
  • 高品質なテキストレイアウト実装を備えており、世界中のさまざまな言語を表示できる
  • 用途は2つ示されている
    • Node と node-canvas を使ってバックエンドで PDF や画像を生成
    • ブラウザで canvas に改行されるリッチテキストをレンダリング

主な機能

  • 30以上の CSS プロパティをサポートし、float のような複雑なプロパティも含む
  • テキスト関連機能が広い
    • 双方向テキストと RTL テキスト
    • 書記素単位のフォントフォールバック
    • 色付きのダイアクリティカルマーク
    • 望ましい改行処理。たとえば先頭の padding を次の行へ持ち越す
    • 最適化された shaping
  • 入力方式は HTML/CSS だけでなく、スタイルをオブジェクトで渡すhyperscript h() APIにも対応
  • OpenType/TrueType バッファは登録可能で、登録は必須
  • <img> は JPEG、BMP、PNG、GIF をサポートするが、バックエンドごとの対応は異なる場合がある
  • 継承・カスケードされたスタイルは二重計算しない
  • 型定義が充実しており、テストも多く、高速動作を目標としている

CSS対応状況

  • Inline formatting で動作する項目には colordirectionfont-familyfont-sizefont-stretchfont-stylefont-weightline-heighttext-alignvertical-alignwhite-spaceword-breakoverflow-wrapword-wrapword-spacing などがある
  • Inline formatting で font-variantletter-spacingtab-sizetext-decorationunicode-bidi計画中の状態
  • Block formatting では clearfloat が動作する
    • writing-modehorizontal-tbvertical-lrvertical-rl は部分実装状態
    • BFC では実装されているが IFC ではまだ未実装
  • Boxes and positioning では複数のボックス・配置プロパティが動作する
    • background-clipbackground-colorborder-colorborder-styleborder-width
    • toprightbottomleft
    • box-sizing
    • display: blockinlineinline-blockflow-rootnone
    • heightmarginpaddingoverflowwidthz-indexzoom
    • position: relative
  • Boxes and positioning で display: tablemin/max-heightmin/max-widthposition: absoluteposition: fixedtransform計画中の状態

基本的な使用フロー

  • Dropflowはブラウザと同じように継承済み・計算済みスタイルを持つDOMを基盤に動作する
  • 一般的な流れは次のとおり
    • レイアウト前に FontFace でフォントを登録
    • flow.style() でスタイルオブジェクトを生成
    • flow.h() で DOM を生成
    • flow.dom() でレンダリングするツリーを準備
    • renderToCanvas() で canvas 全体にレイアウトとペイントを実行
  • HTML API も提供されるが、性能が重要でない場合や学習目的の場合にのみ推奨される
    • パースは追加時間を生み、バンドルサイズも大きく増やす
    • HTML パースは @fb55 のおかげで高速だと記されている
    • 現在は style HTML 属性のみ対応し、class はまだ動作しない

性能特性

  • 性能は最上位の目標であり、正確性の次に重要
  • 例に基づく性能値が示されている
    • 複数の inline span と異なるフォントを含む8段落を HTML から画像へ変換するのに、2019 MacBook Pro で9ms、2012 MacBook Pro で13ms
    • 500超の段落からなる The Little Prince を HTML から画像へ変換するのに、2019 MacBook Pro で160ms 未満、2012 MacBook Pro で250ms 未満
    • 10文字の単語を生成してレイアウトだけを行うのに、2019 MacBook Pro で25µs 未満、2012 MacBook Pro で50µs 未満
  • 最速の性能はhyperscript APIで DOM を直接構築し、一般的な HTML/CSS パース段階を省くときに得られる
  • style オブジェクトを再利用すると、さらに大きな利点が得られる
  • 異なる幅で reflow する処理は、レイアウトツリーを作り直すより速い

API構成

  • 基本段階は2つ
    • フォント登録
    • Hyperscript API または Parse API で DOM を生成
  • 単純な利用では DOM をそのまま canvas にレンダリングできる
    • renderToCanvas(el, canvas) は canvas の width と height を viewport サイズとして使い、全体レイアウトをレンダリングする
  • より低レベルの API を使うとレイアウトを保持できる
    • 依存リソースの読み込み
    • DOM の layout 生成
    • layout の reflow
    • HTML5 canvas のような対象への paint
  • この方法は、別サイズで reflow したり、見えないレイアウトを paint しなかったり、intrinsics を取得したりするのに使える

フォント処理

  • Dropflowプログラムの最初の段階は、CSS font プロパティで選択されるフォントを登録すること
  • Dropflowはシステムフォントを検索しないため、少なくとも一度は FontFace を作成して追加する必要がある
  • フォント登録 API は CSS Font Loading API のサブセットを実装し、非標準の loadSync メソッドを追加している
  • サーバー側では file:/// URL を readFileSync で同期ロードできる
  • ArrayBuffer はブラウザと同様にコンストラクタ内で即時ロードされる
  • registerNotoFonts はすべての Noto Sans フォントファミリーを登録する
    • フォントは FontSource が配布し、jsDelivr がホストしている
    • Noto Sans フォントは200を超え、CJK フォントの unicodeRange 文字列も大きいため、大きな import になる
    • ブラウザの本番利用では、個別フォントを登録するほうがよいとされている
    • Latin には italic フォントが登録され、すべてのスクリプトには normal 400 と bold 700 が登録される
  • 中国語・韓国語・日本語は共通の Unicode コードポイントを共有するが、文字のレンダリングは異なる場合があるため、可能なら言語別フォントを使うほうがよい

レイアウト、reflow、paint

  • layout(el)box tree、fragmentation tree、glyph を含む layout を作る
    • box tree は DOM tree とおおむね対応するが、匿名テキストコンテンツのために box が増えたり、display: none のために減ったりすることがある
  • reflow(layout, width = 640, height = 480) は box を配置し、テキストを行分割して paint 可能な状態にする
    • block box の margin collapsing
    • HarfBuzz へテキストを渡す
    • フォントフォールバックの反復
    • 改行と break point に応じた reshaping
    • float 配置と clear 処理
    • direction とテキスト方向に応じた shaped text span と background の配置
    • floatinline-blockabsolute コンテンツの intrinsics 計算
    • normal flow の後で position を処理
  • Paint 対象は現在Canvas と SVGをサポート
    • paintToCanvas はブラウザ canvas、node-canvas、同様の標準準拠 context に paint する
    • paintToSvg は SVG 文字列を作り、FontFace に渡した URL を参照する @font-face 規則を含む
    • paintToSvgElements は既存 SVG 内に描画する用途のため、<svg>@font-face 規則を追加しない
    • paintToHtml は絶対配置要素のフラットリストを生成し、推奨はされないが開発中には有用な場合がある

DOM APIと環境フック

  • Hyperscript と Parse API で得たルート HTMLElement は、ブラウザの querySelector 系のように tag name、idclass で要素を探すメソッドを提供する
    • query(selector) は1つの HTMLElement または null を返す
    • queryAll(selector)HTMLElement[] を返す
  • HTMLElement は接続された render box を持つことがある
    • 通常は1つだが、inline と block コンテンツが混在する場合は複数になることがある
    • BlockContainer は absolute positioned element、floated element、inline-block、block-level element に対して生成される
    • ReplacedBox は画像に対して生成される
  • Dropflowはさまざまな環境に適応できるよう設計されている
    • ブラウザでは fetch でフォントと画像を読み込み、フォントバッファを document.fonts に登録する
    • Nodejs では fs.readFileSync でフォントを同期ロードできる
    • canvas バックエンドと node-canvas があれば、node-canvasregisterFont を呼び出す
    • node-canvas は font buffer をサポートしないため file:// URL を使う必要がある
  • @napi-rs/canvasskia-canvas を使うには、flow.environment.registerFont を対応するフォント登録 API へ接続する数行のコードが必要
  • 環境には6つのフックがある
    • wasmLocator
    • registerFont
    • resolveUrl
    • resolveUrlSync
    • createDecodedImage
    • destroyDecodedImage

HarfBuzzベースのテキスト shaping

  • Glyph layout は WebAssembly にコンパイルされた HarfBuzz が担当する
  • measureText API でテキスト span の位置を決める方式では得にくい正確性を目標としている
  • 例として Google Sheets で "AV""V" だけを別色で塗ると kerning が失われ、文字間隔が本来より広がる
    • 文字ごとに2回の measureTextfillText 呼び出しが発生し、contextual glyph advance が失われるため
  • Dropflowは色の切り替わり位置ではなく、より粗い shaping boundary で HarfBuzz を使い、フォントをより正確にサポートする
  • WebAssembly にコンパイルされた HarfBuzz は CanvasRenderingContext2DmeasureText と近い性能指標を出せる
    • measureText ほど速くはないが、大きく遅いわけでもないとされる
    • どちらもテキストレイアウトスタックの支配的なボトルネックではないとされる
  • measureText ベースのテキストレイアウトは高速化のために word cache を必要とし、GSuite アプリがこの方式を使っているとされる
    • word cache は空白をまたいで効果を持つフォントをサポートできない
    • そのようなフォントをサポートするには、段落の break index で二分探索が必要であり、段落全体を HarfBuzz に渡すよりはるかに遅いとされる
    • 色付きのダイアクリティカルマークは measureText では不可能

依存プロジェクト

  • Dropflowには package.json 依存関係はないが、複数プロジェクトの成果を活用している
  • JavaScript 依存コードはプロジェクトにチェックインされており、集中維持と dependency-of-dependency 問題を避けるため、さまざまな程度で修正されている
  • 主なプロジェクトは次のとおり

3件のコメント

 
winterjung 2024-03-23

元のタイトルは「Show HN: Dropflow, a CSS layout engine for node or <canvas>」だったのですね。現在は「GN⁺: HN紹介: Nodeまたは<canvas>向けCSSレイアウトエンジン、Dropflow</canvas>」として入っていますね。

 
dlehals2 2024-03-22

タイトルにタグがあるので、詳細ページのタイトル部分が崩れますね..笑 エスケープを..

 
GN⁺ 2024-03-22
Hacker News のコメント
  • 最近バックエンドで見栄えのよい PDF を作る基本的な方法は、ヘッドレスブラウザを立ち上げ、ブラウザ API で HTML/CSS を PDF に変換することだが、サーバー上でブラウザインスタンスを動かし、大きなワークロードに合わせてスケールさせるコストはかなり大きい
    これは状況を変えるツールで、これからはブラウザのオーバーヘッドなしに HTML/CSS で PDF をデザインして生成できる

    • Web がここまで来たのは驚き。見栄えのよい PDF 文書を作る最善の方法が、サーバー上でWeb ブラウザをそのまま実行することだなんて、90年代や2000年代には想像しにくかったはず
    • ブラウザを使うと、生成された PDF がベクターとフォントを使うという利点がある。一方で Canvas ベースだと PDF 内では大半が画像になるだろうが、多くの用途では大きな問題ではないかもしれない
    • https://ekoopmans.github.io/html2pdf.js/ を使ったことがあり、かなりうまく動いた
    • 少し混乱している。サイドプロジェクトのバックエンドで PDF 生成のために Prawn ライブラリをかなり長く使ってきた: https://github.com/prawnpdf/prawn
      ただし自分が生成する PDF は確かに見栄えがよいとは言えないので、そこが違いなのかもしれない
    • いくつかのクライアント向けに PDF レンダラーを作ってきたが、PDF で最大の要件はアクセシビリティだった
      どれも ADA 準拠が必要だったので、Canvas レンダラーに切り替えるのは難しい。そうするとアクセシビリティが失われるから
  • 本当に良さそう。前職では没入型オンライン学習プラットフォームを開発していて、Oculus Quest 2 で国防総省職員の外国語教育を行い、WebXR、Three.js などを使っていた
    Unity3D は一度で十分だったし、アプリストア審査も通したくなかった。自社のデバイス群があったので問題なかった
    最大の難題の一つは教材コンテンツのワークフローを作ることで、ほぼ一人でやらなければならなかった。実際の語学講師たちが PowerPoint で PDF を作り、自分が作った専用エディタで PDF をコンテンツデータベースにアップロードして訓練環境に配置した後、PDFJS で Canvas 要素にレンダリングして 3D の四角形のテクスチャとして使っていた
    こういうツールがあれば、人々が PowerPoint で回り道して教材を作る必要はなかったはず。PowerPoint 方式は、Photoshop で画像を作らせていた以前の試みよりワークフロー速度を大きく改善したが、アプリ内に標識エディタを作れたなら、「環境内でどう見えるか推測 → PDF にエクスポート → DB にアップロード → 実際の見た目を確認」という反復をなくせて、もっと良かったはず
    どうせプロダクトではなくサービス販売しか知らない事業開発チームやマーケティングチームはいなかったので、大した意味はなかっただろうけど

  • 自分のプロジェクト https://htwins.net/scale2 や、SVG または Canvas を使う他の作業でこういうものを探していた

    • これをどう作ったのか、制作記をぜひ読んでみたい
  • Flexbox で苦労しているなら、レスポンシブレイアウトを作るときに複数のプロパティに気を配らなくて済むよう、プロセスを単純化してくれるツールを使える: https://flexboxcss.com

    • 実際に見たサイトの中で、ニューモーフィックデザインを使っている初めての例。美的センスがよく、実戦で見たことはなかった
  • 素晴らしい。こういうツールは、ブラウザレンダリングエンジンという魔法の箱を理解可能なものにしてくれるので、とても重要
    HTML と CSS レンダリングについて完全に機械可読な仕様を作れれば、レンダラーを生成できるはず。ブラウザごとの特異な挙動は、その上の拡張として置けばよい。https://github.com/tawesoft/html5spec のようなものが実際のエンジンで使われる形になるといい

    • これを見て、Ladybird がゼロから書かれているブラウザであることを思い出したし、実際に仕様にバグがないか確認するのにもかなり役立っていた
  • 最近気になっていたことにかなり近い。CSS と SVG を、グラフィックスおよび UI ライブラリ上の抽象化レイヤーとして使えるか考えていた
    node-canvas は初めて聞いたが、そのうち描画部分を埋めてくれるもののようだ。このツールは、UI ライブラリで自分に必要なすべてであるレイアウト部分を担えそうに見える
    CSS 実装がどれほど難しかったのかも気になる。かなり複雑だと聞いている

    • CSS でネイティブグラフィックスライブラリをターゲットにする Sciter という別のプロジェクトもある: https://sciter.com
      CSS 実装は難しかったが、最大の障壁は知識があまり表に出ていないことだった
      最も難しい部分はテキストレイアウト。グリフを扱い、RTL のために逆方向に走査するのは頭が痛いし、改行も本当に複雑になる。必要な知識が一か所にまとまっていないので、さらに難解
      初期にブロックレイアウトを終えた後、数年間は週に数時間だけ作業しながら、テキストシェーピングと項目化の細部、やるべきことと避けるべきことを学ばなければならなかった。多くは Pango [1] のソースコードを読みながら学び、残りは Google 検索でつなぎ合わせた
      それ以外は W3C 仕様がほぼすべてを扱っている。CSS2 標準 [2] は、これまで読んだ中で最も美しい文書の一つだった。内部的に一貫していて簡潔で、長年の熟考と試行錯誤が反映された結果だ。CSS3 も素晴らしいが、CSS2 がすべての基盤
      [1] https://gitlab.gnome.org/GNOME/pango/
      [2] https://www.w3.org/TR/CSS22/
  • 世の中への大きな貢献。「誰かが $X をやるべきなのに」とみんなが思っているが、誰もやっていなかった典型例に見える
    レイアウトに CSS を使うのが好きな立場として、最近は主に Flexbox と Grid に頼っている。まだ対応していないのは十分理解できるが、いつか対応する計画があるのか気になる。あるなら、他の人はどう手伝えるだろうか?

  • 本当にすごい。HTML をプログラムで PNG に変換するのがどれほど難しいか、ほとんどの人はよく分かっていないと思う
    Node とブラウザの違い、または HTML と Canvas の違いのために、無数の小さな問題にぶつかることになる

  • 役に立ちそう。まず CSS を理解し、その上にレイアウトエンジンを作るのにどれほど多くの作業が必要か、想像しにくい