From df5ae7ebbee0a3d4de82cf8892144bec150f3aa4 Mon Sep 17 00:00:00 2001 From: Kumpelinus Date: Mon, 10 Mar 2025 13:07:16 +0100 Subject: [PATCH] Implement a to_html method for FormattedText Also fix a small issue with ansi formatting where it duplicated text. --- azalea-chat/src/component.rs | 49 +++++++++++++++++++++++++++++++++++- azalea-chat/src/style.rs | 40 +++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs index e96ead43..c60132b8 100644 --- a/azalea-chat/src/component.rs +++ b/azalea-chat/src/component.rs @@ -107,7 +107,10 @@ impl FormattedText { for component in self.clone().into_iter() { let component_text = match &component { Self::Text(c) => c.text.to_string(), - Self::Translatable(c) => c.to_string(), + Self::Translatable(c) => match c.read() { + Ok(c) => c.to_string(), + Err(_) => c.key.to_string(), + }, }; let component_style = &component.get_base().style; @@ -125,6 +128,50 @@ impl FormattedText { built_string } + + pub fn to_html(&self) -> String { + // default the default_style to white if it's not set + self.to_html_with_custom_style(&DEFAULT_STYLE) + } + + pub fn to_html_with_custom_style(&self, default_style: &Style) -> String { + let mut html_output = String::new(); + let mut running_style = default_style.clone(); + + for component in self.clone().into_iter() { + let component_style = &component.get_base().style; + // Calculate the effective style by merging the running style with the component's style. + let effective_style = running_style.merged_with(component_style); + let style_string = effective_style.get_html_style(); + + // Get the component text and replace newlines with
+ let component_text = match &component { + Self::Text(c) => c.text.to_string(), + Self::Translatable(c) => match c.read() { + Ok(text) => text.to_string(), + Err(_) => c.key.to_string(), + }, + }.replace("\n", "
"); + + // Append the styled span for this component. + html_output.push_str(&format!( + "{}", + style_string, component_text + )); + + // Update the running style: + // If the component requests a reset, revert to default. + if component_style.reset { + running_style = default_style.clone(); + } else { + running_style.apply(component_style); + } + } + + html_output + } + + } impl IntoIterator for FormattedText { diff --git a/azalea-chat/src/style.rs b/azalea-chat/src/style.rs index 26fa2633..133bae9f 100644 --- a/azalea-chat/src/style.rs +++ b/azalea-chat/src/style.rs @@ -559,6 +559,20 @@ impl Style { } } + /// Returns a new style that is a merge of self and other. + /// For any field that `other` does not specify (is None), self’s value is used. + pub fn merged_with(&self, other: &Style) -> Style { + Style { + color: other.color.clone().or(self.color.clone()), + bold: other.bold.or(self.bold), + italic: other.italic.or(self.italic), + underlined: other.underlined.or(self.underlined), + strikethrough: other.strikethrough.or(self.strikethrough), + obfuscated: other.obfuscated.or(self.obfuscated), + reset: other.reset, // if reset is true in the new style, that takes precedence + } + } + /// Apply a ChatFormatting to this style pub fn apply_formatting(&mut self, formatting: &ChatFormatting) { match *formatting { @@ -576,6 +590,32 @@ impl Style { } } } + + pub fn get_html_style(&self) -> String { + let mut style = String::new(); + if let Some(color) = &self.color { + style.push_str(&format!("color: {};", color.format_value())); + } + if let Some(bold) = self.bold { + style.push_str(&format!("font-weight: {};", if bold { "bold" } else { "normal" })); + } + if let Some(italic) = self.italic { + style.push_str(&format!("font-style: {};", if italic { "italic" } else { "normal" })); + } + if let Some(underlined) = self.underlined { + style.push_str(&format!("text-decoration: {};", if underlined { "underline" } else { "none" })); + } + if let Some(strikethrough) = self.strikethrough { + style.push_str(&format!("text-decoration: {};", if strikethrough { "line-through" } else { "none" })); + } + if let Some(obfuscated) = self.obfuscated { + if obfuscated { + style.push_str("filter: blur(2px);"); + } + } + + style + } } #[cfg(feature = "simdnbt")]