spatialrust_io/ply/
header.rs1use std::io::BufRead;
2
3use crate::error::{ply_format, ply_parse, IoError};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum PlyFormat {
8 Ascii,
10 BinaryLittleEndian,
12}
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum PlyPropertyKind {
17 Char,
19 UChar,
21 Short,
23 UShort,
25 Int,
27 UInt,
29 Float,
31 Double,
33}
34
35impl PlyPropertyKind {
36 #[must_use]
38 pub const fn size_bytes(self) -> usize {
39 match self {
40 Self::Char | Self::UChar => 1,
41 Self::Short | Self::UShort => 2,
42 Self::Int | Self::UInt | Self::Float => 4,
43 Self::Double => 8,
44 }
45 }
46
47 fn parse(token: &str) -> Result<Self, IoError> {
48 match token {
49 "char" | "int8" => Ok(Self::Char),
50 "uchar" | "uint8" => Ok(Self::UChar),
51 "short" | "int16" => Ok(Self::Short),
52 "ushort" | "uint16" => Ok(Self::UShort),
53 "int" | "int32" => Ok(Self::Int),
54 "uint" | "uint32" => Ok(Self::UInt),
55 "float" | "float32" => Ok(Self::Float),
56 "double" | "float64" => Ok(Self::Double),
57 _ => Err(ply_parse(format!("unsupported PLY property type `{token}`"))),
58 }
59 }
60
61 fn as_token(self) -> &'static str {
62 match self {
63 Self::Char => "char",
64 Self::UChar => "uchar",
65 Self::Short => "short",
66 Self::UShort => "ushort",
67 Self::Int => "int",
68 Self::UInt => "uint",
69 Self::Float => "float",
70 Self::Double => "double",
71 }
72 }
73}
74
75#[derive(Clone, Debug, PartialEq, Eq)]
77pub struct PlyProperty {
78 pub name: String,
80 pub kind: PlyPropertyKind,
82}
83
84#[derive(Clone, Debug, PartialEq, Eq)]
86pub struct PlyHeader {
87 pub format: PlyFormat,
89 pub vertex_count: usize,
91 pub properties: Vec<PlyProperty>,
93}
94
95impl PlyHeader {
96 pub fn parse<R: BufRead>(reader: &mut R) -> Result<Self, IoError> {
98 let first = read_non_empty_line(reader)?;
99 if first != "ply" {
100 return Err(ply_format(format!("expected PLY magic, found `{first}`")));
101 }
102
103 let mut format = None;
104 let mut vertex_count = None;
105 let mut properties = Vec::new();
106 let mut current_element: Option<String> = None;
107
108 loop {
109 let line = read_non_empty_line(reader)?;
110 if line == "end_header" {
111 break;
112 }
113
114 let mut parts = line.split_whitespace();
115 let keyword = parts.next().ok_or_else(|| ply_parse("empty PLY header line"))?;
116 match keyword {
117 "format" => {
118 let token =
119 parts.next().ok_or_else(|| ply_parse("missing PLY format token"))?;
120 let version =
121 parts.next().ok_or_else(|| ply_parse("missing PLY format version"))?;
122 if version != "1.0" {
123 return Err(ply_format(format!("unsupported PLY version `{version}`")));
124 }
125 format = Some(match token {
126 "ascii" => PlyFormat::Ascii,
127 "binary_little_endian" => PlyFormat::BinaryLittleEndian,
128 "binary_big_endian" => {
129 return Err(ply_format("binary_big_endian PLY is not supported"));
130 }
131 _ => return Err(ply_format(format!("unsupported PLY format `{token}`"))),
132 });
133 }
134 "element" => {
135 let name = parts
136 .next()
137 .ok_or_else(|| ply_parse("missing PLY element name"))?
138 .to_owned();
139 let count = parts
140 .next()
141 .ok_or_else(|| ply_parse("missing PLY element count"))?
142 .parse::<usize>()
143 .map_err(|_| ply_parse("invalid PLY element count"))?;
144 if name == "vertex" {
145 if vertex_count.is_some() {
146 return Err(ply_format("duplicate vertex element in PLY header"));
147 }
148 vertex_count = Some(count);
149 } else {
150 return Err(ply_format(format!(
151 "unsupported PLY element `{name}` (only vertex is supported)"
152 )));
153 }
154 current_element = Some(name);
155 }
156 "property" => {
157 let element = current_element
158 .as_deref()
159 .ok_or_else(|| ply_parse("PLY property declared before element"))?;
160 if element != "vertex" {
161 return Err(ply_format("only vertex properties are supported"));
162 }
163 let kind_token =
164 parts.next().ok_or_else(|| ply_parse("missing PLY property type"))?;
165 let name = parts
166 .next()
167 .ok_or_else(|| ply_parse("missing PLY property name"))?
168 .to_owned();
169 properties
170 .push(PlyProperty { name, kind: PlyPropertyKind::parse(kind_token)? });
171 }
172 "comment" | "obj_info" => {}
173 _ => return Err(ply_parse(format!("unsupported PLY header keyword `{keyword}`"))),
174 }
175 }
176
177 Ok(Self {
178 format: format.ok_or_else(|| ply_format("missing PLY format line"))?,
179 vertex_count: vertex_count.ok_or_else(|| ply_format("missing vertex element"))?,
180 properties,
181 })
182 }
183
184 #[must_use]
186 pub fn vertex_stride(&self) -> usize {
187 self.properties.iter().map(|property| property.kind.size_bytes()).sum()
188 }
189
190 pub fn write_header<W: std::io::Write>(&self, writer: &mut W) -> Result<(), IoError> {
192 writeln!(writer, "ply")?;
193 let format = match self.format {
194 PlyFormat::Ascii => "ascii 1.0",
195 PlyFormat::BinaryLittleEndian => "binary_little_endian 1.0",
196 };
197 writeln!(writer, "format {format}")?;
198 writeln!(writer, "element vertex {}", self.vertex_count)?;
199 for property in &self.properties {
200 writeln!(writer, "property {} {}", property.kind.as_token(), property.name)?;
201 }
202 writeln!(writer, "end_header")?;
203 Ok(())
204 }
205}
206
207fn read_non_empty_line<R: BufRead>(reader: &mut R) -> Result<String, IoError> {
208 loop {
209 let mut line = String::new();
210 let read = reader.read_line(&mut line)?;
211 if read == 0 {
212 return Err(ply_parse("unexpected EOF while reading PLY header"));
213 }
214 let trimmed = line.trim();
215 if trimmed.is_empty() {
216 continue;
217 }
218 return Ok(trimmed.to_owned());
219 }
220}