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_layer::popup;
20use zng_wgt_scroll::{LazyMode, lazy};
21use zng_wgt_stack::{Stack, StackDirection};
22use zng_wgt_text::*;
23
24#[doc(hidden)]
25pub use zng_wgt_text::__formatx;
26
27#[widget($crate::AnsiText {
31 ($txt:literal) => {
32 txt = $crate::__formatx!($txt);
33 };
34 ($txt:expr) => {
35 txt = $txt;
36 };
37 ($txt:tt, $($format:tt)*) => {
38 txt = $crate::__formatx!($txt, $($format)*);
39 };
40})]
41#[rustfmt::skip]
42pub struct AnsiText(
43 FontMix<
44 TextSpacingMix<
45 ParagraphMix<
46 LangMix<
47 WidgetBase
48 >>>>
49);
50impl AnsiText {
51 fn widget_intrinsic(&mut self) {
52 widget_set! {
53 self;
54 font_family = ["JetBrains Mono", "Consolas", "monospace"];
55 rich_text = true;
56
57 popup::context_capture = default_popup_context_capture();
58 when #txt_selectable {
59 cursor = CursorIcon::Text;
60 zng_wgt_menu::context::context_menu_fn = WidgetFn::new(default_context_menu);
61 }
62 };
63
64 self.widget_builder().push_build_action(|wgt| {
65 let txt = wgt.capture_var_or_default(property_id!(txt));
66 let child = ansi_node(txt);
67 wgt.set_child(child);
68 });
69 }
70
71 widget_impl! {
72 pub txt(text: impl IntoVar<Txt>);
74
75 pub zng_wgt_text::txt_selectable(enabled: impl IntoVar<bool>);
79 }
80}
81
82pub use ansi_parse::*;
83mod ansi_parse {
84
85 use super::*;
86
87 #[derive(Debug)]
89 #[non_exhaustive]
90 pub struct AnsiTxt<'a> {
91 pub txt: &'a str,
93 pub style: AnsiStyle,
95 }
96
97 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
101 #[non_exhaustive]
102 pub struct AnsiStyle {
103 pub background_color: AnsiColor,
105 pub color: AnsiColor,
107 pub weight: AnsiWeight,
109 pub italic: bool,
111 pub underline: bool,
113 pub strikethrough: bool,
115 pub invert_color: bool,
117 pub hidden: bool,
119 pub blink: bool,
121 }
122 impl Default for AnsiStyle {
123 fn default() -> Self {
124 Self {
125 background_color: AnsiColor::Black,
126 color: AnsiColor::White,
127 weight: Default::default(),
128 italic: false,
129 underline: false,
130 strikethrough: false,
131 invert_color: false,
132 hidden: false,
133 blink: false,
134 }
135 }
136 }
137
138 #[allow(missing_docs)]
142 #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
143 pub enum AnsiColor {
144 Black,
145 Red,
146 Green,
147 Yellow,
148 Blue,
149 Magenta,
150 Cyan,
151 White,
152 BrightBlack,
154 BrightRed,
155 BrightGreen,
156 BrightYellow,
157 BrightBlue,
158 BrightMagenta,
159 BrightCyan,
160 BrightWhite,
161 Ansi256(u8),
163 TrueColor(u8, u8, u8),
165 }
166 impl_from_and_into_var! {
167 fn from(color: AnsiColor) -> Rgba {
168 match color {
169 AnsiColor::Black => rgb(0, 0, 0),
170 AnsiColor::Red => rgb(205, 49, 49),
171 AnsiColor::Green => rgb(13, 188, 121),
172 AnsiColor::Yellow => rgb(229, 229, 16),
173 AnsiColor::Blue => rgb(36, 114, 200),
174 AnsiColor::Magenta => rgb(188, 63, 188),
175 AnsiColor::Cyan => rgb(17, 168, 205),
176 AnsiColor::White => rgb(229, 229, 229),
177 AnsiColor::BrightBlack => rgb(102, 102, 102),
178 AnsiColor::BrightRed => rgb(241, 76, 76),
179 AnsiColor::BrightGreen => rgb(35, 209, 139),
180 AnsiColor::BrightYellow => rgb(245, 245, 67),
181 AnsiColor::BrightBlue => rgb(59, 142, 234),
182 AnsiColor::BrightMagenta => rgb(214, 112, 214),
183 AnsiColor::BrightCyan => rgb(41, 184, 219),
184 AnsiColor::BrightWhite => rgb(229, 229, 229),
185 AnsiColor::Ansi256(c) => {
186 let (r, g, b) = X_TERM_256[c as usize];
187 rgb(r, g, b)
188 }
189 AnsiColor::TrueColor(r, g, b) => rgb(r, g, b),
190 }
191 }
192 }
193
194 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
198 pub enum AnsiWeight {
199 #[default]
201 Normal,
202 Bold,
204 Faint,
206 }
207 impl_from_and_into_var! {
208 fn from(weight: AnsiWeight) -> FontWeight {
209 match weight {
210 AnsiWeight::Normal => FontWeight::NORMAL,
211 AnsiWeight::Bold => FontWeight::BOLD,
212 AnsiWeight::Faint => FontWeight::LIGHT,
213 }
214 }
215 }
216
217 pub struct AnsiTextParser<'a> {
223 source: &'a str,
224 pub style: AnsiStyle,
226
227 esc: &'a [&'a str],
228 }
229 impl<'a> AnsiTextParser<'a> {
230 pub fn new(source: &'a str) -> Self {
232 Self::with_esc(source, &["\x1b"])
233 }
234
235 pub fn with_esc(source: &'a str, esc: &'a [&'a str]) -> Self {
237 Self {
238 source,
239 style: AnsiStyle::default(),
240 esc,
241 }
242 }
243 }
244 impl<'a> Iterator for AnsiTextParser<'a> {
245 type Item = AnsiTxt<'a>;
246
247 fn next(&mut self) -> Option<Self::Item> {
248 fn is_esc_end(byte: u8) -> bool {
249 (0x40..=0x7e).contains(&byte)
250 }
251 fn strip_esc<'a>(s: &'a str, esc: &[&str]) -> Option<&'a str> {
252 for esc in esc {
253 if esc.is_empty() {
254 continue;
255 }
256 if let Some(s) = s.strip_prefix(esc)
257 && let Some(s) = s.strip_prefix('[')
258 {
259 return Some(s);
260 }
261 }
262 None
263 }
264 fn find_esc(s: &str, esc: &[&str]) -> Option<usize> {
265 for esc in esc {
266 if esc.is_empty() {
267 continue;
268 }
269 let mut s = s;
270 if let Some(i) = s.find(esc) {
271 s = &s[i + esc.len()..];
272 if s.starts_with('[') {
273 return Some(i);
274 }
275 } else {
276 continue;
277 }
278 }
279 None
280 }
281
282 loop {
283 if self.source.is_empty() {
284 return None;
285 } else if let Some(source) = strip_esc(self.source, self.esc) {
286 let mut esc_end = 0;
287 while esc_end < source.len() && !is_esc_end(source.as_bytes()[esc_end]) {
288 esc_end += 1;
289 }
290 esc_end += 1;
291
292 let (esc, source) = source.split_at(esc_end);
293
294 let esc = &esc[..(esc.len() - 1)];
295 self.style.set(esc);
296
297 self.source = source;
298 continue;
299 } else if let Some(i) = find_esc(self.source, self.esc) {
300 let (txt, source) = self.source.split_at(i);
301 self.source = source;
302 return Some(AnsiTxt {
303 txt,
304 style: self.style.clone(),
305 });
306 } else {
307 return Some(AnsiTxt {
308 txt: std::mem::take(&mut self.source),
309 style: self.style.clone(),
310 });
311 }
312 }
313 }
314 }
315
316 impl AnsiStyle {
317 fn set(&mut self, esc_codes: &str) {
318 let mut esc_codes = esc_codes.split(';');
319 while let Some(code) = esc_codes.next() {
320 match code {
321 "0" => *self = Self::default(),
322 "1" => self.weight = AnsiWeight::Bold,
323 "2" => self.weight = AnsiWeight::Faint,
324 "3" => self.italic = true,
325 "4" => self.underline = true,
326 "5" => self.blink = true,
327 "7" => self.invert_color = true,
328 "8" => self.hidden = true,
329 "9" => self.strikethrough = true,
330 "22" => self.weight = AnsiWeight::Normal,
331 "23" => self.italic = false,
332 "24" => self.underline = false,
333 "25" => self.blink = false,
334 "27" => self.invert_color = false,
335 "28" => self.hidden = false,
336 "29" => self.strikethrough = false,
337 "30" => self.color = AnsiColor::Black,
338 "31" => self.color = AnsiColor::Red,
339 "32" => self.color = AnsiColor::Green,
340 "33" => self.color = AnsiColor::Yellow,
341 "34" => self.color = AnsiColor::Blue,
342 "35" => self.color = AnsiColor::Magenta,
343 "36" => self.color = AnsiColor::Cyan,
344 "37" => self.color = AnsiColor::White,
345 "40" => self.color = AnsiColor::Black,
346 "41" => self.color = AnsiColor::Red,
347 "42" => self.color = AnsiColor::Green,
348 "43" => self.color = AnsiColor::Yellow,
349 "44" => self.color = AnsiColor::Blue,
350 "45" => self.color = AnsiColor::Magenta,
351 "46" => self.color = AnsiColor::Cyan,
352 "47" => self.color = AnsiColor::White,
353 "90" => self.color = AnsiColor::BrightBlack,
354 "91" => self.color = AnsiColor::BrightRed,
355 "92" => self.color = AnsiColor::BrightGreen,
356 "93" => self.color = AnsiColor::BrightYellow,
357 "94" => self.color = AnsiColor::BrightBlue,
358 "95" => self.color = AnsiColor::BrightMagenta,
359 "96" => self.color = AnsiColor::BrightCyan,
360 "97" => self.color = AnsiColor::BrightWhite,
361 "100" => self.background_color = AnsiColor::BrightBlack,
362 "101" => self.background_color = AnsiColor::BrightRed,
363 "102" => self.background_color = AnsiColor::BrightGreen,
364 "103" => self.background_color = AnsiColor::BrightYellow,
365 "104" => self.background_color = AnsiColor::BrightBlue,
366 "105" => self.background_color = AnsiColor::BrightMagenta,
367 "106" => self.background_color = AnsiColor::BrightCyan,
368 "107" => self.background_color = AnsiColor::BrightWhite,
369 "38" | "48" => {
370 let target = if code == "38" {
371 &mut self.color
372 } else {
373 &mut self.background_color
374 };
375 match esc_codes.next() {
376 Some("5") => {
377 let c = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
378 *target = AnsiColor::Ansi256(c)
379 }
380 Some("2") => {
381 let r = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
382 let g = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
383 let b = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
384
385 *target = AnsiColor::TrueColor(r, g, b);
386 }
387 _ => {}
388 }
389 }
390 _ => (),
391 }
392 }
393 }
394 }
395}
396
397pub use ansi_fn::*;
398mod ansi_fn {
399 use std::time::Duration;
400
401 use super::{AnsiColor, AnsiStyle, AnsiWeight};
402
403 use super::*;
404
405 #[non_exhaustive]
409 pub struct TextFnArgs {
410 pub txt: Txt,
412 pub style: AnsiStyle,
414 }
415 impl TextFnArgs {
416 pub fn new(txt: impl Into<Txt>, style: AnsiStyle) -> Self {
418 Self { txt: txt.into(), style }
419 }
420 }
421
422 #[non_exhaustive]
426 pub struct LineFnArgs {
427 pub index: u32,
429 pub page_index: u32,
431 pub text: UiVec,
433 }
434
435 impl LineFnArgs {
436 pub fn new(index: u32, page_index: u32, text: UiVec) -> Self {
438 Self { index, page_index, text }
439 }
440 }
441
442 #[non_exhaustive]
446 pub struct PageFnArgs {
447 pub index: u32,
449
450 pub lines: UiVec,
452 }
453
454 impl PageFnArgs {
455 pub fn new(index: u32, lines: UiVec) -> Self {
457 Self { index, lines }
458 }
459 }
460
461 #[non_exhaustive]
465 pub struct PanelFnArgs {
466 pub pages: UiVec,
468 }
469
470 impl PanelFnArgs {
471 pub fn new(pages: UiVec) -> Self {
473 Self { pages }
474 }
475 }
476
477 context_var! {
478 pub static TEXT_FN_VAR: WidgetFn<TextFnArgs> = wgt_fn!(|args: TextFnArgs| { default_text_fn(args) });
482
483 pub static LINE_FN_VAR: WidgetFn<LineFnArgs> = wgt_fn!(|args: LineFnArgs| { default_line_fn(args) });
487
488 pub static PAGE_FN_VAR: WidgetFn<PageFnArgs> = wgt_fn!(|args: PageFnArgs| { default_page_fn(args) });
492
493 pub static PANEL_FN_VAR: WidgetFn<PanelFnArgs> = wgt_fn!(|args: PanelFnArgs| { default_panel_fn(args) });
499
500 pub static BLINK_INTERVAL_VAR: Duration = Duration::ZERO;
504
505 pub static LINES_PER_PAGE_VAR: u32 = 200;
509
510 pub static ALTERNATE_ESC_VAR: Txt = r"\x1b";
514 }
515
516 pub fn default_text_fn(args: TextFnArgs) -> UiNode {
523 let mut text = Text::widget_new();
524
525 widget_set! {
526 &mut text;
527 txt = args.txt;
528 }
529
530 if args.style.background_color != AnsiColor::Black {
531 widget_set! {
532 &mut text;
533 background_color = args.style.background_color;
534 }
535 }
536 if args.style.color != AnsiColor::White {
537 widget_set! {
538 &mut text;
539 font_color = args.style.color;
540 }
541 }
542
543 if args.style.weight != AnsiWeight::Normal {
544 widget_set! {
545 &mut text;
546 font_weight = args.style.weight;
547 }
548 }
549 if args.style.italic {
550 widget_set! {
551 &mut text;
552 font_style = FontStyle::Italic;
553 }
554 }
555
556 if args.style.underline {
557 widget_set! {
558 &mut text;
559 underline = 1, LineStyle::Solid;
560 }
561 }
562 if args.style.strikethrough {
563 widget_set! {
564 &mut text;
565 strikethrough = 1, LineStyle::Solid;
566 }
567 }
568
569 if args.style.invert_color {
570 widget_set! {
571 &mut text;
572 invert_color = true;
573 }
574 }
575
576 if args.style.hidden {
577 widget_set! {
578 &mut text;
579 visibility = Visibility::Hidden;
580 }
581 }
582 if args.style.blink && !args.style.hidden {
583 let opacity = var(1.fct());
584
585 let interval = BLINK_INTERVAL_VAR.get();
586 if interval != Duration::ZERO && interval != Duration::MAX {
587 opacity.step_oci(0.fct(), interval).perm();
588
589 widget_set! {
590 &mut text;
591 opacity;
592 }
593 }
594 }
595
596 text.widget_build()
597 }
598
599 pub fn default_line_fn(mut args: LineFnArgs) -> UiNode {
603 if args.text.is_empty() {
604 Text!("")
605 } else if args.text.len() == 1 {
606 args.text.remove(0)
607 } else {
608 Stack! {
609 rich_text = true;
610 direction = StackDirection::start_to_end();
611 children = args.text;
612 }
613 }
614 }
615
616 pub fn default_page_fn(mut args: PageFnArgs) -> UiNode {
620 use crate::prelude::*;
621
622 if args.lines.is_empty() {
623 UiNode::nil()
624 } else if args.lines.len() == 1 {
625 args.lines.remove(0)
626 } else {
627 let len = args.lines.len();
628 Stack! {
629 rich_text = true;
630 direction = StackDirection::top_to_bottom();
631 children = args.lines;
632 lazy = LazyMode::lazy_vertical(wgt_fn!(|_| {
633 let height_sample = zng_wgt_text::node::line_placeholder(50);
634 zng_wgt_stack::lazy_sample(len, StackDirection::top_to_bottom(), 0, height_sample)
635 }));
636 }
637 }
638 }
639
640 pub fn default_panel_fn(mut args: PanelFnArgs) -> UiNode {
644 use crate::prelude::*;
645
646 if args.pages.is_empty() {
647 UiNode::nil()
648 } else if args.pages.len() == 1 {
649 args.pages.remove(0)
650 } else {
651 Stack! {
652 rich_text = true;
653 direction = StackDirection::top_to_bottom();
654 children = args.pages;
655 }
656 }
657 }
658
659 #[property(CONTEXT, default(BLINK_INTERVAL_VAR), widget_impl(AnsiText))]
665 pub fn blink_interval(child: impl IntoUiNode, interval: impl IntoVar<Duration>) -> UiNode {
666 with_context_var(child, BLINK_INTERVAL_VAR, interval)
667 }
668
669 #[property(CONTEXT, default(TEXT_FN_VAR), widget_impl(AnsiText))]
673 pub fn text_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<TextFnArgs>>) -> UiNode {
674 with_context_var(child, TEXT_FN_VAR, wgt_fn)
675 }
676
677 #[property(CONTEXT, default(LINE_FN_VAR), widget_impl(AnsiText))]
681 pub fn line_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<LineFnArgs>>) -> UiNode {
682 with_context_var(child, LINE_FN_VAR, wgt_fn)
683 }
684
685 #[property(CONTEXT, default(PAGE_FN_VAR), widget_impl(AnsiText))]
693 pub fn page_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<PageFnArgs>>) -> UiNode {
694 with_context_var(child, PAGE_FN_VAR, wgt_fn)
695 }
696
697 #[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(AnsiText))]
699 pub fn panel_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<PanelFnArgs>>) -> UiNode {
700 with_context_var(child, PANEL_FN_VAR, wgt_fn)
701 }
702
703 #[property(CONTEXT, default(LINES_PER_PAGE_VAR), widget_impl(AnsiText))]
707 pub fn lines_per_page(child: impl IntoUiNode, count: impl IntoVar<u32>) -> UiNode {
708 with_context_var(child, LINES_PER_PAGE_VAR, count)
709 }
710 #[property(CONTEXT, default(ALTERNATE_ESC_VAR))]
721 fn alternate_esc(child: impl IntoUiNode, esc: impl IntoVar<Txt>) -> UiNode {
722 with_context_var(child, ALTERNATE_ESC_VAR, esc)
723 }
724}
725
726fn generate_ansi(txt: &Var<Txt>) -> UiNode {
727 use ansi_fn::*;
728 use std::mem;
729
730 txt.with(|txt| {
731 let text_fn = TEXT_FN_VAR.get();
732 let line_fn = LINE_FN_VAR.get();
733 let page_fn = PAGE_FN_VAR.get();
734 let panel_fn = PANEL_FN_VAR.get();
735 let lines_per_page = LINES_PER_PAGE_VAR.get() as usize;
736 let alt_esc = ALTERNATE_ESC_VAR.get();
737
738 let mut pages = Vec::with_capacity(4);
739 let mut lines = Vec::with_capacity(50);
740
741 for (i, line) in txt.lines().enumerate() {
742 let text = ansi_parse::AnsiTextParser::with_esc(line, &["\x1b", alt_esc.as_str()])
743 .filter_map(|txt| {
744 text_fn.call_checked(TextFnArgs {
745 txt: txt.txt.to_txt(),
746 style: txt.style,
747 })
748 })
749 .collect();
750
751 lines.push(line_fn(LineFnArgs {
752 index: i as u32,
753 page_index: lines.len() as u32,
754 text,
755 }));
756
757 if lines.len() == lines_per_page {
758 let lines = mem::replace(&mut lines, Vec::with_capacity(50));
759 pages.push(page_fn(PageFnArgs {
760 index: pages.len() as u32,
761 lines: lines.into(),
762 }));
763 }
764 }
765
766 if !lines.is_empty() {
767 pages.push(page_fn(PageFnArgs {
768 index: pages.len() as u32,
769 lines: lines.into(),
770 }));
771 }
772
773 panel_fn(PanelFnArgs { pages: pages.into() })
774 })
775}
776
777pub fn ansi_node(txt: impl IntoVar<Txt>) -> UiNode {
779 let txt = txt.into_var();
780 match_node(UiNode::nil(), move |c, op| match op {
781 UiNodeOp::Init => {
782 WIDGET
783 .sub_var(&txt)
784 .sub_var(&TEXT_FN_VAR)
785 .sub_var(&LINE_FN_VAR)
786 .sub_var(&PAGE_FN_VAR)
787 .sub_var(&PANEL_FN_VAR)
788 .sub_var(&LINES_PER_PAGE_VAR)
789 .sub_var(&BLINK_INTERVAL_VAR)
790 .sub_var(&ALTERNATE_ESC_VAR);
791
792 *c.node() = generate_ansi(&txt);
793 }
794 UiNodeOp::Deinit => {
795 c.deinit();
796 *c.node() = UiNode::nil();
797 }
798 UiNodeOp::Update { .. } => {
799 use ansi_fn::*;
800
801 if txt.is_new()
802 || TEXT_FN_VAR.is_new()
803 || LINE_FN_VAR.is_new()
804 || PAGE_FN_VAR.is_new()
805 || PANEL_FN_VAR.is_new()
806 || LINES_PER_PAGE_VAR.is_new()
807 || BLINK_INTERVAL_VAR.is_new()
808 {
809 c.node().deinit();
810 *c.node() = generate_ansi(&txt);
811 c.node().init();
812 WIDGET.update_info().layout().render();
813 }
814 }
815 _ => {}
816 })
817}
818
819pub fn default_context_menu(args: zng_wgt_menu::context::ContextMenuArgs) -> UiNode {
824 use zng_wgt_button::Button;
825 let id = args.anchor_id;
826 zng_wgt_menu::context::ContextMenu!(ui_vec![
827 Button!(zng_ext_clipboard::COPY_CMD.scoped(id)),
828 Button!(zng_wgt_text::cmd::SELECT_ALL_CMD.scoped(id)),
829 ])
830}
831
832pub fn default_popup_context_capture() -> popup::ContextCapture {
838 popup::ContextCapture::context_vars_except(Text::context_vars_set_except_lang)
839}
840
841static X_TERM_256: [(u8, u8, u8); 256] = [
842 (0, 0, 0),
843 (128, 0, 0),
844 (0, 128, 0),
845 (128, 128, 0),
846 (0, 0, 128),
847 (128, 0, 128),
848 (0, 128, 128),
849 (192, 192, 192),
850 (128, 128, 128),
851 (255, 0, 0),
852 (0, 255, 0),
853 (255, 255, 0),
854 (0, 0, 255),
855 (255, 0, 255),
856 (0, 255, 255),
857 (255, 255, 255),
858 (0, 0, 0),
859 (0, 0, 95),
860 (0, 0, 135),
861 (0, 0, 175),
862 (0, 0, 215),
863 (0, 0, 255),
864 (0, 95, 0),
865 (0, 95, 95),
866 (0, 95, 135),
867 (0, 95, 175),
868 (0, 95, 215),
869 (0, 95, 255),
870 (0, 135, 0),
871 (0, 135, 95),
872 (0, 135, 135),
873 (0, 135, 175),
874 (0, 135, 215),
875 (0, 135, 255),
876 (0, 175, 0),
877 (0, 175, 95),
878 (0, 175, 135),
879 (0, 175, 175),
880 (0, 175, 215),
881 (0, 175, 255),
882 (0, 215, 0),
883 (0, 215, 95),
884 (0, 215, 135),
885 (0, 215, 175),
886 (0, 215, 215),
887 (0, 215, 255),
888 (0, 255, 0),
889 (0, 255, 95),
890 (0, 255, 135),
891 (0, 255, 175),
892 (0, 255, 215),
893 (0, 255, 255),
894 (95, 0, 0),
895 (95, 0, 95),
896 (95, 0, 135),
897 (95, 0, 175),
898 (95, 0, 215),
899 (95, 0, 255),
900 (95, 95, 0),
901 (95, 95, 95),
902 (95, 95, 135),
903 (95, 95, 175),
904 (95, 95, 215),
905 (95, 95, 255),
906 (95, 135, 0),
907 (95, 135, 95),
908 (95, 135, 135),
909 (95, 135, 175),
910 (95, 135, 215),
911 (95, 135, 255),
912 (95, 175, 0),
913 (95, 175, 95),
914 (95, 175, 135),
915 (95, 175, 175),
916 (95, 175, 215),
917 (95, 175, 255),
918 (95, 215, 0),
919 (95, 215, 95),
920 (95, 215, 135),
921 (95, 215, 175),
922 (95, 215, 215),
923 (95, 215, 255),
924 (95, 255, 0),
925 (95, 255, 95),
926 (95, 255, 135),
927 (95, 255, 175),
928 (95, 255, 215),
929 (95, 255, 255),
930 (135, 0, 0),
931 (135, 0, 95),
932 (135, 0, 135),
933 (135, 0, 175),
934 (135, 0, 215),
935 (135, 0, 255),
936 (135, 95, 0),
937 (135, 95, 95),
938 (135, 95, 135),
939 (135, 95, 175),
940 (135, 95, 215),
941 (135, 95, 255),
942 (135, 135, 0),
943 (135, 135, 95),
944 (135, 135, 135),
945 (135, 135, 175),
946 (135, 135, 215),
947 (135, 135, 255),
948 (135, 175, 0),
949 (135, 175, 95),
950 (135, 175, 135),
951 (135, 175, 175),
952 (135, 175, 215),
953 (135, 175, 255),
954 (135, 215, 0),
955 (135, 215, 95),
956 (135, 215, 135),
957 (135, 215, 175),
958 (135, 215, 215),
959 (135, 215, 255),
960 (135, 255, 0),
961 (135, 255, 95),
962 (135, 255, 135),
963 (135, 255, 175),
964 (135, 255, 215),
965 (135, 255, 255),
966 (175, 0, 0),
967 (175, 0, 95),
968 (175, 0, 135),
969 (175, 0, 175),
970 (175, 0, 215),
971 (175, 0, 255),
972 (175, 95, 0),
973 (175, 95, 95),
974 (175, 95, 135),
975 (175, 95, 175),
976 (175, 95, 215),
977 (175, 95, 255),
978 (175, 135, 0),
979 (175, 135, 95),
980 (175, 135, 135),
981 (175, 135, 175),
982 (175, 135, 215),
983 (175, 135, 255),
984 (175, 175, 0),
985 (175, 175, 95),
986 (175, 175, 135),
987 (175, 175, 175),
988 (175, 175, 215),
989 (175, 175, 255),
990 (175, 215, 0),
991 (175, 215, 95),
992 (175, 215, 135),
993 (175, 215, 175),
994 (175, 215, 215),
995 (175, 215, 255),
996 (175, 255, 0),
997 (175, 255, 95),
998 (175, 255, 135),
999 (175, 255, 175),
1000 (175, 255, 215),
1001 (175, 255, 255),
1002 (215, 0, 0),
1003 (215, 0, 95),
1004 (215, 0, 135),
1005 (215, 0, 175),
1006 (215, 0, 215),
1007 (215, 0, 255),
1008 (215, 95, 0),
1009 (215, 95, 95),
1010 (215, 95, 135),
1011 (215, 95, 175),
1012 (215, 95, 215),
1013 (215, 95, 255),
1014 (215, 135, 0),
1015 (215, 135, 95),
1016 (215, 135, 135),
1017 (215, 135, 175),
1018 (215, 135, 215),
1019 (215, 135, 255),
1020 (215, 175, 0),
1021 (215, 175, 95),
1022 (215, 175, 135),
1023 (215, 175, 175),
1024 (215, 175, 215),
1025 (215, 175, 255),
1026 (215, 215, 0),
1027 (215, 215, 95),
1028 (215, 215, 135),
1029 (215, 215, 175),
1030 (215, 215, 215),
1031 (215, 215, 255),
1032 (215, 255, 0),
1033 (215, 255, 95),
1034 (215, 255, 135),
1035 (215, 255, 175),
1036 (215, 255, 215),
1037 (215, 255, 255),
1038 (255, 0, 0),
1039 (255, 0, 95),
1040 (255, 0, 135),
1041 (255, 0, 175),
1042 (255, 0, 215),
1043 (255, 0, 255),
1044 (255, 95, 0),
1045 (255, 95, 95),
1046 (255, 95, 135),
1047 (255, 95, 175),
1048 (255, 95, 215),
1049 (255, 95, 255),
1050 (255, 135, 0),
1051 (255, 135, 95),
1052 (255, 135, 135),
1053 (255, 135, 175),
1054 (255, 135, 215),
1055 (255, 135, 255),
1056 (255, 175, 0),
1057 (255, 175, 95),
1058 (255, 175, 135),
1059 (255, 175, 175),
1060 (255, 175, 215),
1061 (255, 175, 255),
1062 (255, 215, 0),
1063 (255, 215, 95),
1064 (255, 215, 135),
1065 (255, 215, 175),
1066 (255, 215, 215),
1067 (255, 215, 255),
1068 (255, 255, 0),
1069 (255, 255, 95),
1070 (255, 255, 135),
1071 (255, 255, 175),
1072 (255, 255, 215),
1073 (255, 255, 255),
1074 (8, 8, 8),
1075 (18, 18, 18),
1076 (28, 28, 28),
1077 (38, 38, 38),
1078 (48, 48, 48),
1079 (58, 58, 58),
1080 (68, 68, 68),
1081 (78, 78, 78),
1082 (88, 88, 88),
1083 (98, 98, 98),
1084 (108, 108, 108),
1085 (118, 118, 118),
1086 (128, 128, 128),
1087 (138, 138, 138),
1088 (148, 148, 148),
1089 (158, 158, 158),
1090 (168, 168, 168),
1091 (178, 178, 178),
1092 (188, 188, 188),
1093 (198, 198, 198),
1094 (208, 208, 208),
1095 (218, 218, 218),
1096 (228, 228, 228),
1097 (238, 238, 238),
1098];