Skip to content

Conversation

@waywardmonkeys
Copy link
Contributor

  • Add structured parsing errors (kind + byte offset/span) for CSS OpenType settings strings.
  • Rename list parsers to parse_css_list.
  • Update Parley resolver to use the fallible CSS parser.
  • Add unit tests covering success cases and key error offsets/spans.

Comment on lines +64 to +65
at: usize,
span: Option<(usize, usize)>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Usually I discourage the use of architecture-dependent types like usize to keep behaviour consistent across platforms, but I think it's fine for this type of struct. Curious what your thoughts are.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Anything else would be super annoying in terms of casts.

Comment on lines 228 to 235
while pos < self.len {
let ch = self.source[pos];
ch.is_ascii_whitespace() || ch == b','
} {
pos += 1;
if ch.is_ascii_whitespace() || ch == b',' {
pos += 1;
} else {
break;
}
}
Copy link
Contributor

@taj-p taj-p Jan 8, 2026

Choose a reason for hiding this comment

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

I believe these lines allow an arbitrary number and order of and ,. That's technically not allowed in the CSS spec IIUC, but I understand if you'd prefer to keep the leniency.

i.e., this test passes:

    #[test]
    fn parse_feature_settings_css_list_ok() {
        let parsed: Result<Vec<_>, _> =
            Setting::<u16>::parse_css_list(r#""liga" on,,, ,   ,,,    'kern', "dlig" off, "salt" 3,"#).collect();
        let settings = parsed.unwrap();

        assert_eq!(settings.len(), 4);
        assert_eq!(settings[0].tag, Tag::parse("liga").unwrap());
        assert_eq!(settings[0].value, 1);
        assert_eq!(settings[1].tag, Tag::parse("kern").unwrap());
        assert_eq!(settings[1].value, 1);
        assert_eq!(settings[2].tag, Tag::parse("dlig").unwrap());
        assert_eq!(settings[2].value, 0);
        assert_eq!(settings[3].tag, Tag::parse("salt").unwrap());
        assert_eq!(settings[3].value, 3);
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Better to start out lenient and make it less lenient if needed in the future. Working on a revision.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I've pushed a commit that addresses this, adds more tests, and hardens some other stuff in parsing.

@waywardmonkeys waywardmonkeys force-pushed the opentype-settings-parse branch from df22fc3 to 60373c0 Compare January 8, 2026 06:12
@waywardmonkeys
Copy link
Contributor Author

Only partially related to this, but ... would be good to have something, somewhere for handling an equivalent of the font-variant family of CSS properties and converting them into this sort of feature / setting list. (But not as part of this PR.)

Copy link
Collaborator

@dfrg dfrg left a comment

Choose a reason for hiding this comment

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

Generally LGTM with a couple questions.

self.tmp_variations.clear();
self.tmp_variations
.extend(FontVariation::parse_list(source));
.extend(FontVariation::parse_css_list(source).map_while(Result::ok));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should this be filter_map instead? Seems useful to skip invalid entries when possible.

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'll just give the Codex response here which I think I agree with:

filter_map(Result::ok) only helps if the iterator can continue producing items after an error. Our parse_css_list iterators don’t recover: on the first Err they set done = true and terminate. In that world:

  • map_while(Result::ok) = “take items until first error”
  • filter_map(Result::ok) = “drop the error, but there are no later items anyway”

So switching to filter_map wouldn’t actually “skip invalid entries when possible” today; it would just slightly change the signal of intent (and arguably mislead future readers into thinking we do recovery).

If the intent is “stop at first parse error, keep earlier valid ones” (matching the old take_while(ok) behavior), map_while(Result::ok) is the clearer expression.
If the intent is “try to salvage later entries”, we’d need to change the parser to recover and keep iterating; then filter_map would make sense.


/// A single OpenType setting (tag + value).
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct Setting<T> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

What do you think about hiding this and just exposing concrete FontVariation and FontFeature types instead? I’m trying to think of a case where we want to handle these generically and none comes to mind.

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've done this as a separate commit in the series ... it ended up being sizable once I took it to the logical endpoint. Or what I think is the logical endpoint.

@dfrg
Copy link
Collaborator

dfrg commented Jan 8, 2026

Only partially related to this, but ... would be good to have something, somewhere for handling an equivalent of the font-variant family of CSS properties and converting them into this sort of feature / setting list. (But not as part of this PR.)

Seems like a reasonable thing to include.

@nicoburns
Copy link
Collaborator

I feel like all parsing in the text_primitives crate ought to be gated behind a parsing (or whatever) feature. But these specific changes LGTM.

waywardmonkeys and others added 4 commits January 9, 2026 10:41
- Add structured parsing errors (kind + byte offset/span) for CSS OpenType settings strings.
- Rename list parsers to `parse_css_list`.
- Update Parley resolver to use the fallible CSS parser.
- Add unit tests covering success cases and key error offsets/spans.
- Make `ParseCssList::next` strict about separators (no ,, / leading comma)
- Improve `u16` feature value error classification (`InvalidSyntax` vs `OutOfRange`)
- Add regression tests for separator “soup” and value errors
…e/variation types

* Replace `Setting<T>` with `FontFeature`/`FontVariation` and export them from `text_primitives`
* Add `FontVariations`/`FontFeatures` (`Source`/`List`) wrappers and make them `Into<StyleProperty>`
* Add ergonomic constructors: `empty()` and `From<&[T]>`/`From<&[T; N]>` for list usage
* Update resolve logic and tests to the new API surface
@waywardmonkeys waywardmonkeys force-pushed the opentype-settings-parse branch from 60373c0 to 7508192 Compare January 9, 2026 04:00
@waywardmonkeys
Copy link
Contributor Author

Only partially related to this, but ... would be good to have something, somewhere for handling an equivalent of the font-variant family of CSS properties and converting them into this sort of feature / setting list. (But not as part of this PR.)

Seems like a reasonable thing to include.

I've written something for this ... but it is also a fair bit of stuff. I think I'd rather submit as a followup.

@waywardmonkeys
Copy link
Contributor Author

I've added the FeatureSpec stuff as a commit in this series ... but if there's a lot of work to be done to it, then I'm happy to pull it out and make it a separate PR (or potentially drop it).

@waywardmonkeys waywardmonkeys force-pushed the opentype-settings-parse branch 3 times, most recently from 0c4fdae to 49cd65d Compare January 9, 2026 07:16
@dfrg
Copy link
Collaborator

dfrg commented Jan 9, 2026

I've added the FeatureSpec stuff as a commit in this series ... but if there's a lot of work to be done to it, then I'm happy to pull it out and make it a separate PR (or potentially drop it).

My gut feeling is that these don't quite fit this crate, at least as designed. If they were a direct mapping of the various CSS font-variant- properties then I think they would make more sense.

Browsers are also capable of simulating at least sub, super and small-caps so a layout engine should probably be able to intercept these and decide how to proceed rather than just turning them into OpenType features.

If you don't mind, my preference would be to pull these out into a separate PR for further discussion.

@waywardmonkeys
Copy link
Contributor Author

I agree. I do want to land this though with the other commits here, including removing the public Setting.

@dfrg
Copy link
Collaborator

dfrg commented Jan 10, 2026

Good to go!

@waywardmonkeys waywardmonkeys force-pushed the opentype-settings-parse branch from 49cd65d to 7508192 Compare January 10, 2026 01:58
@waywardmonkeys waywardmonkeys added this pull request to the merge queue Jan 10, 2026
Merged via the queue into linebender:main with commit b4dc8bd Jan 10, 2026
48 checks passed
@waywardmonkeys waywardmonkeys deleted the opentype-settings-parse branch January 10, 2026 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants