-
Notifications
You must be signed in to change notification settings - Fork 131
feat: HTML/CSS → Grida IR import pipeline #608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
6c627e1
feat: replace L0 HTML test fixtures with 21 systematic, supported-onl…
softmarshmallow 1bb6eb1
feat: HTML→IR conversion pipeline with CSS cascade support
softmarshmallow 7822147
merge: resolve conflicts with origin/main
softmarshmallow 1856624
fix: serialize HTML tests to prevent Stylo global DOM race condition
softmarshmallow 7f02f7b
feat: add L0 fixtures for effects CSS properties
softmarshmallow cf60d26
feat(html): map CSS effects and blend modes to Grida IR
softmarshmallow 18d73e1
feat(html): map text-decoration color/style and add transform fixture
softmarshmallow 53324e4
docs: add format mapping reference and TODO trackers
softmarshmallow 1ee2be0
docs: add docusaurus index and cross-link format mapping docs
softmarshmallow 572c7e3
refactor: consolidate tracking docs, remove stale duplicates
softmarshmallow c12c795
fix: address review findings — dead code, thread safety doc, fixtures
softmarshmallow File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,263 +1,136 @@ | ||
| # csscascade | ||
|
|
||
| A modern, Rust-native **CSS Cascade & Style Resolution Engine** designed for building browser‑like rendering pipelines. `csscascade` takes an HTML (and later SVG) DOM tree and produces a **style-resolved static tree** ready for layout and painting. | ||
| A Rust CSS cascade & style resolution engine for building non-browser rendering pipelines. | ||
|
|
||
| Today the crate is powered by [**Stylo**](https://github.com/servo/stylo) — Servo’s production CSS engine. Stylo handles parsing, selectors, specificity, and computed values (and compiles cleanly for `wasm32-unknown-emscripten`), while `csscascade` focuses on DOM adapters, HTML/SVG attribute normalization, font parsing/selection, layout hand-off, and everything else outside the CSS engine’s scope. | ||
| Given an HTML DOM tree and CSS, `csscascade` produces a **style-resolved static tree** — every node carrying its fully computed CSS — ready for layout and painting. | ||
|
|
||
| This crate implements the hardest and most fundamental part of a rendering engine: the transformation from loosely-typed DOM nodes + CSS rules into a **fully computed, normalized, strongly-typed tree**. | ||
| Powered by [Stylo](https://github.com/servo/stylo) (Servo/Firefox's CSS engine). Stylo is the only production-grade, embeddable CSS engine available in Rust; reimplementing the cascade (selectors, specificity, inheritance, shorthand expansion, `!important`, `var()`, `@media`, ...) from scratch is not viable. | ||
|
|
||
| Future support for SVG is planned (HTML + SVG share >90% of style logic). | ||
| ## Pipeline | ||
|
|
||
| --- | ||
|
|
||
| ### “Isn’t a full CSS engine overkill?” | ||
|
|
||
| Not really. Stylo stays surprisingly lean—our builds land around ~1.5 MB when compiled with `wasm-unknown-unknown` and roughly ~2.5 MB when targeting `wasm32-unknown-emscripten`. More importantly, reproducing the entirety of CSS3 (selectors, cascade rules, media queries, shorthand expansion, inheritance, etc.) is phenomenally difficult; sooner or later any serious renderer ends up needing a browser-grade engine. Stylo already solves that problem with production-ready accuracy, so we embrace it and focus on the rest of the pipeline. | ||
|
|
||
| --- | ||
|
|
||
| ## What this crate does | ||
|
|
||
| ### ✔ 1. Parse and walk an HTML/XML tree | ||
|
|
||
| Accepts a DOM-like tree (any structure implementing the crate's DOM traits). | ||
|
|
||
| ### ✔ 2. Perform full CSS cascade | ||
|
|
||
| Backed by Stylo’s battle-tested cascade implementation: | ||
|
|
||
| - Selector matching | ||
| - Specificity and importance resolution | ||
| - Inheritance | ||
| - Initial values | ||
| - Presentation attribute mapping (SVG-ready) | ||
| - Shorthand expansion | ||
|
|
||
| ### ✔ 3. Produce a Style‑Resolved Tree | ||
|
|
||
| A new tree where **every node has its final computed style** attached. | ||
| This tree contains: | ||
|
|
||
| - resolved display modes | ||
| - resolved text properties | ||
| - resolved sizing/box model values | ||
| - resolved transforms & opacity | ||
| - fully computed inline and block styles | ||
|
|
||
| ### ✔ 4. Ready for layout engine consumption | ||
|
|
||
| `csscascade` does **not** perform layout. | ||
| It outputs a static, fully resolved element tree specifically designed to be fed into your layout engine (block/inline/flex/grid/etc.). | ||
|
|
||
| ### ✔ 5. Ready for painting after layout | ||
|
|
||
| Since the style is computed and normalized, the next stages can be: | ||
|
|
||
| - layout engine | ||
| - display list generation | ||
| - painting/rendering | ||
|
|
||
| --- | ||
|
|
||
| ## Why this crate exists | ||
|
|
||
| Rendering HTML and SVG is deceptively hard. Even before layout and paint, a renderer must: | ||
|
|
||
| - parse the DOM | ||
| - run the CSS cascade | ||
| - normalize presentation attributes | ||
| - compute final styles | ||
| - build a static, render‑ready tree | ||
|
|
||
| None of these steps are specific to a browser — they are fundamental requirements for **any** engine that wants to render HTML or SVG, whether for graphics, documents, UI, or design tools. | ||
|
|
||
| The goal of `csscascade` is **not** to help you build a browser. Instead, it’s designed for developers building: | ||
|
|
||
| - static HTML/SVG renderers | ||
| - document processors | ||
| - canvas-based UI engines | ||
| - PDF or image generators | ||
| - design tools (like Figma‑style or illustration tools) | ||
| - “bring your own renderer” pipelines | ||
|
|
||
| It handles the universally hard parts (CSS cascade, style normalization, static tree production) by delegating the CSS engine to Stylo and layering our own DOM/font/layout glue on top, so your engine can focus on **layout and painting**, not CSS correctness. | ||
|
|
||
| --- | ||
|
|
||
| ## Intended Audience | ||
|
|
||
| ### ✔ 1. Engine and renderer authors (non‑browser) | ||
|
|
||
| This crate is specifically for people building a **static** renderer — not a real DOM, not a browser, not an interactive layout engine. | ||
|
|
||
| If you: | ||
|
|
||
| - want to parse HTML/SVG once | ||
| - resolve styles correctly | ||
| - produce a clean, immutable tree | ||
| - feed it to your own layout + painting pipeline | ||
|
|
||
| …then `csscascade` is designed for you. | ||
| ``` | ||
| HTML string | ||
| │ | ||
| ▼ | ||
| html5ever ─── parse ──► RcDom (reference-counted DOM) | ||
| │ | ||
| ▼ | ||
| csscascade ── cascade ─► StyledTree (computed styles per node) | ||
| │ | ||
| ▼ | ||
| taffy ─────── layout ──► positioned boxes | ||
| │ | ||
| ▼ | ||
| grida-canvas ─ convert ► IR nodes (rectangles, text, etc.) | ||
| ``` | ||
|
|
||
| ### ✔ 2. Tools that need correct CSS without a browser | ||
| This mirrors the SVG import path (`usvg` → `from_usvg` → IR), but for HTML/CSS each stage is a separate crate because no single library (like `usvg` for SVG) handles the full pipeline. | ||
|
|
||
| For example: | ||
| ## Current State | ||
|
|
||
| - SVG → PNG converters | ||
| - HTML → PDF generators | ||
| - print engines | ||
| - design tools | ||
| - WASM/canvas engines | ||
| **Working:** | ||
|
|
||
| ### ✖ Not for browser makers | ||
| - HTML parsing via html5ever into `RcDom` | ||
| - DOM tree representation (`rcdom` module) | ||
| - `Tree` / `StyledNode` abstractions for style-resolved output | ||
| - Stylo infrastructure wired up (`Device`, `Stylist`, `SharedRwLock`) | ||
| - Serialization — re-emit HTML, optionally with computed styles inlined | ||
| - Builder pattern for programmatic tree construction | ||
| - Working examples: `print_tree`, `print_rcdom`, `html2html` | ||
|
|
||
| If you need: | ||
| **Proof-of-concept (not yet integrated):** | ||
|
|
||
| - live DOM mutation | ||
| - dynamic style recalculation | ||
| - reflow/repaint cycles | ||
| - incremental layout | ||
| - event-driven DOM | ||
| - `examples/exp_impl_telement.rs` — demonstrates full per-element cascade via Stylo's `TElement` trait. This works but has not been promoted to the main crate API. | ||
|
|
||
| …this crate intentionally does **not** target that use case. | ||
| **Stubbed / not yet functional:** | ||
|
|
||
| --- | ||
| - `StyleRuntime::compute_for()` returns default `ComputedValues` instead of actually resolving per-element styles | ||
| - `SimpleFontProvider` returns hardcoded metrics, not real font data | ||
|
|
||
| ## What this crate produces | ||
| ## Roadmap | ||
|
|
||
| `csscascade` outputs a **style‑resolved static tree** — every node has fully computed CSS (straight from Stylo) applied, ready for layout. | ||
| ### Phase 1 — Cascade Resolution ✅ | ||
|
|
||
| ### (planned) Optional layout integration | ||
| Per-element style resolution via Stylo's `TElement` trait. | ||
|
|
||
| A future feature flag will allow the crate to output a **layout‑computed, render‑ready tree**, so you can plug it directly into your painter. | ||
| - [x] Promote `TElement` implementation into the crate (`adapter.rs`) | ||
| - [x] Collect CSS from `<style>` elements and inline `style` attributes | ||
| - [x] Inject a user-agent stylesheet (default HTML element styles) | ||
| - [x] Validate against HTML fixture files in `/fixtures/test-html/L0/` | ||
|
|
||
| --- | ||
| ### Phase 2 — Font & Media Integration (partial) | ||
|
|
||
| ``` | ||
| Input DOM Tree (HTML / XML / SVG) | ||
| ↓ | ||
| csscascade front-end (DOM adapters, CSS collection) | ||
| ↓ | ||
| Stylo engine | ||
| ↓ | ||
| Style‑Resolved Static Tree | ||
| ↓ | ||
| Layout Engine (e.g. taffy) | ||
| ↓ | ||
| Painting | ||
| ``` | ||
| Replace stubs with real providers so computed values reflect the runtime environment. | ||
|
|
||
| ### Style‑Resolved Tree | ||
| - [ ] Integrate font metrics with the Skia backend (or system fonts) | ||
| - [ ] Plumb viewport size, DPR, and `prefers-color-scheme` from the host | ||
|
|
||
| Each node in the output tree includes: | ||
| ### Phase 3 — IR Conversion ✅ | ||
|
|
||
| - tag name | ||
| - attributes | ||
| - fully computed CSS style | ||
| - resolved defaults | ||
| - resolved inheritance | ||
| - normalized values | ||
| HTML → Grida IR pipeline implemented in `crates/grida-canvas/src/html/mod.rs`. | ||
|
|
||
| This tree is static and does not update unless the DOM or styles change. | ||
| - [x] Map block/flex containers → Container IR nodes | ||
| - [x] Map text nodes → TextSpan IR nodes with font/color properties | ||
| - [x] Map background, border, opacity, gradients, shadows, effects, blend modes | ||
| - [x] Handle `display: none` (exclusion) | ||
| - [x] Layout pass via `taffy` (integrated into grida-canvas layout engine) | ||
| - [ ] `visibility: hidden` — needs dedicated IR field (not opacity:0) | ||
|
|
||
| --- | ||
| ### Phase 4 — Completeness | ||
|
|
||
| ## Goals | ||
| - [ ] CSS `transform` → `AffineTransform` | ||
| - [ ] CSS custom properties / `var()` (Stylo supports these; needs plumbing) | ||
| - [ ] `@media` evaluation hooks | ||
| - [ ] External stylesheet loading (`<link rel="stylesheet">`) | ||
|
|
||
| ### Primary Goals | ||
| For the full property-by-property tracking, see `docs/wg/format/css.md` and `docs/wg/format/html.md`. | ||
|
|
||
| - Accurate CSS cascade implementation powered by Stylo | ||
| - Browser-inspired computed style model | ||
| - Shared resolution logic for HTML and SVG | ||
| - Zero heap allocations in hot paths where possible | ||
| - Fast traversal + predictable output | ||
| ## Architecture | ||
|
|
||
| ### Future Goals | ||
| The crate has two modules: | ||
|
|
||
| - SVG presentation attribute mapping | ||
| - CSS variables (`var()`) resolution (via Stylo custom property support) | ||
| - Support for user-agent stylesheets / UA defaults | ||
| - Inline style parsing & extraction from DOM trees | ||
| - @media evaluation hooks that inform downstream layout engines | ||
| | Module | Purpose | | ||
| |--------|---------| | ||
| | `rcdom` | Reference-counted DOM (from html5ever). Parses HTML into a tree of `Node` handles. | | ||
| | `tree` | Style-resolved tree. Wraps `RcDom` nodes with Stylo `ComputedValues`. Contains `StyleRuntime` (Stylo orchestration) and `StyledNode` (output). | | ||
|
|
||
| --- | ||
| The planned module structure (from ARCHITECTURE.md) fans this out into `dom/`, `stylesheets/`, `stylo_bridge/`, `cascade/`, `fonts/`, `tree/`, and `layout_hooks/`. The current proof-of-concept keeps everything in `tree/mod.rs` intentionally; it will be split once the API stabilizes. | ||
|
|
||
| ## Non‑Goals (for now) | ||
| See [ARCHITECTURE.md](./ARCHITECTURE.md) for the full design document (note: aspirational, may be ahead of implementation). | ||
|
|
||
| - Layout algorithms (block, inline, flex, grid) — use `taffy` or your own engine | ||
| - Painting or rasterization | ||
| - Re‑implementing Stylo’s internals (parser, selector engine, cascade) | ||
| - JavaScript‑style dynamic live updates | ||
| ## Why Stylo | ||
|
|
||
| These are intentionally separate stages. | ||
| | Engine | Language | Embeddable? | | ||
| |--------|----------|-------------| | ||
| | Blink (Chrome) | C++ | No — deeply coupled to Blink internals | | ||
| | WebKit | C++ | No — same problem | | ||
| | Stylo (Servo/Firefox) | Rust | **Yes** — designed as a standalone crate | | ||
|
|
||
| --- | ||
| There is no alternative. Any Rust project that needs correct CSS cascade either uses Stylo or builds an incomplete reimplementation. | ||
|
|
||
| ## Example Usage | ||
| ## Examples | ||
|
|
||
| ```rust | ||
| use csscascade::{Cascade, StyledTree}; | ||
| use your_dom_library::Document; | ||
| ```sh | ||
| # Parse HTML and re-serialize | ||
| cargo run --example html2html | ||
|
|
||
| let dom: Document = parse_html("<div class=\"title\">Hello</div>"); | ||
| let css = " | ||
| .title { | ||
| font-size: 24px; | ||
| font-weight: bold; | ||
| } | ||
| "; | ||
| # Print DOM tree structure | ||
| cargo run --example print_rcdom | ||
|
|
||
| let cascade = Cascade::new(css); | ||
| let styled: StyledTree = cascade.apply(&dom); | ||
| # Parse HTML and print with computed styles | ||
| cargo run --example print_tree | ||
|
|
||
| // Pass styled to your layout engine | ||
| layout_engine.layout(&styled); | ||
| # Experimental: full cascade with TElement (proof of concept) | ||
| cargo run --example exp_impl_telement | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Powered by Stylo | ||
|
|
||
| - **Why Stylo?** It is the most complete, accurate, lightweight CSS engine available in Rust, already proven inside Servo and compatible with `wasm32-unknown-emscripten`. | ||
| - **What csscascade adds:** DOM traversal traits, HTML/SVG attribute normalization, stylesheet collection from `<style>`/external sources, font parsing and selection, and integration points for layout engines such as `taffy`. | ||
| - **What Stylo omits (and we provide):** HTML parsing/processing, DOM ownership, font backends, layout, and painting. This separation lets us reuse Stylo wholesale while still solving the remaining renderer problems. | ||
| ## Non-Goals | ||
|
|
||
| --- | ||
|
|
||
| ## Philosophy & Design Principles | ||
|
|
||
| - **Engine‑agnostic:** DOM input is not tied to any specific parser. | ||
| - **Format‑agnostic:** HTML and SVG share a unified style pipeline. | ||
| - **Separation of concerns:** Stylo handles CSS internals; csscascade handles DOM plumbing, fonts, and layout hand-off. | ||
| - **Deterministic:** Same input always yields the same resolved tree. | ||
| - **Modern CSS:** Built on Stylo’s constantly updated property set (variables, calc, etc.). | ||
|
|
||
| --- | ||
|
|
||
| ## Roadmap | ||
|
|
||
| - [ ] SVG presentation attributes → CSS mapping | ||
| - [ ] CSS variable resolution | ||
| - [ ] Custom property support | ||
| - [ ] UA stylesheet injection | ||
| - [ ] Integration examples (HTML, SVG, Grida Canvas) | ||
| - [ ] Full W3C cascade compliance test suite | ||
|
|
||
| --- | ||
| - Layout algorithms — use `taffy` or equivalent | ||
| - Painting / rasterization — downstream concern | ||
| - Live DOM mutation or incremental reflow — this is a static, single-pass engine | ||
| - Replacing Stylo internals — we embed it, not reimplement it | ||
|
|
||
| ## License | ||
|
|
||
| MIT or Apache-2.0 | ||
|
|
||
| --- | ||
|
|
||
| ## Contributing | ||
|
|
||
| Contributions are welcome! | ||
| If you're building a rendering engine, layout system, or visualization tool, this crate aims to be the foundational CSS cascade layer you can rely on. | ||
|
|
||
| --- | ||
|
|
||
| ## Status | ||
|
|
||
| ⚠️ **Early development** — API may change as SVG support and additional CSS features are added. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: gridaco/grida
Length of output: 1372
🏁 Script executed:
sed -n '40,70p' crates/csscascade/README.mdRepository: gridaco/grida
Length of output: 1264
Reconcile Phase 1 completion status with actual implementation.
Phase 1 is marked complete (✅), but the README itself contradicts this in two places: (1) the "Proof-of-concept" section states TElement "has not been promoted to the main crate API," and (2) the "Stubbed / not yet functional" section confirms
StyleRuntime::compute_for()returns defaults instead of resolved styles. The code attree/mod.rs:527–530contains a TODO explicitly stating "integrate with actual Stylist lookups per element."Update Phase 1 sub-items to accurately reflect completion: The
adapter.rsTElement implementation exists, but the cascade resolution pipeline doesn't use it yet. Either uncheck the items that depend oncompute_for()integration, or move them to Phase 2 and clarify that Phase 1 only covers adapter scaffolding.🤖 Prompt for AI Agents