Skip to content

Commit

Permalink
fix(svg): Render underline color
Browse files Browse the repository at this point in the history
  • Loading branch information
epage committed Feb 17, 2024
1 parent 8238abd commit 63b2a4a
Show file tree
Hide file tree
Showing 3 changed files with 2,173 additions and 1,709 deletions.
208 changes: 90 additions & 118 deletions crates/anstyle-svg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ impl Term {
}
}

const FG: &str = "--fg";
let fg_color = render_rgb(anstyle_lossy::color_to_rgb(self.fg_color, self.palette));
const BG: &str = "--bg";
let bg_color = render_rgb(anstyle_lossy::color_to_rgb(self.bg_color, self.palette));
const FG: &str = "fg";
let fg_color = rgb_value(self.fg_color, self.palette);
const BG: &str = "bg";
let bg_color = rgb_value(self.bg_color, self.palette);
let font_family = self.font_family;

let line_height = 18;
Expand All @@ -81,69 +81,75 @@ impl Term {
)
.unwrap();
writeln!(&mut buffer, r#" <style>"#).unwrap();
writeln!(&mut buffer, r#" :root {{"#).unwrap();
writeln!(&mut buffer, r#" {FG}: {fg_color};"#).unwrap();
writeln!(&mut buffer, r#" {BG}: {bg_color};"#).unwrap();
for (name, color) in ansi_name_colors(self.palette) {
writeln!(&mut buffer, r#" {name}: {color};"#).unwrap();
}
for (name, color) in ansi256_name_colors(&styled, self.palette) {
writeln!(&mut buffer, r#" {name}: {color};"#).unwrap();
writeln!(&mut buffer, r#" .{FG} {{ fill: {fg_color} }}"#).unwrap();
writeln!(&mut buffer, r#" .{BG} {{ background: {bg_color} }}"#).unwrap();
for (name, rgb) in color_styles(&styled, self.palette) {
if name.starts_with(FG_PREFIX) {
writeln!(&mut buffer, r#" .{name} {{ fill: {rgb} }}"#).unwrap();
}
if name.starts_with(BG_PREFIX) {
writeln!(&mut buffer, r#" .{name} {{ background: {rgb} }}"#).unwrap();
}
if name.starts_with(UNDERLINE_PREFIX) {
writeln!(
&mut buffer,
r#" .{name} {{ text-decoration-line: underline; text-decoration-color: {rgb} }}"#
)
.unwrap();
}
}
writeln!(&mut buffer, r#" .container {{"#).unwrap();
writeln!(&mut buffer, r#" padding: 0 10px;"#).unwrap();
writeln!(&mut buffer, r#" fill: var({FG});"#).unwrap();
writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap();
writeln!(&mut buffer, r#" }}"#).unwrap();
writeln!(&mut buffer, r#" .bold {{ font-weight: bold; }}"#).unwrap();
writeln!(&mut buffer, r#" .italic {{ font-style: italic; }}"#).unwrap();
writeln!(
&mut buffer,
r#" .underline {{ text-decoration: underline; }}"#
r#" .underline {{ text-decoration-line: underline; }}"#
)
.unwrap();
writeln!(
&mut buffer,
r#" .double-underline {{ text-decoration: underline; text-decoration-style: double; }}"#
r#" .double-underline {{ text-decoration-line: underline; text-decoration-style: double; }}"#
)
.unwrap();
writeln!(
&mut buffer,
r#" .curly-underline {{ text-decoration: underline; text-decoration-style: wavy; }}"#
r#" .curly-underline {{ text-decoration-line: underline; text-decoration-style: wavy; }}"#
)
.unwrap();
writeln!(
&mut buffer,
r#" .dotted-underline {{ text-decoration: underline; text-decoration-style: dotted; }}"#
r#" .dotted-underline {{ text-decoration-line: underline; text-decoration-style: dotted; }}"#
)
.unwrap();
writeln!(
&mut buffer,
r#" .dashed-underline {{ text-decoration: underline; text-decoration-style: dashed; }}"#
r#" .dashed-underline {{ text-decoration-line: underline; text-decoration-style: dashed; }}"#
)
.unwrap();
writeln!(
&mut buffer,
r#" .strikethrough {{ text-decoration: line-through; }}"#
r#" .strikethrough {{ text-decoration-line: line-through; }}"#
)
.unwrap();
writeln!(&mut buffer, r#" .dimmed {{ opacity: 0.7; }}"#).unwrap();
writeln!(&mut buffer, r#" tspan {{"#).unwrap();
writeln!(&mut buffer, r#" font: 14px {font_family};"#).unwrap();
writeln!(&mut buffer, r#" fill: var({FG});"#).unwrap();
writeln!(&mut buffer, r#" white-space: pre;"#).unwrap();
writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap();
writeln!(&mut buffer, r#" }}"#).unwrap();
writeln!(&mut buffer, r#" </style>"#).unwrap();
if self.background {
writeln!(
&mut buffer,
r#" <rect width="100%" height="100%" y="0" rx="4.5" style="fill: var({BG});" />"#
r#" <rect width="100%" height="100%" y="0" rx="4.5" class="{BG}" />"#
)
.unwrap();
}
let mut text_y = line_height;
write!(&mut buffer, r#" <text class="container">"#).unwrap();
write!(&mut buffer, r#" <text class="container {FG} {BG}">"#).unwrap();
write!(&mut buffer, r#" <tspan x="0px" y="{text_y}px">"#).unwrap();
for (style, string) in &styled {
if string.is_empty() {
Expand Down Expand Up @@ -172,8 +178,11 @@ const FG_COLOR: anstyle::Color = anstyle::Color::Ansi(anstyle::AnsiColor::White)
const BG_COLOR: anstyle::Color = anstyle::Color::Ansi(anstyle::AnsiColor::Black);

fn write_span(buffer: &mut String, style: &anstyle::Style, fragment: &str) {
let fg_color = style.get_fg_color().map(render_color);
let bg_color = style.get_bg_color().map(render_color);
let fg_color = style.get_fg_color().map(|c| color_name(FG_PREFIX, c));
let bg_color = style.get_bg_color().map(|c| color_name(BG_PREFIX, c));
let underline_color = style
.get_underline_color()
.map(|c| color_name(UNDERLINE_PREFIX, c));
let effects = style.get_effects();
let underline = effects.contains(anstyle::Effects::UNDERLINE);
let double_underline = effects.contains(anstyle::Effects::DOUBLE_UNDERLINE);
Expand All @@ -187,22 +196,16 @@ fn write_span(buffer: &mut String, style: &anstyle::Style, fragment: &str) {
let dimmed = effects.contains(anstyle::Effects::DIMMED);

let fragment = html_escape::encode_text(fragment);
let mut style = String::new();
if let Some(color) = fg_color {
if color.starts_with("--") {
write!(&mut style, "fill: var({color});").unwrap();
} else {
write!(&mut style, "fill: {color};").unwrap();
}
let mut classes = Vec::new();
if let Some(class) = fg_color.as_deref() {
classes.push(class);
}
if let Some(color) = bg_color {
if color.starts_with("--") {
write!(&mut style, "background: var({color});").unwrap();
} else {
write!(&mut style, "background: {color};").unwrap();
}
if let Some(class) = bg_color.as_deref() {
classes.push(class);
}
if let Some(class) = underline_color.as_deref() {
classes.push(class);
}
let mut classes = Vec::new();
if underline {
classes.push("underline");
}
Expand Down Expand Up @@ -237,9 +240,6 @@ fn write_span(buffer: &mut String, style: &anstyle::Style, fragment: &str) {
let classes = classes.join(" ");
write!(buffer, r#" class="{classes}""#).unwrap();
}
if !style.is_empty() {
write!(buffer, r#" style="{style}""#).unwrap();
}
write!(buffer, r#">"#).unwrap();
write!(buffer, "{fragment}").unwrap();
write!(buffer, r#"</tspan>"#).unwrap();
Expand All @@ -251,101 +251,73 @@ impl Default for Term {
}
}

const ANSI: [anstyle::AnsiColor; 16] = [
anstyle::AnsiColor::Black,
anstyle::AnsiColor::Red,
anstyle::AnsiColor::Green,
anstyle::AnsiColor::Yellow,
anstyle::AnsiColor::Blue,
anstyle::AnsiColor::Magenta,
anstyle::AnsiColor::Cyan,
anstyle::AnsiColor::White,
anstyle::AnsiColor::BrightBlack,
anstyle::AnsiColor::BrightRed,
anstyle::AnsiColor::BrightGreen,
anstyle::AnsiColor::BrightYellow,
anstyle::AnsiColor::BrightBlue,
anstyle::AnsiColor::BrightMagenta,
anstyle::AnsiColor::BrightCyan,
anstyle::AnsiColor::BrightWhite,
];
const ANSI_NAMES: [&str; 16] = [
"--black",
"--red",
"--green",
"--yellow",
"--blue",
"--magenta",
"--cyan",
"--white",
"--bright-black",
"--bright-red",
"--bright-green",
"--bright-yellow",
"--bright-blue",
"--bright-magenta",
"--bright-cyan",
"--bright-white",
"black",
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
"bright-black",
"bright-red",
"bright-green",
"bright-yellow",
"bright-blue",
"bright-magenta",
"bright-cyan",
"bright-white",
];

fn render_rgb(color: anstyle::RgbColor) -> String {
fn rgb_value(color: anstyle::Color, palette: anstyle_lossy::palette::Palette) -> String {
let color = anstyle_lossy::color_to_rgb(color, palette);
let anstyle::RgbColor(r, g, b) = color;

format!("#{r:02X}{g:02X}{b:02X}")
}

fn render_ansi(color: anstyle::AnsiColor) -> &'static str {
let color = anstyle::Ansi256Color::from_ansi(color);
let index = color.index() as usize;
ANSI_NAMES[index]
}

fn render_ansi256(color: anstyle::Ansi256Color) -> String {
let index = color.index();
format!("--ansi256-{index}")
}
const FG_PREFIX: &str = "fg";
const BG_PREFIX: &str = "bg";
const UNDERLINE_PREFIX: &str = "underline";

fn render_color(color: anstyle::Color) -> String {
fn color_name(prefix: &str, color: anstyle::Color) -> String {
match color {
anstyle::Color::Ansi(c) => render_ansi(c).to_owned(),
anstyle::Color::Ansi256(c) => render_ansi256(c),
anstyle::Color::Rgb(c) => render_rgb(c),
anstyle::Color::Ansi(color) => {
let color = anstyle::Ansi256Color::from_ansi(color);
let index = color.index() as usize;
let name = ANSI_NAMES[index];
format!("{prefix}-{name}")
}
anstyle::Color::Ansi256(color) => {
let index = color.index();
format!("{prefix}-ansi256-{index:03}")
}
anstyle::Color::Rgb(color) => {
let anstyle::RgbColor(r, g, b) = color;
format!("{prefix}-rgb-{r:02X}{g:02X}{b:02X}")
}
}
}

fn ansi_name_colors(
palette: anstyle_lossy::palette::Palette,
) -> impl Iterator<Item = (&'static str, String)> {
ANSI.into_iter().map(move |ansi| {
let name = render_ansi(ansi);
let color = render_rgb(anstyle_lossy::color_to_rgb(
anstyle::Color::Ansi(ansi),
palette,
));
(name, color)
})
}

fn ansi256_name_colors(
fn color_styles(
styled: &[(anstyle::Style, String)],
palette: anstyle_lossy::palette::Palette,
) -> impl Iterator<Item = (String, String)> {
let mut ansi256 = std::collections::BTreeSet::new();
let mut colors = std::collections::BTreeMap::new();
for (style, _) in styled {
if let Some(anstyle::Color::Ansi256(color)) = style.get_fg_color() {
ansi256.insert(color);
if let Some(color) = style.get_fg_color() {
colors.insert(color_name(FG_PREFIX, color), rgb_value(color, palette));
}
if let Some(color) = style.get_bg_color() {
colors.insert(color_name(BG_PREFIX, color), rgb_value(color, palette));
}
if let Some(anstyle::Color::Ansi256(color)) = style.get_bg_color() {
ansi256.insert(color);
if let Some(color) = style.get_underline_color() {
colors.insert(
color_name(UNDERLINE_PREFIX, color),
rgb_value(color, palette),
);
}
}

ansi256.into_iter().map(move |ansi256| {
let name = render_ansi256(ansi256);
let color = render_rgb(anstyle_lossy::color_to_rgb(
anstyle::Color::Ansi256(ansi256),
palette,
));
(name, color)
})
colors.into_iter()
}
Loading

0 comments on commit 63b2a4a

Please sign in to comment.