1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
6#![warn(missing_docs)]
7#![warn(clippy::print_stderr)]
8#![warn(clippy::print_stdout)]
9
10mod styled_str;
11use anstyle::{Ansi256Color, AnsiColor, Color, RgbColor, Style};
12use anstyle_lossy::palette::Palette;
13use roff::{bold, italic, Roff};
14use styled_str::StyledStr;
15
16mod control_requests {
18 pub(crate) const CREATE_COLOR: &str = "defcolor";
20 pub(crate) const BACKGROUND: &str = "fcolor";
22 pub(crate) const FOREGROUND: &str = "gcolor";
24}
25
26pub fn to_roff(styled_text: &str) -> Roff {
40 let mut doc = Roff::new();
41 let mut previous_fg_color = None;
42 let mut previous_bg_color = None;
43 for styled in styled_str::styled_stream(styled_text) {
44 if previous_fg_color != styled.style.get_fg_color() {
45 add_color_to_roff(
46 &mut doc,
47 control_requests::FOREGROUND,
48 &styled.style.get_fg_color(),
49 );
50 previous_fg_color = styled.style.get_fg_color();
51 }
52 if previous_bg_color != styled.style.get_bg_color() {
53 add_color_to_roff(
54 &mut doc,
55 control_requests::BACKGROUND,
56 &styled.style.get_bg_color(),
57 );
58 previous_bg_color = styled.style.get_bg_color();
59 }
60 set_effects_and_text(&styled, &mut doc);
61 }
62 doc
63}
64
65fn set_effects_and_text(styled: &StyledStr<'_>, doc: &mut Roff) {
66 let effects = styled.style.get_effects();
74 if effects.contains(anstyle::Effects::BOLD) | has_bright_fg(&styled.style) {
75 doc.text([bold(styled.text)]);
76 } else if effects.contains(anstyle::Effects::ITALIC) {
77 doc.text([italic(styled.text)]);
78 } else {
79 doc.text([roff::roman(styled.text)]);
80 }
81}
82
83fn has_bright_fg(style: &Style) -> bool {
84 style
85 .get_fg_color()
86 .as_ref()
87 .map(is_bright)
88 .unwrap_or(false)
89}
90
91fn is_bright(fg_color: &Color) -> bool {
93 if let Color::Ansi(color) = fg_color {
94 matches!(
95 color,
96 AnsiColor::BrightRed
97 | AnsiColor::BrightBlue
98 | AnsiColor::BrightBlack
99 | AnsiColor::BrightCyan
100 | AnsiColor::BrightGreen
101 | AnsiColor::BrightWhite
102 | AnsiColor::BrightYellow
103 | AnsiColor::BrightMagenta
104 )
105 } else {
106 false
107 }
108}
109
110fn add_color_to_roff(doc: &mut Roff, control_request: &str, color: &Option<Color>) {
111 match color {
112 Some(Color::Rgb(c)) => {
113 let name = rgb_name(c);
117 doc.control(
118 control_requests::CREATE_COLOR,
119 [name.as_str(), "rgb", to_hex(c).as_str()],
120 )
121 .control(control_request, [name.as_str()]);
122 }
123
124 Some(Color::Ansi(c)) => {
125 doc.control(control_request, [ansi_color_to_roff(c)]);
126 }
127 Some(Color::Ansi256(c)) => {
128 add_color_to_roff(doc, control_request, &Some(xterm_to_ansi_or_rgb(*c)));
132 }
133 None => {
134 doc.control(control_request, ["default"]);
136 }
137 }
138}
139
140fn xterm_to_ansi_or_rgb(color: Ansi256Color) -> Color {
142 match color.into_ansi() {
143 Some(ansi_color) => Color::Ansi(ansi_color),
144 None => Color::Rgb(anstyle_lossy::xterm_to_rgb(color, Palette::default())),
145 }
146}
147
148fn rgb_name(c: &RgbColor) -> String {
149 format!("hex_{}", to_hex(c).as_str())
150}
151
152fn to_hex(rgb: &RgbColor) -> String {
153 let val: usize = ((rgb.0 as usize) << 16) + ((rgb.1 as usize) << 8) + (rgb.2 as usize);
154 format!("#{val:06x}")
155}
156
157fn ansi_color_to_roff(color: &AnsiColor) -> &'static str {
159 match color {
160 AnsiColor::Black | AnsiColor::BrightBlack => "black",
161 AnsiColor::Red | AnsiColor::BrightRed => "red",
162 AnsiColor::Green | AnsiColor::BrightGreen => "green",
163 AnsiColor::Yellow | AnsiColor::BrightYellow => "yellow",
164 AnsiColor::Blue | AnsiColor::BrightBlue => "blue",
165 AnsiColor::Magenta | AnsiColor::BrightMagenta => "magenta",
166 AnsiColor::Cyan | AnsiColor::BrightCyan => "cyan",
167 AnsiColor::White | AnsiColor::BrightWhite => "white",
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use anstyle::RgbColor;
175
176 #[test]
177 fn test_to_hex() {
178 assert_eq!(to_hex(&RgbColor(0, 0, 0)).as_str(), "#000000");
179 assert_eq!(to_hex(&RgbColor(255, 0, 0)).as_str(), "#ff0000");
180 assert_eq!(to_hex(&RgbColor(0, 255, 0)).as_str(), "#00ff00");
181 assert_eq!(to_hex(&RgbColor(0, 0, 255)).as_str(), "#0000ff");
182 }
183}
184
185#[doc = include_str!("../README.md")]
186#[cfg(doctest)]
187pub struct ReadmeDoctests;