Skip to main content

r/interpreter/graphics/
color.rs

1//! R color system — 657 named colors, hex parsing, color builtins.
2//!
3//! The named color table matches R's `colors()` output exactly, including all
4//! X11/CSS colors and the gray0–gray100 / grey0–grey100 series. Lookup is
5//! case-insensitive via binary search on lowercase names.
6
7use crate::interpreter::value::*;
8use crate::interpreter::BuiltinContext;
9use minir_macros::{builtin, interpreter_builtin};
10
11// region: RColor type
12
13/// An RGBA color value. Alpha defaults to 255 (fully opaque).
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct RColor {
16    pub r: u8,
17    pub g: u8,
18    pub b: u8,
19    pub a: u8,
20}
21
22impl RColor {
23    pub const BLACK: RColor = RColor::rgb(0, 0, 0);
24    pub const WHITE: RColor = RColor::rgb(255, 255, 255);
25    pub const TRANSPARENT: RColor = RColor {
26        r: 255,
27        g: 255,
28        b: 255,
29        a: 0,
30    };
31
32    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
33        RColor { r, g, b, a: 255 }
34    }
35
36    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
37        RColor { r, g, b, a }
38    }
39
40    /// Look up a named R color (case-insensitive).
41    pub fn from_name(name: &str) -> Option<RColor> {
42        let lower = name.to_ascii_lowercase();
43        // Handle "transparent" specially — it's not in the sorted table
44        if lower == "transparent" {
45            return Some(RColor::TRANSPARENT);
46        }
47        NAMED_COLORS
48            .binary_search_by_key(&lower.as_str(), |&(n, _)| n)
49            .ok()
50            .map(|idx| {
51                let (_, (r, g, b)) = NAMED_COLORS[idx];
52                RColor::rgb(r, g, b)
53            })
54    }
55
56    /// Parse any R color specification:
57    /// - Named color (e.g. "red", "cornflowerblue")
58    /// - Hex "#RRGGBB" or "#RRGGBBAA"
59    /// - Integer string for palette index (e.g. "1", "2")
60    /// - "transparent"
61    pub fn from_r_value(val: &str, palette: &[RColor]) -> Result<RColor, RError> {
62        let trimmed = val.trim();
63
64        // Hex color
65        if trimmed.starts_with('#') {
66            return Self::from_hex(trimmed);
67        }
68
69        // Integer palette index
70        if let Ok(idx) = trimmed.parse::<usize>() {
71            if idx == 0 {
72                // In R, color index 0 means "no color" — use transparent
73                return Ok(RColor::TRANSPARENT);
74            }
75            if palette.is_empty() {
76                return Err(RError::new(
77                    RErrorKind::Other,
78                    format!("palette index {idx} requested but palette is empty"),
79                ));
80            }
81            // R palette indices are 1-based and wrap around
82            let wrapped = ((idx - 1) % palette.len()) + 1;
83            return Ok(palette[wrapped - 1]);
84        }
85
86        // Named color
87        if let Some(color) = Self::from_name(trimmed) {
88            return Ok(color);
89        }
90
91        Err(RError::new(
92            RErrorKind::Other,
93            format!("invalid color name '{trimmed}'"),
94        ))
95    }
96
97    /// Parse a hex color string: "#RRGGBB" or "#RRGGBBAA".
98    pub fn from_hex(hex: &str) -> Result<RColor, RError> {
99        let hex = hex.trim();
100        if !hex.starts_with('#') {
101            return Err(RError::new(
102                RErrorKind::Other,
103                format!("invalid hex color '{hex}': must start with '#'"),
104            ));
105        }
106        let digits = &hex[1..];
107        match digits.len() {
108            6 => {
109                let r = u8::from_str_radix(&digits[0..2], 16);
110                let g = u8::from_str_radix(&digits[2..4], 16);
111                let b = u8::from_str_radix(&digits[4..6], 16);
112                match (r, g, b) {
113                    (Ok(r), Ok(g), Ok(b)) => Ok(RColor::rgb(r, g, b)),
114                    _ => Err(RError::new(
115                        RErrorKind::Other,
116                        format!("invalid hex color '{hex}': contains non-hex digits"),
117                    )),
118                }
119            }
120            8 => {
121                let r = u8::from_str_radix(&digits[0..2], 16);
122                let g = u8::from_str_radix(&digits[2..4], 16);
123                let b = u8::from_str_radix(&digits[4..6], 16);
124                let a = u8::from_str_radix(&digits[6..8], 16);
125                match (r, g, b, a) {
126                    (Ok(r), Ok(g), Ok(b), Ok(a)) => Ok(RColor::rgba(r, g, b, a)),
127                    _ => Err(RError::new(
128                        RErrorKind::Other,
129                        format!("invalid hex color '{hex}': contains non-hex digits"),
130                    )),
131                }
132            }
133            _ => Err(RError::new(
134                RErrorKind::Other,
135                format!(
136                    "invalid hex color '{hex}': expected 6 or 8 hex digits after '#', got {}",
137                    digits.len()
138                ),
139            )),
140        }
141    }
142
143    /// Format as "#RRGGBB" (if fully opaque) or "#RRGGBBAA".
144    pub fn to_hex(&self) -> String {
145        if self.a == 255 {
146            format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
147        } else {
148            format!("#{:02X}{:02X}{:02X}{:02X}", self.r, self.g, self.b, self.a)
149        }
150    }
151}
152
153impl std::fmt::Display for RColor {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        write!(f, "{}", self.to_hex())
156    }
157}
158
159// endregion
160
161// region: Default palette
162
163/// R's default palette: c("black","red","green3","blue","cyan","magenta","yellow","gray")
164pub const DEFAULT_PALETTE: [RColor; 8] = [
165    RColor::rgb(0, 0, 0),       // black
166    RColor::rgb(255, 0, 0),     // red
167    RColor::rgb(0, 205, 0),     // green3
168    RColor::rgb(0, 0, 255),     // blue
169    RColor::rgb(0, 255, 255),   // cyan
170    RColor::rgb(255, 0, 255),   // magenta
171    RColor::rgb(255, 255, 0),   // yellow
172    RColor::rgb(190, 190, 190), // gray
173];
174
175/// Returns the default palette as a `Vec<RColor>`.
176pub fn default_palette() -> Vec<RColor> {
177    DEFAULT_PALETTE.to_vec()
178}
179
180// endregion
181
182// region: Named color table (657 entries, sorted by name)
183
184/// All 657 R named colors, sorted alphabetically for binary search.
185/// Generated from R's `colors()` output and `col2rgb()` values.
186pub const NAMED_COLORS: &[(&str, (u8, u8, u8))] = &[
187    ("aliceblue", (240, 248, 255)),
188    ("antiquewhite", (250, 235, 215)),
189    ("antiquewhite1", (255, 239, 219)),
190    ("antiquewhite2", (238, 223, 204)),
191    ("antiquewhite3", (205, 192, 176)),
192    ("antiquewhite4", (139, 131, 120)),
193    ("aquamarine", (127, 255, 212)),
194    ("aquamarine1", (127, 255, 212)),
195    ("aquamarine2", (118, 238, 198)),
196    ("aquamarine3", (102, 205, 170)),
197    ("aquamarine4", (69, 139, 116)),
198    ("azure", (240, 255, 255)),
199    ("azure1", (240, 255, 255)),
200    ("azure2", (224, 238, 238)),
201    ("azure3", (193, 205, 205)),
202    ("azure4", (131, 139, 139)),
203    ("beige", (245, 245, 220)),
204    ("bisque", (255, 228, 196)),
205    ("bisque1", (255, 228, 196)),
206    ("bisque2", (238, 213, 183)),
207    ("bisque3", (205, 183, 158)),
208    ("bisque4", (139, 125, 107)),
209    ("black", (0, 0, 0)),
210    ("blanchedalmond", (255, 235, 205)),
211    ("blue", (0, 0, 255)),
212    ("blue1", (0, 0, 255)),
213    ("blue2", (0, 0, 238)),
214    ("blue3", (0, 0, 205)),
215    ("blue4", (0, 0, 139)),
216    ("blueviolet", (138, 43, 226)),
217    ("brown", (165, 42, 42)),
218    ("brown1", (255, 64, 64)),
219    ("brown2", (238, 59, 59)),
220    ("brown3", (205, 51, 51)),
221    ("brown4", (139, 35, 35)),
222    ("burlywood", (222, 184, 135)),
223    ("burlywood1", (255, 211, 155)),
224    ("burlywood2", (238, 197, 145)),
225    ("burlywood3", (205, 170, 125)),
226    ("burlywood4", (139, 115, 85)),
227    ("cadetblue", (95, 158, 160)),
228    ("cadetblue1", (152, 245, 255)),
229    ("cadetblue2", (142, 229, 238)),
230    ("cadetblue3", (122, 197, 205)),
231    ("cadetblue4", (83, 134, 139)),
232    ("chartreuse", (127, 255, 0)),
233    ("chartreuse1", (127, 255, 0)),
234    ("chartreuse2", (118, 238, 0)),
235    ("chartreuse3", (102, 205, 0)),
236    ("chartreuse4", (69, 139, 0)),
237    ("chocolate", (210, 105, 30)),
238    ("chocolate1", (255, 127, 36)),
239    ("chocolate2", (238, 118, 33)),
240    ("chocolate3", (205, 102, 29)),
241    ("chocolate4", (139, 69, 19)),
242    ("coral", (255, 127, 80)),
243    ("coral1", (255, 114, 86)),
244    ("coral2", (238, 106, 80)),
245    ("coral3", (205, 91, 69)),
246    ("coral4", (139, 62, 47)),
247    ("cornflowerblue", (100, 149, 237)),
248    ("cornsilk", (255, 248, 220)),
249    ("cornsilk1", (255, 248, 220)),
250    ("cornsilk2", (238, 232, 205)),
251    ("cornsilk3", (205, 200, 177)),
252    ("cornsilk4", (139, 136, 120)),
253    ("cyan", (0, 255, 255)),
254    ("cyan1", (0, 255, 255)),
255    ("cyan2", (0, 238, 238)),
256    ("cyan3", (0, 205, 205)),
257    ("cyan4", (0, 139, 139)),
258    ("darkblue", (0, 0, 139)),
259    ("darkcyan", (0, 139, 139)),
260    ("darkgoldenrod", (184, 134, 11)),
261    ("darkgoldenrod1", (255, 185, 15)),
262    ("darkgoldenrod2", (238, 173, 14)),
263    ("darkgoldenrod3", (205, 149, 12)),
264    ("darkgoldenrod4", (139, 101, 8)),
265    ("darkgray", (169, 169, 169)),
266    ("darkgreen", (0, 100, 0)),
267    ("darkgrey", (169, 169, 169)),
268    ("darkkhaki", (189, 183, 107)),
269    ("darkmagenta", (139, 0, 139)),
270    ("darkolivegreen", (85, 107, 47)),
271    ("darkolivegreen1", (202, 255, 112)),
272    ("darkolivegreen2", (188, 238, 104)),
273    ("darkolivegreen3", (162, 205, 90)),
274    ("darkolivegreen4", (110, 139, 61)),
275    ("darkorange", (255, 140, 0)),
276    ("darkorange1", (255, 127, 0)),
277    ("darkorange2", (238, 118, 0)),
278    ("darkorange3", (205, 102, 0)),
279    ("darkorange4", (139, 69, 0)),
280    ("darkorchid", (153, 50, 204)),
281    ("darkorchid1", (191, 62, 255)),
282    ("darkorchid2", (178, 58, 238)),
283    ("darkorchid3", (154, 50, 205)),
284    ("darkorchid4", (104, 34, 139)),
285    ("darkred", (139, 0, 0)),
286    ("darksalmon", (233, 150, 122)),
287    ("darkseagreen", (143, 188, 143)),
288    ("darkseagreen1", (193, 255, 193)),
289    ("darkseagreen2", (180, 238, 180)),
290    ("darkseagreen3", (155, 205, 155)),
291    ("darkseagreen4", (105, 139, 105)),
292    ("darkslateblue", (72, 61, 139)),
293    ("darkslategray", (47, 79, 79)),
294    ("darkslategray1", (151, 255, 255)),
295    ("darkslategray2", (141, 238, 238)),
296    ("darkslategray3", (121, 205, 205)),
297    ("darkslategray4", (82, 139, 139)),
298    ("darkslategrey", (47, 79, 79)),
299    ("darkturquoise", (0, 206, 209)),
300    ("darkviolet", (148, 0, 211)),
301    ("deeppink", (255, 20, 147)),
302    ("deeppink1", (255, 20, 147)),
303    ("deeppink2", (238, 18, 137)),
304    ("deeppink3", (205, 16, 118)),
305    ("deeppink4", (139, 10, 80)),
306    ("deepskyblue", (0, 191, 255)),
307    ("deepskyblue1", (0, 191, 255)),
308    ("deepskyblue2", (0, 178, 238)),
309    ("deepskyblue3", (0, 154, 205)),
310    ("deepskyblue4", (0, 104, 139)),
311    ("dimgray", (105, 105, 105)),
312    ("dimgrey", (105, 105, 105)),
313    ("dodgerblue", (30, 144, 255)),
314    ("dodgerblue1", (30, 144, 255)),
315    ("dodgerblue2", (28, 134, 238)),
316    ("dodgerblue3", (24, 116, 205)),
317    ("dodgerblue4", (16, 78, 139)),
318    ("firebrick", (178, 34, 34)),
319    ("firebrick1", (255, 48, 48)),
320    ("firebrick2", (238, 44, 44)),
321    ("firebrick3", (205, 38, 38)),
322    ("firebrick4", (139, 26, 26)),
323    ("floralwhite", (255, 250, 240)),
324    ("forestgreen", (34, 139, 34)),
325    ("gainsboro", (220, 220, 220)),
326    ("ghostwhite", (248, 248, 255)),
327    ("gold", (255, 215, 0)),
328    ("gold1", (255, 215, 0)),
329    ("gold2", (238, 201, 0)),
330    ("gold3", (205, 173, 0)),
331    ("gold4", (139, 117, 0)),
332    ("goldenrod", (218, 165, 32)),
333    ("goldenrod1", (255, 193, 37)),
334    ("goldenrod2", (238, 180, 34)),
335    ("goldenrod3", (205, 155, 29)),
336    ("goldenrod4", (139, 105, 20)),
337    ("gray", (190, 190, 190)),
338    ("gray0", (0, 0, 0)),
339    ("gray1", (3, 3, 3)),
340    ("gray10", (26, 26, 26)),
341    ("gray100", (255, 255, 255)),
342    ("gray11", (28, 28, 28)),
343    ("gray12", (31, 31, 31)),
344    ("gray13", (33, 33, 33)),
345    ("gray14", (36, 36, 36)),
346    ("gray15", (38, 38, 38)),
347    ("gray16", (41, 41, 41)),
348    ("gray17", (43, 43, 43)),
349    ("gray18", (46, 46, 46)),
350    ("gray19", (48, 48, 48)),
351    ("gray2", (5, 5, 5)),
352    ("gray20", (51, 51, 51)),
353    ("gray21", (54, 54, 54)),
354    ("gray22", (56, 56, 56)),
355    ("gray23", (59, 59, 59)),
356    ("gray24", (61, 61, 61)),
357    ("gray25", (64, 64, 64)),
358    ("gray26", (66, 66, 66)),
359    ("gray27", (69, 69, 69)),
360    ("gray28", (71, 71, 71)),
361    ("gray29", (74, 74, 74)),
362    ("gray3", (8, 8, 8)),
363    ("gray30", (77, 77, 77)),
364    ("gray31", (79, 79, 79)),
365    ("gray32", (82, 82, 82)),
366    ("gray33", (84, 84, 84)),
367    ("gray34", (87, 87, 87)),
368    ("gray35", (89, 89, 89)),
369    ("gray36", (92, 92, 92)),
370    ("gray37", (94, 94, 94)),
371    ("gray38", (97, 97, 97)),
372    ("gray39", (99, 99, 99)),
373    ("gray4", (10, 10, 10)),
374    ("gray40", (102, 102, 102)),
375    ("gray41", (105, 105, 105)),
376    ("gray42", (107, 107, 107)),
377    ("gray43", (110, 110, 110)),
378    ("gray44", (112, 112, 112)),
379    ("gray45", (115, 115, 115)),
380    ("gray46", (117, 117, 117)),
381    ("gray47", (120, 120, 120)),
382    ("gray48", (122, 122, 122)),
383    ("gray49", (125, 125, 125)),
384    ("gray5", (13, 13, 13)),
385    ("gray50", (127, 127, 127)),
386    ("gray51", (130, 130, 130)),
387    ("gray52", (133, 133, 133)),
388    ("gray53", (135, 135, 135)),
389    ("gray54", (138, 138, 138)),
390    ("gray55", (140, 140, 140)),
391    ("gray56", (143, 143, 143)),
392    ("gray57", (145, 145, 145)),
393    ("gray58", (148, 148, 148)),
394    ("gray59", (150, 150, 150)),
395    ("gray6", (15, 15, 15)),
396    ("gray60", (153, 153, 153)),
397    ("gray61", (156, 156, 156)),
398    ("gray62", (158, 158, 158)),
399    ("gray63", (161, 161, 161)),
400    ("gray64", (163, 163, 163)),
401    ("gray65", (166, 166, 166)),
402    ("gray66", (168, 168, 168)),
403    ("gray67", (171, 171, 171)),
404    ("gray68", (173, 173, 173)),
405    ("gray69", (176, 176, 176)),
406    ("gray7", (18, 18, 18)),
407    ("gray70", (179, 179, 179)),
408    ("gray71", (181, 181, 181)),
409    ("gray72", (184, 184, 184)),
410    ("gray73", (186, 186, 186)),
411    ("gray74", (189, 189, 189)),
412    ("gray75", (191, 191, 191)),
413    ("gray76", (194, 194, 194)),
414    ("gray77", (196, 196, 196)),
415    ("gray78", (199, 199, 199)),
416    ("gray79", (201, 201, 201)),
417    ("gray8", (20, 20, 20)),
418    ("gray80", (204, 204, 204)),
419    ("gray81", (207, 207, 207)),
420    ("gray82", (209, 209, 209)),
421    ("gray83", (212, 212, 212)),
422    ("gray84", (214, 214, 214)),
423    ("gray85", (217, 217, 217)),
424    ("gray86", (219, 219, 219)),
425    ("gray87", (222, 222, 222)),
426    ("gray88", (224, 224, 224)),
427    ("gray89", (227, 227, 227)),
428    ("gray9", (23, 23, 23)),
429    ("gray90", (229, 229, 229)),
430    ("gray91", (232, 232, 232)),
431    ("gray92", (235, 235, 235)),
432    ("gray93", (237, 237, 237)),
433    ("gray94", (240, 240, 240)),
434    ("gray95", (242, 242, 242)),
435    ("gray96", (245, 245, 245)),
436    ("gray97", (247, 247, 247)),
437    ("gray98", (250, 250, 250)),
438    ("gray99", (252, 252, 252)),
439    ("green", (0, 255, 0)),
440    ("green1", (0, 255, 0)),
441    ("green2", (0, 238, 0)),
442    ("green3", (0, 205, 0)),
443    ("green4", (0, 139, 0)),
444    ("greenyellow", (173, 255, 47)),
445    ("grey", (190, 190, 190)),
446    ("grey0", (0, 0, 0)),
447    ("grey1", (3, 3, 3)),
448    ("grey10", (26, 26, 26)),
449    ("grey100", (255, 255, 255)),
450    ("grey11", (28, 28, 28)),
451    ("grey12", (31, 31, 31)),
452    ("grey13", (33, 33, 33)),
453    ("grey14", (36, 36, 36)),
454    ("grey15", (38, 38, 38)),
455    ("grey16", (41, 41, 41)),
456    ("grey17", (43, 43, 43)),
457    ("grey18", (46, 46, 46)),
458    ("grey19", (48, 48, 48)),
459    ("grey2", (5, 5, 5)),
460    ("grey20", (51, 51, 51)),
461    ("grey21", (54, 54, 54)),
462    ("grey22", (56, 56, 56)),
463    ("grey23", (59, 59, 59)),
464    ("grey24", (61, 61, 61)),
465    ("grey25", (64, 64, 64)),
466    ("grey26", (66, 66, 66)),
467    ("grey27", (69, 69, 69)),
468    ("grey28", (71, 71, 71)),
469    ("grey29", (74, 74, 74)),
470    ("grey3", (8, 8, 8)),
471    ("grey30", (77, 77, 77)),
472    ("grey31", (79, 79, 79)),
473    ("grey32", (82, 82, 82)),
474    ("grey33", (84, 84, 84)),
475    ("grey34", (87, 87, 87)),
476    ("grey35", (89, 89, 89)),
477    ("grey36", (92, 92, 92)),
478    ("grey37", (94, 94, 94)),
479    ("grey38", (97, 97, 97)),
480    ("grey39", (99, 99, 99)),
481    ("grey4", (10, 10, 10)),
482    ("grey40", (102, 102, 102)),
483    ("grey41", (105, 105, 105)),
484    ("grey42", (107, 107, 107)),
485    ("grey43", (110, 110, 110)),
486    ("grey44", (112, 112, 112)),
487    ("grey45", (115, 115, 115)),
488    ("grey46", (117, 117, 117)),
489    ("grey47", (120, 120, 120)),
490    ("grey48", (122, 122, 122)),
491    ("grey49", (125, 125, 125)),
492    ("grey5", (13, 13, 13)),
493    ("grey50", (127, 127, 127)),
494    ("grey51", (130, 130, 130)),
495    ("grey52", (133, 133, 133)),
496    ("grey53", (135, 135, 135)),
497    ("grey54", (138, 138, 138)),
498    ("grey55", (140, 140, 140)),
499    ("grey56", (143, 143, 143)),
500    ("grey57", (145, 145, 145)),
501    ("grey58", (148, 148, 148)),
502    ("grey59", (150, 150, 150)),
503    ("grey6", (15, 15, 15)),
504    ("grey60", (153, 153, 153)),
505    ("grey61", (156, 156, 156)),
506    ("grey62", (158, 158, 158)),
507    ("grey63", (161, 161, 161)),
508    ("grey64", (163, 163, 163)),
509    ("grey65", (166, 166, 166)),
510    ("grey66", (168, 168, 168)),
511    ("grey67", (171, 171, 171)),
512    ("grey68", (173, 173, 173)),
513    ("grey69", (176, 176, 176)),
514    ("grey7", (18, 18, 18)),
515    ("grey70", (179, 179, 179)),
516    ("grey71", (181, 181, 181)),
517    ("grey72", (184, 184, 184)),
518    ("grey73", (186, 186, 186)),
519    ("grey74", (189, 189, 189)),
520    ("grey75", (191, 191, 191)),
521    ("grey76", (194, 194, 194)),
522    ("grey77", (196, 196, 196)),
523    ("grey78", (199, 199, 199)),
524    ("grey79", (201, 201, 201)),
525    ("grey8", (20, 20, 20)),
526    ("grey80", (204, 204, 204)),
527    ("grey81", (207, 207, 207)),
528    ("grey82", (209, 209, 209)),
529    ("grey83", (212, 212, 212)),
530    ("grey84", (214, 214, 214)),
531    ("grey85", (217, 217, 217)),
532    ("grey86", (219, 219, 219)),
533    ("grey87", (222, 222, 222)),
534    ("grey88", (224, 224, 224)),
535    ("grey89", (227, 227, 227)),
536    ("grey9", (23, 23, 23)),
537    ("grey90", (229, 229, 229)),
538    ("grey91", (232, 232, 232)),
539    ("grey92", (235, 235, 235)),
540    ("grey93", (237, 237, 237)),
541    ("grey94", (240, 240, 240)),
542    ("grey95", (242, 242, 242)),
543    ("grey96", (245, 245, 245)),
544    ("grey97", (247, 247, 247)),
545    ("grey98", (250, 250, 250)),
546    ("grey99", (252, 252, 252)),
547    ("honeydew", (240, 255, 240)),
548    ("honeydew1", (240, 255, 240)),
549    ("honeydew2", (224, 238, 224)),
550    ("honeydew3", (193, 205, 193)),
551    ("honeydew4", (131, 139, 131)),
552    ("hotpink", (255, 105, 180)),
553    ("hotpink1", (255, 110, 180)),
554    ("hotpink2", (238, 106, 167)),
555    ("hotpink3", (205, 96, 144)),
556    ("hotpink4", (139, 58, 98)),
557    ("indianred", (205, 92, 92)),
558    ("indianred1", (255, 106, 106)),
559    ("indianred2", (238, 99, 99)),
560    ("indianred3", (205, 85, 85)),
561    ("indianred4", (139, 58, 58)),
562    ("ivory", (255, 255, 240)),
563    ("ivory1", (255, 255, 240)),
564    ("ivory2", (238, 238, 224)),
565    ("ivory3", (205, 205, 193)),
566    ("ivory4", (139, 139, 131)),
567    ("khaki", (240, 230, 140)),
568    ("khaki1", (255, 246, 143)),
569    ("khaki2", (238, 230, 133)),
570    ("khaki3", (205, 198, 115)),
571    ("khaki4", (139, 134, 78)),
572    ("lavender", (230, 230, 250)),
573    ("lavenderblush", (255, 240, 245)),
574    ("lavenderblush1", (255, 240, 245)),
575    ("lavenderblush2", (238, 224, 229)),
576    ("lavenderblush3", (205, 193, 197)),
577    ("lavenderblush4", (139, 131, 134)),
578    ("lawngreen", (124, 252, 0)),
579    ("lemonchiffon", (255, 250, 205)),
580    ("lemonchiffon1", (255, 250, 205)),
581    ("lemonchiffon2", (238, 233, 191)),
582    ("lemonchiffon3", (205, 201, 165)),
583    ("lemonchiffon4", (139, 137, 112)),
584    ("lightblue", (173, 216, 230)),
585    ("lightblue1", (191, 239, 255)),
586    ("lightblue2", (178, 223, 238)),
587    ("lightblue3", (154, 192, 205)),
588    ("lightblue4", (104, 131, 139)),
589    ("lightcoral", (240, 128, 128)),
590    ("lightcyan", (224, 255, 255)),
591    ("lightcyan1", (224, 255, 255)),
592    ("lightcyan2", (209, 238, 238)),
593    ("lightcyan3", (180, 205, 205)),
594    ("lightcyan4", (122, 139, 139)),
595    ("lightgoldenrod", (238, 221, 130)),
596    ("lightgoldenrod1", (255, 236, 139)),
597    ("lightgoldenrod2", (238, 220, 130)),
598    ("lightgoldenrod3", (205, 190, 112)),
599    ("lightgoldenrod4", (139, 129, 76)),
600    ("lightgoldenrodyellow", (250, 250, 210)),
601    ("lightgray", (211, 211, 211)),
602    ("lightgreen", (144, 238, 144)),
603    ("lightgrey", (211, 211, 211)),
604    ("lightpink", (255, 182, 193)),
605    ("lightpink1", (255, 174, 185)),
606    ("lightpink2", (238, 162, 173)),
607    ("lightpink3", (205, 140, 149)),
608    ("lightpink4", (139, 95, 101)),
609    ("lightsalmon", (255, 160, 122)),
610    ("lightsalmon1", (255, 160, 122)),
611    ("lightsalmon2", (238, 149, 114)),
612    ("lightsalmon3", (205, 129, 98)),
613    ("lightsalmon4", (139, 87, 66)),
614    ("lightseagreen", (32, 178, 170)),
615    ("lightskyblue", (135, 206, 250)),
616    ("lightskyblue1", (176, 226, 255)),
617    ("lightskyblue2", (164, 211, 238)),
618    ("lightskyblue3", (141, 182, 205)),
619    ("lightskyblue4", (96, 123, 139)),
620    ("lightslateblue", (132, 112, 255)),
621    ("lightslategray", (119, 136, 153)),
622    ("lightslategrey", (119, 136, 153)),
623    ("lightsteelblue", (176, 196, 222)),
624    ("lightsteelblue1", (202, 225, 255)),
625    ("lightsteelblue2", (188, 210, 238)),
626    ("lightsteelblue3", (162, 181, 205)),
627    ("lightsteelblue4", (110, 123, 139)),
628    ("lightyellow", (255, 255, 224)),
629    ("lightyellow1", (255, 255, 224)),
630    ("lightyellow2", (238, 238, 209)),
631    ("lightyellow3", (205, 205, 180)),
632    ("lightyellow4", (139, 139, 122)),
633    ("limegreen", (50, 205, 50)),
634    ("linen", (250, 240, 230)),
635    ("magenta", (255, 0, 255)),
636    ("magenta1", (255, 0, 255)),
637    ("magenta2", (238, 0, 238)),
638    ("magenta3", (205, 0, 205)),
639    ("magenta4", (139, 0, 139)),
640    ("maroon", (176, 48, 96)),
641    ("maroon1", (255, 52, 179)),
642    ("maroon2", (238, 48, 167)),
643    ("maroon3", (205, 41, 144)),
644    ("maroon4", (139, 28, 98)),
645    ("mediumaquamarine", (102, 205, 170)),
646    ("mediumblue", (0, 0, 205)),
647    ("mediumorchid", (186, 85, 211)),
648    ("mediumorchid1", (224, 102, 255)),
649    ("mediumorchid2", (209, 95, 238)),
650    ("mediumorchid3", (180, 82, 205)),
651    ("mediumorchid4", (122, 55, 139)),
652    ("mediumpurple", (147, 112, 219)),
653    ("mediumpurple1", (171, 130, 255)),
654    ("mediumpurple2", (159, 121, 238)),
655    ("mediumpurple3", (137, 104, 205)),
656    ("mediumpurple4", (93, 71, 139)),
657    ("mediumseagreen", (60, 179, 113)),
658    ("mediumslateblue", (123, 104, 238)),
659    ("mediumspringgreen", (0, 250, 154)),
660    ("mediumturquoise", (72, 209, 204)),
661    ("mediumvioletred", (199, 21, 133)),
662    ("midnightblue", (25, 25, 112)),
663    ("mintcream", (245, 255, 250)),
664    ("mistyrose", (255, 228, 225)),
665    ("mistyrose1", (255, 228, 225)),
666    ("mistyrose2", (238, 213, 210)),
667    ("mistyrose3", (205, 183, 181)),
668    ("mistyrose4", (139, 125, 123)),
669    ("moccasin", (255, 228, 181)),
670    ("navajowhite", (255, 222, 173)),
671    ("navajowhite1", (255, 222, 173)),
672    ("navajowhite2", (238, 207, 161)),
673    ("navajowhite3", (205, 179, 139)),
674    ("navajowhite4", (139, 121, 94)),
675    ("navy", (0, 0, 128)),
676    ("navyblue", (0, 0, 128)),
677    ("oldlace", (253, 245, 230)),
678    ("olivedrab", (107, 142, 35)),
679    ("olivedrab1", (192, 255, 62)),
680    ("olivedrab2", (179, 238, 58)),
681    ("olivedrab3", (154, 205, 50)),
682    ("olivedrab4", (105, 139, 34)),
683    ("orange", (255, 165, 0)),
684    ("orange1", (255, 165, 0)),
685    ("orange2", (238, 154, 0)),
686    ("orange3", (205, 133, 0)),
687    ("orange4", (139, 90, 0)),
688    ("orangered", (255, 69, 0)),
689    ("orangered1", (255, 69, 0)),
690    ("orangered2", (238, 64, 0)),
691    ("orangered3", (205, 55, 0)),
692    ("orangered4", (139, 37, 0)),
693    ("orchid", (218, 112, 214)),
694    ("orchid1", (255, 131, 250)),
695    ("orchid2", (238, 122, 233)),
696    ("orchid3", (205, 105, 201)),
697    ("orchid4", (139, 71, 137)),
698    ("palegoldenrod", (238, 232, 170)),
699    ("palegreen", (152, 251, 152)),
700    ("palegreen1", (154, 255, 154)),
701    ("palegreen2", (144, 238, 144)),
702    ("palegreen3", (124, 205, 124)),
703    ("palegreen4", (84, 139, 84)),
704    ("paleturquoise", (175, 238, 238)),
705    ("paleturquoise1", (187, 255, 255)),
706    ("paleturquoise2", (174, 238, 238)),
707    ("paleturquoise3", (150, 205, 205)),
708    ("paleturquoise4", (102, 139, 139)),
709    ("palevioletred", (219, 112, 147)),
710    ("palevioletred1", (255, 130, 171)),
711    ("palevioletred2", (238, 121, 159)),
712    ("palevioletred3", (205, 104, 137)),
713    ("palevioletred4", (139, 71, 93)),
714    ("papayawhip", (255, 239, 213)),
715    ("peachpuff", (255, 218, 185)),
716    ("peachpuff1", (255, 218, 185)),
717    ("peachpuff2", (238, 203, 173)),
718    ("peachpuff3", (205, 175, 149)),
719    ("peachpuff4", (139, 119, 101)),
720    ("peru", (205, 133, 63)),
721    ("pink", (255, 192, 203)),
722    ("pink1", (255, 181, 197)),
723    ("pink2", (238, 169, 184)),
724    ("pink3", (205, 145, 158)),
725    ("pink4", (139, 99, 108)),
726    ("plum", (221, 160, 221)),
727    ("plum1", (255, 187, 255)),
728    ("plum2", (238, 174, 238)),
729    ("plum3", (205, 150, 205)),
730    ("plum4", (139, 102, 139)),
731    ("powderblue", (176, 224, 230)),
732    ("purple", (160, 32, 240)),
733    ("purple1", (155, 48, 255)),
734    ("purple2", (145, 44, 238)),
735    ("purple3", (125, 38, 205)),
736    ("purple4", (85, 26, 139)),
737    ("red", (255, 0, 0)),
738    ("red1", (255, 0, 0)),
739    ("red2", (238, 0, 0)),
740    ("red3", (205, 0, 0)),
741    ("red4", (139, 0, 0)),
742    ("rosybrown", (188, 143, 143)),
743    ("rosybrown1", (255, 193, 193)),
744    ("rosybrown2", (238, 180, 180)),
745    ("rosybrown3", (205, 155, 155)),
746    ("rosybrown4", (139, 105, 105)),
747    ("royalblue", (65, 105, 225)),
748    ("royalblue1", (72, 118, 255)),
749    ("royalblue2", (67, 110, 238)),
750    ("royalblue3", (58, 95, 205)),
751    ("royalblue4", (39, 64, 139)),
752    ("saddlebrown", (139, 69, 19)),
753    ("salmon", (250, 128, 114)),
754    ("salmon1", (255, 140, 105)),
755    ("salmon2", (238, 130, 98)),
756    ("salmon3", (205, 112, 84)),
757    ("salmon4", (139, 76, 57)),
758    ("sandybrown", (244, 164, 96)),
759    ("seagreen", (46, 139, 87)),
760    ("seagreen1", (84, 255, 159)),
761    ("seagreen2", (78, 238, 148)),
762    ("seagreen3", (67, 205, 128)),
763    ("seagreen4", (46, 139, 87)),
764    ("seashell", (255, 245, 238)),
765    ("seashell1", (255, 245, 238)),
766    ("seashell2", (238, 229, 222)),
767    ("seashell3", (205, 197, 191)),
768    ("seashell4", (139, 134, 130)),
769    ("sienna", (160, 82, 45)),
770    ("sienna1", (255, 130, 71)),
771    ("sienna2", (238, 121, 66)),
772    ("sienna3", (205, 104, 57)),
773    ("sienna4", (139, 71, 38)),
774    ("skyblue", (135, 206, 235)),
775    ("skyblue1", (135, 206, 255)),
776    ("skyblue2", (126, 192, 238)),
777    ("skyblue3", (108, 166, 205)),
778    ("skyblue4", (74, 112, 139)),
779    ("slateblue", (106, 90, 205)),
780    ("slateblue1", (131, 111, 255)),
781    ("slateblue2", (122, 103, 238)),
782    ("slateblue3", (105, 89, 205)),
783    ("slateblue4", (71, 60, 139)),
784    ("slategray", (112, 128, 144)),
785    ("slategray1", (198, 226, 255)),
786    ("slategray2", (185, 211, 238)),
787    ("slategray3", (159, 182, 205)),
788    ("slategray4", (108, 123, 139)),
789    ("slategrey", (112, 128, 144)),
790    ("snow", (255, 250, 250)),
791    ("snow1", (255, 250, 250)),
792    ("snow2", (238, 233, 233)),
793    ("snow3", (205, 201, 201)),
794    ("snow4", (139, 137, 137)),
795    ("springgreen", (0, 255, 127)),
796    ("springgreen1", (0, 255, 127)),
797    ("springgreen2", (0, 238, 118)),
798    ("springgreen3", (0, 205, 102)),
799    ("springgreen4", (0, 139, 69)),
800    ("steelblue", (70, 130, 180)),
801    ("steelblue1", (99, 184, 255)),
802    ("steelblue2", (92, 172, 238)),
803    ("steelblue3", (79, 148, 205)),
804    ("steelblue4", (54, 100, 139)),
805    ("tan", (210, 180, 140)),
806    ("tan1", (255, 165, 79)),
807    ("tan2", (238, 154, 73)),
808    ("tan3", (205, 133, 63)),
809    ("tan4", (139, 90, 43)),
810    ("thistle", (216, 191, 216)),
811    ("thistle1", (255, 225, 255)),
812    ("thistle2", (238, 210, 238)),
813    ("thistle3", (205, 181, 205)),
814    ("thistle4", (139, 123, 139)),
815    ("tomato", (255, 99, 71)),
816    ("tomato1", (255, 99, 71)),
817    ("tomato2", (238, 92, 66)),
818    ("tomato3", (205, 79, 57)),
819    ("tomato4", (139, 54, 38)),
820    ("turquoise", (64, 224, 208)),
821    ("turquoise1", (0, 245, 255)),
822    ("turquoise2", (0, 229, 238)),
823    ("turquoise3", (0, 197, 205)),
824    ("turquoise4", (0, 134, 139)),
825    ("violet", (238, 130, 238)),
826    ("violetred", (208, 32, 144)),
827    ("violetred1", (255, 62, 150)),
828    ("violetred2", (238, 58, 140)),
829    ("violetred3", (205, 50, 120)),
830    ("violetred4", (139, 34, 82)),
831    ("wheat", (245, 222, 179)),
832    ("wheat1", (255, 231, 186)),
833    ("wheat2", (238, 216, 174)),
834    ("wheat3", (205, 186, 150)),
835    ("wheat4", (139, 126, 102)),
836    ("white", (255, 255, 255)),
837    ("whitesmoke", (245, 245, 245)),
838    ("yellow", (255, 255, 0)),
839    ("yellow1", (255, 255, 0)),
840    ("yellow2", (238, 238, 0)),
841    ("yellow3", (205, 205, 0)),
842    ("yellow4", (139, 139, 0)),
843    ("yellowgreen", (154, 205, 50)),
844];
845
846// endregion
847
848// region: Builtins
849
850/// Return a character vector of all 657 named color names.
851///
852/// Matches R's `colors()` / `colours()` output.
853///
854/// @return character vector of color names
855#[builtin(name = "colors", names = ["colours"], namespace = "grDevices")]
856fn builtin_colors(_args: &[RValue], _named: &[(String, RValue)]) -> Result<RValue, RError> {
857    let names: Vec<Option<String>> = NAMED_COLORS
858        .iter()
859        .map(|&(name, _)| Some(name.to_string()))
860        .collect();
861    Ok(RValue::vec(Vector::Character(names.into())))
862}
863
864/// Convert color specifications to RGB values.
865///
866/// Returns a 3-row (or 4-row if alpha=TRUE) integer matrix with columns
867/// for each input color.
868///
869/// @param col color specification(s) — names, hex strings, or palette indices
870/// @param alpha logical, whether to include the alpha channel (default FALSE)
871/// @return integer matrix with rownames "red", "green", "blue" (and "alpha")
872#[interpreter_builtin(namespace = "grDevices")]
873fn interp_col2rgb(
874    args: &[RValue],
875    named: &[(String, RValue)],
876    ctx: &BuiltinContext,
877) -> Result<RValue, RError> {
878    use crate::interpreter::builtins::CallArgs;
879    let call_args = CallArgs::new(args, named);
880
881    let col_val = call_args
882        .value("col", 0)
883        .ok_or_else(|| RError::new(RErrorKind::Argument, "'col' argument is required"))?;
884
885    let alpha = call_args.logical_flag("alpha", 1, false);
886
887    let palette = ctx.interpreter().color_palette.borrow();
888
889    // Collect color specs from the input
890    let color_specs: Vec<String> = match col_val {
891        RValue::Vector(rv) => match &rv.inner {
892            Vector::Character(chars) => chars
893                .iter()
894                .map(|s| s.clone().unwrap_or_else(|| "NA".to_string()))
895                .collect(),
896            Vector::Integer(ints) => ints
897                .iter_opt()
898                .map(|i| format!("{}", i.unwrap_or(0)))
899                .collect(),
900            Vector::Double(doubles) => doubles
901                .iter_opt()
902                .map(|d| format!("{}", d.map(|v| v as i64).unwrap_or(0)))
903                .collect(),
904            _ => {
905                return Err(RError::new(
906                    RErrorKind::Argument,
907                    "invalid color specification",
908                ))
909            }
910        },
911        RValue::Null => {
912            return Err(RError::new(
913                RErrorKind::Argument,
914                "'col' argument is required",
915            ))
916        }
917        _ => {
918            return Err(RError::new(
919                RErrorKind::Argument,
920                "invalid color specification",
921            ))
922        }
923    };
924
925    let n = color_specs.len();
926    let nrow = if alpha { 4 } else { 3 };
927
928    let mut data: Vec<Option<i64>> = Vec::with_capacity(nrow * n);
929
930    // R stores matrices column-major, so for each color (column) we push r,g,b[,a]
931    for spec in &color_specs {
932        if spec == "NA" {
933            for _ in 0..nrow {
934                data.push(None);
935            }
936        } else {
937            let color = RColor::from_r_value(spec, &palette)?;
938            data.push(Some(i64::from(color.r)));
939            data.push(Some(i64::from(color.g)));
940            data.push(Some(i64::from(color.b)));
941            if alpha {
942                data.push(Some(i64::from(color.a)));
943            }
944        }
945    }
946
947    let mut rv = RVector::from(Vector::Integer(data.into()));
948
949    // Set dim attribute: nrow x n
950    rv.set_attr(
951        "dim".to_string(),
952        RValue::vec(Vector::Integer(
953            vec![
954                Some(i64::try_from(nrow).unwrap_or(3)),
955                Some(i64::try_from(n).unwrap_or(0)),
956            ]
957            .into(),
958        )),
959    );
960
961    // Set rownames
962    let row_names = if alpha {
963        vec![
964            Some("red".to_string()),
965            Some("green".to_string()),
966            Some("blue".to_string()),
967            Some("alpha".to_string()),
968        ]
969    } else {
970        vec![
971            Some("red".to_string()),
972            Some("green".to_string()),
973            Some("blue".to_string()),
974        ]
975    };
976
977    // Set dimnames as a list of (rownames, colnames)
978    let colnames: Vec<Option<String>> = color_specs.iter().map(|s| Some(s.clone())).collect();
979    let dimnames = RList::new(vec![
980        (None, RValue::vec(Vector::Character(row_names.into()))),
981        (
982            None,
983            if colnames.is_empty() {
984                RValue::Null
985            } else {
986                RValue::vec(Vector::Character(colnames.into()))
987            },
988        ),
989    ]);
990    rv.set_attr("dimnames".to_string(), RValue::List(dimnames));
991
992    Ok(RValue::Vector(rv))
993}
994
995/// Create hex color strings from RGB(A) values.
996///
997/// @param red red component(s), 0–1 by default (or 0–maxColorValue)
998/// @param green green component(s)
999/// @param blue blue component(s)
1000/// @param alpha alpha component(s), default 1 (fully opaque)
1001/// @param maxColorValue maximum value for components, default 1
1002/// @return character vector of "#RRGGBB" or "#RRGGBBAA" hex strings
1003#[builtin(namespace = "grDevices")]
1004fn builtin_rgb(args: &[RValue], named: &[(String, RValue)]) -> Result<RValue, RError> {
1005    use crate::interpreter::builtins::CallArgs;
1006    let call_args = CallArgs::new(args, named);
1007
1008    let max_val = call_args
1009        .value("maxColorValue", 4)
1010        .and_then(|v: &RValue| v.as_vector()?.as_double_scalar())
1011        .unwrap_or(1.0);
1012
1013    if max_val <= 0.0 {
1014        return Err(RError::new(
1015            RErrorKind::Other,
1016            "'maxColorValue' must be positive",
1017        ));
1018    }
1019
1020    // Get the red/green/blue/alpha vectors
1021    let reds = get_double_vec(call_args.value("red", 0), "red")?;
1022    let greens = get_double_vec(call_args.value("green", 1), "green")?;
1023    let blues = get_double_vec(call_args.value("blue", 2), "blue")?;
1024    let alphas = match call_args.value("alpha", 3) {
1025        Some(v) => get_double_vec(Some(v), "alpha")?,
1026        None => vec![max_val], // default to fully opaque
1027    };
1028
1029    // Check if names= is provided
1030    let names = call_args.value("names", 5);
1031
1032    // Recycle to max length
1033    let n = reds
1034        .len()
1035        .max(greens.len())
1036        .max(blues.len())
1037        .max(alphas.len());
1038
1039    let has_alpha_arg = call_args.value("alpha", 3).is_some();
1040
1041    let mut result: Vec<Option<String>> = Vec::with_capacity(n);
1042    for i in 0..n {
1043        let r = reds[i % reds.len()];
1044        let g = greens[i % greens.len()];
1045        let b = blues[i % blues.len()];
1046        let a = alphas[i % alphas.len()];
1047
1048        let r8 = clamp_to_u8(r / max_val * 255.0);
1049        let g8 = clamp_to_u8(g / max_val * 255.0);
1050        let b8 = clamp_to_u8(b / max_val * 255.0);
1051        let a8 = clamp_to_u8(a / max_val * 255.0);
1052
1053        if has_alpha_arg || a8 != 255 {
1054            result.push(Some(format!("#{:02X}{:02X}{:02X}{:02X}", r8, g8, b8, a8)));
1055        } else {
1056            result.push(Some(format!("#{:02X}{:02X}{:02X}", r8, g8, b8)));
1057        }
1058    }
1059
1060    let mut rv = RVector::from(Vector::Character(result.into()));
1061
1062    // Set names if provided
1063    if let Some(RValue::Vector(nv)) = names {
1064        if let Vector::Character(names_chars) = &nv.inner {
1065            rv.set_attr(
1066                "names".to_string(),
1067                RValue::vec(Vector::Character(names_chars.clone())),
1068            );
1069        }
1070    }
1071
1072    Ok(RValue::Vector(rv))
1073}
1074
1075/// Get or set the color palette.
1076///
1077/// With no argument, returns the current palette as a character vector.
1078/// With a character vector argument, sets the palette and returns the old one.
1079/// With `"default"`, resets to R's default palette.
1080///
1081/// @param value optional new palette (character vector of color specs, or "default")
1082/// @return character vector of the (previous) palette
1083#[interpreter_builtin(namespace = "grDevices")]
1084fn interp_palette(
1085    args: &[RValue],
1086    _named: &[(String, RValue)],
1087    ctx: &BuiltinContext,
1088) -> Result<RValue, RError> {
1089    let old_palette = ctx.interpreter().color_palette.borrow().clone();
1090    let old_hex: Vec<Option<String>> = old_palette.iter().map(|c| Some(c.to_hex())).collect();
1091
1092    if let Some(val) = args.first() {
1093        match val {
1094            RValue::Null => {
1095                // Return current palette without changing
1096            }
1097            RValue::Vector(rv) => match &rv.inner {
1098                Vector::Character(chars) => {
1099                    // Check for "default" — single string
1100                    if chars.len() == 1 {
1101                        if let Some(Some(s)) = chars.first() {
1102                            if s == "default" {
1103                                *ctx.interpreter().color_palette.borrow_mut() =
1104                                    DEFAULT_PALETTE.to_vec();
1105                                return Ok(RValue::vec(Vector::Character(old_hex.into())));
1106                            }
1107                        }
1108                    }
1109                    // Parse each color spec
1110                    let mut new_palette = Vec::with_capacity(chars.len());
1111                    let current = ctx.interpreter().color_palette.borrow().clone();
1112                    for spec in chars.iter() {
1113                        match spec {
1114                            Some(s) => new_palette.push(RColor::from_r_value(s, &current)?),
1115                            None => {
1116                                return Err(RError::new(
1117                                    RErrorKind::Other,
1118                                    "NA is not a valid color specification",
1119                                ))
1120                            }
1121                        }
1122                    }
1123                    *ctx.interpreter().color_palette.borrow_mut() = new_palette;
1124                }
1125                _ => {
1126                    return Err(RError::new(
1127                        RErrorKind::Argument,
1128                        "palette() requires a character vector",
1129                    ))
1130                }
1131            },
1132            _ => {
1133                return Err(RError::new(
1134                    RErrorKind::Argument,
1135                    "palette() requires a character vector",
1136                ))
1137            }
1138        }
1139    }
1140
1141    Ok(RValue::vec(Vector::Character(old_hex.into())))
1142}
1143
1144// endregion
1145
1146// region: Helpers
1147
1148/// Extract a vector of f64 from an RValue, for use in rgb().
1149fn get_double_vec(val: Option<&RValue>, name: &str) -> Result<Vec<f64>, RError> {
1150    match val {
1151        Some(RValue::Vector(rv)) => {
1152            let doubles = rv.inner.to_doubles();
1153            let result: Vec<f64> = doubles
1154                .iter()
1155                .map(|d| {
1156                    d.ok_or_else(|| {
1157                        RError::new(
1158                            RErrorKind::Other,
1159                            format!("NA value in '{name}' is not allowed"),
1160                        )
1161                    })
1162                })
1163                .collect::<Result<Vec<_>, _>>()?;
1164            Ok(result)
1165        }
1166        Some(RValue::Null) | None => Err(RError::new(
1167            RErrorKind::Argument,
1168            format!("argument '{name}' is missing, with no default"),
1169        )),
1170        _ => Err(RError::new(
1171            RErrorKind::Argument,
1172            format!("invalid '{name}' argument"),
1173        )),
1174    }
1175}
1176
1177/// Clamp a floating-point value to 0–255 and round to nearest u8.
1178fn clamp_to_u8(val: f64) -> u8 {
1179    if val <= 0.0 {
1180        0
1181    } else if val >= 255.0 {
1182        255
1183    } else {
1184        val.round() as u8
1185    }
1186}
1187
1188// endregion