Conversation
…arser Implements the remaining CSS Color 4 Lab-family color functions in the Fabric CSS color parser, following the spec: modern syntax only, percentage references, <hue> units, the `none` keyword for missing components, and parse-time clamping of lightness and chroma. Colors outside of sRGB are gamut mapped with the CSS Color 4 OKLCh chroma-reduction algorithm rather than clipped per channel, which preserves lightness and hue.
This branch has not been deployed
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary:
The Fabric CSS color parser supports
rgb(),hsl(), andhwb(), and has aTODO T213000437for the rest of the CSS Color 4 color functions. This PR implements four of them:lab(),lch(),oklab(), andoklch().These are increasingly how colors are authored on the web. Tailwind CSS v4 defines its whole default palette in
oklch(), and design tokens exported for the web are often in OKLCH. Today those strings don't parse in React Native, so teams sharing tokens between web and native need a conversion step. This was requested in discussions-and-proposals#871.This change is limited to the native CSS parser (
enableNativeCSSParsing). The JSnormalize-colorpath is unchanged. I'm happy to follow up with a matching JS change if maintainers want one.Behavior
Everything follows CSS Color 4:
/ alpha. These functions have no legacy comma syntax, so commas are rejected, as is an alpha that isn't preceded by/.100forlab/lchand1foroklab/oklch.a/b: 100% is125forlaband0.4foroklab.150forlchand0.4foroklch.<angle>unit (deg,rad,grad,turn); out-of-range values wrap.none: accepted for any component, including alpha, and resolves to zero (missing components).lab()/lch()use the D50 white point with Bradford adaptation to D65. All conversions use the spec's sample-code matrices, computed in double precision.oklab(0.7 0.4 0.4)clips torgb(255, 0, 0)but gamut-maps torgb(255, 97, 0). Many Tailwind v4 colors are outside sRGB, so this affects real palettes.radhue that overflows when converted to degrees) makes the value invalid instead of reaching an undefined float-to-int conversion. The gamut-mapping binary search is bounded, so extreme chroma values still converge.Hue and alpha handling of the existing
rgb()/hsl()/hwb()functions is unchanged.color()andcolor-mix()remain in the TODO.Changelog:
[GENERAL] [ADDED] - Support
lab(),lch(),oklab(), andoklch()colors in the native CSS parserTest Plan:
Unit tests: added 10 tests to
CSSColorTest.cpp, covering each function, percentages, every angle unit, hue wrapping, alpha,nonecomponents, lightness and chroma clamping, gamut mapping, and 25 invalid inputs. Expected values come from ColorAide, whoseminde-chromafit implements the spec's gamut-mapping algorithm.Open-source CI doesn't build the
react/renderer/cssgtests, so I compiled them directly with React Native's compiler flags (-std=c++20 -Wall -Werror -Wpedantic -fexceptions -frtti) against googletest 1.15.2 and fast_float 8.0.0 (the version React Native pins):That's all 318 existing CSS parser tests plus the 10 new ones.
Differential testing against the reference implementation: I built a small CLI around
parseCSSProperty<CSSColor>and compared it with ColorAide:lab/lch/oklab/oklchcolors, biased toward vivid and extreme-lightness values so about 93% need gamut mapping: 82,999 exact 8-bit matches. The one off-by-one comes fromCSSNumberstoring values asfloat. When ColorAide gets the same float32-rounded input, it matches exactly.Sanitizers: the color tests, the differential runs above, and inputs near
FLT_MAX(for example,lch(50 3e38 3e38)andoklch(0.7 0.1 1e38rad)) all pass under-fsanitize=undefined,float-cast-overflow,float-divide-by-zero. I couldn't run AddressSanitizer: its runtime hangs during startup on my machine (macOS 27).Formatting and API snapshots:
python -m scripts.cxx-api.parser.Not run: Fantom and RNTester. This change is limited to the parser, which runs only when
enableNativeCSSParsingis enabled.