Bug 1920496 - Store ColorFunction inside a computed color r=layout-reviewers,emilio

Differential Revision: https://phabricator.services.mozilla.com/D223136
This commit is contained in:
Tiaan Louw
2024-10-16 13:45:19 +00:00
parent c763e35e0f
commit e781ea2aea
10 changed files with 179 additions and 101 deletions

View File

@@ -70,6 +70,15 @@ namespace mozilla {
class ComputedStyle;
template <typename T>
struct StyleColorFunction {};
template <typename T>
inline bool operator==(const StyleColorFunction<T>& left,
const StyleColorFunction<T>& right) {
return &left == &right;
}
using Matrix4x4Components = float[16];
using StyleMatrix4x4Components = Matrix4x4Components;

View File

@@ -32,7 +32,6 @@ StyleAbsoluteColor StyleColor::ResolveColor(
return aForegroundColor;
}
MOZ_ASSERT(IsColorMix(), "should be the only type left at this point.");
return Servo_ResolveColor(this, &aForegroundColor);
}

View File

@@ -12,15 +12,17 @@ use super::{
parsing::{NumberOrAngle, NumberOrPercentage},
AbsoluteColor, ColorFlags, ColorSpace,
};
use crate::values::{normalize, specified::color::Color as SpecifiedColor};
use crate::values::{
computed::color::Color as ComputedColor, normalize, specified::color::Color as SpecifiedColor,
};
use cssparser::color::{clamp_floor_256_f32, OPAQUE};
/// Represents a specified color function.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub enum ColorFunction {
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
pub enum ColorFunction<Color> {
/// <https://drafts.csswg.org/css-color-4/#rgb-functions>
Rgb(
Option<SpecifiedColor>, // origin
Option<Color>, // origin
ColorComponent<NumberOrPercentage>, // red
ColorComponent<NumberOrPercentage>, // green
ColorComponent<NumberOrPercentage>, // blue
@@ -28,7 +30,7 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#the-hsl-notation>
Hsl(
Option<SpecifiedColor>, // origin
Option<Color>, // origin
ColorComponent<NumberOrAngle>, // hue
ColorComponent<NumberOrPercentage>, // saturation
ColorComponent<NumberOrPercentage>, // lightness
@@ -36,7 +38,7 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#the-hwb-notation>
Hwb(
Option<SpecifiedColor>, // origin
Option<Color>, // origin
ColorComponent<NumberOrAngle>, // hue
ColorComponent<NumberOrPercentage>, // whiteness
ColorComponent<NumberOrPercentage>, // blackness
@@ -44,7 +46,7 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch>
Lab(
Option<SpecifiedColor>, // origin
Option<Color>, // origin
ColorComponent<NumberOrPercentage>, // lightness
ColorComponent<NumberOrPercentage>, // a
ColorComponent<NumberOrPercentage>, // b
@@ -52,7 +54,7 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch>
Lch(
Option<SpecifiedColor>, // origin
Option<Color>, // origin
ColorComponent<NumberOrPercentage>, // lightness
ColorComponent<NumberOrPercentage>, // chroma
ColorComponent<NumberOrAngle>, // hue
@@ -60,7 +62,7 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch>
Oklab(
Option<SpecifiedColor>, // origin
Option<Color>, // origin
ColorComponent<NumberOrPercentage>, // lightness
ColorComponent<NumberOrPercentage>, // a
ColorComponent<NumberOrPercentage>, // b
@@ -68,7 +70,7 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#specifying-oklab-oklch>
Oklch(
Option<SpecifiedColor>, // origin
Option<Color>, // origin
ColorComponent<NumberOrPercentage>, // lightness
ColorComponent<NumberOrPercentage>, // chroma
ColorComponent<NumberOrAngle>, // hue
@@ -76,7 +78,7 @@ pub enum ColorFunction {
),
/// <https://drafts.csswg.org/css-color-4/#color-function>
Color(
Option<SpecifiedColor>, // origin
Option<Color>, // origin
ColorComponent<NumberOrPercentage>, // red / x
ColorComponent<NumberOrPercentage>, // green / y
ColorComponent<NumberOrPercentage>, // blue / z
@@ -85,24 +87,8 @@ pub enum ColorFunction {
),
}
impl ColorFunction {
/// Return true if the color funciton has an origin color specified.
pub fn has_origin_color(&self) -> bool {
match self {
Self::Rgb(origin_color, ..) |
Self::Hsl(origin_color, ..) |
Self::Hwb(origin_color, ..) |
Self::Lab(origin_color, ..) |
Self::Lch(origin_color, ..) |
Self::Oklab(origin_color, ..) |
Self::Oklch(origin_color, ..) |
Self::Color(origin_color, ..) => origin_color.is_some(),
}
}
/// Try to resolve the color function to an [`AbsoluteColor`] that does not
/// contain any variables (currentcolor, color components, etc.).
pub fn resolve_to_absolute(&self) -> Result<AbsoluteColor, ()> {
impl ColorFunction<AbsoluteColor> {
fn resolve_to_absolute(&self) -> Result<AbsoluteColor, ()> {
macro_rules! alpha {
($alpha:expr, $origin_color:expr) => {{
$alpha
@@ -111,17 +97,6 @@ impl ColorFunction {
}};
}
macro_rules! resolved_origin_color {
($origin_color:expr,$color_space:expr) => {{
match $origin_color {
Some(color) => color
.resolve_to_absolute()
.map(|color| color.to_color_space($color_space)),
None => None,
}
}};
}
Ok(match self {
ColorFunction::Rgb(origin_color, r, g, b, alpha) => {
#[inline]
@@ -137,17 +112,19 @@ impl ColorFunction {
))
}
// Ensure that the origin color is in the rgb(..) format and not in
// the color(srgb ..) format.
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Srgb)
.map(|origin| origin.into_srgb_legacy());
let origin_color =
origin_color.map(|o| o.to_color_space(ColorSpace::Srgb).into_srgb_legacy());
AbsoluteColor::srgb_legacy(
resolve(r, origin_color.as_ref())?,
resolve(g, origin_color.as_ref())?,
resolve(b, origin_color.as_ref())?,
alpha!(alpha, origin_color.as_ref()).unwrap_or(0.0),
)
let r = resolve(r, origin_color.as_ref())?;
let g = resolve(g, origin_color.as_ref())?;
let b = resolve(b, origin_color.as_ref())?;
let alpha = alpha!(alpha, origin_color.as_ref()).unwrap_or(0.0);
if origin_color.is_some() {
AbsoluteColor::new(ColorSpace::Srgb, r, g, b, alpha)
} else {
AbsoluteColor::srgb_legacy(r, g, b, alpha)
}
},
ColorFunction::Hsl(origin_color, h, s, l, alpha) => {
// Percent reference range for S and L: 0% = 0.0, 100% = 100.0
@@ -161,7 +138,7 @@ impl ColorFunction {
// color to be out of gamut and not clamp.
let use_rgb_sytax = origin_color.is_none();
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Hsl);
let origin_color = origin_color.map(|o| o.to_color_space(ColorSpace::Hsl));
let mut result = AbsoluteColor::new(
ColorSpace::Hsl,
@@ -202,7 +179,7 @@ impl ColorFunction {
const WHITENESS_RANGE: f32 = 100.0;
const BLACKNESS_RANGE: f32 = 100.0;
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Hwb);
let origin_color = origin_color.map(|o| o.to_color_space(ColorSpace::Hwb));
let mut result = AbsoluteColor::new(
ColorSpace::Hwb,
@@ -237,7 +214,7 @@ impl ColorFunction {
const LIGHTNESS_RANGE: f32 = 100.0;
const A_B_RANGE: f32 = 125.0;
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Lab);
let origin_color = origin_color.map(|o| o.to_color_space(ColorSpace::Lab));
AbsoluteColor::new(
ColorSpace::Lab,
@@ -256,7 +233,7 @@ impl ColorFunction {
const LIGHTNESS_RANGE: f32 = 100.0;
const CHROMA_RANGE: f32 = 150.0;
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Lch);
let origin_color = origin_color.map(|o| o.to_color_space(ColorSpace::Lch));
AbsoluteColor::new(
ColorSpace::Lch,
@@ -275,7 +252,7 @@ impl ColorFunction {
const LIGHTNESS_RANGE: f32 = 1.0;
const A_B_RANGE: f32 = 0.4;
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Oklab);
let origin_color = origin_color.map(|o| o.to_color_space(ColorSpace::Oklab));
AbsoluteColor::new(
ColorSpace::Oklab,
@@ -294,7 +271,7 @@ impl ColorFunction {
const LIGHTNESS_RANGE: f32 = 1.0;
const CHROMA_RANGE: f32 = 0.4;
let origin_color = resolved_origin_color!(origin_color, ColorSpace::Oklch);
let origin_color = origin_color.map(|o| o.to_color_space(ColorSpace::Oklch));
AbsoluteColor::new(
ColorSpace::Oklch,
@@ -308,7 +285,7 @@ impl ColorFunction {
)
},
ColorFunction::Color(origin_color, r, g, b, alpha, color_space) => {
let origin_color = resolved_origin_color!(origin_color, *color_space);
let origin_color = origin_color.map(|o| o.to_color_space(*color_space));
AbsoluteColor::new(
(*color_space).into(),
r.resolve(origin_color.as_ref())?.map(|c| c.to_number(1.0)),
@@ -321,7 +298,83 @@ impl ColorFunction {
}
}
impl style_traits::ToCss for ColorFunction {
impl ColorFunction<SpecifiedColor> {
/// Return true if the color funciton has an origin color specified.
pub fn has_origin_color(&self) -> bool {
match self {
Self::Rgb(origin_color, ..) |
Self::Hsl(origin_color, ..) |
Self::Hwb(origin_color, ..) |
Self::Lab(origin_color, ..) |
Self::Lch(origin_color, ..) |
Self::Oklab(origin_color, ..) |
Self::Oklch(origin_color, ..) |
Self::Color(origin_color, ..) => origin_color.is_some(),
}
}
/// Try to resolve the color function to an [`AbsoluteColor`] that does not
/// contain any variables (currentcolor, color components, etc.).
pub fn resolve_to_absolute(&self) -> Result<AbsoluteColor, ()> {
// Map the color function to one with an absolute origin color.
let resolvable = self.map_origin_color(|o| o.resolve_to_absolute());
resolvable.resolve_to_absolute()
}
}
impl<Color> ColorFunction<Color> {
/// Map the origin color to another type. Return None from `f` if the conversion fails.
pub fn map_origin_color<U>(&self, f: impl FnOnce(&Color) -> Option<U>) -> ColorFunction<U> {
macro_rules! map {
($f:ident, $o:expr, $c0:expr, $c1:expr, $c2:expr, $alpha:expr) => {{
ColorFunction::$f(
$o.as_ref().and_then(f),
$c0.clone(),
$c1.clone(),
$c2.clone(),
$alpha.clone(),
)
}};
}
match self {
ColorFunction::Rgb(o, c0, c1, c2, alpha) => map!(Rgb, o, c0, c1, c2, alpha),
ColorFunction::Hsl(o, c0, c1, c2, alpha) => map!(Hsl, o, c0, c1, c2, alpha),
ColorFunction::Hwb(o, c0, c1, c2, alpha) => map!(Hwb, o, c0, c1, c2, alpha),
ColorFunction::Lab(o, c0, c1, c2, alpha) => map!(Lab, o, c0, c1, c2, alpha),
ColorFunction::Lch(o, c0, c1, c2, alpha) => map!(Lch, o, c0, c1, c2, alpha),
ColorFunction::Oklab(o, c0, c1, c2, alpha) => map!(Oklab, o, c0, c1, c2, alpha),
ColorFunction::Oklch(o, c0, c1, c2, alpha) => map!(Oklch, o, c0, c1, c2, alpha),
ColorFunction::Color(o, c0, c1, c2, alpha, color_space) => ColorFunction::Color(
o.as_ref().and_then(f),
c0.clone(),
c1.clone(),
c2.clone(),
alpha.clone(),
color_space.clone(),
),
}
}
}
impl ColorFunction<ComputedColor> {
/// Resolve a computed color function to an absolute computed color.
pub fn resolve_to_absolute(&self, current_color: &AbsoluteColor) -> AbsoluteColor {
// Map the color function to one with an absolute origin color.
let resolvable = self.map_origin_color(|o| Some(o.resolve_to_absolute(current_color)));
match resolvable.resolve_to_absolute() {
Ok(color) => color,
Err(..) => {
debug_assert!(
false,
"the color could not be resolved even with a currentcolor specified?"
);
AbsoluteColor::TRANSPARENT_BLACK
},
}
}
}
impl<C: style_traits::ToCss> style_traits::ToCss for ColorFunction<C> {
fn to_css<W>(&self, dest: &mut style_traits::CssWriter<W>) -> std::fmt::Result
where
W: std::fmt::Write,

View File

@@ -13,6 +13,7 @@ use super::{
use crate::{
parser::ParserContext,
values::{
animated::ToAnimatedValue,
generics::calc::CalcUnits,
specified::calc::{CalcNode as SpecifiedCalcNode, Leaf as SpecifiedLeaf},
},
@@ -209,3 +210,15 @@ impl<ValueType: ToCss> ToCss for ColorComponent<ValueType> {
Ok(())
}
}
impl<ValueType> ToAnimatedValue for ColorComponent<ValueType> {
type AnimatedValue = Self;
fn to_animated_value(self, _context: &crate::values::animated::Context) -> Self::AnimatedValue {
self
}
fn from_animated_value(animated: Self::AnimatedValue) -> Self {
animated
}
}

View File

@@ -4,12 +4,10 @@
//! Color support functions.
/// cbindgen:ignore
mod color_function;
/// cbindgen:ignore
pub mod convert;
mod color_function;
pub mod component;
pub mod mix;
pub mod parsing;

View File

@@ -169,7 +169,7 @@ fn parse_color_function<'i, 't>(
context: &ParserContext,
name: CowRcStr<'i>,
arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction, ParseError<'i>> {
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
let origin_color = parse_origin_color(context, arguments)?;
let color = match_ignore_ascii_case! { &name,
@@ -215,7 +215,7 @@ fn parse_rgb<'i, 't>(
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction, ParseError<'i>> {
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
let maybe_red = parse_number_or_percentage(context, arguments, true, RGB_CHANNEL_KEYWORDS)?;
// If the first component is not "none" and is followed by a comma, then we
@@ -259,7 +259,7 @@ fn parse_hsl<'i, 't>(
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction, ParseError<'i>> {
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
let hue = parse_number_or_angle(context, arguments, true, HSL_CHANNEL_KEYWORDS)?;
// If the hue is not "none" and is followed by a comma, then we are parsing
@@ -299,7 +299,7 @@ fn parse_hwb<'i, 't>(
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction, ParseError<'i>> {
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
let hue = parse_number_or_angle(context, arguments, true, HWB_CHANNEL_KEYWORDS)?;
let whiteness = parse_number_or_percentage(context, arguments, true, HWB_CHANNEL_KEYWORDS)?;
let blackness = parse_number_or_percentage(context, arguments, true, HWB_CHANNEL_KEYWORDS)?;
@@ -328,8 +328,8 @@ fn parse_lab_like<'i, 't>(
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
origin_color: Option<SpecifiedColor>,
into_color: IntoLabFn<ColorFunction>,
) -> Result<ColorFunction, ParseError<'i>> {
into_color: IntoLabFn<ColorFunction<SpecifiedColor>>,
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
let lightness = parse_number_or_percentage(context, arguments, true, LAB_CHANNEL_KEYWORDS)?;
let a = parse_number_or_percentage(context, arguments, true, LAB_CHANNEL_KEYWORDS)?;
let b = parse_number_or_percentage(context, arguments, true, LAB_CHANNEL_KEYWORDS)?;
@@ -352,8 +352,8 @@ fn parse_lch_like<'i, 't>(
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
origin_color: Option<SpecifiedColor>,
into_color: IntoLchFn<ColorFunction>,
) -> Result<ColorFunction, ParseError<'i>> {
into_color: IntoLchFn<ColorFunction<SpecifiedColor>>,
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
let lightness = parse_number_or_percentage(context, arguments, true, LCH_CHANNEL_KEYWORDS)?;
let chroma = parse_number_or_percentage(context, arguments, true, LCH_CHANNEL_KEYWORDS)?;
let hue = parse_number_or_angle(context, arguments, true, LCH_CHANNEL_KEYWORDS)?;
@@ -369,7 +369,7 @@ fn parse_color_with_color_space<'i, 't>(
context: &ParserContext,
arguments: &mut Parser<'i, 't>,
origin_color: Option<SpecifiedColor>,
) -> Result<ColorFunction, ParseError<'i>> {
) -> Result<ColorFunction<SpecifiedColor>, ParseError<'i>> {
let color_space = PredefinedColorSpace::parse(arguments)?;
let allowed_channel_keywords = match color_space {
@@ -398,8 +398,8 @@ fn parse_color_with_color_space<'i, 't>(
))
}
/// Eithee.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
/// Either a percentage or a number.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
pub enum NumberOrPercentage {
/// `<number>`.
Number(f32),
@@ -448,7 +448,7 @@ impl ColorComponentType for NumberOrPercentage {
}
/// Either an angle or a number.
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToShmem)]
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedValue, ToShmem)]
pub enum NumberOrAngle {
/// `<number>`.
Number(f32),

View File

@@ -31,6 +31,7 @@ impl ToCss for Color {
{
match *self {
Self::Absolute(ref c) => c.to_css(dest),
Self::ColorFunction(ref color_function) => color_function.to_css(dest),
Self::CurrentColor => dest.write_str("currentcolor"),
Self::ColorMix(ref m) => m.to_css(dest),
}
@@ -64,6 +65,9 @@ impl Color {
match *self {
Self::Absolute(c) => c,
Self::ColorFunction(ref color_function) => {
color_function.resolve_to_absolute(current_color)
},
Self::CurrentColor => *current_color,
Self::ColorMix(ref mix) => {
let left = mix.left.resolve_to_absolute(current_color);

View File

@@ -4,8 +4,7 @@
//! Generic types for color properties.
use crate::color::mix::ColorInterpolationMethod;
use crate::color::AbsoluteColor;
use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorFunction};
use crate::values::specified::percentage::ToPercentage;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss};
@@ -17,6 +16,8 @@ use style_traits::{CssWriter, ToCss};
pub enum GenericColor<Percentage> {
/// The actual numeric color.
Absolute(AbsoluteColor),
/// A unresolvable color.
ColorFunction(Box<ColorFunction<Self>>),
/// The `CurrentColor` keyword.
CurrentColor,
/// The color-mix() function.

View File

@@ -6,7 +6,7 @@
use super::AllowQuirks;
use crate::color::mix::ColorInterpolationMethod;
use crate::color::{parsing, AbsoluteColor, ColorFlags, ColorFunction, ColorSpace};
use crate::color::{parsing, AbsoluteColor, ColorFunction, ColorSpace};
use crate::media_queries::Device;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
@@ -117,7 +117,7 @@ pub enum Color {
Absolute(Box<Absolute>),
/// A color function that could not be resolved to a [Color::Absolute] color at parse time.
/// Right now this is only the case for relative colors with `currentColor` as the origin.
ColorFunction(Box<ColorFunction>),
ColorFunction(Box<ColorFunction<Self>>),
/// A system color.
#[cfg(feature = "gecko")]
System(SystemColor),
@@ -786,29 +786,17 @@ impl Color {
ComputedColor::Absolute(color)
},
Color::ColorFunction(ref color_function) => {
let has_origin_color = color_function.has_origin_color();
debug_assert!(color_function.has_origin_color(),
"no need for a ColorFunction if it doesn't contain an unresolvable origin color");
let Ok(mut absolute) = color_function.resolve_to_absolute() else {
// TODO(tlouw): The specified color must contain `currentColor` or some other
// unresolvable origin color, so here we have to store the whole
// [ColorFunction] in the computed color.
return Some(ComputedColor::Absolute(AbsoluteColor::BLACK));
};
// A special case when the color was a rgb(..) function and had an origin color,
// the result must be in the color(srgb ..) syntax, to avoid clipped channels
// into gamut limits.
let mut absolute = match absolute.color_space {
_ if has_origin_color && absolute.is_legacy_syntax() => {
absolute.flags.remove(ColorFlags::IS_LEGACY_SRGB);
absolute
},
_ => absolute,
};
adjust_absolute_color!(absolute);
ComputedColor::Absolute(absolute)
// Try to eagerly resolve the color function before making it a computed color.
if let Ok(absolute) = color_function.resolve_to_absolute() {
ComputedColor::Absolute(absolute)
} else {
let color_function = color_function
.map_origin_color(|origin_color| origin_color.to_computed_color(context));
ComputedColor::ColorFunction(Box::new(color_function))
}
},
Color::LightDark(ref ld) => ld.compute(context?),
Color::ColorMix(ref mix) => {
@@ -852,6 +840,11 @@ impl ToComputedValue for Color {
fn from_computed_value(computed: &ComputedColor) -> Self {
match *computed {
ComputedColor::Absolute(ref color) => Self::from_absolute_color(color.clone()),
ComputedColor::ColorFunction(ref color_function) => {
let color_function =
color_function.map_origin_color(|o| Some(Self::from_computed_value(o)));
Self::ColorFunction(Box::new(color_function))
},
ComputedColor::CurrentColor => Color::CurrentColor,
ComputedColor::ColorMix(ref mix) => {
Color::ColorMix(Box::new(ToComputedValue::from_computed_value(&**mix)))

View File

@@ -84,6 +84,7 @@ exclude = [
"NS_LogDtor",
"SelectorList",
"AuthorStyles",
"ColorFunction",
]
include = [
"AnchorName",
@@ -328,7 +329,14 @@ include = [
"AnchorSizeFunction",
"Margin",
]
item_types = ["enums", "structs", "unions", "typedefs", "functions", "constants"]
item_types = [
"enums",
"structs",
"unions",
"typedefs",
"functions",
"constants",
]
renaming_overrides_prefixing = true
# Prevent some renaming for Gecko types that cbindgen doesn't otherwise understand.