From a59f2f11b55b2c0bb78fc522b2d3d18301a34676 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 28 Jul 2025 21:52:18 +0200 Subject: [PATCH 1/5] Create new `LINE_HEIGHT` constant --- crates/anstyle-svg/src/lib.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/anstyle-svg/src/lib.rs b/crates/anstyle-svg/src/lib.rs index 1dae062e..e07ff707 100644 --- a/crates/anstyle-svg/src/lib.rs +++ b/crates/anstyle-svg/src/lib.rs @@ -108,8 +108,7 @@ impl Term { let bg_color = rgb_value(self.bg_color, self.palette); let font_family = self.font_family; - let line_height = 18; - let height = styled_lines.len() * line_height + self.padding_px * 2; + let height = styled_lines.len() * LINE_HEIGHT + self.padding_px * 2; let max_width = styled_lines .iter() .map(|l| l.iter().map(|e| e.text.width()).sum()) @@ -148,13 +147,13 @@ impl Term { } writeln!(&mut buffer, r#" .container {{"#).unwrap(); writeln!(&mut buffer, r#" padding: 0 10px;"#).unwrap(); - writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap(); + writeln!(&mut buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap(); writeln!(&mut buffer, r#" }}"#).unwrap(); write_effects_in_use(&mut buffer, &elements); writeln!(&mut buffer, r#" tspan {{"#).unwrap(); writeln!(&mut buffer, r#" font: 14px {font_family};"#).unwrap(); writeln!(&mut buffer, r#" white-space: pre;"#).unwrap(); - writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap(); + writeln!(&mut buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap(); writeln!(&mut buffer, r#" }}"#).unwrap(); writeln!(&mut buffer, r#" "#).unwrap(); writeln!(&mut buffer).unwrap(); @@ -169,7 +168,7 @@ impl Term { } let text_x = self.padding_px; - let mut text_y = self.padding_px + line_height; + let mut text_y = self.padding_px + LINE_HEIGHT; writeln!( &mut buffer, r#" "# @@ -200,7 +199,7 @@ impl Term { writeln!(&mut buffer).unwrap(); writeln!(&mut buffer, r#""#).unwrap(); - text_y += line_height; + text_y += LINE_HEIGHT; } writeln!(&mut buffer, r#" "#).unwrap(); writeln!(&mut buffer).unwrap(); @@ -229,8 +228,6 @@ impl Term { let bg_color = rgb_value(self.bg_color, self.palette); let font_family = self.font_family; - let line_height = 18; - let mut buffer = String::new(); writeln!(&mut buffer, r#""#).unwrap(); writeln!(&mut buffer, r#""#).unwrap(); @@ -269,13 +266,13 @@ impl Term { } } writeln!(&mut buffer, r#" .container {{"#).unwrap(); - writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap(); + writeln!(&mut buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap(); writeln!(&mut buffer, r#" }}"#).unwrap(); write_effects_in_use(&mut buffer, &elements); writeln!(&mut buffer, r#" span {{"#).unwrap(); writeln!(&mut buffer, r#" font: 14px {font_family};"#).unwrap(); writeln!(&mut buffer, r#" white-space: pre;"#).unwrap(); - writeln!(&mut buffer, r#" line-height: {line_height}px;"#).unwrap(); + writeln!(&mut buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap(); writeln!(&mut buffer, r#" }}"#).unwrap(); writeln!(&mut buffer, r#" "#).unwrap(); writeln!(&mut buffer, r#""#).unwrap(); @@ -521,6 +518,7 @@ fn rgb_value(color: anstyle::Color, palette: Palette) -> String { const FG_PREFIX: &str = "fg"; const BG_PREFIX: &str = "bg"; const UNDERLINE_PREFIX: &str = "underline"; +const LINE_HEIGHT: usize = 18; fn color_name(prefix: &str, color: anstyle::Color) -> String { match color { From 93ad6c239ce09c8c28e6043e83792770bb00f8ef Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 28 Jul 2025 21:53:45 +0200 Subject: [PATCH 2/5] Move `FG` and `BG` constants alongside the other constants to remove code duplication --- crates/anstyle-svg/src/lib.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/anstyle-svg/src/lib.rs b/crates/anstyle-svg/src/lib.rs index e07ff707..695c4ba0 100644 --- a/crates/anstyle-svg/src/lib.rs +++ b/crates/anstyle-svg/src/lib.rs @@ -95,9 +95,6 @@ impl Term { use std::fmt::Write as _; use unicode_width::UnicodeWidthStr as _; - const FG: &str = "fg"; - const BG: &str = "bg"; - let mut styled = adapter::AnsiBytes::new(); let mut elements = styled.extract_next(ansi.as_bytes()).collect::>(); preprocess_invert_style(&mut elements, self.bg_color, self.fg_color); @@ -215,9 +212,6 @@ impl Term { pub fn render_html(&self, ansi: &str) -> String { use std::fmt::Write as _; - const FG: &str = "fg"; - const BG: &str = "bg"; - let mut styled = adapter::AnsiBytes::new(); let mut elements = styled.extract_next(ansi.as_bytes()).collect::>(); preprocess_invert_style(&mut elements, self.bg_color, self.fg_color); @@ -515,6 +509,8 @@ fn rgb_value(color: anstyle::Color, palette: Palette) -> String { format!("#{r:02X}{g:02X}{b:02X}") } +const FG: &str = "fg"; +const BG: &str = "bg"; const FG_PREFIX: &str = "fg"; const BG_PREFIX: &str = "bg"; const UNDERLINE_PREFIX: &str = "underline"; From e514fbf47e47807e8f74e727e0575e38c379c55d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 28 Jul 2025 21:56:27 +0200 Subject: [PATCH 3/5] Create new `Term::render_classes` method --- crates/anstyle-svg/src/lib.rs | 74 +++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/crates/anstyle-svg/src/lib.rs b/crates/anstyle-svg/src/lib.rs index 695c4ba0..e3d1fb40 100644 --- a/crates/anstyle-svg/src/lib.rs +++ b/crates/anstyle-svg/src/lib.rs @@ -218,10 +218,6 @@ impl Term { let styled_lines = split_lines(&elements); - let fg_color = rgb_value(self.fg_color, self.palette); - let bg_color = rgb_value(self.bg_color, self.palette); - let font_family = self.font_family; - let mut buffer = String::new(); writeln!(&mut buffer, r#""#).unwrap(); writeln!(&mut buffer, r#""#).unwrap(); @@ -238,36 +234,7 @@ impl Term { ) .unwrap(); writeln!(&mut buffer, r#" "#).unwrap(); writeln!(&mut buffer, r#""#).unwrap(); writeln!(&mut buffer).unwrap(); @@ -306,6 +273,45 @@ impl Term { writeln!(&mut buffer, r#""#).unwrap(); buffer } + + fn render_classes(&self, buffer: &mut String, elements: &[adapter::Element]) { + use std::fmt::Write as _; + + let fg_color = rgb_value(self.fg_color, self.palette); + let bg_color = rgb_value(self.bg_color, self.palette); + let font_family = self.font_family; + + writeln!(buffer, r#" .{FG} {{ color: {fg_color} }}"#).unwrap(); + writeln!(buffer, r#" .{BG} {{ background: {bg_color} }}"#).unwrap(); + for (name, rgb) in color_styles(elements, self.palette) { + if name.starts_with(FG_PREFIX) { + writeln!(buffer, r#" .{name} {{ color: {rgb} }}"#).unwrap(); + } + if name.starts_with(BG_PREFIX) { + writeln!( + buffer, + r#" .{name} {{ background: {rgb}; user-select: none; }}"# + ) + .unwrap(); + } + if name.starts_with(UNDERLINE_PREFIX) { + writeln!( + buffer, + r#" .{name} {{ text-decoration-line: underline; text-decoration-color: {rgb} }}"# + ) + .unwrap(); + } + } + writeln!(buffer, r#" .container {{"#).unwrap(); + writeln!(buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap(); + writeln!(buffer, r#" }}"#).unwrap(); + write_effects_in_use(buffer, elements); + writeln!(buffer, r#" span {{"#).unwrap(); + writeln!(buffer, r#" font: 14px {font_family};"#).unwrap(); + writeln!(buffer, r#" white-space: pre;"#).unwrap(); + writeln!(buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap(); + writeln!(buffer, r#" }}"#).unwrap(); + } } const FG_COLOR: anstyle::Color = anstyle::Color::Ansi(anstyle::AnsiColor::White); From 2b78e5ff6baae08cd278bf2bfa2716bd2542c3e2 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 28 Jul 2025 22:48:37 +0200 Subject: [PATCH 4/5] Create new `Term::render_content` method --- crates/anstyle-svg/src/lib.rs | 48 ++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/crates/anstyle-svg/src/lib.rs b/crates/anstyle-svg/src/lib.rs index e3d1fb40..8777532f 100644 --- a/crates/anstyle-svg/src/lib.rs +++ b/crates/anstyle-svg/src/lib.rs @@ -246,27 +246,7 @@ impl Term { } writeln!(&mut buffer).unwrap(); - writeln!(&mut buffer, r#"
"#).unwrap(); - for line in &styled_lines { - if line.iter().any(|e| e.style.get_bg_color().is_some()) { - for element in line { - if element.text.is_empty() { - continue; - } - write_bg_span(&mut buffer, "span", &element.style, &element.text); - } - writeln!(&mut buffer, r#"
"#).unwrap(); - } - - for element in line { - if element.text.is_empty() { - continue; - } - write_fg_span(&mut buffer, "span", element, &element.text); - } - writeln!(&mut buffer, r#"
"#).unwrap(); - } - writeln!(&mut buffer, r#"
"#).unwrap(); + self.render_content(&mut buffer, styled_lines); writeln!(&mut buffer).unwrap(); writeln!(&mut buffer, r#""#).unwrap(); @@ -312,6 +292,32 @@ impl Term { writeln!(buffer, r#" line-height: {LINE_HEIGHT}px;"#).unwrap(); writeln!(buffer, r#" }}"#).unwrap(); } + + fn render_content(&self, buffer: &mut String, styled_lines: Vec>) { + use std::fmt::Write as _; + + writeln!(buffer, r#"
"#).unwrap(); + for line in &styled_lines { + if line.iter().any(|e| e.style.get_bg_color().is_some()) { + for element in line { + if element.text.is_empty() { + continue; + } + write_bg_span(buffer, "span", &element.style, &element.text); + } + writeln!(buffer, r#"
"#).unwrap(); + } + + for element in line { + if element.text.is_empty() { + continue; + } + write_fg_span(buffer, "span", element, &element.text); + } + writeln!(buffer, r#"
"#).unwrap(); + } + writeln!(buffer, r#"
"#).unwrap(); + } } const FG_COLOR: anstyle::Color = anstyle::Color::Ansi(anstyle::AnsiColor::White); From 90425dd08a7d46b23446b913e40a7fe2b4309fcb Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 28 Jul 2025 22:51:10 +0200 Subject: [PATCH 5/5] Add new `HtmlFragments` type and new `Term::render_html_fragments` method --- crates/anstyle-svg/src/lib.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/anstyle-svg/src/lib.rs b/crates/anstyle-svg/src/lib.rs index 8777532f..3e313be7 100644 --- a/crates/anstyle-svg/src/lib.rs +++ b/crates/anstyle-svg/src/lib.rs @@ -318,6 +318,40 @@ impl Term { } writeln!(buffer, r#" "#).unwrap(); } + + /// Returns the various parts needed to create an HTML page. + pub fn render_html_fragments(&self, ansi: &str) -> HtmlFragments { + let mut styled = adapter::AnsiBytes::new(); + let mut elements = styled.extract_next(ansi.as_bytes()).collect::>(); + preprocess_invert_style(&mut elements, self.bg_color, self.fg_color); + + let styled_lines = split_lines(&elements); + + let mut style = String::new(); + let mut body = String::new(); + + self.render_classes(&mut style, &elements); + self.render_content(&mut body, styled_lines); + HtmlFragments { style, body } + } +} + +/// Contains the different parts of a HTML rendered page. +pub struct HtmlFragments { + style: String, + body: String, +} + +impl HtmlFragments { + /// Content that can be used directly in a `