Skip to content
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

[WIP] Parse Style from and serialize to CSS syntax #460

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

SimonSapin
Copy link
Contributor

@SimonSapin SimonSapin commented Apr 19, 2023

This is an incomplete work in progress, submitted in case someone wants to have a look at the approach.

This requires and is based on top of #459, so consider looking at per-commit diff.

Objective

Fixes #440

TODO: update RELEASES.md.

Context

See discussion in #440

Feedback wanted

No proc macro so far, but I’ve made liberal use of private traits and macro_rules! to reduce redundant code and risk of copy-paste errors. Hopefully the code is still approachable.

@SimonSapin
Copy link
Contributor Author

"unused variable" warnings are deliberate, they represent Style struct fields / CSS properties for which parsing or serialization still needs to be implemented.

src/lib.rs Outdated Show resolved Hide resolved
@nicoburns nicoburns added the enhancement New feature or request label Apr 23, 2023
bors-servo added a commit to servo/rust-cssparser that referenced this pull request Apr 30, 2023
Encapsulate unsafe in `_cssparser_internal_to_lowercase` macro

# Objective

- Allow the `_cssparser_internal_to_lowercase` macro to be used in crates that set `#[forbid(unsafe)]`.
- Document the safety invariant for this unsafe block

## Changes made

- Wrap the `unsafe` block in the `_cssparser_internal_to_lowercase` macro in an `#[inline(always)]` function.
-  Add comments

## Context

- DioxusLabs/taffy#460 (comment)
Comment on lines +147 to +148
// TODO: deal with shorthand v.s. longhand properties per
// https://drafts.csswg.org/cssom/#serialize-a-css-declaration-block
if *display != Style::DEFAULT.display {
decl!("display", display);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Given that Taffy's default (flex) is different to the web default (block), I'm thinking it might be better to unconditionally serialize display here for compatibility.

};
let css_inches = value / units_per_inch;
let css_pxs = css_inches * 96.;
let taffy_units = css_pxs * taffy.config.pixel_ratio;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it might make more sense to have Dimension::Length store logical pixels. That would simplify this code (it wouldn't have to care about pixel_ratio: it can just naively pass px values through to the Length variant), and would also match how I would expect users of Taffy's underlying Rust API to specify styles.

I think the pixel_ratio config option on the Taffy struct is really good idea, and actually enables this parsing code to ignore pixel ratio, becuase it means that Taffy can operate on a "logical pixels in, physical pixels out basis" (or perhaps even "unrounded logical pixels AND rounded physical pixels out").

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So Dimension::Length would be in CSS px and values in Layout would not be in the same unit anymore but differ by pixel_ratio? That could work, but I feel the latter half should be a separate PR. But yeah it’d be nice not to need to pass &Taffy around in all parsing and serialization functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

#478 attempts to implement your proposal, which would replace conversion during parsing and serialization.

Comment on lines +285 to +289
trait CssValue: Sized {
fn parse<'i, 't>(input: &mut Parser<'i, 't>, taffy: &Taffy) -> Result<Self, ParseError<'i>>;

fn serialize(&self, dest: &mut String, taffy: &Taffy);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm thinking it might make sense to split this up into two traits (one for parsing, one for serializing). That way we can put parsing and serializing code behind separate feature flags if we want to.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm also wondering if it would make sense to just make these the Display and FromStr traits. Do you have any insight into the pros and cons of using the standard traits vs. custom ones?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I’m not super convinced of the value of enabling one of parsing or serializing without the other but if you prefer yes it could be two traits.

Parsing needs a signature different than FromStr. For parsing a value of various Rust types while parsing a declaration, we want to keep using the same tokenizer and cssparser::Parser. Finding the right start and end to extract a &str slice and create another tokenizer for that would be extra work, and would lose the ability for errors to carry accurate source location (line numbers).

If a &Taffy parameter is no longer needed (see above) then serialization could have the same signature as Display but I think it still represents different intent. Values that can contain arbitrary text may need backslash-escapes for serialization to CSS syntax while Display (for presentation to users of the program) probably doesn’t want that.

Comment on lines +281 to +285
macro_rules! keywords_only {
( $(
$( #[$meta: meta] )*
$keyword: literal => $enum_variant: ident,
)+ ) => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Documentation on this macro (what it does and how it works) would be good

@nicoburns
Copy link
Collaborator

FWIW, the fact that cssparser uses proc_macros (and syn/quote) and that we'll be paying that price for this feature anyway, makes me much less averse to using them in this PR. I would be interested to see what that would look like. It would certainly be nice to just add an attribute to the main struct definitions rather than having macro definition. I'm still wary of it's affect on compile times though.

(although I'm also wondering whether cssparser really needs them. Looks to me that it's uses of them are fairly trivial and could easily be replaced with manual MAX_LENGTH implementations along with a CI script to enforce correctness. But I guess most people will have syn somewhere in their dependency tree anyway, so perhaps it's better to just not worry about it?)

@SimonSapin
Copy link
Contributor Author

cssparser’s proc macros used to do more but they’ve shrunk over time. And I think const fn is powerful enough now that it could replace them entirely, although the diagnostics are worse for invalid (uppercase) input: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c8e590525b1ac8f7304cf2c08394af2f

SimonSapin added a commit to SimonSapin/taffy that referenced this pull request May 8, 2023
SimonSapin added a commit to SimonSapin/taffy that referenced this pull request May 8, 2023
SimonSapin added a commit to SimonSapin/taffy that referenced this pull request May 8, 2023
@SimonSapin
Copy link
Contributor Author

I tried using those const fns in cssparser existing macro_rules but ran into difficulties with $x: expr v.s. $x: pat. However even if that was overcome, syn is still used both through phf-macros and for the match_byte! macro (which was a build script from when proper proc macros were not stable yet, but that’s changing.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Parse Style from (and serialize to?) CSS syntax
3 participants