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")]