serde_ply/
lib.rs

1//! Serde-based PLY file format serialization and deserialization.
2//!
3//! This crate provides fast, flexible PLY file parsing and writing using serde.
4//! It supports both ASCII and binary formats, streaming/chunked processing,
5//! and the full PLY specification including list properties.
6//!
7//! # Quick Start
8//!
9//! ```rust
10//! use serde::{Deserialize, Serialize};
11//! use serde_ply::{from_str, to_string, SerializeOptions};
12//!
13//! #[derive(Deserialize, Serialize)]
14//! struct Vertex {
15//!     x: f32,
16//!     y: f32,
17//!     z: f32,
18//! }
19//!
20//! #[derive(Deserialize, Serialize)]
21//! struct Mesh {
22//!     vertex: Vec<Vertex>,
23//! }
24//!
25//! let ply_text = r#"ply
26//! format ascii 1.0
27//! element vertex 2
28//! property float x
29//! property float y
30//! property float z
31//! end_header
32//! 0.0 0.0 0.0
33//! 1.0 1.0 1.0
34//! "#;
35//!
36//! let mesh: Mesh = from_str(ply_text)?;
37//! let output = to_string(&mesh, SerializeOptions::ascii())?;
38//! # Ok::<(), Box<dyn std::error::Error>>(())
39//! ```
40
41mod de;
42mod error;
43mod ser;
44
45pub use de::{
46    chunked::{PlyChunkedReader, RowVisitor},
47    PlyReader,
48};
49pub use de::{from_bytes, from_reader, from_str};
50pub use error::{DeserializeError, SerializeError};
51pub use ser::{to_bytes, to_string, to_writer, SerializeOptions};
52
53use std::io::BufRead;
54
55use std::fmt::{self, Display};
56use std::str::FromStr;
57
58/// PLY file format encoding.
59///
60/// Determines how data is stored in the PLY file - as text or binary with specific byte ordering.
61#[derive(Debug, Clone, Copy, PartialEq)]
62pub enum PlyFormat {
63    /// Plain text format
64    Ascii,
65    /// Binary format with little-endian byte order
66    BinaryLittleEndian,
67    /// Binary format with big-endian byte order
68    BinaryBigEndian,
69}
70
71impl fmt::Display for PlyFormat {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            PlyFormat::Ascii => write!(f, "ascii"),
75            PlyFormat::BinaryLittleEndian => write!(f, "binary_little_endian"),
76            PlyFormat::BinaryBigEndian => write!(f, "binary_big_endian"),
77        }
78    }
79}
80
81/// Scalar data type used in PLY properties.
82///
83/// Maps PLY type names to Rust types. PLY supports both canonical names
84/// (like `float32`) and legacy aliases (like `float`).
85#[derive(Debug, Clone, Copy, PartialEq)]
86pub enum ScalarType {
87    I8,
88    U8,
89    I16,
90    U16,
91    I32,
92    U32,
93    F32,
94    F64,
95}
96
97impl ScalarType {
98    pub(crate) fn parse(s: &str) -> Result<Self, DeserializeError> {
99        match s {
100            "char" | "int8" => Ok(ScalarType::I8),
101            "uchar" | "uint8" => Ok(ScalarType::U8),
102            "short" | "int16" => Ok(ScalarType::I16),
103            "ushort" | "uint16" => Ok(ScalarType::U16),
104            "int" | "int32" => Ok(ScalarType::I32),
105            "uint" | "uint32" => Ok(ScalarType::U32),
106            "float" | "float32" => Ok(ScalarType::F32),
107            "double" | "float64" => Ok(ScalarType::F64),
108            _ => Err(DeserializeError(std::io::Error::new(
109                std::io::ErrorKind::InvalidData,
110                format!("Unknown scalar type: {}", s),
111            ))),
112        }
113    }
114}
115
116impl FromStr for ScalarType {
117    type Err = DeserializeError;
118
119    fn from_str(s: &str) -> Result<Self, Self::Err> {
120        Self::parse(s)
121    }
122}
123
124impl Display for ScalarType {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        match self {
127            ScalarType::I8 => write!(f, "int8"),
128            ScalarType::U8 => write!(f, "uint8"),
129            ScalarType::I16 => write!(f, "int16"),
130            ScalarType::U16 => write!(f, "uint16"),
131            ScalarType::I32 => write!(f, "int32"),
132            ScalarType::U32 => write!(f, "uint32"),
133            ScalarType::F32 => write!(f, "float32"),
134            ScalarType::F64 => write!(f, "float64"),
135        }
136    }
137}
138
139/// PLY property type definition.
140///
141/// Properties can be either single scalar values or variable-length lists.
142/// Lists store a count followed by that many data elements.
143#[derive(Debug, Clone)]
144pub enum PropertyType {
145    /// Single scalar value
146    Scalar(ScalarType),
147    /// Variable-length list with count and data types
148    List {
149        count_type: ScalarType,
150        data_type: ScalarType,
151    },
152}
153
154/// Definition of a single property within a PLY element.
155///
156/// Contains the property name and its type (scalar or list).
157#[derive(Debug, Clone)]
158pub struct PlyProperty {
159    pub name: String,
160    pub property_type: PropertyType,
161}
162
163/// Definition of a PLY element type.
164///
165/// Elements define the structure of data rows in a PLY file. Common examples
166/// are "vertex" and "face" elements. Each element has a count and list of properties.
167#[derive(Debug, Clone)]
168pub struct ElementDef {
169    pub name: String,
170    pub count: usize,
171    pub properties: Vec<PlyProperty>,
172}
173
174impl ElementDef {
175    /// Find a property by name.
176    pub fn get_property(&self, name: &str) -> Option<&PlyProperty> {
177        self.properties.iter().find(|p| p.name == name)
178    }
179
180    /// Check if the element has a property.
181    pub fn has_property(&self, name: &str) -> bool {
182        self.get_property(name).is_some()
183    }
184}
185
186/// PLY file header containing format, elements, and metadata.
187///
188/// The header defines the structure of the entire PLY file including
189/// data format, element definitions, and optional comments.
190#[derive(Debug, Clone)]
191pub struct PlyHeader {
192    pub format: PlyFormat,
193    pub elem_defs: Vec<ElementDef>,
194    pub comments: Vec<String>,
195    pub obj_info: Vec<String>,
196}
197
198impl PlyHeader {
199    pub(crate) fn parse<R: BufRead>(mut reader: R) -> Result<Self, DeserializeError> {
200        let mut line = String::new();
201        reader.read_line(&mut line)?;
202        if line.trim() != "ply" {
203            return Err(DeserializeError(std::io::Error::new(
204                std::io::ErrorKind::InvalidData,
205                "File must start with 'ply'",
206            )));
207        }
208
209        let mut format = None;
210        let mut elements = Vec::new();
211        let mut comments = Vec::new();
212        let mut obj_info = Vec::new();
213        let mut current_element: Option<ElementDef> = None;
214
215        loop {
216            let mut line = String::new();
217            let bytes_read = reader.read_line(&mut line)?;
218            if bytes_read == 0 {
219                return Err(DeserializeError(std::io::Error::new(
220                    std::io::ErrorKind::UnexpectedEof,
221                    "Unexpected end of file",
222                )));
223            }
224
225            // We have reached the end of the header. Don't really care
226            // about whitespace here, BUT we do have to be sure that the newline IS present.
227            // If its not, the line ends at EOF, and the next chunk of data could then incorrectly contain this newline.
228            if line.trim() == "end_header" && line.ends_with("\n") {
229                break;
230            }
231            let parts: Vec<&str> = line.split_whitespace().collect();
232            if parts.is_empty() {
233                continue;
234            }
235            match parts[0] {
236                "format" => {
237                    if parts.len() < 3 {
238                        return Err(DeserializeError(std::io::Error::new(
239                            std::io::ErrorKind::InvalidData,
240                            "Invalid format line",
241                        )));
242                    }
243                    format = Some(match parts[1] {
244                        "ascii" => PlyFormat::Ascii,
245                        "binary_little_endian" => PlyFormat::BinaryLittleEndian,
246                        "binary_big_endian" => PlyFormat::BinaryBigEndian,
247                        _ => {
248                            return Err(DeserializeError(std::io::Error::new(
249                                std::io::ErrorKind::InvalidData,
250                                format!("Unknown format: {}", parts[1]),
251                            )))
252                        }
253                    });
254                }
255                "comment" => {
256                    comments.push(parts[1..].join(" "));
257                }
258                "obj_info" => {
259                    obj_info.push(parts[1..].join(" "));
260                }
261                "element" => {
262                    if parts.len() < 3 {
263                        return Err(DeserializeError(std::io::Error::new(
264                            std::io::ErrorKind::InvalidData,
265                            "Invalid element line",
266                        )));
267                    }
268
269                    if let Some(element) = current_element.take() {
270                        elements.push(element);
271                    }
272
273                    let name = parts[1].to_string();
274                    let count = parts[2].parse::<usize>().map_err(|_| {
275                        DeserializeError(std::io::Error::new(
276                            std::io::ErrorKind::InvalidData,
277                            format!("Invalid element count: {}", parts[2]),
278                        ))
279                    })?;
280
281                    current_element = Some(ElementDef {
282                        name,
283                        count,
284                        properties: Vec::new(),
285                    });
286                }
287                "property" => {
288                    let element = current_element.as_mut().ok_or_else(|| {
289                        DeserializeError(std::io::Error::new(
290                            std::io::ErrorKind::InvalidData,
291                            "Property without element",
292                        ))
293                    })?;
294
295                    if parts.len() < 3 {
296                        return Err(DeserializeError(std::io::Error::new(
297                            std::io::ErrorKind::InvalidData,
298                            "Invalid property line",
299                        )));
300                    }
301
302                    if parts[1] == "list" {
303                        // List property: property list <count_type> <data_type> <name>
304                        if parts.len() < 5 {
305                            return Err(DeserializeError(std::io::Error::new(
306                                std::io::ErrorKind::InvalidData,
307                                "Invalid list property line",
308                            )));
309                        }
310                        let count_type = ScalarType::parse(parts[2])?;
311                        let data_type = ScalarType::parse(parts[3])?;
312                        let name = parts[4].to_string();
313
314                        element.properties.push(PlyProperty {
315                            property_type: PropertyType::List {
316                                count_type,
317                                data_type,
318                            },
319                            name,
320                        });
321                    } else {
322                        let data_type = ScalarType::parse(parts[1])?;
323                        let name = parts[2].to_string();
324
325                        element.properties.push(PlyProperty {
326                            property_type: PropertyType::Scalar(data_type),
327                            name,
328                        });
329                    }
330                }
331                _ => {}
332            }
333        }
334        if let Some(element) = current_element {
335            elements.push(element);
336        }
337        let format = format.ok_or_else(|| {
338            DeserializeError(std::io::Error::new(
339                std::io::ErrorKind::InvalidData,
340                "Missing format specification",
341            ))
342        })?;
343        Ok(PlyHeader {
344            format,
345            elem_defs: elements,
346            comments,
347            obj_info,
348        })
349    }
350
351    /// Find an element definition by name.
352    pub fn get_element(&self, name: &str) -> Option<ElementDef> {
353        self.elem_defs.iter().find(|e| e.name == name).cloned()
354    }
355
356    /// Check if the header contains an element with the given name.
357    pub fn has_element(&self, name: &str) -> bool {
358        self.elem_defs.iter().any(|e| e.name == name)
359    }
360}
361
362/// Wrapper to serialize PLY lists with `u16` count type.
363///
364/// By default, PLY lists use `u8` for the element count. Use this wrapper
365/// when you need larger counts.
366///
367/// # Example
368/// ```rust
369/// use serde::{Serialize, Deserialize};
370/// use serde_ply::ListCountU16;
371///
372/// #[derive(Serialize, Deserialize)]
373/// struct Face {
374///     // This list can have up to 65535 vertices
375///     vertex_indices: ListCountU16<Vec<u32>>,
376/// }
377/// ```
378#[derive(Debug)]
379pub struct ListCountU16<T>(pub T);
380
381/// Wrapper to serialize PLY lists with `u32` count type.
382///
383/// Use this wrapper when you need very large element counts in lists.
384///
385/// # Example
386/// ```rust
387/// use serde::{Serialize, Deserialize};
388/// use serde_ply::ListCountU32;
389///
390/// #[derive(Serialize, Deserialize)]
391/// struct LargeFace {
392///     // This list can have up to 4 billion vertices
393///     vertex_indices: ListCountU32<Vec<u32>>,
394/// }
395/// ```
396#[derive(Debug)]
397pub struct ListCountU32<T>(pub T);
398
399// Implement common traits for all ListCount types
400macro_rules! impl_list_count_traits {
401    ($wrapper:ident) => {
402        impl<T> From<T> for $wrapper<T> {
403            fn from(inner: T) -> Self {
404                $wrapper(inner)
405            }
406        }
407        impl<T> std::ops::Deref for $wrapper<T> {
408            type Target = T;
409            fn deref(&self) -> &Self::Target {
410                &self.0
411            }
412        }
413
414        impl<T> std::ops::DerefMut for $wrapper<T> {
415            fn deref_mut(&mut self) -> &mut Self::Target {
416                &mut self.0
417            }
418        }
419
420        impl<T> serde::Serialize for $wrapper<T>
421        where
422            T: serde::Serialize,
423        {
424            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
425            where
426                S: serde::Serializer,
427            {
428                serializer.serialize_newtype_struct(stringify!($wrapper), &self.0)
429            }
430        }
431
432        impl<'de, T> serde::Deserialize<'de> for $wrapper<T>
433        where
434            T: serde::Deserialize<'de>,
435        {
436            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
437            where
438                D: serde::Deserializer<'de>,
439            {
440                T::deserialize(deserializer).map($wrapper)
441            }
442        }
443    };
444}
445
446impl_list_count_traits!(ListCountU16);
447impl_list_count_traits!(ListCountU32);