1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12zng_wgt::enable_widget_macros!();
13
14use zng_ext_font::*;
15use zng_wgt::{prelude::*, *};
16use zng_wgt_fill::*;
17use zng_wgt_filter::*;
18use zng_wgt_input::{CursorIcon, cursor};
19use zng_wgt_scroll::{LazyMode, lazy};
20use zng_wgt_stack::{Stack, StackDirection};
21use zng_wgt_text::*;
22
23#[doc(hidden)]
24pub use zng_wgt_text::__formatx;
25
26#[widget($crate::AnsiText {
30 ($txt:literal) => {
31 txt = $crate::__formatx!($txt);
32 };
33 ($txt:expr) => {
34 txt = $txt;
35 };
36 ($txt:tt, $($format:tt)*) => {
37 txt = $crate::__formatx!($txt, $($format)*);
38 };
39})]
40#[rustfmt::skip]
41pub struct AnsiText(
42 FontMix<
43 TextSpacingMix<
44 ParagraphMix<
45 LangMix<
46 WidgetBase
47 >>>>
48);
49impl AnsiText {
50 fn widget_intrinsic(&mut self) {
51 widget_set! {
52 self;
53 font_family = ["JetBrains Mono", "Consolas", "monospace"];
54 rich_text = true;
55
56 when #txt_selectable {
57 cursor = CursorIcon::Text;
58 }
59 };
60
61 self.widget_builder().push_build_action(|wgt| {
62 let txt = wgt.capture_var_or_default(property_id!(txt));
63 let child = ansi_node(txt);
64 wgt.set_child(child);
65 });
66 }
67
68 widget_impl! {
69 pub txt(text: impl IntoVar<Txt>);
71
72 pub zng_wgt_text::txt_selectable(enabled: impl IntoVar<bool>);
76 }
77}
78
79pub use ansi_parse::*;
80mod ansi_parse {
81
82 use super::*;
83
84 #[derive(Debug)]
86 #[non_exhaustive]
87 pub struct AnsiTxt<'a> {
88 pub txt: &'a str,
90 pub style: AnsiStyle,
92 }
93
94 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
98 #[non_exhaustive]
99 pub struct AnsiStyle {
100 pub background_color: AnsiColor,
102 pub color: AnsiColor,
104 pub weight: AnsiWeight,
106 pub italic: bool,
108 pub underline: bool,
110 pub strikethrough: bool,
112 pub invert_color: bool,
114 pub hidden: bool,
116 pub blink: bool,
118 }
119 impl Default for AnsiStyle {
120 fn default() -> Self {
121 Self {
122 background_color: AnsiColor::Black,
123 color: AnsiColor::White,
124 weight: Default::default(),
125 italic: false,
126 underline: false,
127 strikethrough: false,
128 invert_color: false,
129 hidden: false,
130 blink: false,
131 }
132 }
133 }
134
135 #[allow(missing_docs)]
139 #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
140 pub enum AnsiColor {
141 Black,
142 Red,
143 Green,
144 Yellow,
145 Blue,
146 Magenta,
147 Cyan,
148 White,
149 BrightBlack,
151 BrightRed,
152 BrightGreen,
153 BrightYellow,
154 BrightBlue,
155 BrightMagenta,
156 BrightCyan,
157 BrightWhite,
158 Ansi256(u8),
160 TrueColor(u8, u8, u8),
162 }
163 impl_from_and_into_var! {
164 fn from(color: AnsiColor) -> Rgba {
165 match color {
166 AnsiColor::Black => rgb(0, 0, 0),
167 AnsiColor::Red => rgb(205, 49, 49),
168 AnsiColor::Green => rgb(13, 188, 121),
169 AnsiColor::Yellow => rgb(229, 229, 16),
170 AnsiColor::Blue => rgb(36, 114, 200),
171 AnsiColor::Magenta => rgb(188, 63, 188),
172 AnsiColor::Cyan => rgb(17, 168, 205),
173 AnsiColor::White => rgb(229, 229, 229),
174 AnsiColor::BrightBlack => rgb(102, 102, 102),
175 AnsiColor::BrightRed => rgb(241, 76, 76),
176 AnsiColor::BrightGreen => rgb(35, 209, 139),
177 AnsiColor::BrightYellow => rgb(245, 245, 67),
178 AnsiColor::BrightBlue => rgb(59, 142, 234),
179 AnsiColor::BrightMagenta => rgb(214, 112, 214),
180 AnsiColor::BrightCyan => rgb(41, 184, 219),
181 AnsiColor::BrightWhite => rgb(229, 229, 229),
182 AnsiColor::Ansi256(c) => {
183 let (r, g, b) = X_TERM_256[c as usize];
184 rgb(r, g, b)
185 }
186 AnsiColor::TrueColor(r, g, b) => rgb(r, g, b),
187 }
188 }
189 }
190
191 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
195 pub enum AnsiWeight {
196 #[default]
198 Normal,
199 Bold,
201 Faint,
203 }
204 impl_from_and_into_var! {
205 fn from(weight: AnsiWeight) -> FontWeight {
206 match weight {
207 AnsiWeight::Normal => FontWeight::NORMAL,
208 AnsiWeight::Bold => FontWeight::BOLD,
209 AnsiWeight::Faint => FontWeight::LIGHT,
210 }
211 }
212 }
213
214 pub struct AnsiTextParser<'a> {
220 source: &'a str,
221 pub style: AnsiStyle,
223 }
224 impl<'a> AnsiTextParser<'a> {
225 pub fn new(source: &'a str) -> Self {
227 Self {
228 source,
229 style: AnsiStyle::default(),
230 }
231 }
232 }
233 impl<'a> Iterator for AnsiTextParser<'a> {
234 type Item = AnsiTxt<'a>;
235
236 fn next(&mut self) -> Option<Self::Item> {
237 const CSI: &str = "\x1b[";
238
239 fn is_esc_end(byte: u8) -> bool {
240 (0x40..=0x7e).contains(&byte)
241 }
242
243 loop {
244 if self.source.is_empty() {
245 return None;
246 } else if let Some(source) = self.source.strip_prefix(CSI) {
247 let mut esc_end = 0;
248 while esc_end < source.len() && !is_esc_end(source.as_bytes()[esc_end]) {
249 esc_end += 1;
250 }
251 esc_end += 1;
252
253 let (esc, source) = source.split_at(esc_end);
254
255 let esc = &esc[..(esc.len() - 1)];
256 self.style.set(esc);
257
258 self.source = source;
259 continue;
260 } else if let Some(i) = self.source.find(CSI) {
261 let (txt, source) = self.source.split_at(i);
262 self.source = source;
263 return Some(AnsiTxt {
264 txt,
265 style: self.style.clone(),
266 });
267 } else {
268 return Some(AnsiTxt {
269 txt: std::mem::take(&mut self.source),
270 style: self.style.clone(),
271 });
272 }
273 }
274 }
275 }
276
277 impl AnsiStyle {
278 fn set(&mut self, esc_codes: &str) {
279 let mut esc_codes = esc_codes.split(';');
280 while let Some(code) = esc_codes.next() {
281 match code {
282 "0" => *self = Self::default(),
283 "1" => self.weight = AnsiWeight::Bold,
284 "2" => self.weight = AnsiWeight::Faint,
285 "3" => self.italic = true,
286 "4" => self.underline = true,
287 "5" => self.blink = true,
288 "7" => self.invert_color = true,
289 "8" => self.hidden = true,
290 "9" => self.strikethrough = true,
291 "22" => self.weight = AnsiWeight::Normal,
292 "23" => self.italic = false,
293 "24" => self.underline = false,
294 "25" => self.blink = false,
295 "27" => self.invert_color = false,
296 "28" => self.hidden = false,
297 "29" => self.strikethrough = false,
298 "30" => self.color = AnsiColor::Black,
299 "31" => self.color = AnsiColor::Red,
300 "32" => self.color = AnsiColor::Green,
301 "33" => self.color = AnsiColor::Yellow,
302 "34" => self.color = AnsiColor::Blue,
303 "35" => self.color = AnsiColor::Magenta,
304 "36" => self.color = AnsiColor::Cyan,
305 "37" => self.color = AnsiColor::White,
306 "40" => self.color = AnsiColor::Black,
307 "41" => self.color = AnsiColor::Red,
308 "42" => self.color = AnsiColor::Green,
309 "43" => self.color = AnsiColor::Yellow,
310 "44" => self.color = AnsiColor::Blue,
311 "45" => self.color = AnsiColor::Magenta,
312 "46" => self.color = AnsiColor::Cyan,
313 "47" => self.color = AnsiColor::White,
314 "90" => self.color = AnsiColor::BrightBlack,
315 "91" => self.color = AnsiColor::BrightRed,
316 "92" => self.color = AnsiColor::BrightGreen,
317 "93" => self.color = AnsiColor::BrightYellow,
318 "94" => self.color = AnsiColor::BrightBlue,
319 "95" => self.color = AnsiColor::BrightMagenta,
320 "96" => self.color = AnsiColor::BrightCyan,
321 "97" => self.color = AnsiColor::BrightWhite,
322 "100" => self.background_color = AnsiColor::BrightBlack,
323 "101" => self.background_color = AnsiColor::BrightRed,
324 "102" => self.background_color = AnsiColor::BrightGreen,
325 "103" => self.background_color = AnsiColor::BrightYellow,
326 "104" => self.background_color = AnsiColor::BrightBlue,
327 "105" => self.background_color = AnsiColor::BrightMagenta,
328 "106" => self.background_color = AnsiColor::BrightCyan,
329 "107" => self.background_color = AnsiColor::BrightWhite,
330 "38" | "48" => {
331 let target = if code == "38" {
332 &mut self.color
333 } else {
334 &mut self.background_color
335 };
336 match esc_codes.next() {
337 Some("5") => {
338 let c = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
339 *target = AnsiColor::Ansi256(c)
340 }
341 Some("2") => {
342 let r = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
343 let g = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
344 let b = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
345
346 *target = AnsiColor::TrueColor(r, g, b);
347 }
348 _ => {}
349 }
350 }
351 _ => (),
352 }
353 }
354 }
355 }
356}
357
358pub use ansi_fn::*;
359mod ansi_fn {
360 use std::time::Duration;
361
362 use super::{AnsiColor, AnsiStyle, AnsiWeight};
363
364 use super::*;
365
366 #[non_exhaustive]
370 pub struct TextFnArgs {
371 pub txt: Txt,
373 pub style: AnsiStyle,
375 }
376 impl TextFnArgs {
377 pub fn new(txt: impl Into<Txt>, style: AnsiStyle) -> Self {
379 Self { txt: txt.into(), style }
380 }
381 }
382
383 #[non_exhaustive]
387 pub struct LineFnArgs {
388 pub index: u32,
390 pub page_index: u32,
392 pub text: UiVec,
394 }
395
396 impl LineFnArgs {
397 pub fn new(index: u32, page_index: u32, text: UiVec) -> Self {
399 Self { index, page_index, text }
400 }
401 }
402
403 #[non_exhaustive]
407 pub struct PageFnArgs {
408 pub index: u32,
410
411 pub lines: UiVec,
413 }
414
415 impl PageFnArgs {
416 pub fn new(index: u32, lines: UiVec) -> Self {
418 Self { index, lines }
419 }
420 }
421
422 #[non_exhaustive]
426 pub struct PanelFnArgs {
427 pub pages: UiVec,
429 }
430
431 impl PanelFnArgs {
432 pub fn new(pages: UiVec) -> Self {
434 Self { pages }
435 }
436 }
437
438 context_var! {
439 pub static TEXT_FN_VAR: WidgetFn<TextFnArgs> = wgt_fn!(|args: TextFnArgs| { default_text_fn(args) });
443
444 pub static LINE_FN_VAR: WidgetFn<LineFnArgs> = wgt_fn!(|args: LineFnArgs| { default_line_fn(args) });
448
449 pub static PAGE_FN_VAR: WidgetFn<PageFnArgs> = wgt_fn!(|args: PageFnArgs| { default_page_fn(args) });
453
454 pub static PANEL_FN_VAR: WidgetFn<PanelFnArgs> = wgt_fn!(|args: PanelFnArgs| { default_panel_fn(args) });
460
461 pub static BLINK_INTERVAL_VAR: Duration = Duration::ZERO;
465
466 pub static LINES_PER_PAGE_VAR: u32 = 200;
470 }
471
472 pub fn default_text_fn(args: TextFnArgs) -> UiNode {
479 let mut text = Text::widget_new();
480
481 widget_set! {
482 &mut text;
483 txt = args.txt;
484 }
485
486 if args.style.background_color != AnsiColor::Black {
487 widget_set! {
488 &mut text;
489 background_color = args.style.background_color;
490 }
491 }
492 if args.style.color != AnsiColor::White {
493 widget_set! {
494 &mut text;
495 font_color = args.style.color;
496 }
497 }
498
499 if args.style.weight != AnsiWeight::Normal {
500 widget_set! {
501 &mut text;
502 font_weight = args.style.weight;
503 }
504 }
505 if args.style.italic {
506 widget_set! {
507 &mut text;
508 font_style = FontStyle::Italic;
509 }
510 }
511
512 if args.style.underline {
513 widget_set! {
514 &mut text;
515 underline = 1, LineStyle::Solid;
516 }
517 }
518 if args.style.strikethrough {
519 widget_set! {
520 &mut text;
521 strikethrough = 1, LineStyle::Solid;
522 }
523 }
524
525 if args.style.invert_color {
526 widget_set! {
527 &mut text;
528 invert_color = true;
529 }
530 }
531
532 if args.style.hidden {
533 widget_set! {
534 &mut text;
535 visibility = Visibility::Hidden;
536 }
537 }
538 if args.style.blink && !args.style.hidden {
539 let opacity = var(1.fct());
540
541 let interval = BLINK_INTERVAL_VAR.get();
542 if interval != Duration::ZERO && interval != Duration::MAX {
543 opacity.step_oci(0.fct(), interval).perm();
544
545 widget_set! {
546 &mut text;
547 opacity;
548 }
549 }
550 }
551
552 text.widget_build()
553 }
554
555 pub fn default_line_fn(mut args: LineFnArgs) -> UiNode {
559 if args.text.is_empty() {
560 Text!("")
561 } else if args.text.len() == 1 {
562 args.text.remove(0)
563 } else {
564 Stack! {
565 rich_text = true;
566 direction = StackDirection::start_to_end();
567 children = args.text;
568 }
569 }
570 }
571
572 pub fn default_page_fn(mut args: PageFnArgs) -> UiNode {
576 use crate::prelude::*;
577
578 if args.lines.is_empty() {
579 UiNode::nil()
580 } else if args.lines.len() == 1 {
581 args.lines.remove(0)
582 } else {
583 let len = args.lines.len();
584 Stack! {
585 rich_text = true;
586 direction = StackDirection::top_to_bottom();
587 children = args.lines;
588 lazy = LazyMode::lazy_vertical(wgt_fn!(|_| {
589 let height_sample = zng_wgt_text::node::line_placeholder(50);
590 zng_wgt_stack::lazy_sample(len, StackDirection::top_to_bottom(), 0, height_sample)
591 }));
592 }
593 }
594 }
595
596 pub fn default_panel_fn(mut args: PanelFnArgs) -> UiNode {
600 use crate::prelude::*;
601
602 if args.pages.is_empty() {
603 UiNode::nil()
604 } else if args.pages.len() == 1 {
605 args.pages.remove(0)
606 } else {
607 Stack! {
608 rich_text = true;
609 direction = StackDirection::top_to_bottom();
610 children = args.pages;
611 }
612 }
613 }
614
615 #[property(CONTEXT, default(BLINK_INTERVAL_VAR), widget_impl(AnsiText))]
621 pub fn blink_interval(child: impl IntoUiNode, interval: impl IntoVar<Duration>) -> UiNode {
622 with_context_var(child, BLINK_INTERVAL_VAR, interval)
623 }
624
625 #[property(CONTEXT, default(TEXT_FN_VAR), widget_impl(AnsiText))]
629 pub fn text_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<TextFnArgs>>) -> UiNode {
630 with_context_var(child, TEXT_FN_VAR, wgt_fn)
631 }
632
633 #[property(CONTEXT, default(LINE_FN_VAR), widget_impl(AnsiText))]
637 pub fn line_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<LineFnArgs>>) -> UiNode {
638 with_context_var(child, LINE_FN_VAR, wgt_fn)
639 }
640
641 #[property(CONTEXT, default(PAGE_FN_VAR), widget_impl(AnsiText))]
649 pub fn page_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<PageFnArgs>>) -> UiNode {
650 with_context_var(child, PAGE_FN_VAR, wgt_fn)
651 }
652
653 #[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(AnsiText))]
655 pub fn panel_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<PanelFnArgs>>) -> UiNode {
656 with_context_var(child, PANEL_FN_VAR, wgt_fn)
657 }
658
659 #[property(CONTEXT, default(LINES_PER_PAGE_VAR), widget_impl(AnsiText))]
663 pub fn lines_per_page(child: impl IntoUiNode, count: impl IntoVar<u32>) -> UiNode {
664 with_context_var(child, LINES_PER_PAGE_VAR, count)
665 }
666}
667
668fn generate_ansi(txt: &Var<Txt>) -> UiNode {
669 use ansi_fn::*;
670 use std::mem;
671
672 txt.with(|txt| {
673 let text_fn = TEXT_FN_VAR.get();
674 let line_fn = LINE_FN_VAR.get();
675 let page_fn = PAGE_FN_VAR.get();
676 let panel_fn = PANEL_FN_VAR.get();
677 let lines_per_page = LINES_PER_PAGE_VAR.get() as usize;
678
679 let mut pages = Vec::with_capacity(4);
680 let mut lines = Vec::with_capacity(50);
681
682 for (i, line) in txt.lines().enumerate() {
683 let text = ansi_parse::AnsiTextParser::new(line)
684 .filter_map(|txt| {
685 text_fn.call_checked(TextFnArgs {
686 txt: txt.txt.to_txt(),
687 style: txt.style,
688 })
689 })
690 .collect();
691
692 lines.push(line_fn(LineFnArgs {
693 index: i as u32,
694 page_index: lines.len() as u32,
695 text,
696 }));
697
698 if lines.len() == lines_per_page {
699 let lines = mem::replace(&mut lines, Vec::with_capacity(50));
700 pages.push(page_fn(PageFnArgs {
701 index: pages.len() as u32,
702 lines: lines.into(),
703 }));
704 }
705 }
706
707 if !lines.is_empty() {
708 pages.push(page_fn(PageFnArgs {
709 index: pages.len() as u32,
710 lines: lines.into(),
711 }));
712 }
713
714 panel_fn(PanelFnArgs { pages: pages.into() })
715 })
716}
717
718pub fn ansi_node(txt: impl IntoVar<Txt>) -> UiNode {
720 let txt = txt.into_var();
721 match_node(UiNode::nil(), move |c, op| match op {
722 UiNodeOp::Init => {
723 WIDGET
724 .sub_var(&txt)
725 .sub_var(&TEXT_FN_VAR)
726 .sub_var(&LINE_FN_VAR)
727 .sub_var(&PAGE_FN_VAR)
728 .sub_var(&PANEL_FN_VAR)
729 .sub_var(&LINES_PER_PAGE_VAR)
730 .sub_var(&BLINK_INTERVAL_VAR);
731
732 *c.node() = generate_ansi(&txt);
733 }
734 UiNodeOp::Deinit => {
735 c.deinit();
736 *c.node() = UiNode::nil();
737 }
738 UiNodeOp::Update { .. } => {
739 use ansi_fn::*;
740
741 if txt.is_new()
742 || TEXT_FN_VAR.is_new()
743 || LINE_FN_VAR.is_new()
744 || PAGE_FN_VAR.is_new()
745 || PANEL_FN_VAR.is_new()
746 || LINES_PER_PAGE_VAR.is_new()
747 || BLINK_INTERVAL_VAR.is_new()
748 {
749 c.node().deinit();
750 *c.node() = generate_ansi(&txt);
751 c.node().init();
752 WIDGET.update_info().layout().render();
753 }
754 }
755 _ => {}
756 })
757}
758
759static X_TERM_256: [(u8, u8, u8); 256] = [
760 (0, 0, 0),
761 (128, 0, 0),
762 (0, 128, 0),
763 (128, 128, 0),
764 (0, 0, 128),
765 (128, 0, 128),
766 (0, 128, 128),
767 (192, 192, 192),
768 (128, 128, 128),
769 (255, 0, 0),
770 (0, 255, 0),
771 (255, 255, 0),
772 (0, 0, 255),
773 (255, 0, 255),
774 (0, 255, 255),
775 (255, 255, 255),
776 (0, 0, 0),
777 (0, 0, 95),
778 (0, 0, 135),
779 (0, 0, 175),
780 (0, 0, 215),
781 (0, 0, 255),
782 (0, 95, 0),
783 (0, 95, 95),
784 (0, 95, 135),
785 (0, 95, 175),
786 (0, 95, 215),
787 (0, 95, 255),
788 (0, 135, 0),
789 (0, 135, 95),
790 (0, 135, 135),
791 (0, 135, 175),
792 (0, 135, 215),
793 (0, 135, 255),
794 (0, 175, 0),
795 (0, 175, 95),
796 (0, 175, 135),
797 (0, 175, 175),
798 (0, 175, 215),
799 (0, 175, 255),
800 (0, 215, 0),
801 (0, 215, 95),
802 (0, 215, 135),
803 (0, 215, 175),
804 (0, 215, 215),
805 (0, 215, 255),
806 (0, 255, 0),
807 (0, 255, 95),
808 (0, 255, 135),
809 (0, 255, 175),
810 (0, 255, 215),
811 (0, 255, 255),
812 (95, 0, 0),
813 (95, 0, 95),
814 (95, 0, 135),
815 (95, 0, 175),
816 (95, 0, 215),
817 (95, 0, 255),
818 (95, 95, 0),
819 (95, 95, 95),
820 (95, 95, 135),
821 (95, 95, 175),
822 (95, 95, 215),
823 (95, 95, 255),
824 (95, 135, 0),
825 (95, 135, 95),
826 (95, 135, 135),
827 (95, 135, 175),
828 (95, 135, 215),
829 (95, 135, 255),
830 (95, 175, 0),
831 (95, 175, 95),
832 (95, 175, 135),
833 (95, 175, 175),
834 (95, 175, 215),
835 (95, 175, 255),
836 (95, 215, 0),
837 (95, 215, 95),
838 (95, 215, 135),
839 (95, 215, 175),
840 (95, 215, 215),
841 (95, 215, 255),
842 (95, 255, 0),
843 (95, 255, 95),
844 (95, 255, 135),
845 (95, 255, 175),
846 (95, 255, 215),
847 (95, 255, 255),
848 (135, 0, 0),
849 (135, 0, 95),
850 (135, 0, 135),
851 (135, 0, 175),
852 (135, 0, 215),
853 (135, 0, 255),
854 (135, 95, 0),
855 (135, 95, 95),
856 (135, 95, 135),
857 (135, 95, 175),
858 (135, 95, 215),
859 (135, 95, 255),
860 (135, 135, 0),
861 (135, 135, 95),
862 (135, 135, 135),
863 (135, 135, 175),
864 (135, 135, 215),
865 (135, 135, 255),
866 (135, 175, 0),
867 (135, 175, 95),
868 (135, 175, 135),
869 (135, 175, 175),
870 (135, 175, 215),
871 (135, 175, 255),
872 (135, 215, 0),
873 (135, 215, 95),
874 (135, 215, 135),
875 (135, 215, 175),
876 (135, 215, 215),
877 (135, 215, 255),
878 (135, 255, 0),
879 (135, 255, 95),
880 (135, 255, 135),
881 (135, 255, 175),
882 (135, 255, 215),
883 (135, 255, 255),
884 (175, 0, 0),
885 (175, 0, 95),
886 (175, 0, 135),
887 (175, 0, 175),
888 (175, 0, 215),
889 (175, 0, 255),
890 (175, 95, 0),
891 (175, 95, 95),
892 (175, 95, 135),
893 (175, 95, 175),
894 (175, 95, 215),
895 (175, 95, 255),
896 (175, 135, 0),
897 (175, 135, 95),
898 (175, 135, 135),
899 (175, 135, 175),
900 (175, 135, 215),
901 (175, 135, 255),
902 (175, 175, 0),
903 (175, 175, 95),
904 (175, 175, 135),
905 (175, 175, 175),
906 (175, 175, 215),
907 (175, 175, 255),
908 (175, 215, 0),
909 (175, 215, 95),
910 (175, 215, 135),
911 (175, 215, 175),
912 (175, 215, 215),
913 (175, 215, 255),
914 (175, 255, 0),
915 (175, 255, 95),
916 (175, 255, 135),
917 (175, 255, 175),
918 (175, 255, 215),
919 (175, 255, 255),
920 (215, 0, 0),
921 (215, 0, 95),
922 (215, 0, 135),
923 (215, 0, 175),
924 (215, 0, 215),
925 (215, 0, 255),
926 (215, 95, 0),
927 (215, 95, 95),
928 (215, 95, 135),
929 (215, 95, 175),
930 (215, 95, 215),
931 (215, 95, 255),
932 (215, 135, 0),
933 (215, 135, 95),
934 (215, 135, 135),
935 (215, 135, 175),
936 (215, 135, 215),
937 (215, 135, 255),
938 (215, 175, 0),
939 (215, 175, 95),
940 (215, 175, 135),
941 (215, 175, 175),
942 (215, 175, 215),
943 (215, 175, 255),
944 (215, 215, 0),
945 (215, 215, 95),
946 (215, 215, 135),
947 (215, 215, 175),
948 (215, 215, 215),
949 (215, 215, 255),
950 (215, 255, 0),
951 (215, 255, 95),
952 (215, 255, 135),
953 (215, 255, 175),
954 (215, 255, 215),
955 (215, 255, 255),
956 (255, 0, 0),
957 (255, 0, 95),
958 (255, 0, 135),
959 (255, 0, 175),
960 (255, 0, 215),
961 (255, 0, 255),
962 (255, 95, 0),
963 (255, 95, 95),
964 (255, 95, 135),
965 (255, 95, 175),
966 (255, 95, 215),
967 (255, 95, 255),
968 (255, 135, 0),
969 (255, 135, 95),
970 (255, 135, 135),
971 (255, 135, 175),
972 (255, 135, 215),
973 (255, 135, 255),
974 (255, 175, 0),
975 (255, 175, 95),
976 (255, 175, 135),
977 (255, 175, 175),
978 (255, 175, 215),
979 (255, 175, 255),
980 (255, 215, 0),
981 (255, 215, 95),
982 (255, 215, 135),
983 (255, 215, 175),
984 (255, 215, 215),
985 (255, 215, 255),
986 (255, 255, 0),
987 (255, 255, 95),
988 (255, 255, 135),
989 (255, 255, 175),
990 (255, 255, 215),
991 (255, 255, 255),
992 (8, 8, 8),
993 (18, 18, 18),
994 (28, 28, 28),
995 (38, 38, 38),
996 (48, 48, 48),
997 (58, 58, 58),
998 (68, 68, 68),
999 (78, 78, 78),
1000 (88, 88, 88),
1001 (98, 98, 98),
1002 (108, 108, 108),
1003 (118, 118, 118),
1004 (128, 128, 128),
1005 (138, 138, 138),
1006 (148, 148, 148),
1007 (158, 158, 158),
1008 (168, 168, 168),
1009 (178, 178, 178),
1010 (188, 188, 188),
1011 (198, 198, 198),
1012 (208, 208, 208),
1013 (218, 218, 218),
1014 (228, 228, 228),
1015 (238, 238, 238),
1016];