chunkedge_binary/
text_component.rs1use std::borrow::Cow;
2use std::io::Write;
3
4use anyhow::ensure;
5use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
6use chunkedge_nbt::Tag;
7use chunkedge_text::{IntoText, Text};
8
9use crate::{Decode, Encode};
10
11#[derive(Clone, Debug, PartialEq)]
12#[repr(transparent)] pub struct TextComponent {
14 pub text: Text,
15}
16
17impl TextComponent {
18 pub fn from_cow_text<'a>(cow: Cow<'a, Text>) -> Cow<'a, TextComponent> {
24 match cow {
25 Cow::Borrowed(b) => {
26 let ptr = b as *const Text as *const TextComponent;
28 Cow::Borrowed(unsafe { &*ptr })
29 }
30 Cow::Owned(o) => Cow::Owned(TextComponent { text: o }),
31 }
32 }
33
34 pub fn as_text(&self) -> &Text {
35 &self.text
36 }
37}
38
39impl<'a> IntoText<'a> for TextComponent {
40 fn into_cow_text(self) -> Cow<'a, Text> {
41 Cow::Owned(self.text)
43 }
44}
45
46pub trait IntoTextComponent<'a> {
47 fn into_text_component(self) -> TextComponent;
48 fn into_cow_text_component(self) -> Cow<'a, TextComponent>;
49}
50
51impl<'a, T: IntoText<'a>> IntoTextComponent<'a> for T {
52 fn into_text_component(self) -> TextComponent {
53 TextComponent {
54 text: self.into_cow_text().into_owned(),
55 }
56 }
57
58 fn into_cow_text_component(self) -> Cow<'a, TextComponent> {
59 let cow = self.into_cow_text();
60 TextComponent::from_cow_text(cow)
61 }
62}
63
64impl From<Text> for TextComponent {
65 fn from(value: Text) -> Self {
66 TextComponent { text: value }
67 }
68}
69
70impl Encode for TextComponent {
71 fn encode(&self, mut w: impl Write) -> anyhow::Result<()> {
72 if self.text.is_plain() {
73 let mut w = w;
75 w.write_u8(Tag::String as u8)?;
76
77 let chunkedge_text::TextContent::Text { text: string } = &self.text.content else {
78 unreachable!()
80 };
81
82 let len = string.len();
83
84 match u16::try_from(len) {
85 Ok(n) => w.write_u16::<BigEndian>(n)?,
86 Err(_) => {
87 return Err(anyhow::anyhow!(
88 "string of length {len} exceeds maximum of u16::MAX"
89 ));
90 }
91 }
92
93 w.write_all(string.as_bytes())?;
95 Ok(())
96 } else {
97 w.write_u8(Tag::Compound as u8)?;
99 self.text.encode(&mut w)
100 }
101 }
102}
103
104impl Decode<'_> for TextComponent {
105 fn decode(r: &mut &'_ [u8]) -> anyhow::Result<Self> {
106 let tag_id = r.read_u8()?;
107 match tag_id {
108 val if val == Tag::String as u8 => Ok(TextComponent {
109 text: {
110 let len = r.read_u16::<BigEndian>()?.into();
111 ensure!(
112 len <= r.len(),
113 "string of length {} exceeds remainder of input {}",
114 len,
115 r.len()
116 );
117
118 let (left, right) = r.split_at(len);
119
120 *r = right; String::from_utf8_lossy(left).into_owned().into()
123 },
124 }),
125 val if val == Tag::Compound as u8 => {
126 Ok(TextComponent {
128 text: Decode::decode(r)?,
129 })
130 }
131 _ => Err(anyhow::anyhow!(
132 "unexpected tag ID {tag_id} when decoding TextComponent"
133 )),
134 }
135 }
136}