Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

305 changes: 89 additions & 216 deletions crates/csscascade/README.md
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/`
Comment on lines +52 to +59
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify cascade resolution implementation status

echo "=== Checking StyleRuntime::compute_for implementation ==="
rg -n "fn compute_for" crates/csscascade/src/ -A 5

echo ""
echo "=== Checking if TElement is actually used for cascade ==="
rg -n "TElement" crates/csscascade/src/ --type rust

echo ""
echo "=== Check for stub/TODO comments in cascade path ==="
rg -n -i "stub|todo|not.*implement" crates/csscascade/src/ --type rust

Repository: gridaco/grida

Length of output: 1372


🏁 Script executed:

sed -n '40,70p' crates/csscascade/README.md

Repository: 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 at tree/mod.rs:527–530 contains a TODO explicitly stating "integrate with actual Stylist lookups per element."

Update Phase 1 sub-items to accurately reflect completion: The adapter.rs TElement implementation exists, but the cascade resolution pipeline doesn't use it yet. Either uncheck the items that depend on compute_for() integration, or move them to Phase 2 and clarify that Phase 1 only covers adapter scaffolding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/csscascade/README.md` around lines 52 - 59, README Phase 1 status is
inconsistent with the code: while TElement scaffolding exists in adapter.rs, the
cascade resolution pipeline isn’t integrated because StyleRuntime::compute_for()
is still returning defaults (see TODO in tree::mod.rs). Update the Phase 1
checklist to reflect that only the TElement adapter has been promoted (leave
items that require actual cascade resolution unchecked or move them to Phase 2)
and clarify in the "Proof-of-concept" / "Stubbed / not yet functional" sections
that full per-element resolution is pending integration with
StyleRuntime::compute_for() and the TODO in tree::mod.rs.


---
### 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.
Loading
Loading