mirror of
https://github.com/neocturne/MinedMap.git
synced 2025-03-04 17:23:33 +01:00
world: implement decoding raw JSON text into a linear list of formatted strings
This commit is contained in:
parent
f78dd795ca
commit
61d456846a
3 changed files with 238 additions and 1 deletions
|
@ -1,7 +1,155 @@
|
||||||
//! Newtype and helper methods for handling Minecraft Raw JSON Text
|
//! Newtype and helper methods for handling Minecraft Raw JSON Text
|
||||||
|
|
||||||
|
use std::{collections::VecDeque, sync::Arc};
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
/// 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, Deserialize, Default)]
|
||||||
|
pub struct FormattedText {
|
||||||
|
#[serde(default)]
|
||||||
|
/// Text content
|
||||||
|
pub text: String,
|
||||||
|
/// Text color
|
||||||
|
pub color: Option<Arc<String>>,
|
||||||
|
/// Bold formatting
|
||||||
|
pub bold: Option<bool>,
|
||||||
|
/// Italic formatting
|
||||||
|
pub italic: Option<bool>,
|
||||||
|
/// Underlines formatting
|
||||||
|
pub underlined: Option<bool>,
|
||||||
|
/// Strikethrough formatting
|
||||||
|
pub strikethrough: Option<bool>,
|
||||||
|
/// Obfuscated formatting
|
||||||
|
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_else(|| parent.color.clone()),
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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)]
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
/// Minecraft Raw JSON Text
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct JSONText(String);
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,3 +5,4 @@ pub mod de;
|
||||||
pub mod json_text;
|
pub mod json_text;
|
||||||
pub mod layer;
|
pub mod layer;
|
||||||
pub mod section;
|
pub mod section;
|
||||||
|
pub mod sign;
|
||||||
|
|
88
src/world/sign.rs
Normal file
88
src/world/sign.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
//! Processing of sign text
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
de,
|
||||||
|
json_text::{FormattedText, FormattedTextList, JSONText},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Version-independent reference to (front or back) sign text
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct RawSignText<'a> {
|
||||||
|
/// Lines of sign text
|
||||||
|
///
|
||||||
|
/// A regular sign always has 4 lines of text. The back of pre-1.20
|
||||||
|
/// signs is represented as a [SignText] without any `messages`.
|
||||||
|
pub messages: Vec<&'a JSONText>,
|
||||||
|
/// Sign color
|
||||||
|
///
|
||||||
|
/// Defaults to "black".
|
||||||
|
pub color: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RawSignText<'a> {
|
||||||
|
/// Decodes the [RawSignText] into a [SignText]
|
||||||
|
pub fn decode(&self) -> SignText {
|
||||||
|
let color = self.color.map(|c| Arc::new(c.to_owned()));
|
||||||
|
let parent = FormattedText {
|
||||||
|
color,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
SignText(
|
||||||
|
self.messages
|
||||||
|
.iter()
|
||||||
|
.map(|message| message.deserialize().linearize(&parent))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a de::BlockEntitySignV1_20Text> for RawSignText<'a> {
|
||||||
|
fn from(value: &'a de::BlockEntitySignV1_20Text) -> Self {
|
||||||
|
RawSignText {
|
||||||
|
messages: value.messages.iter().collect(),
|
||||||
|
color: value.color.as_deref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper methods for [de::BlockEntitySign]
|
||||||
|
pub trait BlockEntitySignExt {
|
||||||
|
/// Returns the front and back text of a sign in a version-indepentent format
|
||||||
|
fn text(&self) -> (RawSignText, RawSignText);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockEntitySignExt for de::BlockEntitySign {
|
||||||
|
fn text(&self) -> (RawSignText, RawSignText) {
|
||||||
|
match self {
|
||||||
|
de::BlockEntitySign::V0 {
|
||||||
|
text1,
|
||||||
|
text2,
|
||||||
|
text3,
|
||||||
|
text4,
|
||||||
|
color,
|
||||||
|
} => (
|
||||||
|
RawSignText {
|
||||||
|
messages: vec![text1, text2, text3, text4],
|
||||||
|
color: color.as_deref(),
|
||||||
|
},
|
||||||
|
Default::default(),
|
||||||
|
),
|
||||||
|
de::BlockEntitySign::V1_20 {
|
||||||
|
front_text,
|
||||||
|
back_text,
|
||||||
|
} => (front_text.into(), back_text.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserialized and linearized sign text
|
||||||
|
pub struct SignText(pub Vec<FormattedTextList>);
|
||||||
|
|
||||||
|
impl SignText {
|
||||||
|
/// Checks if all lines of the sign text are empty
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.iter().all(|line| line.is_empty())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue