//! Newtype and helper methods for handling Minecraft Raw JSON Text use std::{collections::VecDeque, fmt::Display}; use bincode::{Decode, Encode}; use minedmap_resource::Color; use serde::{Deserialize, Serialize}; /// A span of formatted text /// /// A [JSONText] consists of a tree of [FormattedText] nodes (canonically /// represented as a [FormattedTextTree], but other kinds are possible with /// is handled by [DeserializedText]. /// /// Formatting that is not set in a node is inherited from the parent. #[derive( Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Encode, Decode, )] pub struct FormattedText { #[serde(default)] /// Text content pub text: String, /// Text color #[serde(skip_serializing_if = "Option::is_none", with = "json_color")] pub color: Option<Color>, /// Bold formatting #[serde(skip_serializing_if = "Option::is_none")] pub bold: Option<bool>, /// Italic formatting #[serde(skip_serializing_if = "Option::is_none")] pub italic: Option<bool>, /// Underlines formatting #[serde(skip_serializing_if = "Option::is_none")] pub underlined: Option<bool>, /// Strikethrough formatting #[serde(skip_serializing_if = "Option::is_none")] pub strikethrough: Option<bool>, /// Obfuscated formatting #[serde(skip_serializing_if = "Option::is_none")] pub obfuscated: Option<bool>, } impl FormattedText { /// Fills in unset formatting fields from a parent node pub fn inherit(self, parent: &Self) -> Self { FormattedText { text: self.text, color: self.color.or(parent.color), bold: self.bold.or(parent.bold), italic: self.italic.or(parent.italic), underlined: self.underlined.or(parent.underlined), strikethrough: self.strikethrough.or(parent.strikethrough), obfuscated: self.obfuscated.or(parent.obfuscated), } } } impl Display for FormattedText { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.text.fmt(f) } } /// A tree of [FormattedText] nodes /// /// Each node including the root has a `text` and a list of children (`extra`). #[derive(Debug, Deserialize, Default)] pub struct FormattedTextTree { /// Root node content #[serde(flatten)] text: FormattedText, /// List of child trees #[serde(default)] extra: VecDeque<DeserializedText>, } impl From<String> for FormattedTextTree { fn from(value: String) -> Self { FormattedTextTree { text: FormattedText { text: value, ..Default::default() }, extra: VecDeque::new(), } } } /// List of [FormattedText] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Encode, Decode)] pub struct FormattedTextList(pub Vec<FormattedText>); impl FormattedTextList { /// Returns `true` when [FormattedTextList] does not contain any text pub fn is_empty(&self) -> bool { self.0.iter().all(|text| text.text.is_empty()) } } impl Display for FormattedTextList { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for text in &self.0 { text.fmt(f)?; } Ok(()) } } /// Raw deserialized [JSONText] /// /// A [JSONText] can contain various different JSON types. #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum DeserializedText { /// Unformatted string String(String), /// Unformatted number (will be converted to a string) Number(f32), /// Unformatted boolean (will be converted to a string) Boolean(bool), /// List of [DeserializedText] /// /// The tail elements are appended as children of the head element. List(VecDeque<DeserializedText>), /// The canonical [FormattedTextTree] structure Object(FormattedTextTree), } impl DeserializedText { /// Converts a [DeserializedText] into the regular [FormattedTextTree] format /// /// Most variants are simply converted to strings. A list is handled by /// appending all tail elements to the `extra` field of the head. pub fn canonicalize(self) -> FormattedTextTree { match self { DeserializedText::Object(obj) => obj, DeserializedText::String(s) => FormattedTextTree::from(s), DeserializedText::Number(n) => FormattedTextTree::from(n.to_string()), DeserializedText::Boolean(b) => FormattedTextTree::from(b.to_string()), DeserializedText::List(mut list) => { let mut obj = list .pop_front() .map(|t| t.canonicalize()) .unwrap_or_default(); obj.extra.append(&mut list); obj } } } /// Converts the tree of [FormattedText] nodes into a linear list by /// copying formatting flags into each node. pub fn linearize(self, parent: &FormattedText) -> FormattedTextList { let obj = self.canonicalize(); let mut ret = vec![obj.text.inherit(parent)]; for extra in obj.extra { ret.append(&mut extra.linearize(&ret[0]).0); } FormattedTextList(ret) } } impl Default for DeserializedText { fn default() -> Self { DeserializedText::Object(FormattedTextTree::from(String::new())) } } /// Minecraft Raw JSON Text #[derive(Debug, Deserialize)] pub struct JSONText(pub String); impl JSONText { /// Deserializes a [JSONText] into a [DeserializedText] pub fn deserialize(&self) -> DeserializedText { serde_json::from_str(&self.0).unwrap_or_default() } } mod json_color { //! Helpers for serializing and deserializing [FormattedText](super::FormattedText) colors use minedmap_resource::Color; use serde::{ Deserializer, Serializer, de::{self, Visitor}, ser::Error as _, }; /// Named JSON text colors static COLORS: phf::Map<&'static str, Color> = phf::phf_map! { "black" => Color([0x00, 0x00, 0x00]), "dark_blue" => Color([0x00, 0x00, 0xAA]), "dark_green" => Color([0x00, 0xAA, 0x00]), "dark_aqua" => Color([0x00, 0xAA, 0xAA]), "dark_red" => Color([0xAA, 0x00, 0x00]), "dark_purple" => Color([0xAA, 0x00, 0xAA]), "gold" => Color([0xFF, 0xAA, 0x00]), "gray" => Color([0xAA, 0xAA, 0xAA]), "dark_gray" => Color([0x55, 0x55, 0x55]), "blue" => Color([0x55, 0x55, 0xFF]), "green" => Color([0x55, 0xFF, 0x55]), "aqua" => Color([0x55, 0xFF, 0xFF]), "red" => Color([0xFF, 0x55, 0x55]), "light_purple" => Color([0xFF, 0x55, 0xFF]), "yellow" => Color([0xFF, 0xFF, 0x55]), "white" => Color([0xFF, 0xFF, 0xFF]), }; /// serde serialize function for [FormattedText::color](super::FormattedText::color) pub fn serialize<S>(color: &Option<Color>, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let &Some(color) = color else { return Err(S::Error::custom("serialize called for None sign color")); }; let text = format!("#{:02x}{:02x}{:02x}", color.0[0], color.0[1], color.0[2]); serializer.serialize_str(&text) } /// serde [Visitor] for use by [deserialize] struct ColorVisitor; impl Visitor<'_> for ColorVisitor { type Value = Option<Color>; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("a string representing a color") } fn visit_str<E>(self, color: &str) -> Result<Self::Value, E> where E: de::Error, { if let Some(hex) = color.strip_prefix("#") { if let Ok(value) = u32::from_str_radix(hex, 16) { return Ok(Some(Color([ (value >> 16) as u8, (value >> 8) as u8, value as u8, ]))); } } Ok(COLORS.get(color).copied()) } } /// serde deserialize function for [FormattedText::color](super::FormattedText::color) pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Color>, D::Error> where D: Deserializer<'de>, { deserializer.deserialize_str(ColorVisitor) } }