DioxusLabs / DioxusLabs/taffy

Parse `Style` from (and serialize to?) CSS syntax

Open
#440 15 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
3.6k
Forks
226
Avg merge
10h 41m
Merged PRs (30d)
40

Description

This is a roadmap item (https://github.com/DioxusLabs/taffy/issues/345) that I’m interesting in working on. (Though I make no promise in terms of timeline or my future availability.) Below is an opinionated proposal of how this could be done.

User-visible changes

The taffy crate gains a new Cargo feature named css, disabled by default. Enabling it:

  • Adds a dependency from taffy to https://crates.io/crates/cssparser
  • Adds a dependency from taffy to taffy-css-derive, a new proc macro crate that lives in the same repository. Since many Rust types are involved, manually implementing everything without proc-macros would be quite repetitive and prone copy-paste errors. This crate is similar to Servo’s style_derive. It depends on syn and quote.
  • Adds these APIs to taffy::style:
    impl Style {
        pub fn parse_css(input: &str) -> (Self, Vec<CssParseError>) {..}
        pub fn serialize_css(&self) -> String {..} // maybe?
    }
    
    #[derive(Clone, Debug)]
    pub struct CssParseError {..}
    impl Display for CssParseError {..}
    impl Error for CssParseError {..}
    

Specifications

As an "input" of layout, taffy::style::Style is the internal representation of computed values for a given element. Style::DEFAULT is effectively initial values.

Style::parse_css returns computed values as if the input string was a list of declarations from an HTML style attribute (so with syntax like display: flex; margin: 10%) and as if there were no other applicable declarations and no parent element for inheritance. Parsing is infallible: a Style struct is always returned. Unspecified properties get their initial value. Parse errors (reported on the side) cause parts of the input to be ignored until the parser can recover. (For a declaration list, recovery is at the next semicolon.) Parse errors include invalid declaration syntax, unsupported property, invalid/unsupported value, etc.

Relative length units (font-relative like 1.5em or viewport-relative like 80vw) are not supported. (Per the above, using them cause the declaration to be ignored and to emit a parse error.)

Style::serialize_css is similar to serialize a CSS declaration block. It omits properties that have their initial values (so Style::DEFAULT.serialize_css() is the empty string). It round-trips without errors: Style::parse_css(x.serialize_css()) == (x, vec![]) for any x: Style

Testing

Use snapshot testing with https://insta.rs/ to check the derive(Debug) output for various CSS inputs

Open API and behavior questions

  • API naming. Feel free to propose any change. (from_css / to_css, parse / impl Display, …)

  • Should serialization to CSS syntax be included? It’s relatively easy to do while also doing parsing, but maybe not as useful.

  • How are absolute length units anchored? CSS has seven of them. For historical reasons they all have a fixed ratio to each other: 1in is 96px is 72pt is 6pc is 2.54cm is 25.4mm is 101.6Q. See reference pixel discussion.

    Taffy documents Dimension::Points as: “Points are abstract absolute units. Users of Taffy may define what they correspond to in their application (pixels, logical pixels, mm, etc) as they see fit.” The enum variant name suggests 100pt should parse as Dimension::Points(100_f32), but on the web the px unit is much more common and getting Dimension(75_f32) from 100px might be unexpected. I don’t really like this idea but maybe to force users to consider units, all units except pt could be artificially unsupported?

    (By the way, I didn’t find this explicitly documented but I assume Dimension::Points(100_f32) maps to 100_f32 in taffy::layout::Layout?)

    Or there could be a configurable "DPI" or "device pixel ratio". A taffy::node::Taffy method seems like the natural place for it (next to enable_rounding / disable_rounding), but would require either Taffy::parse_css() or Style::parse_css(&Taffy) instead of Style::parse_css().

Open licensing questions

cssparser is licensed under MPL-2.0. Is that OK for a dependency?

Similarly, would it be OK for taffy-css-derive to be licensed under MPL-2.0? This would allow copying some code from style_derive. Alternatively taffy-css-derive could be entirely new code, MIT-licensed like taffy, obviously at the cost of more time and effort.

Disclaimer: although it’s been years since I’ve looked at it closely, I’ve contributed to style_derive in the past.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

The proposal centers on taffy::style::Style, with taffy::node::Taffy mentioned for unit configuration; begin by inspecting those APIs and the existing style types. Review the proposed cssparser and taffy-css-derive integration, then use insta snapshots for CSS inputs. Done requires decisions on parsing, serialization, units, feature/dependency licensing, and round-trip and error behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
css, rust
Domain
frontend, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.