1pub mod delimited;
4pub mod obj;
5pub mod pcd;
6pub mod ply;
7pub mod pts;
8pub mod ptx;
9pub mod stl;
10
11#[cfg(feature = "copc")]
12pub mod copc;
13#[cfg(feature = "e57")]
14pub mod e57;
15#[cfg(feature = "geospatial")]
16pub mod geojson;
17#[cfg(feature = "geospatial")]
18pub mod geotiff;
19#[cfg(feature = "las")]
20pub mod las;
21
22pub mod asciigrid;
23#[cfg(feature = "dxf")]
24pub mod dxf;
25#[cfg(feature = "gltf")]
26pub mod gltf;
27#[cfg(feature = "gpkg")]
28pub mod gpkg;
29#[cfg(feature = "robotics")]
30pub mod robotics;
31#[cfg(feature = "sensor")]
32pub mod sensor;
33#[cfg(feature = "shapefile")]
34pub mod shapefile;
35
36use crate::error::{Error, Result};
37use crate::format::Format;
38use crate::types::Geometry;
39use std::fs::File;
40use std::io::{BufReader, BufWriter};
41use std::path::Path;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum Delimiter {
46 Auto,
47 Whitespace,
48 Comma,
49 Tab,
50 Semicolon,
51}
52
53impl Delimiter {
54 #[inline]
55 pub(crate) fn split_into_slice<'a>(self, line: &'a str, fields: &mut [&'a str]) -> usize {
56 let mut count = 0;
57 let limit = fields.len();
58 match self {
59 Self::Auto => Self::detect(line).split_into_slice(line, fields),
60 Self::Whitespace => {
61 for part in line.split_whitespace() {
62 if count < limit {
63 fields[count] = part;
64 count += 1;
65 } else {
66 break;
67 }
68 }
69 count
70 }
71 Self::Comma => {
72 for part in line.split(',') {
73 if count < limit {
74 fields[count] = part.trim();
75 count += 1;
76 } else {
77 break;
78 }
79 }
80 count
81 }
82 Self::Tab => {
83 for part in line.split('\t') {
84 if count < limit {
85 fields[count] = part.trim();
86 count += 1;
87 } else {
88 break;
89 }
90 }
91 count
92 }
93 Self::Semicolon => {
94 for part in line.split(';') {
95 if count < limit {
96 fields[count] = part.trim();
97 count += 1;
98 } else {
99 break;
100 }
101 }
102 count
103 }
104 }
105 }
106
107 #[inline]
108 pub(crate) fn detect(line: &str) -> Self {
109 if line.contains(',') {
110 Self::Comma
111 } else if line.contains(';') {
112 Self::Semicolon
113 } else if line.contains('\t') {
114 Self::Tab
115 } else {
116 Self::Whitespace
117 }
118 }
119
120 #[inline]
121 pub(crate) const fn as_str(self) -> &'static str {
122 match self {
123 Self::Auto | Self::Whitespace => " ",
124 Self::Comma => ",",
125 Self::Tab => "\t",
126 Self::Semicolon => ";",
127 }
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct ColumnMapping {
134 pub x: usize,
135 pub y: usize,
136 pub z: usize,
137 pub intensity: Option<usize>,
138 pub red: Option<usize>,
139 pub green: Option<usize>,
140 pub blue: Option<usize>,
141 pub classification: Option<usize>,
142 pub gps_time: Option<usize>,
143 pub normal_x: Option<usize>,
144 pub normal_y: Option<usize>,
145 pub normal_z: Option<usize>,
146}
147
148impl Default for ColumnMapping {
149 fn default() -> Self {
150 Self {
151 x: 0,
152 y: 1,
153 z: 2,
154 intensity: Some(3),
155 red: Some(4),
156 green: Some(5),
157 blue: Some(6),
158 classification: Some(7),
159 gps_time: Some(8),
160 normal_x: Some(9),
161 normal_y: Some(10),
162 normal_z: Some(11),
163 }
164 }
165}
166
167impl ColumnMapping {
168 pub(crate) fn from_header(header: &[&str]) -> Option<Self> {
169 fn find(header: &[&str], names: &[&str]) -> Option<usize> {
170 header.iter().position(|value| {
171 let normalized = value
172 .trim()
173 .trim_matches('"')
174 .trim_matches('\'')
175 .to_ascii_lowercase()
176 .replace([' ', '-', '.'], "_");
177 names.iter().any(|candidate| normalized == *candidate)
178 })
179 }
180
181 let x = find(header, &["x", "easting", "east", "lon", "longitude"])?;
182 let y = find(header, &["y", "northing", "north", "lat", "latitude"])?;
183 let z = find(header, &["z", "elevation", "height", "altitude"])?;
184 Some(Self {
185 x,
186 y,
187 z,
188 intensity: find(header, &["intensity", "i"]),
189 red: find(header, &["red", "r"]),
190 green: find(header, &["green", "g"]),
191 blue: find(header, &["blue", "b"]),
192 classification: find(header, &["classification", "class", "label"]),
193 gps_time: find(header, &["gps_time", "gpstime", "time", "timestamp"]),
194 normal_x: find(header, &["normal_x", "nx", "n_x"]),
195 normal_y: find(header, &["normal_y", "ny", "n_y"]),
196 normal_z: find(header, &["normal_z", "nz", "n_z"]),
197 })
198 }
199}
200
201#[derive(Debug, Clone, PartialEq, Eq)]
203pub struct DelimitedOptions {
204 pub delimiter: Delimiter,
205 pub has_header: Option<bool>,
207 pub columns: ColumnMapping,
208 pub write_header: bool,
209 pub precision: usize,
210}
211
212impl Default for DelimitedOptions {
213 fn default() -> Self {
214 Self {
215 delimiter: Delimiter::Auto,
216 has_header: None,
217 columns: ColumnMapping::default(),
218 write_header: false,
219 precision: 6,
220 }
221 }
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum PlyEncoding {
227 Ascii,
228 BinaryLittleEndian,
229}
230
231#[derive(Debug, Clone, PartialEq, Eq)]
233pub struct PlyOptions {
234 pub encoding: PlyEncoding,
235 pub precision: usize,
236}
237
238impl Default for PlyOptions {
239 fn default() -> Self {
240 Self {
241 encoding: PlyEncoding::Ascii,
242 precision: 6,
243 }
244 }
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
249pub enum PcdEncoding {
250 Ascii,
251 Binary,
252}
253
254#[derive(Debug, Clone, PartialEq, Eq)]
256pub struct PcdOptions {
257 pub encoding: PcdEncoding,
258 pub precision: usize,
259}
260
261impl Default for PcdOptions {
262 fn default() -> Self {
263 Self {
264 encoding: PcdEncoding::Ascii,
265 precision: 6,
266 }
267 }
268}
269
270#[derive(Debug, Clone, PartialEq, Eq)]
272pub struct StlOptions {
273 pub binary: bool,
274}
275
276impl Default for StlOptions {
277 fn default() -> Self {
278 Self { binary: true }
279 }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Default)]
284pub struct NativeOptions {
285 pub delimited: DelimitedOptions,
286 pub ply: PlyOptions,
287 pub pcd: PcdOptions,
288 pub stl: StlOptions,
289}
290
291pub fn read_path(
292 path: impl AsRef<Path>,
293 format: Format,
294 options: &NativeOptions,
295) -> Result<Geometry> {
296 let path = path.as_ref();
297
298 #[cfg(feature = "shapefile")]
299 if matches!(format, Format::Shapefile) {
300 return shapefile::read(path);
301 }
302
303 #[cfg(feature = "gltf")]
304 if matches!(format, Format::Gltf | Format::Glb) {
305 return gltf::read(path);
306 }
307
308 #[cfg(feature = "gpkg")]
309 if matches!(format, Format::Gpkg) {
310 return gpkg::read(path);
311 }
312
313 #[cfg(feature = "robotics")]
314 if matches!(format, Format::RosBag) {
315 return robotics::read_rosbag(path);
316 }
317
318 #[cfg(feature = "robotics")]
319 if matches!(format, Format::Ros2Bag) {
320 return robotics::read_ros2bag(path);
321 }
322
323 #[cfg(feature = "robotics")]
324 if matches!(format, Format::PointCloud2) {
325 return robotics::read_pc2(path);
326 }
327
328 #[cfg(feature = "sensor")]
329 if matches!(format, Format::Pcap) {
330 return sensor::read_pcap(path);
331 }
332
333 #[cfg(feature = "sensor")]
334 if matches!(format, Format::UdpPackets) {
335 return sensor::read_udppackets(path);
336 }
337
338 #[cfg(feature = "sensor")]
339 if matches!(format, Format::VendorRaw) {
340 return sensor::read_vendorraw(path);
341 }
342
343 let file = File::open(path)?;
344 let mut reader = BufReader::new(file);
345 match format {
346 Format::Xyz | Format::Txt | Format::Csv => {
347 let mut opts = options.delimited.clone();
348 if matches!(format, Format::Csv) && matches!(opts.delimiter, Delimiter::Auto) {
349 opts.delimiter = Delimiter::Comma;
350 opts.write_header = true;
351 }
352 delimited::read(&mut reader, format, &opts).map(Geometry::PointCloud)
353 }
354 Format::Pts => pts::read(&mut reader).map(Geometry::PointCloud),
355 Format::Ptx => ptx::read(&mut reader).map(Geometry::PointCloud),
356 Format::Ply => ply::read(&mut reader),
357 Format::Pcd => pcd::read(&mut reader),
358 Format::Obj => obj::read(&mut reader),
359 Format::Stl => stl::read(&mut reader),
360 Format::AsciiGrid => asciigrid::read(&mut reader),
361
362 #[cfg(feature = "dxf")]
363 Format::Dxf => dxf::read(&mut reader),
364
365 #[cfg(feature = "las")]
366 Format::Las | Format::Laz => las::read(reader),
367
368 #[cfg(feature = "copc")]
369 Format::Copc => copc::read(&mut reader),
370
371 #[cfg(feature = "e57")]
372 Format::E57 => e57::read(&mut reader),
373
374 #[cfg(feature = "geospatial")]
375 Format::GeoTiff | Format::Cog => geotiff::read(reader),
376
377 #[cfg(feature = "geospatial")]
378 Format::GeoJson => geojson::read(reader),
379
380 _ => Err(Error::unsupported(format, "read", format.adapter_hint())),
381 }
382}
383
384pub fn write_path(
385 path: impl AsRef<Path>,
386 format: Format,
387 geometry: &Geometry,
388 options: &NativeOptions,
389) -> Result<()> {
390 let path = path.as_ref();
391
392 #[cfg(feature = "e57")]
393 if matches!(format, Format::E57) {
394 let cloud = as_cloud_for_point_format(geometry, format)?;
395 return e57::write_to_path(path, cloud);
396 }
397
398 #[cfg(feature = "shapefile")]
399 if matches!(format, Format::Shapefile) {
400 let cloud = as_cloud_for_point_format(geometry, format)?;
401 return shapefile::write(path, cloud);
402 }
403
404 #[cfg(feature = "gltf")]
405 if matches!(format, Format::Gltf) {
406 return gltf::write_gltf(path, geometry);
407 }
408
409 #[cfg(feature = "gltf")]
410 if matches!(format, Format::Glb) {
411 return gltf::write_glb(path, geometry);
412 }
413
414 #[cfg(feature = "gpkg")]
415 if matches!(format, Format::Gpkg) {
416 let cloud = as_cloud_for_point_format(geometry, format)?;
417 return gpkg::write(path, cloud);
418 }
419
420 #[cfg(feature = "robotics")]
421 if matches!(format, Format::RosBag) {
422 let cloud = as_cloud_for_point_format(geometry, format)?;
423 return robotics::write_rosbag(path, cloud);
424 }
425
426 #[cfg(feature = "robotics")]
427 if matches!(format, Format::Ros2Bag) {
428 let cloud = as_cloud_for_point_format(geometry, format)?;
429 return robotics::write_ros2bag(path, cloud);
430 }
431
432 #[cfg(feature = "robotics")]
433 if matches!(format, Format::PointCloud2) {
434 let cloud = as_cloud_for_point_format(geometry, format)?;
435 return robotics::write_pc2(path, cloud);
436 }
437
438 #[cfg(feature = "sensor")]
439 if matches!(format, Format::Pcap) {
440 let cloud = as_cloud_for_point_format(geometry, format)?;
441 return sensor::write_pcap(path, cloud);
442 }
443
444 #[cfg(feature = "sensor")]
445 if matches!(format, Format::UdpPackets) {
446 let cloud = as_cloud_for_point_format(geometry, format)?;
447 return sensor::write_udppackets(path, cloud);
448 }
449
450 #[cfg(feature = "sensor")]
451 if matches!(format, Format::VendorRaw) {
452 let cloud = as_cloud_for_point_format(geometry, format)?;
453 return sensor::write_vendorraw(path, cloud);
454 }
455
456 let file = File::create(path)?;
457 let mut writer = BufWriter::new(file);
458 match format {
459 Format::Xyz | Format::Txt | Format::Csv => {
460 let cloud = as_cloud_for_point_format(geometry, format)?;
461 let mut opts = options.delimited.clone();
462 if matches!(format, Format::Csv) {
463 if matches!(opts.delimiter, Delimiter::Auto) {
464 opts.delimiter = Delimiter::Comma;
465 }
466 opts.write_header = true;
467 }
468 delimited::write(&mut writer, format, cloud, &opts)
469 }
470 Format::Pts => pts::write(&mut writer, as_cloud_for_point_format(geometry, format)?),
471 Format::Ptx => ptx::write(&mut writer, as_cloud_for_point_format(geometry, format)?),
472 Format::Ply => ply::write(&mut writer, geometry, &options.ply),
473 Format::Pcd => pcd::write(
474 &mut writer,
475 as_cloud_for_point_format(geometry, format)?,
476 &options.pcd,
477 ),
478 Format::Obj => obj::write(&mut writer, geometry),
479 Format::Stl => stl::write(&mut writer, geometry, &options.stl),
480 Format::AsciiGrid => {
481 let cloud = as_cloud_for_point_format(geometry, format)?;
482 asciigrid::write(&mut writer, cloud)
483 }
484
485 #[cfg(feature = "dxf")]
486 Format::Dxf => dxf::write(&mut writer, geometry),
487
488 #[cfg(feature = "las")]
489 Format::Las | Format::Laz => {
490 let cloud = as_cloud_for_point_format(geometry, format)?;
491 las::write(writer, cloud)
492 }
493
494 #[cfg(feature = "geospatial")]
495 Format::GeoTiff | Format::Cog => {
496 let cloud = as_cloud_for_point_format(geometry, format)?;
497 geotiff::write(writer, cloud)
498 }
499
500 #[cfg(feature = "geospatial")]
501 Format::GeoJson => {
502 let cloud = as_cloud_for_point_format(geometry, format)?;
503 geojson::write(writer, cloud)
504 }
505
506 _ => Err(Error::unsupported(format, "write", format.adapter_hint())),
507 }
508}
509
510fn as_cloud_for_point_format(
511 geometry: &Geometry,
512 format: Format,
513) -> Result<&crate::types::PointCloud> {
514 match geometry {
515 Geometry::PointCloud(cloud) => Ok(cloud),
516 Geometry::Mesh(_) => Err(Error::LossyConversionBlocked {
517 from: "mesh",
518 to: format,
519 reason: "the destination is a point-cloud format and cannot preserve faces".to_string(),
520 }),
521 }
522}
523
524#[cold]
525#[inline(never)]
526fn numeric_parse_error(format: Format, line: usize, name: &str, value: &str) -> Error {
527 Error::parse(
528 format,
529 line,
530 format!("expected numeric {name}, got '{value}'"),
531 )
532}
533
534#[inline]
535pub(crate) fn parse_f64(format: Format, line: usize, name: &str, value: &str) -> Result<f64> {
536 value
537 .parse::<f64>()
538 .map_err(|_| numeric_parse_error(format, line, name, value))
539}
540
541#[inline]
542pub(crate) fn parse_f32(format: Format, line: usize, name: &str, value: &str) -> Result<f32> {
543 value
544 .parse::<f32>()
545 .map_err(|_| numeric_parse_error(format, line, name, value))
546}
547
548#[cold]
549#[inline(never)]
550fn range_parse_error(format: Format, line: usize, name: &str, value: &str, limit: &str) -> Error {
551 Error::parse(
552 format,
553 line,
554 format!("expected {name} in range {limit}, got '{value}'"),
555 )
556}
557
558#[inline]
559pub(crate) fn parse_u8(format: Format, line: usize, name: &str, value: &str) -> Result<u8> {
560 if let Ok(v) = value.parse::<u8>() {
561 return Ok(v);
562 }
563 let as_float = parse_f64(format, line, name, value)?;
564 if as_float.fract() == 0.0 && (0.0..=u8::MAX as f64).contains(&as_float) {
565 Ok(as_float as u8)
566 } else {
567 Err(range_parse_error(format, line, name, value, "0..255"))
568 }
569}
570
571#[inline]
572pub(crate) fn parse_u16(format: Format, line: usize, name: &str, value: &str) -> Result<u16> {
573 if let Ok(v) = value.parse::<u16>() {
574 return Ok(v);
575 }
576 let as_float = parse_f64(format, line, name, value)?;
577 if as_float.fract() == 0.0 && (0.0..=u16::MAX as f64).contains(&as_float) {
578 Ok(as_float as u16)
579 } else {
580 Err(range_parse_error(format, line, name, value, "0..65535"))
581 }
582}
583
584#[inline]
585pub(crate) fn write_fmt_f64<W: std::io::Write>(
586 writer: &mut W,
587 value: f64,
588 precision: usize,
589) -> std::io::Result<()> {
590 if value == 0.0 {
591 write!(writer, "{:.*}", precision, 0.0)
592 } else {
593 write!(writer, "{:.*}", precision, value)
594 }
595}
596
597#[inline]
598pub(crate) fn fmt_f64(value: f64, precision: usize) -> String {
599 if value == 0.0 {
600 return format!("{:.*}", precision, 0.0);
602 }
603 format!("{:.*}", precision, value)
604}
605
606#[inline]
607pub(crate) fn write_f32_le<W: std::io::Write>(writer: &mut W, value: f32) -> Result<()> {
608 writer.write_all(&value.to_le_bytes())?;
609 Ok(())
610}
611
612#[inline]
613pub(crate) fn write_f64_le<W: std::io::Write>(writer: &mut W, value: f64) -> Result<()> {
614 writer.write_all(&value.to_le_bytes())?;
615 Ok(())
616}
617
618#[inline]
619pub(crate) fn write_u16_le<W: std::io::Write>(writer: &mut W, value: u16) -> Result<()> {
620 writer.write_all(&value.to_le_bytes())?;
621 Ok(())
622}
623
624#[inline]
625pub(crate) fn write_u32_le<W: std::io::Write>(writer: &mut W, value: u32) -> Result<()> {
626 writer.write_all(&value.to_le_bytes())?;
627 Ok(())
628}
629
630#[inline]
631pub(crate) fn read_exact<const N: usize, R: std::io::Read>(reader: &mut R) -> Result<[u8; N]> {
632 let mut bytes = [0_u8; N];
633 reader.read_exact(&mut bytes)?;
634 Ok(bytes)
635}
636
637#[inline]
638pub(crate) fn read_f32_le<R: std::io::Read>(reader: &mut R) -> Result<f32> {
639 Ok(f32::from_le_bytes(read_exact(reader)?))
640}
641
642#[inline]
643pub(crate) fn read_f64_le<R: std::io::Read>(reader: &mut R) -> Result<f64> {
644 Ok(f64::from_le_bytes(read_exact(reader)?))
645}
646
647#[inline]
648pub(crate) fn read_u16_le<R: std::io::Read>(reader: &mut R) -> Result<u16> {
649 Ok(u16::from_le_bytes(read_exact(reader)?))
650}
651
652#[inline]
653pub(crate) fn read_u32_le<R: std::io::Read>(reader: &mut R) -> Result<u32> {
654 Ok(u32::from_le_bytes(read_exact(reader)?))
655}
656
657#[cfg(test)]
658mod tests {
659 use super::*;
660 use std::io::Cursor;
661
662 #[test]
663 fn test_delimiter_logic() {
664 assert_eq!(Delimiter::detect("1,2,3"), Delimiter::Comma);
665 assert_eq!(Delimiter::detect("1;2;3"), Delimiter::Semicolon);
666 assert_eq!(Delimiter::detect("1\t2\t3"), Delimiter::Tab);
667 assert_eq!(Delimiter::detect("1 2 3"), Delimiter::Whitespace);
668
669 assert_eq!(Delimiter::Comma.as_str(), ",");
670 assert_eq!(Delimiter::Tab.as_str(), "\t");
671 assert_eq!(Delimiter::Semicolon.as_str(), ";");
672 assert_eq!(Delimiter::Auto.as_str(), " ");
673
674 let mut fields = [""; 4];
675 let count = Delimiter::Comma.split_into_slice("a, b , c", &mut fields);
676 assert_eq!(count, 3);
677 assert_eq!(fields[0], "a");
678 assert_eq!(fields[1], "b");
679 assert_eq!(fields[2], "c");
680
681 let count_tab = Delimiter::Tab.split_into_slice("a\tb\tc", &mut fields);
682 assert_eq!(count_tab, 3);
683 assert_eq!(fields[1], "b");
684
685 let count_semi = Delimiter::Semicolon.split_into_slice("a;b;c", &mut fields);
686 assert_eq!(count_semi, 3);
687 assert_eq!(fields[1], "b");
688
689 let count_white = Delimiter::Whitespace.split_into_slice("a b c", &mut fields);
690 assert_eq!(count_white, 3);
691 assert_eq!(fields[2], "c");
692
693 let count_auto = Delimiter::Auto.split_into_slice("a,b,c", &mut fields);
694 assert_eq!(count_auto, 3);
695 assert_eq!(fields[1], "b");
696 }
697
698 #[test]
699 fn test_column_mapping() {
700 let header = [
701 "x",
702 "y",
703 "elevation",
704 "intensity",
705 "red",
706 "green",
707 "blue",
708 "classification",
709 "gps_time",
710 "normal_x",
711 "normal_y",
712 "normal_z",
713 ];
714 let mapping = ColumnMapping::from_header(&header).unwrap();
715 assert_eq!(mapping.x, 0);
716 assert_eq!(mapping.y, 1);
717 assert_eq!(mapping.z, 2);
718 assert_eq!(mapping.intensity, Some(3));
719 assert_eq!(mapping.red, Some(4));
720 assert_eq!(mapping.green, Some(5));
721 assert_eq!(mapping.blue, Some(6));
722 assert_eq!(mapping.classification, Some(7));
723 assert_eq!(mapping.gps_time, Some(8));
724 assert_eq!(mapping.normal_x, Some(9));
725
726 let bad_header = ["intensity"];
727 assert!(ColumnMapping::from_header(&bad_header).is_none());
728 }
729
730 #[test]
731 fn test_numeric_writing_and_parsing() {
732 let mut writer = Vec::new();
733 write_fmt_f64(&mut writer, 0.0, 4).unwrap();
734 assert_eq!(String::from_utf8(writer).unwrap(), "0.0000");
735
736 let mut writer2 = Vec::new();
737 write_fmt_f64(&mut writer2, 1.234, 2).unwrap();
738 assert_eq!(String::from_utf8(writer2).unwrap(), "1.23");
739
740 assert_eq!(fmt_f64(0.0, 3), "0.000");
741 assert_eq!(fmt_f64(-1.23, 1), "-1.2");
742
743 assert!(parse_f64(Format::Pts, 1, "test", "abc").is_err());
744 assert!(parse_f32(Format::Pts, 1, "test", "abc").is_err());
745
746 assert_eq!(parse_u8(Format::Pts, 1, "test", "123").unwrap(), 123);
747 assert_eq!(parse_u8(Format::Pts, 1, "test", "123.0").unwrap(), 123);
748 assert!(parse_u8(Format::Pts, 1, "test", "256").is_err());
749 assert!(parse_u8(Format::Pts, 1, "test", "abc").is_err());
750 assert!(parse_u8(Format::Pts, 1, "test", "1.5").is_err());
751
752 assert_eq!(parse_u16(Format::Pts, 1, "test", "1000").unwrap(), 1000);
753 assert_eq!(parse_u16(Format::Pts, 1, "test", "1000.0").unwrap(), 1000);
754 assert!(parse_u16(Format::Pts, 1, "test", "70000").is_err());
755 assert!(parse_u16(Format::Pts, 1, "test", "abc").is_err());
756 assert!(parse_u16(Format::Pts, 1, "test", "1.5").is_err());
757 }
758
759 #[test]
760 fn test_binary_helpers() {
761 let mut bin = Vec::new();
762 write_f32_le(&mut bin, 1.5f32).unwrap();
763 write_f64_le(&mut bin, 2.5f64).unwrap();
764 write_u16_le(&mut bin, 10u16).unwrap();
765 write_u32_le(&mut bin, 20u32).unwrap();
766
767 let mut cursor = Cursor::new(bin);
768 assert_eq!(read_f32_le(&mut cursor).unwrap(), 1.5f32);
769 assert_eq!(read_f64_le(&mut cursor).unwrap(), 2.5f64);
770 assert_eq!(read_u16_le(&mut cursor).unwrap(), 10u16);
771 assert_eq!(read_u32_le(&mut cursor).unwrap(), 20u32);
772 }
773}