MinedMap/src/world/json_text.rs

261 lines
7.3 KiB
Rust

//! 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)
}
}