color/
flags.rs

1// Copyright 2024 the Color Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Types for tracking [`DynamicColor`](crate::DynamicColor) state.
5
6use crate::x11_colors;
7
8/// Flags indicating [`DynamicColor`](crate::DynamicColor) state.
9///
10/// The "missing" flags indicate whether a specific color component is missing (either the three
11/// color channels or the alpha channel).
12///
13/// The "named" flag represents whether the dynamic color was parsed from one of the named colors
14/// in [CSS Color Module Level 4 § 6.1][css-named-colors] or named color space functions in [CSS
15/// Color Module Level 4 § 4.1][css-named-color-spaces].
16///
17/// The latter is primarily useful for serializing to a CSS-compliant string format.
18///
19/// [css-named-colors]: https://www.w3.org/TR/css-color-4/#named-colors
20/// [css-named-color-spaces]: https://www.w3.org/TR/css-color-4/#color-syntax
21#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
22#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
23pub struct Flags {
24    /// A bitset of missing color components.
25    missing: Missing,
26
27    /// The named source a [`crate::DynamicColor`] was constructed from. Meanings:
28    /// - 0 - not constructed from a named source;
29    /// - 255 - constructed from a named color space function;
30    /// - otherwise - the 1-based index into [`crate::x11_colors::NAMES`].
31    name: u8,
32}
33
34// Ensure the amount of colors fits into the `Flags::name` packing.
35#[cfg(test)]
36const _: () = const {
37    if x11_colors::NAMES.len() > 253 {
38        panic!("There are more X11 color names than can be packed into Flags.");
39    }
40};
41
42/// Missing color components, extracted from [`Flags`].
43///
44/// Some bitwise operations are implemented on this type, making certain manipulations more
45/// ergonomic.
46#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
47#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
48pub struct Missing(u8);
49
50impl Flags {
51    /// Construct flags with the given missing components.
52    #[inline]
53    pub const fn from_missing(missing: Missing) -> Self {
54        Self { missing, name: 0 }
55    }
56
57    /// Set the missing components.
58    #[inline]
59    #[warn(
60        clippy::missing_const_for_fn,
61        reason = "can be made const with MSRV 1.83"
62    )]
63    pub fn set_missing(&mut self, missing: Missing) {
64        self.missing = missing;
65    }
66
67    /// Returns the missing components from the flags.
68    #[inline]
69    pub const fn missing(self) -> Missing {
70        self.missing
71    }
72
73    /// Set the flags to indicate the color was specified as one of the named colors. `name_ix` is
74    /// the index into [`crate::x11_colors::NAMES`].
75    pub(crate) fn set_named_color(&mut self, name_ix: usize) {
76        debug_assert!(
77            name_ix < x11_colors::NAMES.len(),
78            "Expected an X11 color name index no larger than: {}. Got: {}.",
79            x11_colors::NAMES.len(),
80            name_ix
81        );
82
83        #[expect(
84            clippy::cast_possible_truncation,
85            reason = "name_ix is guaranteed to small enough by the above condition and by the test on the length of `x11_colors::NAMES`"
86        )]
87        {
88            self.name = name_ix as u8 + 1;
89        }
90    }
91
92    /// Set the flags to indicate the color was specified using one of the named color space
93    /// functions.
94    #[warn(
95        clippy::missing_const_for_fn,
96        reason = "can be made const with MSRV 1.83"
97    )]
98    pub(crate) fn set_named_color_space(&mut self) {
99        self.name = 255;
100    }
101
102    /// Returns `true` if the flags indicate the color was generated from a named color or named
103    /// color space function.
104    #[inline]
105    pub const fn named(self) -> bool {
106        self.name != 0
107    }
108
109    /// If the color was constructed from a named color, returns that name.
110    ///
111    /// See also [`parse_color`][crate::parse_color].
112    pub const fn color_name(self) -> Option<&'static str> {
113        let name_ix = self.name;
114        if name_ix == 0 || name_ix == 255 {
115            None
116        } else {
117            Some(x11_colors::NAMES[name_ix as usize - 1])
118        }
119    }
120
121    /// Discard the color name or color space name from the flags.
122    #[inline]
123    #[warn(
124        clippy::missing_const_for_fn,
125        reason = "can be made const with MSRV 1.83"
126    )]
127    pub fn discard_name(&mut self) {
128        self.name = 0;
129    }
130}
131
132impl core::fmt::Debug for Flags {
133    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
134        f.debug_struct("Flags")
135            .field("missing", &self.missing)
136            .field("name", &self.name)
137            .field("named", &self.named())
138            .field("color_name", &self.color_name())
139            .finish()
140    }
141}
142
143impl Missing {
144    /// The set containing no missing components.
145    pub const EMPTY: Self = Self(0);
146
147    /// The set containing a single component index.
148    #[inline]
149    pub const fn single(ix: usize) -> Self {
150        debug_assert!(ix <= 3, "color component index must be 0, 1, 2 or 3");
151        Self(1 << ix)
152    }
153
154    /// Returns `true` if the set contains the component index.
155    #[inline]
156    pub const fn contains(self, ix: usize) -> bool {
157        (self.0 & Self::single(ix).0) != 0
158    }
159
160    /// Add a missing component index to the set.
161    #[inline]
162    #[warn(
163        clippy::missing_const_for_fn,
164        reason = "can be made const with MSRV 1.83"
165    )]
166    pub fn insert(&mut self, ix: usize) {
167        self.0 |= Self::single(ix).0;
168    }
169
170    /// Returns `true` if the set contains no indices.
171    #[inline]
172    pub const fn is_empty(self) -> bool {
173        self.0 == 0
174    }
175}
176
177impl core::ops::BitAnd for Missing {
178    type Output = Self;
179
180    #[inline]
181    fn bitand(self, rhs: Self) -> Self {
182        Self(self.0 & rhs.0)
183    }
184}
185
186impl core::ops::BitOr for Missing {
187    type Output = Self;
188
189    #[inline]
190    fn bitor(self, rhs: Self) -> Self {
191        Self(self.0 | rhs.0)
192    }
193}
194
195impl core::ops::Not for Missing {
196    type Output = Self;
197
198    #[inline]
199    fn not(self) -> Self::Output {
200        Self(!self.0)
201    }
202}
203
204impl core::fmt::Debug for Missing {
205    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
206        f.debug_tuple("Missing")
207            .field(&format_args!("{:#010b}", self.0))
208            .finish()
209    }
210}