palette_derive/convert/
util.rs

1use std::collections::HashMap;
2
3use proc_macro2::Span;
4use syn::{parse_quote, Generics, Result, Type};
5
6use crate::{
7    color_types::{ColorInfo, MetaTypeSource},
8    meta::TypeItemAttributes,
9    util,
10};
11
12pub fn white_point_type(
13    white_point: Option<&Type>,
14    rgb_standard: Option<&Type>,
15    luma_standard: Option<&Type>,
16    internal: bool,
17) -> Option<(Type, WhitePointSource)> {
18    white_point
19        .map(|white_point| (white_point.clone(), WhitePointSource::WhitePoint))
20        .or_else(|| {
21            rgb_standard.map(|rgb_standard| {
22                let rgb_standard_path = util::path(["rgb", "RgbStandard"], internal);
23                let rgb_space_path = util::path(["rgb", "RgbSpace"], internal);
24                (
25                    parse_quote!(<<#rgb_standard as #rgb_standard_path>::Space as #rgb_space_path>::WhitePoint),
26                    WhitePointSource::RgbStandard,
27                )
28            })
29        })
30        .or_else(|| {
31            luma_standard.map(|luma_standard| {
32                let luma_standard_path = util::path(["luma", "LumaStandard"], internal);
33                (
34                    parse_quote!(<#luma_standard as #luma_standard_path>::WhitePoint),
35                    WhitePointSource::LumaStandard,
36                )
37            })
38        })
39}
40
41pub fn component_type(component: Option<Type>) -> Type {
42    component.unwrap_or_else(|| parse_quote!(f32))
43}
44
45pub(crate) fn get_convert_color_type(
46    color: &ColorInfo,
47    white_point: &Type,
48    component: &Type,
49    meta: &TypeItemAttributes,
50    generics: &mut Generics,
51) -> syn::Result<(Type, UsedInput)> {
52    let mut used_input = UsedInput::default();
53    let color_type = color.get_type(
54        MetaTypeSource::Generics(generics),
55        component,
56        white_point,
57        &mut used_input,
58        InputUser::Target,
59        meta,
60    )?;
61
62    Ok((color_type, used_input))
63}
64
65pub(crate) fn find_nearest_color<'a>(
66    color: &'a ColorInfo,
67    meta: &TypeItemAttributes,
68) -> Result<&'a ColorInfo> {
69    let mut stack = vec![(color, 0)];
70    let mut found = None;
71    let mut visited = HashMap::new();
72
73    // Make sure there is at least one valid color in the skip list
74    assert!(!meta.skip_derives.is_empty());
75
76    while let Some((color, distance)) = stack.pop() {
77        if meta.skip_derives.contains(color.name) {
78            if let Some((_, found_distance)) = found {
79                if distance < found_distance {
80                    found = Some((color, distance));
81                    continue;
82                }
83            } else {
84                found = Some((color, distance));
85                continue;
86            }
87        }
88
89        if let Some(&previous_distance) = visited.get(color.name) {
90            if previous_distance <= distance {
91                continue;
92            }
93        }
94
95        visited.insert(color.name, distance);
96
97        // Start by pushing the plan B routes...
98        for group in &meta.color_groups {
99            for candidate in group.colors {
100                if color.name == candidate.preferred_source {
101                    stack.push((&candidate.info, distance + 1));
102                }
103            }
104        }
105
106        // ...then push the preferred routes. They will be popped first.
107        for group in &meta.color_groups {
108            for candidate in group.colors {
109                if color.name == candidate.info.name {
110                    let preferred = group
111                        .find_by_name(candidate.preferred_source)
112                        .expect("preferred sources have to exist in the group");
113                    stack.push((preferred, distance + 1));
114                }
115            }
116        }
117    }
118
119    if let Some((color, _)) = found {
120        Ok(color)
121    } else {
122        Err(::syn::parse::Error::new(
123            Span::call_site(),
124            format!(
125                "none of the skipped colors can be used for converting from {}",
126                color.name
127            ),
128        ))
129    }
130}
131
132#[derive(PartialEq, Eq, Clone, Copy, Debug)]
133pub enum WhitePointSource {
134    WhitePoint,
135    RgbStandard,
136    LumaStandard,
137    ConcreteType,
138    GeneratedGeneric,
139}
140
141#[derive(Debug, Default)]
142pub struct UsedInput {
143    pub white_point: InputUsage,
144}
145
146#[derive(Debug, Default)]
147pub struct InputUsage {
148    used_by_target: bool,
149    used_by_nearest: bool,
150}
151
152impl InputUsage {
153    pub(crate) fn set_used(&mut self, user: InputUser) {
154        match user {
155            InputUser::Target => self.used_by_target = true,
156            InputUser::Nearest => self.used_by_nearest = true,
157        }
158    }
159
160    pub(crate) fn is_used(&self) -> bool {
161        self.used_by_target || self.used_by_nearest
162    }
163
164    pub(crate) fn is_unconstrained(&self) -> bool {
165        !self.used_by_target && self.used_by_nearest
166    }
167}
168
169#[derive(Clone, Copy)]
170pub enum InputUser {
171    Target,
172    Nearest,
173}