use std::str::FromStr;
use geo_types::Coord;
use quick_xml::{
Decoder,
events::{BytesStart, attributes::Attribute},
};
use crate::{Error, Result};
const FILE_COORD_MAX: f64 = ((i32::MAX / 1000) - 1) as f64;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Code {
pub major: u16,
pub minor: u16,
pub patch: u16,
}
impl Code {
pub fn new(major: u16, minor: u16, patch: u16) -> Code {
Code {
major,
minor,
patch,
}
}
}
impl FromStr for Code {
type Err = Error;
fn from_str(value: &str) -> Result<Self> {
let mut parts = value.split('.').take(3);
Ok(Code {
major: parts.next().ok_or(Error::EmptyCode)?.parse()?,
minor: parts.next().and_then(|i| i.parse().ok()).unwrap_or(0),
patch: parts.next().and_then(|i| i.parse().ok()).unwrap_or(0),
})
}
}
impl std::fmt::Display for Code {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.patch > 0 {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
} else if self.minor > 0 {
write!(f, "{}.{}", self.major, self.minor)
} else if self.major > 0 {
write!(f, "{}", self.major)
} else {
write!(f, "")
}
}
}
pub(crate) fn parse_attr_raw<T: FromStr>(value: std::borrow::Cow<'_, [u8]>) -> Option<T> {
std::str::from_utf8(value.as_ref())
.ok()
.and_then(|s| T::from_str(s).ok())
}
pub(crate) fn try_get_attr_raw<T: FromStr>(bytes: &BytesStart<'_>, attr: &str) -> Option<T> {
bytes
.try_get_attribute(attr)
.ok()
.flatten()
.and_then(|a| parse_attr_raw(a.value))
}
pub(crate) fn parse_attr<T: FromStr>(attr: Attribute<'_>, decoder: Decoder) -> Option<T> {
attr.decode_and_unescape_value(decoder)
.ok()
.and_then(|s| T::from_str(&s).ok())
}
pub(crate) fn try_get_attr<T: FromStr>(bytes: &BytesStart<'_>, attr: &str) -> Option<T> {
bytes
.try_get_attribute(attr)
.ok()
.flatten()
.and_then(|a| parse_attr(a, bytes.decoder()))
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct UnitF64(f64);
impl UnitF64 {
pub fn get(self) -> f64 {
self.0
}
pub fn clamped_from(value: f64) -> Self {
if value.is_nan() {
Self(0.)
} else {
Self(value.clamp(0., 1.))
}
}
pub fn one() -> Self {
UnitF64(1.)
}
pub fn zero() -> Self {
UnitF64(0.)
}
}
impl TryFrom<f64> for UnitF64 {
type Error = Error;
fn try_from(v: f64) -> std::result::Result<Self, Self::Error> {
if v.is_finite() && (0.0..=1.0).contains(&v) {
Ok(Self(v))
} else {
Err(Error::NotInUnitInterval)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct NonNegativeF64(f64);
impl NonNegativeF64 {
pub fn get(self) -> f64 {
self.0
}
pub fn clamped_from(value: f64) -> Self {
if value.is_nan() {
Self(0.)
} else {
Self(value.max(0.))
}
}
pub fn one() -> Self {
NonNegativeF64(1.)
}
pub fn zero() -> Self {
NonNegativeF64(0.)
}
pub(crate) fn to_file_value(self) -> Result<u32> {
Ok(to_file_value(self.0)?.try_into()?)
}
pub(crate) fn from_file_value(value: u32) -> Self {
NonNegativeF64(from_file_value(value as i32))
}
}
impl TryFrom<f64> for NonNegativeF64 {
type Error = Error;
fn try_from(v: f64) -> std::result::Result<Self, Self::Error> {
if v >= 0. {
Ok(Self(v))
} else {
Err(Error::NotNonNegativeF64)
}
}
}
pub(crate) fn to_file_coords(map_coord: Coord) -> Result<Coord<i32>> {
Ok(Coord {
x: to_file_value(map_coord.x)?,
y: -to_file_value(map_coord.y)?,
})
}
pub(crate) fn to_file_value(value: f64) -> Result<i32> {
if value.abs() > FILE_COORD_MAX {
return Err(Error::MapCoordOutOfBounds);
}
Ok((value * 1000.).round() as i32)
}
pub(crate) fn from_file_value(value: i32) -> f64 {
value as f64 / 1000.
}
pub(crate) fn from_file_coords(file_coord: Coord<i32>) -> Coord {
Coord {
x: from_file_value(file_coord.x),
y: -from_file_value(file_coord.y),
}
}